Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions .github/ACTIONLINT.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,49 @@ ShellCheck is available on the host. A passing local run does not emulate
GitHub-hosted runners, exercise repository settings, prove secret or environment
configuration, perform a live CometAPI request, or prove npm Trusted Publishing.
Those remain separate remote evidence.

## Dependency update review

Dependabot keeps production updates on their existing compatibility policy and
limits development version updates to minor and patch releases. Toolchain
major upgrades need a coordinated change that verifies compiler, linter,
bundler, test runner, and Node.js type compatibility with `npm ci` and
`npm run verify`. Do not use `--force` or `--legacy-peer-deps` to suppress peer
dependency conflicts. Security updates remain eligible; the version-update
filter does not suppress them. See the official
[Dependabot allow reference](https://docs.github.com/en/code-security/reference/supply-chain-security/dependabot-options-reference#allow).

Packed fixtures declare the supported OpenAI range and select the exact install
version from the root lock file at runtime. Update their source declarations
only when intentionally changing the minimum supported range; lock-file patch
updates do not require copying the new version into each fixture.

GitHub Actions version updates are grouped into one review. Every action
remains pinned to a full commit SHA. The Publish contract additionally pins the
parsed executable workflow with `PUBLISH_WORKFLOW_CONTRACT_SHA256` in
`scripts/release-workflow-validation.mjs`. An action SHA change in
`publish.yml` must fail that contract until a maintainer reviews the upstream
action changes and updates the digest in the same change. This is an expected
release safety check, not a transient failure to retry.

After reviewing the complete Publish workflow diff and each action's upstream
changes, calculate the candidate digest from the repository root:

```bash
node --input-type=module <<'EOF'
import { createHash } from "node:crypto";
import { readFileSync } from "node:fs";
import { parse } from "yaml";

const workflow = parse(readFileSync(".github/workflows/publish.yml", "utf8"));
console.log(createHash("sha256").update(JSON.stringify(workflow)).digest("hex"));
EOF
```

Update the constant only after confirming that the reviewed action pins and
all release gates remain correct. Run `npm run verify` to cover the workflow
mutation regressions and pinned `actionlint` validation. Do not remove the
digest check or relax release permissions, immutable-tag checks, environment
approvals, live request budgets, or publication replay limits. Existing open
Dependabot branches retain their old contents until refreshed or replaced;
changing the update policy does not repair those branches by itself.
12 changes: 12 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,14 @@ updates:
time: "04:00"
timezone: Etc/UTC
open-pull-requests-limit: 10
allow:
- dependency-type: production
# Toolchain majors require a coordinated peer-dependency migration.
# This filter affects version updates, not security updates.
- dependency-type: development
update-types:
- version-update:semver-minor
- version-update:semver-patch
groups:
production-dependencies:
dependency-type: production
Expand All @@ -27,3 +35,7 @@ updates:
time: "04:30"
timezone: Etc/UTC
open-pull-requests-limit: 10
groups:
github-actions:
patterns:
- "*"
2 changes: 1 addition & 1 deletion fixtures/commonjs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@
"test": "node index.cjs"
},
"dependencies": {
"openai": "6.47.0"
"openai": "^6.47.0"
}
}
2 changes: 1 addition & 1 deletion fixtures/esm/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@
"test": "node index.mjs"
},
"dependencies": {
"openai": "6.47.0"
"openai": "^6.47.0"
}
}
2 changes: 1 addition & 1 deletion fixtures/openai-host/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"typecheck": "tsc --noEmit --project tsconfig.json && tsc --noEmit --project tsconfig.nonexact.json"
},
"dependencies": {
"openai": "6.47.0"
"openai": "^6.47.0"
},
"devDependencies": {
"typescript": "5.9.3"
Expand Down
34 changes: 24 additions & 10 deletions scripts/test-fixtures.mjs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import assert from "node:assert/strict";
import { cpSync, existsSync } from "node:fs";
import { cpSync, existsSync, writeFileSync } from "node:fs";
import { basename, join, resolve } from "node:path";

import {
Expand All @@ -23,15 +23,9 @@ if (tarballArgument !== -1 && !suppliedTarball) {
const lock = readJSON(join(ROOT, "package-lock.json"));
const lockedOpenAIVersion = lock.packages["node_modules/openai"]?.version;
assert.ok(lockedOpenAIVersion, "package-lock.json must lock OpenAI");
for (const fixture of ["esm", "commonjs", "openai-host"]) {
const manifest = readJSON(join(ROOT, "fixtures", fixture, "package.json"));
assert.equal(
manifest.dependencies?.openai,
lockedOpenAIVersion,
`${fixture} must pin the locked OpenAI version`,
);
}

const supportedOpenAIRange = readJSON(join(ROOT, "package.json")).dependencies
?.openai;
assert.ok(supportedOpenAIRange, "package.json must declare OpenAI support");
try {
const tarball =
(suppliedTarball ? resolve(ROOT, suppliedTarball) : undefined) ??
Expand All @@ -42,11 +36,31 @@ try {
cpSync(join(ROOT, "fixtures", fixture), fixtureDirectory, {
recursive: true,
});
const manifestPath = join(fixtureDirectory, "package.json");
const manifest = readJSON(manifestPath);
assert.equal(
manifest.dependencies?.openai,
supportedOpenAIRange,
`${fixture} must declare the supported OpenAI range before installation`,
);
manifest.dependencies.openai = lockedOpenAIVersion;
writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`);
run(
"npm",
["install", "--ignore-scripts", "--no-audit", "--no-fund", tarball],
{ cwd: fixtureDirectory },
);
const dependencyTree = JSON.parse(
run("npm", ["ls", "openai", "--all", "--json"], {
capture: true,
cwd: fixtureDirectory,
}),
);
assert.equal(
dependencyTree.dependencies?.openai?.version,
lockedOpenAIVersion,
`${fixture} must install the locked OpenAI version`,
);

if (fixture === "openai-host") {
const nestedOpenAI = join(
Expand Down
Loading