From 7dcba9f905fd9aef0d37d79023207327bd5d4d9b Mon Sep 17 00:00:00 2001 From: lzrs Date: Tue, 28 Jul 2026 12:51:54 -0700 Subject: [PATCH 1/2] sdg(liaison): update PHILOSOPHY.md --- specs/PHILOSOPHY.md | 1 + 1 file changed, 1 insertion(+) diff --git a/specs/PHILOSOPHY.md b/specs/PHILOSOPHY.md index 2fa277d..9afa27f 100644 --- a/specs/PHILOSOPHY.md +++ b/specs/PHILOSOPHY.md @@ -12,4 +12,5 @@ IMPORTANT: This file may only be edited and interpreted by Liaison. Only Liaison - Developer's default working mode for operational setup (stated 2026-07-28): "do as much for me as you can and then let me know what i need to do manually" — automate everything the process can, then hand Developer one concise checklist of only the steps requiring their accounts/credentials, rather than asking permission step-by-step along the way. - Developer does docs/README/website work outside the SDG process on `claude/*` branches and merges it to `main` directly (first: modularcloud/xspec PR #1, merged 2026-07-27). Such branches and any related working-tree WIP are Developer's space: SDG agents never fold them into SDG commits, never clean them up, and never re-raise them as anomalies. Ruling 2026-07-28 ("disregard work in progress, just publish main branch"): when unrelated local WIP sits in the checkout, ignore it — leave it on disk, never commit it, never delete it — and base all process work on `origin/main` (using a clean worktree or equivalent if the dirty checkout blocks branch mechanics). - Operational and infrastructure setup work is not a patch in Developer's eyes: "this is not intended to be a patch, its just a one off set up task" (2026-07-28, correcting the npm-publishing work after triage drafted a Bug Report patch for it). Work whose substance is release/deploy/distribution machinery with zero product-behavior change routes as one-off release/devops execution under DEVOPS.md — not through the patch pipeline — and any patch artifacts created by such a misclassification are retired, not refined. Reserve the patch taxonomy for changes to specified product behavior (IP) or to the harness's ability to catch defects (Bug Report). +- Developer prefers tokenless, workload-identity credentials over long-lived stored secrets for automation: asked "can we use OIDC?" (2026-07-28) immediately after receiving the NPM_TOKEN setup checklist, choosing npm Trusted Publishing over a stored automation token. General rule: when a platform offers an OIDC/trusted-publisher path for a credential the process manages, default to it — long-lived secrets are bootstrap-only fallbacks, and Developer checklists should not require creating or rotating a token that workload identity can replace. - Refinement loops that plateau are closed by valve ruling, not run to a spontaneous clean round (first applied 2026-07-10, TEST-SPEC.md at iteration 12 of the xspec initial build). Plateau markers: each fresh review yields only one or two genuine but ever-narrower findings, nothing is re-litigated or reversed, and the upstream documents are already converged. Closure shape: one final iteration whose Driver applies what is necessary and then HALTs, with escape hatches for blocking upstream problems or an indefensible late discovery; residual gaps are deliberately left to the downstream problems-file net, which finds them with implementation eyes when they actually matter. Basis: Developer's revealed preference for bounded forward progress over open-ended polishing (bare "continue" nudges, cost sensitivity shown by the 2026-07-09 credits outage, full delegation of process judgment). From 32cbdaf0dc6fbfc0b8c2253776bff29dba898242 Mon Sep 17 00:00:00 2001 From: lzrs Date: Tue, 28 Jul 2026 13:03:57 -0700 Subject: [PATCH 2/2] sdg(phase-11): switch npm releases to trusted publishing (OIDC) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Publishing now authenticates via npm Trusted Publishing: the Release job upgrades to npm >= 11.5.1 (Node 22 bundles npm 10, which cannot do the OIDC exchange) and npm publish trades the workflow's GitHub Actions OIDC token for short-lived publish credentials — no long-lived secret. The NPM_TOKEN secret is demoted to a bootstrap-only fallback, honored automatically only while the OIDC exchange is unavailable; missing credentials fail the run loudly with setup instructions, never a silent skip. Trigger, green-CI gate, strictly increasing versioning, and vX.Y.Z tagging are unchanged. DEVOPS.md records the new standing credential model and the one-time bootstrap (local tokenless publish preferred, CI-with-token alternative), superseding the token-secret model. Developer ruling 2026-07-28; covered by the standing npm authorization (same artifact, same trigger — only the credential mechanism changes). Co-Authored-By: Claude Fable 5 --- .github/workflows/release.yml | 60 +++++++++++++++++++++++++---------- specs/DEVOPS.md | 5 +-- 2 files changed, 47 insertions(+), 18 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c9996d1..5004fa0 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -14,10 +14,19 @@ # with the patch component incremented. The registry and the # vX.Y.Z tags are the record of released versions; the bump is # not committed back to main. -# Credential the NPM_TOKEN repository secret (granular npm automation -# token). Missing secret fails loudly with instructions — a -# release is never silently skipped. Re-running a failed run is -# safe: the registry refuses to republish an existing version. +# Credential npm Trusted Publishing (OIDC) — no long-lived secret. The job +# upgrades npm to >= 11.5.1 (Node 22 bundles npm 10, which +# cannot do the OIDC exchange); npm publish then trades the +# workflow's GitHub Actions OIDC token for short-lived publish +# credentials, minted only for the Trusted Publisher configured +# on npmjs.com (owner modularcloud, repository xspec, workflow +# release.yml). The NPM_TOKEN repository secret is a +# bootstrap-only fallback, honored automatically when the OIDC +# exchange is unavailable (e.g. before the Trusted Publisher is +# configured). Publish failures fail the run loudly with setup +# instructions — a release is never silently skipped — and +# re-running a failed run is safe: the registry refuses to +# republish an existing version. name: Release on: @@ -26,6 +35,8 @@ on: types: [completed] branches: [main] +# contents: write pushes the vX.Y.Z tag; id-token: write lets npm publish +# mint the GitHub OIDC token used for trusted publishing and provenance. permissions: contents: write id-token: write @@ -45,14 +56,6 @@ jobs: github.event.workflow_run.event == 'push' && github.event.workflow_run.head_branch == 'main' steps: - - name: Require the NPM_TOKEN repository secret - env: - NPM_TOKEN: ${{ secrets.NPM_TOKEN }} - run: | - if [ -z "$NPM_TOKEN" ]; then - echo "::error::The NPM_TOKEN repository secret is not set, so this green main commit was NOT published. Complete the one-time setup in specs/DEVOPS.md (npm releases — credentials), then re-run this workflow run to publish this commit." - exit 1 - fi - uses: actions/checkout@v4 with: ref: ${{ github.event.workflow_run.head_sha }} @@ -60,10 +63,23 @@ jobs: with: node-version: 22 cache: npm - registry-url: https://registry.npmjs.org - run: npm ci - name: Build the product run: npm run build + # The install and build above run on the stock toolchain CI validated; + # the upgrade below affects publishing only. + - name: Upgrade npm for trusted publishing (the OIDC exchange needs >= 11.5.1) + run: | + npm install -g "npm@^11.5.1" + V="$(npm --version)" + node -e ' + const [a, b, c] = process.argv[1].split(".").map(Number); + if (!(a > 11 || (a === 11 && (b > 5 || (b === 5 && c >= 1))))) { + console.error("::error::npm " + process.argv[1] + " cannot do the npm Trusted Publishing OIDC exchange (needs >= 11.5.1), so this green main commit was NOT published. Fix the npm upgrade step in .github/workflows/release.yml, then re-run this workflow run."); + process.exit(1); + } + ' "$V" + echo "Publishing with npm $V" - name: Compute the release version (strictly increasing) id: version run: | @@ -73,12 +89,24 @@ jobs: NEXT="$(node .github/scripts/next-version.mjs "$BASELINE" "$LATEST")" echo "Package $NAME — baseline $BASELINE, latest published ${LATEST:-}, releasing $NEXT" echo "next=$NEXT" >> "$GITHUB_OUTPUT" - - name: Publish to npm + - name: Publish to npm (OIDC trusted publishing; NPM_TOKEN as bootstrap fallback) env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} run: | + if [ -n "$NPM_TOKEN" ]; then + # Bootstrap-only fallback: npm >= 11.5.1 still attempts the OIDC + # exchange first and reaches for this token only when that + # exchange is unavailable. + echo "//registry.npmjs.org/:_authToken=\${NPM_TOKEN}" >> "$HOME/.npmrc" + echo "NPM_TOKEN secret found — kept as bootstrap fallback in case the OIDC exchange is not configured yet." + else + echo "No NPM_TOKEN secret — publishing via the OIDC trusted-publishing exchange." + fi npm version "${{ steps.version.outputs.next }}" --no-git-tag-version - npm publish --provenance --access public + if ! npm publish --provenance --access public; then + echo "::error::Publishing @modularcloud/xspec@${{ steps.version.outputs.next }} failed, so this green main commit was NOT released — see the npm error above. If it is an auth error (E401/E403/E404/ENEEDAUTH), the one-time npm-side setup (specs/DEVOPS.md — npm releases, Credentials) is incomplete: configure the package's Trusted Publisher on npmjs.com (GitHub Actions: owner modularcloud, repository xspec, workflow release.yml) — or, for bootstrap only, set the NPM_TOKEN repository secret. Then re-run this workflow run: it publishes exactly this commit, and duplicate attempts are safe because the registry refuses to republish an existing version." + exit 1 + fi - name: Tag the released commit run: | git tag "v${{ steps.version.outputs.next }}" "${{ github.event.workflow_run.head_sha }}" diff --git a/specs/DEVOPS.md b/specs/DEVOPS.md index 8cd43d6..3ea9594 100644 --- a/specs/DEVOPS.md +++ b/specs/DEVOPS.md @@ -24,8 +24,9 @@ The normal Release-phase flow for a completed patch is: - **Gate.** The publish job runs only when that CI run concluded successfully, and it checks out exactly the commit CI validated — the never-release-with-red-CI rule is enforced mechanically. Nothing beyond green CI gates a release. - **Versioning.** Strictly increasing, computed by `.github/scripts/next-version.mjs` with no Developer involvement: if `package.json`'s version is greater than the latest published version (or nothing is published yet), that version is released — a deliberate minor/major bump is made by landing the `package.json` change on `main`; otherwise the latest published version's patch component is incremented. The registry and the `vX.Y.Z` tags are the record of released versions; the computed bump is not committed back to `main`, so the in-repo version is the floor, not the record. - **Tags.** Each successful publish pushes the lightweight tag `vX.Y.Z` on the released commit, using the workflow's own repository token. -- **Credentials.** Publishing authenticates with the `NPM_TOKEN` GitHub repository secret — a granular npm automation token with read/write access to the package (or the `@modularcloud` scope), created under Developer's npm account. Credentials live only in that secret, never in the repository. If the token is missing or expired, recreate it on npmjs.com and set it again with `gh secret set NPM_TOKEN`. -- **Failure handling.** A missing/invalid secret or any publish error fails the Release run loudly — a release is never silently skipped. Re-running a failed Release run publishes the commit it was gated on and is always safe: the registry refuses to republish an existing version, so duplicate attempts fail rather than corrupt the sequence. A failed run never blocks later landings, which release independently. +- **Credentials.** npm Trusted Publishing (OIDC) — no long-lived secret. The package's Trusted Publisher on npmjs.com is this repository's Release workflow (GitHub Actions: owner `modularcloud`, repository `xspec`, workflow `release.yml`, no environment); `npm publish` in that workflow trades the job's GitHub OIDC token for short-lived publish credentials, and publishes carry provenance. The OIDC exchange needs npm >= 11.5.1, which the job installs itself (Node 22 bundles npm 10). The `NPM_TOKEN` repository secret is a **bootstrap-only fallback**: when present it is honored automatically, but only where the OIDC exchange is unavailable — its one legitimate use is a first-ever publish from CI, and once trusted publishing works it is revoked on npmjs.com and deleted with `gh secret delete NPM_TOKEN`. This model was chosen by Developer on 2026-07-28 and supersedes the earlier token-secret model recorded the same day; the standing npm authorization covers it unchanged (same artifact, same trigger — only the credential mechanism differs). +- **One-time bootstrap (until the package first exists on the registry).** Trusted publishing attaches to an existing package, so the first `@modularcloud/xspec` publish is a one-time Developer bootstrap; afterwards the standing OIDC path runs unaided. Preferred, tokenless: sign in on npmjs.com (creating the `modularcloud` org if needed), publish `0.1.0` locally from a clean checkout of a validated green `main` commit (`npm ci && npm run build && npm publish`), then configure the Trusted Publisher on the package's settings as above, and optionally restrict the package's publishing access to the trusted publisher only (disallow tokens). Alternative, from CI: create a granular npm automation token with read/write access to the package or the `@modularcloud` scope, set it with `gh secret set NPM_TOKEN`, re-run the failed Release run, then configure the Trusted Publisher, revoke the token, and delete the secret. Either way the switchover is automatic: the next green `main` landing publishes tokenless, or re-run a failed Release run to publish its gated commit. +- **Failure handling.** Any publish error — including an unconfigured Trusted Publisher with no fallback token present — fails the Release run loudly with setup instructions; a release is never silently skipped. Re-running a failed Release run publishes the commit it was gated on and is always safe: the registry refuses to republish an existing version, so duplicate attempts fail rather than corrupt the sequence. A failed run never blocks later landings, which release independently. ## Post-update actions