code-foundry 1.48.7 → 1.50.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -7,6 +7,11 @@ on:
7
7
  description: Validation tier (fast, audit, or release).
8
8
  required: true
9
9
  type: string
10
+ release-qualification-receipts:
11
+ description: Reuse a recent exact-tree audit receipt for Release Please PRs.
12
+ required: false
13
+ type: boolean
14
+ default: false
10
15
  runtime-repository:
11
16
  description: Repository containing the Code Foundry runtime.
12
17
  required: false
@@ -70,6 +75,11 @@ on:
70
75
  required: false
71
76
  type: number
72
77
  default: 1
78
+ integration-total-shards:
79
+ description: Integration partitions (1 or 2); sharded scripts must honor INTEGRATION_SHARD_INDEX and INTEGRATION_TOTAL_SHARDS.
80
+ required: false
81
+ type: string
82
+ default: '1'
73
83
  e2e-shard-list:
74
84
  description: Comma-separated E2E shard indices passed to the test lane's matrix, e.g. "1,2".
75
85
  required: false
@@ -98,12 +108,35 @@ permissions:
98
108
  actions: read
99
109
  contents: read
100
110
  packages: read
111
+ pull-requests: read
101
112
  security-events: write
102
113
 
103
114
  jobs:
115
+ qualification:
116
+ name: Qualification
117
+ if: >-
118
+ vars.CI_BILLING_PAUSED != 'true' &&
119
+ inputs.release-qualification-receipts == true && inputs.mode == 'release'
120
+ permissions:
121
+ actions: read
122
+ contents: read
123
+ pull-requests: read
124
+ uses: ./.github/workflows/qualification-receipt.yml
125
+ with:
126
+ phase: verify
127
+ mode: ${{ inputs.mode }}
128
+ enabled: ${{ inputs.release-qualification-receipts }}
129
+ codeql-enabled: true
130
+ runtime-repository: ${{ inputs.runtime-repository }}
131
+ runtime-ref: ${{ inputs.runtime-ref }}
132
+ required-job-names-json: '["Validation / Gate"]'
133
+
104
134
  ci:
105
135
  name: CI
106
- if: vars.CI_BILLING_PAUSED != 'true' && (inputs.mode == 'fast' || inputs.mode == 'audit' || inputs.mode == 'release')
136
+ needs: qualification
137
+ if: >-
138
+ !cancelled() && vars.CI_BILLING_PAUSED != 'true' &&
139
+ (inputs.mode == 'fast' || inputs.mode == 'audit' || inputs.mode == 'release')
107
140
  # Type-Check reads the issue-backed requirements ledger with github.token.
108
141
  permissions:
109
142
  actions: read
@@ -116,13 +149,23 @@ jobs:
116
149
  runtime-ref: ${{ inputs.runtime-ref }}
117
150
  runner: ${{ inputs.ci-runner }}
118
151
  artifact-prefix: ${{ inputs.artifact-prefix }}
152
+ release-build-only: ${{ inputs.mode == 'release' && needs.qualification.outputs.reuse == 'true' }}
153
+ release-package-check: ${{ inputs.mode == 'release' && needs.qualification.outputs.reuse == 'true' }}
119
154
  secrets:
120
155
  TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
121
156
  NEXTAUTH_SECRET: ${{ secrets.NEXTAUTH_SECRET }}
122
157
 
123
158
  test:
124
159
  name: Test
125
- if: vars.CI_BILLING_PAUSED != 'true' && (inputs.mode == 'fast' || inputs.mode == 'audit' || inputs.mode == 'release')
160
+ needs: qualification
161
+ if: >-
162
+ !cancelled() && vars.CI_BILLING_PAUSED != 'true' &&
163
+ (inputs.mode == 'fast' || inputs.mode == 'audit' ||
164
+ (inputs.mode == 'release' && needs.qualification.outputs.reuse != 'true'))
165
+ permissions:
166
+ actions: read
167
+ contents: read
168
+ packages: read
126
169
  uses: ./.github/workflows/test.yml
127
170
  with:
128
171
  runtime-repository: ${{ inputs.runtime-repository }}
@@ -130,11 +173,12 @@ jobs:
130
173
  runner: ${{ inputs.test-runner }}
131
174
  unit-runner: ${{ inputs.unit-runner }}
132
175
  performance-runner: ${{ inputs.performance-runner }}
133
- # Release pull requests change version metadata only; their content was
134
- # audited on the pull requests that merged into main. Integration, E2E,
135
- # smoke, and performance lanes stay in the audit and scheduled lanes.
176
+ # Fast and Release Please pull requests run unit tests only. Until a
177
+ # successful full qualification is verifiable for the exact main tree,
178
+ # release mode keeps its CI and unit-test fallback.
136
179
  unit-only: ${{ inputs.mode == 'fast' || inputs.mode == 'release' }}
