code-foundry 1.17.0 → 1.19.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.
- package/.github/workflows/qualified-foundry-publish.yml +144 -0
- package/CHANGELOG.md +14 -0
- package/docs/PERFORMANCE.md +10 -10
- package/docs/fleet-release-eligibility.md +80 -0
- package/docs/fleet-rollouts.md +4 -3
- package/docs/qualified-publication.md +111 -0
- package/package.json +1 -1
- package/src/cli.mjs +19 -6
- package/src/commands/fleet-core.mjs +567 -0
- package/src/commands/fleet.mjs +9 -562
- package/src/commands/qualified-publication.mjs +361 -0
- package/src/lib/fleet-eligibility.mjs +245 -0
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
name: Qualified Foundry Publication
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
workflow_call:
|
|
5
|
+
inputs:
|
|
6
|
+
tag:
|
|
7
|
+
description: Published immutable release tag at the caller commit.
|
|
8
|
+
required: true
|
|
9
|
+
type: string
|
|
10
|
+
asset:
|
|
11
|
+
description: Exact pre-attached Code Foundry npm tarball name.
|
|
12
|
+
required: true
|
|
13
|
+
type: string
|
|
14
|
+
environment:
|
|
15
|
+
description: Protected GitHub environment for npm publication.
|
|
16
|
+
required: false
|
|
17
|
+
default: npm
|
|
18
|
+
type: string
|
|
19
|
+
secrets:
|
|
20
|
+
NPM_TOKEN:
|
|
21
|
+
required: false
|
|
22
|
+
|
|
23
|
+
permissions:
|
|
24
|
+
attestations: read
|
|
25
|
+
contents: read
|
|
26
|
+
|
|
27
|
+
jobs:
|
|
28
|
+
qualify:
|
|
29
|
+
name: Qualify Node ${{ matrix.node }}
|
|
30
|
+
if: vars.CI_BILLING_PAUSED != 'true' && github.ref == 'refs/heads/main' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch')
|
|
31
|
+
runs-on: ubuntu-24.04
|
|
32
|
+
timeout-minutes: 15
|
|
33
|
+
strategy:
|
|
34
|
+
fail-fast: false
|
|
35
|
+
matrix:
|
|
36
|
+
node: ['20', '22', '24']
|
|
37
|
+
steps:
|
|
38
|
+
- name: Checkout trusted qualification runtime
|
|
39
|
+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
|
40
|
+
with:
|
|
41
|
+
persist-credentials: false
|
|
42
|
+
ref: ${{ github.sha }}
|
|
43
|
+
- name: Setup Node
|
|
44
|
+
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
|
|
45
|
+
with:
|
|
46
|
+
node-version: ${{ matrix.node }}
|
|
47
|
+
- name: Install pinned Actionlint
|
|
48
|
+
run: go install github.com/rhysd/actionlint/cmd/actionlint@v1.7.12
|
|
49
|
+
- name: Download and cryptographically verify the release archive
|
|
50
|
+
env:
|
|
51
|
+
GH_TOKEN: ${{ github.token }}
|
|
52
|
+
RELEASE_TAG: ${{ inputs.tag }}
|
|
53
|
+
RELEASE_ASSET: ${{ inputs.asset }}
|
|
54
|
+
run: |
|
|
55
|
+
set -euo pipefail
|
|
56
|
+
node src/commands/qualified-publication.mjs download \
|
|
57
|
+
"$GITHUB_REPOSITORY" "$RELEASE_TAG" "$GITHUB_SHA" "$RELEASE_ASSET" \
|
|
58
|
+
"$RUNNER_TEMP/candidate" > "$RUNNER_TEMP/download.json"
|
|
59
|
+
- name: Qualify those exact bytes
|
|
60
|
+
env:
|
|
61
|
+
RELEASE_ASSET: ${{ inputs.asset }}
|
|
62
|
+
run: |
|
|
63
|
+
set -euo pipefail
|
|
64
|
+
node src/lib/consumer-qualification.mjs \
|
|
65
|
+
"$RUNNER_TEMP/candidate/$RELEASE_ASSET" "$GITHUB_SHA" \
|
|
66
|
+
"$RUNNER_TEMP/report.json" "$(go env GOPATH)/bin/actionlint"
|
|
67
|
+
- name: Retain this attempt's qualification report
|
|
68
|
+
if: always()
|
|
69
|
+
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
|
70
|
+
with:
|
|
71
|
+
name: qualified-${{ github.run_attempt }}-node-${{ matrix.node }}
|
|
72
|
+
path: ${{ runner.temp }}/report.json
|
|
73
|
+
if-no-files-found: error
|
|
74
|
+
retention-days: 14
|
|
75
|
+
|
|
76
|
+
publish:
|
|
77
|
+
name: Publish qualified archive
|
|
78
|
+
needs: qualify
|
|
79
|
+
if: vars.CI_BILLING_PAUSED != 'true' && github.ref == 'refs/heads/main' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch')
|
|
80
|
+
runs-on: ubuntu-24.04
|
|
81
|
+
timeout-minutes: 15
|
|
82
|
+
environment: ${{ inputs.environment }}
|
|
83
|
+
concurrency:
|
|
84
|
+
group: qualified-foundry-npm-${{ github.repository }}-${{ inputs.tag }}
|
|
85
|
+
cancel-in-progress: false
|
|
86
|
+
permissions:
|
|
87
|
+
actions: read
|
|
88
|
+
attestations: read
|
|
89
|
+
contents: read
|
|
90
|
+
id-token: write
|
|
91
|
+
env:
|
|
92
|
+
HAS_NPM_TOKEN: ${{ secrets.NPM_TOKEN != '' }}
|
|
93
|
+
RELEASE_TAG: ${{ inputs.tag }}
|
|
94
|
+
RELEASE_ASSET: ${{ inputs.asset }}
|
|
95
|
+
steps:
|
|
96
|
+
- name: Checkout trusted publication runtime
|
|
97
|
+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
|
98
|
+
with:
|
|
99
|
+
persist-credentials: false
|
|
100
|
+
ref: ${{ github.sha }}
|
|
101
|
+
- name: Setup Node for trusted publishing
|
|
102
|
+
if: env.HAS_NPM_TOKEN != 'true'
|
|
103
|
+
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
|
|
104
|
+
with:
|
|
105
|
+
node-version: '24'
|
|
106
|
+
- name: Setup Node for token publishing
|
|
107
|
+
if: env.HAS_NPM_TOKEN == 'true'
|
|
108
|
+
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
|
|
109
|
+
with:
|
|
110
|
+
node-version: '24'
|
|
111
|
+
registry-url: https://registry.npmjs.org
|
|
112
|
+
- name: Retrieve only this run attempt's evidence
|
|
113
|
+
env:
|
|
114
|
+
GH_TOKEN: ${{ github.token }}
|
|
115
|
+
run: |
|
|
116
|
+
set -euo pipefail
|
|
117
|
+
for node in 20 22 24; do
|
|
118
|
+
gh run download "$GITHUB_RUN_ID" --repo "$GITHUB_REPOSITORY" \
|
|
119
|
+
--name "qualified-$GITHUB_RUN_ATTEMPT-node-$node" \
|
|
120
|
+
--dir "$RUNNER_TEMP/reports/$node"
|
|
121
|
+
done
|
|
122
|
+
node src/commands/qualified-publication.mjs download \
|
|
123
|
+
"$GITHUB_REPOSITORY" "$RELEASE_TAG" "$GITHUB_SHA" "$RELEASE_ASSET" \
|
|
124
|
+
"$RUNNER_TEMP/candidate" > "$RUNNER_TEMP/download.json"
|
|
125
|
+
- name: Reverify and publish the tested tarball without lifecycle hooks
|
|
126
|
+
env:
|
|
127
|
+
GH_TOKEN: ${{ github.token }}
|
|
128
|
+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
129
|
+
run: |
|
|
130
|
+
set -euo pipefail
|
|
131
|
+
node src/commands/qualified-publication.mjs publish \
|
|
132
|
+
"$GITHUB_REPOSITORY" "$RELEASE_TAG" "$GITHUB_SHA" "$RELEASE_ASSET" \
|
|
133
|
+
"$RUNNER_TEMP/candidate" \
|
|
134
|
+
"$RUNNER_TEMP/reports/20/report.json" \
|
|
135
|
+
"$RUNNER_TEMP/reports/22/report.json" \
|
|
136
|
+
"$RUNNER_TEMP/reports/24/report.json" > "$RUNNER_TEMP/publication.json"
|
|
137
|
+
- name: Retain publication identity
|
|
138
|
+
if: always()
|
|
139
|
+
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
|
140
|
+
with:
|
|
141
|
+
name: qualified-publication-${{ github.run_attempt }}
|
|
142
|
+
path: ${{ runner.temp }}/publication.json
|
|
143
|
+
if-no-files-found: warn
|
|
144
|
+
retention-days: 14
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.19.0](https://github.com/0xPlayerOne/code-foundry/compare/v1.18.0...v1.19.0) (2026-09-09)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* **fleet:** gate upgrades on verified release qualification ([#548](https://github.com/0xPlayerOne/code-foundry/issues/548)) ([3247cc6](https://github.com/0xPlayerOne/code-foundry/commit/3247cc6f2295c65c42bb43e4ec8f87a08f3dd02d))
|
|
9
|
+
|
|
10
|
+
## [1.18.0](https://github.com/0xPlayerOne/code-foundry/compare/v1.17.0...v1.18.0) (2026-09-09)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Features
|
|
14
|
+
|
|
15
|
+
* **releases:** stage qualified assets and publish verified identical archives ([#547](https://github.com/0xPlayerOne/code-foundry/issues/547)) ([dffcb83](https://github.com/0xPlayerOne/code-foundry/commit/dffcb834c8ee8c5c2bcba606350276e6a7de58d0))
|
|
16
|
+
|
|
3
17
|
## [1.17.0](https://github.com/0xPlayerOne/code-foundry/compare/v1.16.0...v1.17.0) (2026-09-09)
|
|
4
18
|
|
|
5
19
|
|
package/docs/PERFORMANCE.md
CHANGED
|
@@ -15,21 +15,19 @@ belong to the run that produced them, not the source tree.
|
|
|
15
15
|
| Format, lint, type-check, and build | 15 s | Bound the local CI feedback loop |
|
|
16
16
|
| Runtime dependencies | 0 | Keep the installed CLI dependency-free |
|
|
17
17
|
| Development dependencies | 4 | Prevent unreviewed toolchain growth |
|
|
18
|
-
| Packed artifact |
|
|
19
|
-
| Unpacked artifact |
|
|
18
|
+
| Packed artifact | 250 kB | Bound registry transfer and install cost |
|
|
19
|
+
| Unpacked artifact | 930 kB | Bound installed footprint |
|
|
20
20
|
| Packed files | 110 | Detect accidental release contents |
|
|
21
21
|
|
|
22
22
|
The performance workflow disables build-cache reads and writes for this task.
|
|
23
23
|
That makes timing comparisons independent of a warm protected-branch cache and
|
|
24
24
|
prevents benchmark code from populating shared cache entries.
|
|
25
25
|
|
|
26
|
-
The
|
|
27
|
-
|
|
28
|
-
packed bytes,
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
packed bytes, 890,418 unpacked bytes, and 103 files on Node 24.18.0, so the
|
|
32
|
-
limits remain intentionally bounded while covering both opt-in feature sets.
|
|
26
|
+
The merged candidate includes the release-integrity verifier, fleet eligibility,
|
|
27
|
+
consumer qualification harness, product-quality profiles, and qualified
|
|
28
|
+
publication workflow. It measured 243,466 packed bytes, 930,389 unpacked bytes,
|
|
29
|
+
and 109 files on Node 24.18.0; the 250 kB, 945 kB, and 110-file limits retain a
|
|
30
|
+
small margin while continuing to bound package growth.
|
|
33
31
|
|
|
34
32
|
## v1.6.1 baseline
|
|
35
33
|
|
|
@@ -63,4 +61,6 @@ pull request, run the complete validation gate, and merge only when the new
|
|
|
63
61
|
result artifact is available. A Release Please PR then carries the change into
|
|
64
62
|
the next version. Never raise a budget solely to clear CI; include before/after
|
|
65
63
|
measurements and the expected effect on local feedback, hosted runner time,
|
|
66
|
-
package transfer, or installed footprint.
|
|
64
|
+
package transfer, or installed footprint. The current budget covers the measured
|
|
65
|
+
combined release-integrity, fleet, consumer-qualification, and product-quality
|
|
66
|
+
package footprint described above.
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# Fleet Release Eligibility
|
|
2
|
+
|
|
3
|
+
Require a verified, qualified runtime before any fleet upgrade mutates repositories.
|
|
4
|
+
|
|
5
|
+
**Dependency:** Installed release-integrity verifier (#537).
|
|
6
|
+
**Activation:** Consumer-owned `.code-foundry-release-policy.json` at the fleet root.
|
|
7
|
+
|
|
8
|
+
The public `upgradeFleet` entrypoint now applies an optional release eligibility
|
|
9
|
+
guard before delegating to the unchanged legacy/manifest rollout implementation.
|
|
10
|
+
The implementation was moved verbatim to `fleet-core.mjs`; discovery exports and
|
|
11
|
+
existing callers retain their interface. No policy file means existing behavior.
|
|
12
|
+
A malformed or symlinked policy is an error, not an opt-out. Dry runs use the same
|
|
13
|
+
guard, and `--force` cannot bypass source identity or qualification.
|
|
14
|
+
|
|
15
|
+
```json
|
|
16
|
+
{
|
|
17
|
+
"schema_version": 1,
|
|
18
|
+
"repository": "owner/code-foundry",
|
|
19
|
+
"workflow": ".github/workflows/release_self-ci.yml",
|
|
20
|
+
"branch": "main",
|
|
21
|
+
"required_jobs": [
|
|
22
|
+
"Qualify consumers / Node 20",
|
|
23
|
+
"Qualify consumers / Node 22",
|
|
24
|
+
"Qualify consumers / Node 24"
|
|
25
|
+
]
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
This is a shape example, not a completed fleet policy. Populate canonical repository
|
|
30
|
+
identity, actual workflow path, and exact job names from the qualified release
|
|
31
|
+
caller's Actions API results. Include the publication job when successful registry
|
|
32
|
+
publication is required before adoption. Require every relevant job: a workflow's
|
|
33
|
+
aggregate success alone can conceal skipped jobs. The qualification gate in #544
|
|
34
|
+
and verified publisher in #547 provide the producer side of this policy.
|
|
35
|
+
|
|
36
|
+
Keep the policy alongside the **consumer workspace's** fleet inventory. Do not put
|
|
37
|
+
private repository names, local layouts, or deployment credentials into this
|
|
38
|
+
reusable baseline. Commit policy changes for review; the guard does not secretly
|
|
39
|
+
change branch protection, environments, rollout cohorts, or required capabilities.
|
|
40
|
+
|
|
41
|
+
## What is verified
|
|
42
|
+
|
|
43
|
+
The proposed runtime must be a clean Git source checkout. Its exact HEAD must
|
|
44
|
+
match the cryptographically verified immutable release tag. An installed npm
|
|
45
|
+
package without Git metadata cannot substitute for a verifiable source checkout;
|
|
46
|
+
use the existing `--source` option with the checked-out release. The verifier comes
|
|
47
|
+
from the trusted running Foundry installation, not the unverified candidate.
|
|
48
|
+
|
|
49
|
+
The guard resolves the configured workflow's identity, retrieves every page of
|
|
50
|
+
runs for the exact source, and selects the latest matching protected-branch push
|
|
51
|
+
or manual run. It rejects pending/failed/cancelled runs, fork provenance, disabled
|
|
52
|
+
workflows, missing/ambiguous/skipped jobs, and incomplete pagination. An older
|
|
53
|
+
success does not override a newer failed run. Job evidence comes from the precise
|
|
54
|
+
run attempt; that attempt is rechecked before proceeding. Source HEAD, cleanliness,
|
|
55
|
+
and the policy's bytes are rechecked before the upgrade callback executes.
|
|
56
|
+
|
|
57
|
+
Successful eligibility emits JSON to stderr with source SHA, policy digest,
|
|
58
|
+
workflow/run/attempt identifiers, and required job names. It does not write an
|
|
59
|
+
approval into the source or silently make PRs ready. Existing resumable rollouts,
|
|
60
|
+
canary validation, draft creation, and provenance markers stay in the original
|
|
61
|
+
fleet engine. The guard is policy enforcement in the supported public entrypoint,
|
|
62
|
+
not a sandbox against someone intentionally importing private implementation files
|
|
63
|
+
or using Git directly. Normal credentials still determine what remote mutations
|
|
64
|
+
are possible after eligibility passes.
|
|
65
|
+
|
|
66
|
+
## Activation and validation
|
|
67
|
+
|
|
68
|
+
First release and validate the qualification/verification producer and confirm
|
|
69
|
+
real workflow/job identities. Then opt a consumer workspace into this policy and
|
|
70
|
+
exercise `fleet upgrade --dry-run --root <fleet-root> --source <clean-release-checkout>`.
|
|
71
|
+
When `--source` is omitted, the installed package is used and an enabled policy
|
|
72
|
+
will reject it unless that installation is itself a clean Git checkout. Older mutable
|
|
73
|
+
releases or unavailable permissions should fail; do not weaken the policy just to
|
|
74
|
+
make an old release eligible. No real fleet inventory is fabricated by this PR.
|
|
75
|
+
|
|
76
|
+
The focused suite verifies guard ordering and failure propagation with GitHub/CLI
|
|
77
|
+
fixtures. Live authenticated verification, exact production job naming, and the
|
|
78
|
+
full existing fleet-engine suite must pass before activation. The original fleet
|
|
79
|
+
engine's Git blob is retained byte-for-byte; the source move still needs full
|
|
80
|
+
repository import/type-check/packaging validation in CI.
|
package/docs/fleet-rollouts.md
CHANGED
|
@@ -55,11 +55,12 @@ Adopt those requirements in consumer repositories before requiring them here.
|
|
|
55
55
|
|
|
56
56
|
```sh
|
|
57
57
|
code-foundry fleet status --root /path/to/fleet
|
|
58
|
-
code-foundry fleet upgrade --root /path/to/fleet --dry-run
|
|
59
|
-
code-foundry fleet upgrade --root /path/to/fleet --create-pr
|
|
58
|
+
code-foundry fleet upgrade --root /path/to/fleet --source /path/to/release-checkout --dry-run
|
|
59
|
+
code-foundry fleet upgrade --root /path/to/fleet --source /path/to/release-checkout --create-pr
|
|
60
60
|
```
|
|
61
61
|
|
|
62
|
-
|
|
62
|
+
Use `--source` to provide the clean Code Foundry release checkout used for the
|
|
63
|
+
upgrade; when omitted, the installed Code Foundry package is used. The existing
|
|
63
64
|
source-version guard still rejects mismatched `--version` requests. Manifest mode
|
|
64
65
|
requires `--create-pr` or `--dry-run` and never syncs original checkouts in place.
|
|
65
66
|
`--force` does not bypass dirty-tree safety in manifest mode. Dry-run reports
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# Qualified Publication
|
|
2
|
+
|
|
3
|
+
Publish only the same immutable Code Foundry archive that passed consumer qualification.
|
|
4
|
+
|
|
5
|
+
**Dependencies:** Consumer qualification (#544) and release integrity (#537).
|
|
6
|
+
**Activation:** Opt-in replacement publisher; no existing publisher is silently changed.
|
|
7
|
+
|
|
8
|
+
The reusable `qualified-foundry-publish.yml` downloads an explicitly named npm
|
|
9
|
+
archive from an already-published immutable release, verifies the release and
|
|
10
|
+
asset attestations against the caller's exact commit, qualifies that same archive
|
|
11
|
+
on Node 20/22/24, and only then admits a protected npm publication job. The final
|
|
12
|
+
job downloads only reports from its own workflow run **and run attempt**, rechecks
|
|
13
|
+
all required fixtures and archive/source identities, repeats cryptographic asset
|
|
14
|
+
verification, validates the package name/version, and publishes the tarball with
|
|
15
|
+
lifecycle scripts disabled. It never publishes a directory or rebuilds the package.
|
|
16
|
+
|
|
17
|
+
The publishing job uses a GitHub environment, serializes publication for a tag,
|
|
18
|
+
and has no dependency installation/build step. Qualification has no npm credential.
|
|
19
|
+
Normal npm trusted publishing is preferred; an explicit optional token supports
|
|
20
|
+
existing consumers. Configure the trusted publisher for the **actual caller and
|
|
21
|
+
reusable-workflow relationship** before enabling this route. Existing version
|
|
22
|
+
publication is not overwritten: retrying an already-published version fails rather
|
|
23
|
+
than treating a registry conflict or network error as proof of identity.
|
|
24
|
+
|
|
25
|
+
## Release producer contract
|
|
26
|
+
|
|
27
|
+
Build and test a package once, attach its tarball to a **draft** release, then
|
|
28
|
+
publish that release with immutability enabled. The archive must contain the
|
|
29
|
+
Code Foundry CLI/templates; this is intentionally not a generic package harness.
|
|
30
|
+
The candidate's `package.json` name must be `code-foundry` and its version must
|
|
31
|
+
match the explicit tag. All qualification modules must exist in the caller commit.
|
|
32
|
+
A recent GitHub CLI with `release verify` and `release verify-asset` is required;
|
|
33
|
+
missing support or authentication errors fail closed.
|
|
34
|
+
|
|
35
|
+
This workflow blocks npm publication, not a GitHub Release that was already
|
|
36
|
+
published. It does **not** add assets after an immutable release is published.
|
|
37
|
+
The `stage` command implements the draft-asset producer: it verifies qualification,
|
|
38
|
+
checks immutability without changing settings, resolves the existing tag to the
|
|
39
|
+
qualified commit, uploads the archive and digest-bound qualification receipt,
|
|
40
|
+
rechecks uploaded digests, then publishes and verifies the release. It requires
|
|
41
|
+
an existing draft and existing tag; it never creates/moves tags or overwrites
|
|
42
|
+
conflicting assets. Settings permission failures block writes. Matching existing
|
|
43
|
+
assets can resume staging; conflicting assets require manual reconciliation.
|
|
44
|
+
The pre-release gate in #544 supplies the required matrix reports.
|
|
45
|
+
The legacy Release Please workflow creates releases without
|
|
46
|
+
this archive staging step. Wire the new `stage` command into a draft-producing
|
|
47
|
+
caller instead of pointing the old direct-release caller at the new publisher:
|
|
48
|
+
|
|
49
|
+
```sh
|
|
50
|
+
node src/commands/qualified-publication.mjs stage \
|
|
51
|
+
"$GITHUB_REPOSITORY" "$TAG" "$SOURCE_SHA" "$ASSET" "$CANDIDATE_DIRECTORY" \
|
|
52
|
+
"$REPORT_NODE_20" "$REPORT_NODE_22" "$REPORT_NODE_24"
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
The stage command needs a credential with immutable-setting read, release
|
|
56
|
+
write, and attestation-read access; a normal Actions token may lack the
|
|
57
|
+
administration-read permission.
|
|
58
|
+
No elevated credential is installed or requested automatically. The producer must
|
|
59
|
+
also prevent concurrent tag mutation (for example with protected release-tag
|
|
60
|
+
rules and a single tag-scoped producer); GitHub does not offer an atomic
|
|
61
|
+
"publish this draft only if the tag still resolves to SHA" operation. The final
|
|
62
|
+
verification blocks npm if that invariant is violated, but cannot undo an
|
|
63
|
+
already-published immutable release.
|
|
64
|
+
|
|
65
|
+
Disable the old npm path before
|
|
66
|
+
activating the replacement to prevent racing publishers. None of those production
|
|
67
|
+
settings or existing release workflows are changed in this PR.
|
|
68
|
+
|
|
69
|
+
Example caller job after its release producer (illustrative job IDs):
|
|
70
|
+
|
|
71
|
+
```yaml
|
|
72
|
+
permissions:
|
|
73
|
+
actions: read
|
|
74
|
+
attestations: read
|
|
75
|
+
contents: read
|
|
76
|
+
id-token: write
|
|
77
|
+
jobs:
|
|
78
|
+
publish:
|
|
79
|
+
needs: release-producer
|
|
80
|
+
uses: ./.github/workflows/qualified-foundry-publish.yml
|
|
81
|
+
with:
|
|
82
|
+
tag: ${{ needs.release-producer.outputs.tag }}
|
|
83
|
+
asset: ${{ needs.release-producer.outputs.npm-asset }}
|
|
84
|
+
environment: npm
|
|
85
|
+
secrets:
|
|
86
|
+
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Only main-branch `push` and `workflow_dispatch` callers are admitted. Pull-request
|
|
90
|
+
events (including fork PRs), release-event shortcuts, and billing-paused runs
|
|
91
|
+
cannot publish through this workflow. The event guard does not itself verify
|
|
92
|
+
branch protection or prohibit a fork's independent main-branch workflow; configure
|
|
93
|
+
branch/environment protections and registry publisher identity separately.
|
|
94
|
+
The tag must resolve to `github.sha`, not a caller-selected old commit. To retry
|
|
95
|
+
an older release after main moves, use a separately reviewed recovery procedure;
|
|
96
|
+
do not weaken the identity gate ad hoc.
|
|
97
|
+
|
|
98
|
+
## Local policy tests and trust
|
|
99
|
+
|
|
100
|
+
`node --test test/qualified-publication.test.mjs` uses CLI/verifier fixtures to
|
|
101
|
+
exercise missing matrix members, skipped checks, absent Actionlint, changed
|
|
102
|
+
archives, invalid asset names, wrong package identity, and prevention of npm
|
|
103
|
+
execution before verification. These tests do not establish live GitHub signing,
|
|
104
|
+
OIDC permissions, registry publication, or runner tool availability.
|
|
105
|
+
|
|
106
|
+
Qualification reports are not standalone signatures: they are trusted only after
|
|
107
|
+
selection from this workflow's successful jobs in the same run attempt. Supplying
|
|
108
|
+
arbitrary local JSON to the library is not a security boundary. The reusable
|
|
109
|
+
workflow and its caller must be reviewed/trusted and protected; repository-owned
|
|
110
|
+
code executes with the permissions of its job. No credentials or production
|
|
111
|
+
resources were configured by adding this feature.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "code-foundry",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.19.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": {
|
package/src/cli.mjs
CHANGED
|
@@ -7,7 +7,7 @@ import { fileURLToPath } from 'node:url'
|
|
|
7
7
|
|
|
8
8
|
const packageRoot = resolve(fileURLToPath(new URL('..', import.meta.url)))
|
|
9
9
|
|
|
10
|
-
/** @typedef {{ target: string, root: string, dryRun: boolean, force: boolean, github: boolean, createPr: boolean, exclude: string[], base: string, head: string, tag: string, workflow: string, mode: string, version: string, repository: string, expectedSha: string, assets: string[], rootProvided: boolean, releaseSubcommand?: string, fleetSubcommand?: string, integritySubcommand?: 'settings'|'release'|'manifest', ciSubcommand?: 'pause'|'resume'|'status' }} Options */
|
|
10
|
+
/** @typedef {{ target: string, root: string, source: string, dryRun: boolean, force: boolean, github: boolean, createPr: boolean, exclude: string[], base: string, head: string, tag: string, workflow: string, mode: string, version: string, versionExplicit: boolean, repository: string, expectedSha: string, assets: string[], rootProvided: boolean, releaseSubcommand?: string, fleetSubcommand?: string, integritySubcommand?: 'settings'|'release'|'manifest', ciSubcommand?: 'pause'|'resume'|'status' }} Options */
|
|
11
11
|
/** @typedef {{ command: string, options: Options }} ParsedArgs */
|
|
12
12
|
|
|
13
13
|
const usage = `code-foundry — initialize and maintain agent-ready repositories
|
|
@@ -25,7 +25,7 @@ Usage:
|
|
|
25
25
|
npx code-foundry release-integrity release --repo OWNER/REPO --tag TAG
|
|
26
26
|
npx code-foundry release-integrity manifest --asset PATH [--root PATH]
|
|
27
27
|
npx code-foundry fleet status [--root PATH]
|
|
28
|
-
npx code-foundry fleet upgrade [--root PATH] [--dry-run] [--create-pr]
|
|
28
|
+
npx code-foundry fleet upgrade [--root PATH] [--source PATH] [--dry-run] [--create-pr]
|
|
29
29
|
|
|
30
30
|
The repository configuration lives in .github/code-foundry.yml.
|
|
31
31
|
init detects the repository, creates that file, and renders the baseline.
|
|
@@ -45,6 +45,7 @@ Options:
|
|
|
45
45
|
--expected-sha SHA Expected source commit for release-integrity verification
|
|
46
46
|
--asset PATH Selected release-integrity artifact (repeatable)
|
|
47
47
|
--root PATH Fleet root containing repositories (default: current directory)
|
|
48
|
+
--source PATH Clean Code Foundry release checkout used for fleet upgrades
|
|
48
49
|
--create-pr Create isolated upgrade branches and pull requests
|
|
49
50
|
--version TAG Runtime tag to report in fleet upgrade branches
|
|
50
51
|
--exclude NAME Skip a repository path or owner/name in fleet operations
|
|
@@ -66,6 +67,7 @@ function parseArgs(argv) {
|
|
|
66
67
|
const options = {
|
|
67
68
|
target: process.cwd(),
|
|
68
69
|
root: process.cwd(),
|
|
70
|
+
source: packageRoot,
|
|
69
71
|
dryRun: false,
|
|
70
72
|
force: false,
|
|
71
73
|
github: false,
|
|
@@ -77,6 +79,7 @@ function parseArgs(argv) {
|
|
|
77
79
|
workflow: '',
|
|
78
80
|
mode: 'auto',
|
|
79
81
|
version: `v${readPackageVersion(packageRoot)}`,
|
|
82
|
+
versionExplicit: false,
|
|
80
83
|
repository: '',
|
|
81
84
|
expectedSha: '',
|
|
82
85
|
assets: [],
|
|
@@ -146,9 +149,17 @@ function parseArgs(argv) {
|
|
|
146
149
|
else if (arg === '--root') {
|
|
147
150
|
options.root = argv.shift() ?? fail('--root requires a path')
|
|
148
151
|
options.rootProvided = true
|
|
152
|
+
} else if (arg === '--source') {
|
|
153
|
+
if (command !== 'fleet' || options.fleetSubcommand !== 'upgrade')
|
|
154
|
+
fail('--source is only supported by fleet upgrade')
|
|
155
|
+
const value = argv.shift()
|
|
156
|
+
if (!value || value.startsWith('-')) fail('--source requires a path')
|
|
157
|
+
options.source = value
|
|
149
158
|
} else if (arg === '--create-pr') options.createPr = true
|
|
150
|
-
else if (arg === '--version')
|
|
151
|
-
|
|
159
|
+
else if (arg === '--version') {
|
|
160
|
+
options.version = argv.shift() ?? fail('--version requires a tag')
|
|
161
|
+
options.versionExplicit = true
|
|
162
|
+
} else if (arg === '--exclude')
|
|
152
163
|
options.exclude.push(argv.shift() ?? fail('--exclude requires a name'))
|
|
153
164
|
else fail(`unknown option: ${arg}; run --help for the supported options`)
|
|
154
165
|
}
|
|
@@ -241,11 +252,13 @@ async function main() {
|
|
|
241
252
|
if (options.fleetSubcommand === 'status')
|
|
242
253
|
console.log(JSON.stringify(discoverRepositories(root), null, 2))
|
|
243
254
|
else
|
|
244
|
-
upgradeFleet(root,
|
|
255
|
+
upgradeFleet(root, resolve(options.source), {
|
|
245
256
|
createPr: options.createPr,
|
|
246
257
|
dryRun: options.dryRun,
|
|
247
258
|
force: options.force,
|
|
248
|
-
version: options.
|
|
259
|
+
version: options.versionExplicit
|
|
260
|
+
? options.version
|
|
261
|
+
: `v${readPackageVersion(resolve(options.source))}`,
|
|
249
262
|
exclude: options.exclude,
|
|
250
263
|
})
|
|
251
264
|
} catch (error) {
|