137
180
  artifact-prefix: ${{ inputs.artifact-prefix }}
181
+ integration-total-shards: ${{ inputs.integration-total-shards }}
138
182
  e2e-shard-list: ${{ inputs.e2e-shard-list }}
139
183
  e2e-total-shards: ${{ inputs.e2e-total-shards }}
140
184
  secrets:
@@ -143,6 +187,8 @@ jobs:
143
187
  security:
144
188
  name: Security
145
189
  if: vars.CI_BILLING_PAUSED != 'true' && inputs.mode == 'audit'
190
+ permissions:
191
+ contents: read
146
192
  uses: ./.github/workflows/security.yml
147
193
  with:
148
194
  runtime-repository: ${{ inputs.runtime-repository }}
@@ -154,6 +200,11 @@ jobs:
154
200
  # Release-please pull requests also analyze with CodeQL so repository
155
201
  # rulesets requiring code scanning results never deadlock the release.
156
202
  if: vars.CI_BILLING_PAUSED != 'true' && (inputs.mode == 'audit' || inputs.mode == 'release')
203
+ permissions:
204
+ actions: read
205
+ contents: read
206
+ packages: read
207
+ security-events: write
157
208
  uses: ./.github/workflows/codeql.yml
158
209
  with:
159
210
  runtime-repository: ${{ inputs.runtime-repository }}
@@ -165,10 +216,12 @@ jobs:
165
216
 
166
217
  eval:
167
218
  name: Eval
168
- # Behavior evals cover content changes; the lean release lane skips them
169
- # because its diff is version metadata only. The reusable workflow skips
170
- # cleanly when the repository has no eval harness.
219
+ # Behavior evals are part of audit qualification. A release-only run may
220
+ # reuse that work only through the exact, unexpired source receipt below.
221
+ # The reusable workflow skips cleanly when there is no eval harness.
171
222
  if: vars.CI_BILLING_PAUSED != 'true' && inputs.mode == 'audit'
223
+ permissions:
224
+ contents: read
172
225
  uses: ./.github/workflows/eval.yml
173
226
  with:
174
227
  runtime-repository: ${{ inputs.runtime-repository }}
@@ -178,16 +231,16 @@ jobs:
178
231
 
179
232
  # Aggregate check: "Validation / Gate" (caller job name + this job name).
180
233
  # Always evaluates; only jobs required for the mode must have succeeded.
181
- # The release tier requires the fast suite plus CodeQL: its diff is version
182
- # metadata (validated against the release policy below), its content was
183
- # audited on the pull requests that merged into main, and the scheduled
184
- # audit lane re-covers drift. Its generated release diff policy executes
185
- # as a conditional step below, so ordinary pull requests still render no
186
- # skipped release-policy row.
234
+ # Release Please branches require CI, CodeQL, and the exact generated-diff
235
+ # audit below. The runtime gate also requires unit tests unless the separate
236
+ # qualification job verified a matching source receipt. Ordinary PRs still
237
+ # render no skipped release-policy row.
187
238
  gate:
188
239
  name: Gate
189
- needs: [ci, test, security, codeql, eval]
240
+ needs: [ci, test, security, codeql, eval, qualification]
190
241
  if: vars.CI_BILLING_PAUSED != 'true' && always()
242
+ permissions:
243
+ contents: read
191
244
  runs-on: ubuntu-slim
192
245
  timeout-minutes: 10
193
246
  env:
@@ -197,6 +250,8 @@ jobs:
197
250
  FOUNDRY_SECURITY: ${{ needs.security.result }}
198
251
  FOUNDRY_CODEQL: ${{ needs.codeql.result }}
199
252
  FOUNDRY_EVAL: ${{ needs.eval.result }}
253
+ FOUNDRY_SOURCE_QUALIFICATION: ${{ needs.qualification.outputs.reuse }}
254
+ FOUNDRY_QUALIFICATION: ${{ needs.qualification.result }}
200
255
  steps:
201
256
  - name: Checkout runtime
202
257
  uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
@@ -216,8 +271,8 @@ jobs:
216
271
  uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
217
272
  with:
218
273
  persist-credentials: false
274
+ # The content audit needs historical blobs after checkout removes credentials.
219
275
  fetch-depth: 0
220
- filter: blob:none
221
276
  - name: Validate generated release diff
222
277
  if: ${{ inputs.mode == 'release' }}
223
278
  env:
@@ -228,3 +283,29 @@ jobs:
228
283
  run: node "$RUNNER_TEMP/code-foundry/src/runtime.mjs" validation release_diff
229
284
  - name: Evaluate aggregate gate
230
285
  run: node "$RUNNER_TEMP/code-foundry/src/runtime.mjs" validation gate
286
+
287
+ qualification-receipt:
288
+ name: Qualification receipt
289
+ needs: [ci, test, security, codeql, eval, gate]
290
+ if: >-
291
+ vars.CI_BILLING_PAUSED != 'true' && inputs.mode == 'audit' &&
292
+ inputs.release-qualification-receipts == true &&
293
+ github.event_name == 'pull_request' && github.event.pull_request.base.ref == 'main' &&
294
+ needs.ci.result == 'success' && needs.test.result == 'success' &&
295
+ needs.security.result == 'success' && needs.codeql.result == 'success' &&
296
+ needs.eval.result == 'success' && needs.gate.result == 'success'
297
+ permissions:
298
+ actions: read
299
+ contents: read
300
+ pull-requests: read
301
+ uses: ./.github/workflows/qualification-receipt.yml
302
+ with:
303
+ phase: produce
304
+ mode: ${{ inputs.mode }}
305
+ enabled: ${{ inputs.release-qualification-receipts }}
306
+ codeql-enabled: true
307
+ runtime-repository: ${{ inputs.runtime-repository }}
308
+ runtime-ref: ${{ inputs.runtime-ref }}
309
+ job-results-json: >-
310
+ {"ci":"${{ needs.ci.result }}","test":"${{ needs.test.result }}","security":"${{ needs.security.result }}","codeql":"${{ needs.codeql.result }}","eval":"${{ needs.eval.result }}","gate":"${{ needs.gate.result }}"}
311
+ required-job-names-json: '["Validation / Gate"]'
@@ -80,15 +80,17 @@ jobs:
80
80
  contents: read
81
81
  issues: read
82
82
  packages: read
83
+ pull-requests: read
83
84
  security-events: write
84
85
  uses: ./.github/workflows/validation.yml
85
86
  with:
86
87
  mode: ${{ needs.mode.outputs.mode }}
87
88
  runtime-repository: 0xPlayerOne/code-foundry
88
- # Pull requests validate the proposed runtime commit. Scheduled and
89
- # manually dispatched audits use a separate default-branch-pinned caller,
90
- # so this caller-selected ref never runs with default-branch cache access.
91
- runtime-ref: ${{ github.sha }}
89
+ # Source PRs validate their proposed runtime commit. A Release Please PR
90
+ # uses its main base, keeping version-only metadata out of the runtime
91
+ # content identity. Scheduled audits use a separate default-branch caller.
92
+ runtime-ref: >-
93
+ ${{ startsWith(github.event.pull_request.head.ref, 'release-please--branches--main') && github.event.pull_request.base.sha || github.sha }}
92
94
  ci-runner: ubuntu-latest
93
95
  test-runner: ubuntu-latest
94
96
  unit-runner: ubuntu-slim
@@ -101,6 +103,7 @@ jobs:
101
103
  rust-max-parallel: 1
102
104
  # The sync renderer rewrites both values from the e2e_shards config key;
103
105
  # the defaults keep repositories without that key on one unsharded job.
106
+ integration-total-shards: '1'
104
107
  e2e-shard-list: '1'
105
108
  e2e-total-shards: '1'
106
109
  secrets:
package/CHANGELOG.md CHANGED
@@ -1,5 +1,54 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.50.0](https://github.com/0xPlayerOne/code-foundry/compare/v1.49.3...v1.50.0) (2026-10-29)
4
+
5
+
6
+ ### Features
7
+
8
+ * **release:** support npm OIDC publishing ([#791](https://github.com/0xPlayerOne/code-foundry/issues/791)) ([74b5155](https://github.com/0xPlayerOne/code-foundry/commit/74b5155157b9f163d70b00b138673c981e19c925))
9
+
10
+ ## [1.49.3](https://github.com/0xPlayerOne/code-foundry/compare/v1.49.2...v1.49.3) (2026-10-29)
11
+
12
+
13
+ ### CI
14
+
15
+ * **validation:** require release checks until evidence exists ([#789](https://github.com/0xPlayerOne/code-foundry/issues/789)) ([a5f8be6](https://github.com/0xPlayerOne/code-foundry/commit/a5f8be64b7b28d2248aa71b80e321aa0fe82f30a))
16
+
17
+ ## [1.49.2](https://github.com/0xPlayerOne/code-foundry/compare/v1.49.1...v1.49.2) (2026-10-29)
18
+
19
+
20
+ ### Bug Fixes
21
+
22
+ * safely categorize npm publication failures ([#787](https://github.com/0xPlayerOne/code-foundry/issues/787)) ([a4268af](https://github.com/0xPlayerOne/code-foundry/commit/a4268af5ce7cd0a32407420424419c0cad01dce8))
23
+
24
+ ## [1.49.1](https://github.com/0xPlayerOne/code-foundry/compare/v1.49.0...v1.49.1) (2026-10-28)
25
+
26
+
27
+ ### Bug Fixes
28
+
29
+ * handle empty security audit matrices ([#785](https://github.com/0xPlayerOne/code-foundry/issues/785)) ([02f0565](https://github.com/0xPlayerOne/code-foundry/commit/02f05652e68267a2aa2df5d5af8f13df9ba1e3aa))
30
+
31
+ ## [1.49.0](https://github.com/0xPlayerOne/code-foundry/compare/v1.48.9...v1.49.0) (2026-10-27)
32
+
33
+
34
+ ### Features
35
+
36
+ * support isolated integration test shards ([#783](https://github.com/0xPlayerOne/code-foundry/issues/783)) ([ad1123f](https://github.com/0xPlayerOne/code-foundry/commit/ad1123ff3bb9654bfbdaef4219490cca3e7e3d20))
37
+
38
+ ## [1.48.9](https://github.com/0xPlayerOne/code-foundry/compare/v1.48.8...v1.48.9) (2026-10-26)
39
+
40
+
41
+ ### Bug Fixes
42
+
43
+ * **release:** preserve fallback stdin and paginate existing releases ([#781](https://github.com/0xPlayerOne/code-foundry/issues/781)) ([ede4d2c](https://github.com/0xPlayerOne/code-foundry/commit/ede4d2c7e3bc7d1174580c9a0bc83bdaa160c2da))
44
+
45
+ ## [1.48.8](https://github.com/0xPlayerOne/code-foundry/compare/v1.48.7...v1.48.8) (2026-10-24)
46
+
47
+
48
+ ### Bug Fixes
49
+
50
+ * **release:** hydrate audit blobs and refresh changing PR state ([#779](https://github.com/0xPlayerOne/code-foundry/issues/779)) ([8734ed7](https://github.com/0xPlayerOne/code-foundry/commit/8734ed77d159818cd5ff613f40a5dab796789d8a))
51
+
3
52
  ## [1.48.7](https://github.com/0xPlayerOne/code-foundry/compare/v1.48.6...v1.48.7) (2026-10-23)
4
53
 
5
54
 
@@ -91,58 +140,9 @@
91
140
 
92
141
  * **doctor:** pin the machine-hook shadow warning ([#753](https://github.com/0xPlayerOne/code-foundry/issues/753)) ([f634269](https://github.com/0xPlayerOne/code-foundry/commit/f634269424580d8d34323319288747587b6d6ae3))
93
142
 
94
- ## [1.47.0](https://github.com/0xPlayerOne/code-foundry/compare/v1.46.6...v1.47.0) (2026-10-08)
95
-
96
-
97
- ### Features
98
-
99
- * **release:** configurable dev channel on top of batched releases ([#749](https://github.com/0xPlayerOne/code-foundry/issues/749)) ([8fd8556](https://github.com/0xPlayerOne/code-foundry/commit/8fd85567a1a89735ab0db589649741253fbbeb21)), closes [#699](https://github.com/0xPlayerOne/code-foundry/issues/699)
100
-
101
- ## [1.46.6](https://github.com/0xPlayerOne/code-foundry/compare/v1.46.5...v1.46.6) (2026-10-08)
102
-
103
-
104
- ### Bug Fixes
105
-
106
- * **pre-commit:** build before lint and type-check when the gate opts in ([#748](https://github.com/0xPlayerOne/code-foundry/issues/748)) ([f56454f](https://github.com/0xPlayerOne/code-foundry/commit/f56454f313a1a5b802b8a17b09d850902de1ba55)), closes [#691](https://github.com/0xPlayerOne/code-foundry/issues/691)
107
-
108
- ## [1.46.5](https://github.com/0xPlayerOne/code-foundry/compare/v1.46.4...v1.46.5) (2026-10-08)
109
-
110
-
111
- ### Bug Fixes
112
-
113
- * **sync:** chain machine-level hooks instead of silencing them ([#747](https://github.com/0xPlayerOne/code-foundry/issues/747)) ([7fcad52](https://github.com/0xPlayerOne/code-foundry/commit/7fcad52d400dbb2aaa9ba46c0c76f14e8d63f660)), closes [#690](https://github.com/0xPlayerOne/code-foundry/issues/690)
114
-
115
- ## [1.46.4](https://github.com/0xPlayerOne/code-foundry/compare/v1.46.3...v1.46.4) (2026-10-08)
116
-
117
-
118
- ### Bug Fixes
119
-
120
- * **release:** preserve current-head validation ownership ([#737](https://github.com/0xPlayerOne/code-foundry/issues/737)) ([aa8bfcf](https://github.com/0xPlayerOne/code-foundry/commit/aa8bfcfbfcd3c213d15ba9e1fbfb5c65ab8ddb6b))
121
-
122
- ## [1.46.3](https://github.com/0xPlayerOne/code-foundry/compare/v1.46.2...v1.46.3) (2026-10-07)
123
-
124
-
125
- ### Bug Fixes
126
-
127
- * **billing:** keep lint standalone; join fast-lane receipts in parallel ([#744](https://github.com/0xPlayerOne/code-foundry/issues/744)) ([574690b](https://github.com/0xPlayerOne/code-foundry/commit/574690b707abffe9350ccc67589540afb82323ee))
128
-
129
- ## [1.46.2](https://github.com/0xPlayerOne/code-foundry/compare/v1.46.1...v1.46.2) (2026-10-07)
130
-
131
-
132
- ### Bug Fixes
133
-
134
- * **billing:** join fast-lane receipts in a second parallel group ([#742](https://github.com/0xPlayerOne/code-foundry/issues/742)) ([c63a39c](https://github.com/0xPlayerOne/code-foundry/commit/c63a39cf5e236d6747bd85cf10b04bd10f690b11))
135
-
136
- ## [1.46.1](https://github.com/0xPlayerOne/code-foundry/compare/v1.46.0...v1.46.1) (2026-10-07)
137
-
138
-
139
- ### Bug Fixes
140
-
141
- * **qualification:** skip parallel-syntax files until actionlint supports them ([#740](https://github.com/0xPlayerOne/code-foundry/issues/740)) ([db98c7f](https://github.com/0xPlayerOne/code-foundry/commit/db98c7f40cae080cdc9dcab7baac4976e5e0656e))
142
-
143
143
  <!-- code-foundry: packaged changelog -->
144
144
 
145
145
  ---
146
146
 
147
- This packaged changelog lists the 20 most recent releases; 255 older releases are omitted to keep the package small.
147
+ This packaged changelog lists the 20 most recent releases; 262 older releases are omitted to keep the package small.
148
148
  See the [full changelog](https://github.com/0xPlayerOne/code-foundry/blob/main/CHANGELOG.md) for the complete history.
@@ -175,6 +175,7 @@ See [Merge queue validation](merge-queues.md) before enabling it.
175
175
  | `rust_nextest` | `true`, `false` | Install cargo-nextest and prefer `cargo nextest run` over `cargo test` in Rust test lanes. |
176
176
  | `e2e_shards` | `1`–`8` | Fan the `Validation / Test / E2E` job into one runner per shard. The repository's `test:e2e`/`e2e` script receives `E2E_SHARD_INDEX` and `E2E_TOTAL_SHARDS` and partitions itself (Playwright takes `--shard=$E2E_SHARD_INDEX/$E2E_TOTAL_SHARDS`); each shard owns a runner-local test database, so shards stay isolated. |
177
177
  | `billing_lanes` | `true`, `false` | Route validation through the merged-lane billing orchestrator: format, build, performance, smoke, and eval run as sequential steps of one shared runner, and profile plus dependency audit share one job, so GitHub's whole-minute rounding stops dominating billed minutes on private repositories. Coverage and the `Validation / Gate` check are unchanged; only merge a lane into the shared job when its work stays under the longest separate lane, so CI completion time does not grow. Requires all runner inputs on standard hosted runners. |
178
+ | `release_qualification_receipts` | `true`, `false` (default `false`) | Opt in to seven-day source qualification receipts for eligible version-only Release Please PRs. Defaults to false; see [workflow policy](WORKFLOWS.md#standard-triggers) for requirements and fallback behavior. |
178
179
  | `release_batching_schedule` | five-field numeric cron | Batch Release Please into scheduled windows. Ordinary pushes skip the producer; release-PR squashes and manual dispatches still run. Batched releases are created privately as drafts and published with prerelease visibility atomically. Manual dispatches publish stable hotfixes. Absent, releases publish per merge. Requires a single root Release Please package. |
179
180
  | `release_batching_prerelease` | `true`, `false` | Defaults to `true` with a schedule. Sync enables `draft` and `force-tag-creation` in Release Please; the producer publishes the draft with the selected visibility. `false` keeps scheduled releases stable-visible. |
180
181
  | `release_batching_soak_hours` | `1`–`336` | Generate a daily promoter that promotes the newest prerelease when the oldest candidate since the last stable has soaked this many hours, stitching batch notes. Requires a batching schedule and prerelease publication. Set `STABLE_PROMOTION_HELD=true` in repository Actions variables to hold scheduled and manual promotion; `force` overrides the hold or soak, and `version` selects a plain semantic version. |
@@ -272,6 +273,7 @@ installed hook predates chaining.
272
273
  | ------------------------- | -------------------------------------------------------------------------------- | ----------------------------------------------------------------------- |
273
274
  | `release_type` | `auto`, `node`, `python`, `rust`, `simple`, `none` | Select a release manifest; `auto` detects one. |
274
275
  | `npm_publish` | `true`, `false` | Opt into npm publication. |
276
+ | `npm_publish_auth` | `auto`, `oidc`, `token` | Select trusted OIDC publication or preserve an explicit token workflow. |
275
277
  | `license` | `gpl-3.0-or-later`, `agpl-3.0-or-later`, `apache-2.0`, `mit`, `preserve`, `none` | License policy for initialized repositories. |
276
278
  | `git_workflow` | `direct`, `staging-release` | Choose the branch topology. |
277
279
  | `merge_strategy` | `squash` or `rebase` | Required topology-specific merge method. |
@@ -300,6 +302,15 @@ described in [Qualified publication](qualified-publication.md). A self-sync of
300
302
  the Code Foundry source does not advance its own `runtime_ref`; this prevents a
301
303
  self-referencing runtime pin from creating a release loop.
302
304
 
305
+ `npm_publish_auth` defaults to `auto`: a configured `NPM_TOKEN` keeps the
306
+ legacy token publisher, while a missing token uses npm trusted publishing.
307
+ Choose `oidc` to force trusted publishing and prevent `NPM_TOKEN` from reaching
308
+ the reusable release workflow; choose `token` to require `NPM_TOKEN`. The
309
+ separate optional `NPM_READ_TOKEN` is passed only to dependency installation
310
+ when private npm packages need read access. For OIDC, configure npm's trusted
311
+ publisher for the package repository and its generated `.github/workflows/release.yml`
312
+ caller, with `id-token: write` available to both caller and reusable job.
313
+
303
314
  ## Synchronization and extensions
304
315
 
305
316
  | Key | Values | Purpose |
@@ -12,8 +12,13 @@ Choose the release path that matches the repository:
12
12
  ## npm publication for consumer repositories
13
13
 
14
14
  Set `npm_publish: true` only when the repository owns an npm package. Configure
15
- npm trusted publishing for the generated `release.yml` workflow whenever
16
- possible. Use an `NPM_TOKEN` secret only when trusted publishing is unavailable.
15
+ npm trusted publishing for the generated `release.yml` workflow. The
16
+ `npm_publish_auth` setting defaults to `auto`: existing `NPM_TOKEN` users keep
17
+ token publication, and tokenless repositories use OIDC. Set it to `oidc` to
18
+ force trusted publishing and keep `NPM_TOKEN` out of publication and install;
19
+ use `NPM_READ_TOKEN` separately when private dependencies must be installed.
20
+ Set `npm_publish_auth: token` only when a long-lived publishing token is still
21
+ required.
17
22
 
18
23
  Declare the package's intended visibility explicitly:
19
24
 
@@ -26,8 +31,11 @@ Declare the package's intended visibility explicitly:
26
31
  ```
27
32
 
28
33
  Publication occurs only from a Release Please tag; ordinary pushes do not
29
- publish. The release workflow fails clearly when npm publication is enabled but
30
- neither trusted publishing nor a token is configured.
34
+ publish. `token` mode fails clearly unless `NPM_TOKEN` is configured. `auto`
35
+ uses OIDC when the publishing token is absent, and `oidc` always uses the
36
+ workflow's short-lived trusted identity. For private dependency installation,
37
+ the workflow consumes `NPM_READ_TOKEN` first; `NPM_TOKEN` is an install fallback
38
+ only outside explicit OIDC mode.
31
39
 
32
40
  After enabling publication, make one controlled release and verify both the
33
41
  registry version and its provenance link before treating the setup as complete.
@@ -43,7 +51,7 @@ callers:
43
51
  3. Publish the immutable GitHub Release after verifying the tag, source commit,
44
52
  archive digest, immutable-release setting, and qualification reports.
45
53
  4. Publish the already-qualified archive through the qualified publisher using npm
46
- trusted publishing or the optional token fallback.
54
+ trusted publishing.
47
55
 
48
56
  The final publisher does not rebuild or reinstall the package. It publishes only
49
57
  the bytes selected from the same workflow run and attempt. See [Consumer
package/docs/WORKFLOWS.md CHANGED
@@ -56,15 +56,27 @@ workflow_dispatch:
56
56
  ```
57
57
 
58
58
  In the `staging-release` topology, pull requests into `staging` run the fast
59
- tier, ordinary pull requests into `main` run the full audit tier, and exact
60
- Release Please pull requests into `main` run a lean release lane — CI, unit
61
- tests, and CodeQL plus the release-diff policy. Their diff is version
62
- metadata only: the content was fully audited on the pull requests that
63
- merged into `main`, and the scheduled audit lane re-covers drift, so the
64
- release lane keeps repository rulesets satisfiable without re-running the
65
- runner-heavy suites. The managed branch rulesets require only the aggregate
66
- `Validation / Gate` check, so skipped non-required jobs never deadlock the
67
- release; do not hand-require individual job contexts on release branches.
59
+ tier and ordinary pull requests into `main` run the full audit tier. Exact
60
+ Release Please pull requests into `main` must pass the release-diff policy and
61
+ configured CodeQL scan. They also run the full CI and unit-test fallback unless
62
+ the workflow verifies a seven-day source qualification receipt. A receipt is
63
+ usable only when a successful full audit tested the exact current `main` tree
64
+ with the same validation workflow, Code Foundry runtime, CodeQL policy, and
65
+ pull-request/run identity. Verification checks the merged source PR, the
66
+ aggregate gate's success in the exact run attempt, the required component
67
+ results, and the artifact's bounded download size and SHA-256 digest. The release-diff preflight must confirm a
68
+ version-only patch. Reuse is currently supported only for a public npm package
69
+ released from the repository root; private roots, workspaces, and unknown
70
+ Release Please layouts use the full CI and test fallback. A root package may
71
+ use `prepublishOnly` only when it exactly invokes the configured package
72
+ manager's `run build` task; other lifecycle hooks and custom prepublish commands
73
+ use the full fallback. With a verified
74
+ receipt, the applicable build is forced even when path filtering would skip it,
75
+ and the versioned root npm package inspection still runs. Only duplicate
76
+ format, lint, type-check, and unit-test work is skipped. The receipt never
77
+ replaces the release-diff audit or configured CodeQL scan.
78
+ The managed branch rulesets require only the aggregate `Validation / Gate`
79
+ check; do not hand-require individual job contexts on release branches.
68
80
  In the
69
81
  `direct` topology (the default) every pull request targets `main` and runs the
70
82
  full audit tier, because there is no integration branch for a fast pass.
@@ -185,9 +197,10 @@ Use concise job names such as `CI / Format`, `Test / Unit`, and
185
197
  included) and security audits run through a detection-built matrix, so a
186
198
  repository only ever shows checks for languages it actually uses — never
187
199
  skipped rows for other languages. The release tier validates the generated
188
- diff as a step inside the gate rather than a separate job, for the same
189
- reason. Required checks should match the jobs actually enabled for the
190
- repository profile — in practice, require only the aggregate
200
+ diff inside the gate and requires CodeQL when configured. It reuses prior
201
+ source tests only when the short-lived exact-tree receipt verifies; otherwise
202
+ it runs the full CI and unit-test fallback. Required checks should match the
203
+ jobs actually enabled for the repository profile — in practice, require only the aggregate
191
204
  `Validation / Gate`, which fails closed unless every generated check
192
205
  succeeds.
193
206
 
@@ -29,17 +29,19 @@ package.
29
29
  The publishing job serializes publication for a tag and has no dependency
30
30
  installation/build step. Qualification has no npm credential. This self-only
31
31
  publisher has no environment approval gate; publication is automatic after its
32
- completed gates. npm trusted publishing is preferred; an explicit optional token
33
- supports environments that cannot use it. Configure the trusted publisher for the
34
- **actual caller and reusable-workflow relationship** before enabling this route.
32
+ completed gates and uses OIDC only. Configure npm trusted publishing for the
33
+ actual caller workflow filename, `release_self-ci.yml`, in repository
34
+ `0xPlayerOne/code-foundry`, with direct publish permission and no GitHub
35
+ environment. Both the caller and reusable publisher grant `id-token: write`.
36
+ For generated consumer workflows, the caller filename is `release.yml` and the
37
+ auth mode remains configurable with `npm_publish_auth`.
35
38
  Existing version publication is not overwritten: retrying an already-published
36
39
  version fails rather than treating a registry conflict or network error as proof
37
40
  of identity.
38
41
 
39
42
  The publisher never rebuilds the package, publishes a directory, or treats an
40
- already-published version conflict as proof of success. The workflow uses npm
41
- trusted publishing when no `NPM_TOKEN` is supplied; the optional token is a
42
- fallback for environments that cannot use trusted publishing.
43
+ already-published version conflict as proof of success. It never accepts or
44
+ forwards `NPM_TOKEN`.
43
45
 
44
46
  Qualification and publication are gated on `main` push or an explicitly
45
47
  requested `workflow_dispatch`. The shared billing pause blocks normal runs. A
@@ -89,15 +91,16 @@ jobs:
89
91
  with:
90
92
  tag: ${{ needs.release-producer.outputs.tag }}
91
93
  asset: ${{ needs.release-producer.outputs.npm-asset }}
92
- secrets:
93
- NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
94
94
  ```
95
95
 
96
96
  For the self repository, `release_self-ci.yml` supplies the release output and
97
- uses the protected `npm` environment. Configure trusted publishing for the
98
- actual caller/reusable-workflow relationship, not only for a similarly named
99
- workflow. Protect both the `release` and `npm` environments with the intended
100
- branch restrictions and approvals.
97
+ uses the qualified publisher job without a GitHub environment. The actual
98
+ caller filename must match npm's trusted-publisher configuration; a similarly
99
+ named reusable workflow does not match. A prior publisher entry named
100
+ `release.yml` did not match the self-publish caller; the repository owner reports
101
+ adding the `release_self-ci.yml` entry. A successful OIDC publication still
102
+ needs to verify that identity before any old trust entry or secret is removed.
103
+ This source change does not edit npm trust settings.
101
104
 
102
105
  ## Identity and retry rules
103
106
 
@@ -131,8 +134,8 @@ administration-read, contents-write, and verification access; it is never expose
131
134
  to qualification jobs. Preflight and staging reuse the producer's validated
132
135
  credential selection, falling back to the workflow token only when the configured
133
136
  token is rejected. Configure npm trusted-publisher identity for the actual
134
- caller/reusable-workflow relationship, or explicitly retain the optional npm
135
- token in the final publisher.
137
+ caller workflow filename. This self-only publisher does not support a token
138
+ fallback.
136
139
 
137
140
  ## Trust boundaries
138
141
 
@@ -168,6 +171,11 @@ publisher from the same source-bound workflow after confirming npm has not alrea
168
171
  accepted that version. Never weaken the SHA guard, move an immutable tag, or treat
169
172
  npm's version-conflict response as success.
170
173
 
174
+ If npm fails, the publisher reports only a fixed diagnostic category for known
175
+ npm error codes. Unknown errors remain generic; stderr, registry URLs, paths, and
176
+ credentials are not logged by this diagnostic. A category is a clue, not proof
177
+ of the cause, and does not change the publication gates.
178
+
171
179
  ## Validation
172
180
 
173
181
  Run the focused local suites before changing this path:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "code-foundry",
3
- "version": "1.48.7",
3
+ "version": "1.50.0",
4
4
  "description": "A fast, language-aware repository factory for agent-ready workflows, testing, security, and release automation.",
5
5
  "homepage": "https://github.com/0xPlayerOne/code-foundry#readme",
6
6
  "bugs": {
@@ -16,6 +16,9 @@
16
16
  },
17
17
  "files": [
18
18
  "src",
19
+ "!src/commands/qualification-receipt.mjs",
20
+ "!src/lib/qualification-receipt.mjs",
21
+ "!src/lib/release-package-contract.mjs",
19
22
  "docs",
20
23
  ".github",
21
24
  "!.github/actions/**",
@@ -31,6 +31,29 @@ export const REQUIRED_FIXTURES = [
31
31
  function ensure(condition, message) {
32
32
  if (!condition) throw new Error(message)
33
33
  }
34
+ // Extract only fixed categories from npm's code line. Never echo stderr or the
35
+ // captured code: npm output may contain credentials, paths, or registry URLs.
36
+ /** @param {string} stderr */
37
+ export function npmFailureCategory(stderr) {
38
+ const categories = {
39
+ E401: 'authentication',
40
+ E403: 'authorization-or-policy',
41
+ E404: 'registry-or-package',
42
+ E409: 'version-conflict',
43
+ EOTP: 'authentication',
44
+ ENEEDAUTH: 'authentication',
45
+ ECONNRESET: 'network',
46
+ ECONNREFUSED: 'network',
47
+ ENOTFOUND: 'network',
48
+ ETIMEDOUT: 'network',
49
+ }
50
+ for (const line of stderr.split(/\r?\n/)) {
51
+ const code = /^npm (?:ERR!|error) code ([A-Z0-9]+)\s*$/.exec(line)?.[1]
52
+ if (code && Object.hasOwn(categories, code))
53
+ return categories[/** @type {keyof typeof categories} */ (code)]
54
+ }
55
+ return 'unknown'
56
+ }
34
57
  /** @type {Run} */
35
58
  export function runCommand(command, args, cwd) {
36
59
  const result = spawnSync(command, args, {
@@ -40,6 +63,10 @@ export function runCommand(command, args, cwd) {
40
63
  maxBuffer: 8 * 1024 * 1024,
41
64
  env: { ...process.env, GH_HOST: 'github.com', GH_PROMPT_DISABLED: '1' },
42
65
  })
66
+ if (command === 'npm' && (result.error || result.status !== 0)) {
67
+ const category = npmFailureCategory(result.stderr ?? '')
68
+ throw new Error(`npm failed (category: ${category}); publication remains blocked`)
69
+ }
43
70
  ensure(
44
71
  !result.error && result.status === 0,
45
72
  `${command} failed (${result.status ?? result.error?.message}); publication remains blocked`