diff --git a/.changeset/quiet-caves-stay.md b/.changeset/quiet-caves-stay.md deleted file mode 100644 index 59ba99f..0000000 --- a/.changeset/quiet-caves-stay.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -"@opencoven/sdk-core": patch -"@opencoven/cave-client": patch ---- - -Add explicit managed-native credential custody for narrow native pairing, -credential status, and forget transports. Managed bridges retain pairing -secrets and bearers outside JavaScript while the SDK validates all exposed -non-secret results. - -Add browser-safe managed Cave and core subpaths plus a native discovery-source -adapter that validates owner-checked discovery bytes and metadata in the SDK. diff --git a/.changeset/sharp-berries-unite.md b/.changeset/sharp-berries-unite.md deleted file mode 100644 index 064435d..0000000 --- a/.changeset/sharp-berries-unite.md +++ /dev/null @@ -1,5 +0,0 @@ ---- - ---- - - diff --git a/README.md b/README.md index f198c31..0c4e6dc 100644 --- a/README.md +++ b/README.md @@ -14,17 +14,21 @@ infrastructure. ## Release status -This source repository is public, but all workspace packages are explicitly -marked private, are not published, and have standard publishing blocked. The -0.1 release inventory contains only `@opencoven/sdk-core`, -`@opencoven/cave-client`, `@opencoven/coven-client`, and `@opencoven/sdk`. -`@opencoven/dev-cli` remains private and is not packed, versioned with, or -published beside that group. Standard publishing also requires -`OPENCOVEN_RELEASE_AUTHORIZATION=publish`; remove or change these gates only as -part of an intentional release process. The 0.1 native Chat/real-authority -conformance release matrix is separately frozen to `darwin-arm64`, -`linux-x64`, and `win32-x64`; see [SUPPORT.md](SUPPORT.md) for the distinction -between Node runtime support and this release gate. +This source repository is public. The reviewed 0.1.0 release-unlock change +opens the repository publication lock — `release.config.json` sets +`publishingEnabled` to `true` and the 0.1 release inventory +(`@opencoven/sdk-core`, `@opencoven/cave-client`, `@opencoven/coven-client`, +and `@opencoven/sdk`) is marked publishable — but no package has been +published and nothing is published by that change alone: activation also +requires the protected `npm-release` environment approval, the +maintainer-authorized `sdk-v` tag at the reviewed commit, and a +release-workflow dispatch from `main`. `@opencoven/dev-cli` remains private +and is not packed, versioned with, or published beside that group. Standard +publishing also requires `OPENCOVEN_RELEASE_AUTHORIZATION=publish`; remove or +change these gates only as part of an intentional release process. The 0.1 +native Chat/real-authority conformance release matrix is separately frozen to +`darwin-arm64`, `linux-x64`, and `win32-x64`; see [SUPPORT.md](SUPPORT.md) for +the distinction between Node runtime support and this release gate. - [Roadmap](docs/ROADMAP.md) - [0.1 read-only release design](docs/superpowers/specs/2026-08-22-sdk-0.1-read-only-release-design.md) diff --git a/RELEASING.md b/RELEASING.md index 353195f..6cebe21 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -1,10 +1,16 @@ # Release process -The repository contains release-readiness automation, but this phase does not publish packages. -It does not create npm package records, configure GitHub -environments, or register trusted publishers. The four-package 0.1 release -group and the private CLI workspace all remain private, and the repository -publication lock remains closed. +The repository contains release-readiness automation. The reviewed 0.1.0 +release-unlock change deliberately opens the repository publication lock — +`publishingEnabled` is `true` in `release.config.json` and the four release +package manifests are publishable — while the second, independent deployment +lock remains in force: a normal publication still requires the protected +GitHub environment `npm-release` to approve the publish job. Opening the +repository lock does not create npm package records, configure GitHub +environments, or register trusted publishers, and it does not by itself tag, +publish, or unlock anything externally; activation happens only through the +maintainer-authorized `sdk-v` tag and a release-workflow dispatch +from `main`. `@opencoven/dev-cli` is not part of the 0.1 release group. Release tooling must not pack, publish, attest, or configure a trusted publisher for it. diff --git a/docs/release/sdk-0.1.0-release-authorization-record.md b/docs/release/sdk-0.1.0-release-authorization-record.md new file mode 100644 index 0000000..185dd12 --- /dev/null +++ b/docs/release/sdk-0.1.0-release-authorization-record.md @@ -0,0 +1,172 @@ +# SDK 0.1.0 first-release authorization record + +**Status: PENDING FRESH MAINTAINER AUTHORIZATION — nothing in this record +authorizes or performs any external mutation.** + +This record is the preparatory deliverable for +[OpenCoven/sdk#41](https://github.com/OpenCoven/sdk/issues/41) (parent +[#31](https://github.com/OpenCoven/sdk/issues/31)). The release decision itself +is **not** made here. Creating, assigning, or commenting on #41 is not +authorization. Every externally visible action listed in +[RELEASING.md](../../RELEASING.md) and in the +[0.1.0 no-publish rehearsal runbook](../workflows/first-release-rehearsal-runbook.md) +requires a fresh, explicit, current maintainer authorization recorded in the +#41 thread at execution time. + +Prepared by the #41 preparatory executor on the stacked branch +`release/sdk-0.1.0-unlock-41` (fork PR review artifact). No tag, no registry +mutation, no unlock activation, no release-workflow dispatch, and no secret +storage was performed or authorized by this record. + +## 1. Proposed release decision (pending) + +| Field | Value | State | +|---|---|---| +| Decision | Approve executing the first public SDK release sequence | **Pending fresh maintainer authorization** | +| Fixed-group version | `0.1.0` (exact, strict SemVer; pinned by `tests/package-manifests.spec.ts` and `tests/release-readiness.spec.ts`) | Proposed | +| Package set | `@opencoven/sdk-core`, `@opencoven/cave-client`, `@opencoven/coven-client`, `@opencoven/sdk` — in this canonical order (`release.config.json` `packages`; `scripts/repository-metadata.mjs` `PUBLIC_PACKAGES`) | Proposed | +| `@opencoven/dev-cli` | **Deferred** — stays private and outside the 0.1 release group. Recorded in-repo by commit `11952ee` ("build: defer private CLI from SDK 0.1 release (#57)", which removed dev-cli from the changesets fixed group), `RELEASING.md` ("`@opencoven/dev-cli` is not part of the 0.1 release group"), and `tests/package-manifests.spec.ts` (CLI stays `private: true`, owns the `opencoven` binary, and is excluded from `PUBLIC_PACKAGES`). What shipped: the CLI remains a private, source-tested workspace; the global `opencoven` binary is **not** part of the first public release, so issue #41's "global CLI behavior if released" validation criterion is **not applicable**. | Verified in-repo | +| Supported Node runtime | `>=24.18.0 <25` (`release.config.json` `supportedNode`; `.node-version` = `24.18.1`; `engines.node` on every workspace package, `engine-strict` in `.npmrc`) | Verified in-repo | +| OS / architecture matrix | The published packages are pure TypeScript/JavaScript: no `os`/`cpu` fields, no `optionalDependencies` native artifacts (verified in the four manifests; see the security review's Native/CLI boundary). Runtime support follows the Node line above on any OS/architecture Node 24.18–24.x supports. Separately, the 0.1 **native Chat/real-authority conformance release-gate matrix** is frozen to `darwin-arm64`, `linux-x64`, `win32-x64` (`release.config.json` `nativeConformancePlatforms`; enforced verbatim by `scripts/release-readiness.mjs`) — this gate matrix, not runtime support, is what finding F-1 blocks on. | Verified in-repo | +| Launch criteria | All of §4 below hold at execution time | Pending | +| Rollback owner | Repository owner/maintainer `@BunsDev` (sole CODEOWNERS owner of `release.config.json`, `scripts/*release*`, `.github/workflows/`; sole required reviewer of the `npm-release` environment). npm rollback follows RELEASING.md §10–11 (deprecate, dist-tag move only after review, corrective release). | Proposed — **requires maintainer confirmation** of the named human | + +## 2. Governance prerequisites — verified with evidence + +### 2a. Verified directly from repository config and workflow files + +- **Two independent locks, by construction.** Repository lock: + `release.config.json` `publishingEnabled` plus the four manifests' + `private` flag, validated on both sides by + `scripts/release-readiness.mjs:268-277`. Deployment lock: the release + workflow's publish job runs only with `environment: npm-release` + (`.github/workflows/release.yml:82`), after `needs: preflight`, only from + `main` (`release.yml:42-46`), and only for `inputs.mode == 'publish'` + (`release.yml:78`). This PR opens the first lock as a reviewed change; it + does not touch the second. +- **Protected-environment and OIDC requirements preserved.** The publish job + keeps `contents: read`, `id-token: write`, `attestations: write` + (`release.yml:85-88`), re-validates publication gates before publishing + (`release.yml:107-113`), and the readiness validator fails if the publish + job's environment or permission block is weakened + (`scripts/release-readiness.mjs:128-172`). Untouched by this change. +- **No token fallback, by construction.** `scripts/publish-release-artifacts.mjs:58-62` + throws when `NPM_TOKEN` or `NODE_AUTH_TOKEN` is present; npm is invoked only + with `--provenance` and exact reviewed tarballs + (`publish-release-artifacts.mjs:26-34`). No workflow file references either + variable; no npm/GitHub secret is stored or requested by this change. +- **Defense-in-depth authorization gate.** Every package manifest keeps + `prepublishOnly: node ../../scripts/require-release-authorization.mjs`, and + the publish script requires `OPENCOVEN_RELEASE_AUTHORIZATION=publish` + (`publish-release-artifacts.mjs:55-57`). Finding F-3 of the security review + records this as in-workflow defense-in-depth, not an independent control. +- **Least-privilege, SHA-pinned automation.** All `uses:` pins in both + workflows are full commit SHAs (`tests/workflow-pins.spec.ts` enforces); + workflow permissions are `contents: read` except the publish job. +- **Release ownership.** `.github/CODEOWNERS` assigns + `/.github/workflows/`, `/release.config.json`, and `/scripts/*release*` to + `@BunsDev`. +- **Fixed-version group mechanics.** `.changeset/config.json` declares exactly + the four release packages as one `fixed` group with exact + `workspace:0.1.0` internal ranges (enforced by + `scripts/release-readiness.mjs` and `tests/package-manifests.spec.ts`). + The pre-release changesets were consumed into the `## 0.1.0` changelog + entries by this PR, so no version bump is pending. + +### 2b. Verified live from the GitHub API with the executor token (read-only, 2026-08-30) + +- **`npm-release` environment exists and is protected** + (`GET /repos/OpenCoven/sdk/environments` → 200): environment `npm-release` + (created 2026-08-28T11:54:54Z), `can_admins_bypass: false`, protection rules + = `required_reviewers` with `prevent_self_review: true` and reviewer + `BunsDev`, plus a branch policy restricted to protected branches. This + matches the maintainer record in + [#41 (comment 5452204150)](https://github.com/OpenCoven/sdk/issues/41#issuecomment-5452204150). +- **Companion publication environments exist**: `publication-candidate` and + `npm-publish` (both created 2026-08-30T00:00Z, `can_admins_bypass: false`, + branch policy restricted to protected branches) — consistent with PR + [#74](https://OpenCoven/sdk/pull/74)'s environment-policy receipts. They are + recorded here as observed state; this change binds nothing to them. +- **Release workflow is active** (`GET /repos/OpenCoven/sdk/actions/workflows` + → 200; `.github/workflows/release.yml`, state `active`). + +### 2c. Recorded by the maintainer in the #41 thread (point-in-time; re-confirm at execution) + +Source: [#41 (comment 5452182687)](https://github.com/OpenCoven/sdk/issues/41#issuecomment-5452182687) +and [#41 (comment 5452204150)](https://github.com/OpenCoven/sdk/issues/41#issuecomment-5452204150), +both by `BunsDev` on 2026-08-28: + +- npm organization ownership is held and org-level **auth-and-writes 2FA** is + enabled. +- All four package names returned registry `E404` — no name conflict; the + documented one-time bootstrap remains required for first publish. +- Before 11:54Z on 2026-08-28 the `npm-release` environment did not exist; it + was then created restricted to `main` with deployment review required (since + independently re-verified live in §2b). + +## 3. Requires maintainer confirmation (not independently verifiable or not yet decided) + +The executor token cannot read these, or no decision exists yet. None is +assumed, guessed, or pre-authorized: + +1. **Branch protection / required checks on `main`.** + `GET /repos/OpenCoven/sdk/branches/main/protection` → 403 ("Resource not + accessible by personal access token"). The 2026-08-28 maintainer audit + records that `main` requires the two Node verification jobs, CodeQL, and + linear history, but **zero approving reviews**. The maintainer must confirm + the branch-protection decision for the release (in particular whether pull + request approval is required before activation merges). +2. **One-time bootstrap owner.** The named human who will hold the + least-privilege, 2FA-protected, revoke-after-use npm automation credential + for the first publish has not been recorded. Must be named in the #41 + thread at execution time. +3. **npm organization/package ownership state at execution time** — org + membership, 2FA, and name availability are point-in-time facts recorded + 2026-08-28; re-confirm immediately before bootstrap. +4. **`npm-release` reviewer binding at execution time** — confirmed live in + §2b, but reviewer assignment is a mutable setting; re-confirm before the + publish-mode dispatch. +5. **Resolution or formal waiver of finding F-1** — the inherited #38 gap: + Chat schema-v2 real-authority records on `darwin-arm64`, `linux-x64`, and + `win32-x64`, the aggregate record, and the PR #74 `conformanceEvidence` + gate. Only a maintainer can resolve or formally waive this before any + external mutation. +6. **Artifact retention count.** The issue asks for "five-or-approved-count + tarballs". The canonical system produces **four** package tarballs plus + `release-manifest.json`, checksums, and attestations. The approved retention + count must be confirmed (the runbook default: the immutable workflow + artifact `opencoven-sdk-0.1.0`, retention extended from the workflow's 7 + days per the maintainer's approved count). +7. **Rollback owner confirmation** — the named human accepting RELEASING.md + §10–11 duty. + +## 4. Launch criteria (all must hold before any external mutation) + +1. Fresh, explicit, current maintainer authorization recorded in #41 for the + specific mutation class about to be executed (tag; verify-mode dispatch; + bootstrap publish; trusted-publisher configuration; normal publication), + per the runbook's stop points. +2. Fork PR #1 (security/first-release-review-40) and this unlock PR are merged + upstream; this branch is rebased on `origin/main` per the PR body's stack + instructions; `corepack pnpm@10.34.0 verify` + `git diff --exit-code` pass + in a clean checkout of the release commit. +3. Finding F-1 (#38 platform records on `darwin-arm64`, `linux-x64`, + `win32-x64`, aggregate record through the PR #74 gate) is **resolved or + formally waived by a maintainer**. +4. `npm-release` environment protection re-verified live (§2b state) with a + named, available reviewer. +5. Branch-protection decision confirmed (§3 item 1). +6. Bootstrap owner named, credential handling plan confirmed (§3 item 2), and + the no-token-storage rules restated: no token in repository files, GitHub + secrets for future use, workflow logs, argv, or shell history; revoke + immediately after bootstrap. +7. Registry state re-proven unchanged (all four names `E404`) immediately + before bootstrap. + +## 5. What this record deliberately does not do + +It does not approve, schedule, or execute: tag creation, release-workflow +dispatch (verify or publish mode), npm bootstrap publishing, trusted-publisher +configuration, environment changes, branch-protection changes, secret storage, +or any registry mutation. Those remain behind the absolute stop in the #41 +prompt, the runbook's authorization gates, and the two-key system. diff --git a/docs/workflows/first-release-rehearsal-runbook.md b/docs/workflows/first-release-rehearsal-runbook.md new file mode 100644 index 0000000..8412462 --- /dev/null +++ b/docs/workflows/first-release-rehearsal-runbook.md @@ -0,0 +1,273 @@ +# SDK 0.1.0 no-publish rehearsal runbook and authorization-gated continuation + +**Status: PREPARED — NOTHING IN THIS RUNBOOK HAS BEEN EXECUTED EXTERNALLY.** +No `sdk-v0.1.0` tag exists, no release workflow has been dispatched, and no +registry mutation has occurred. Every step below that touches anything +external is behind an explicit authorization stop. + +This runbook is the operator procedure for issue +[OpenCoven/sdk#41](https://github.com/OpenCoven/sdk/issues/41) rehearsal steps +(issue sequence step 4) and documents the post-authorization continuation +(steps 5–7). The release decision is recorded, **pending fresh maintainer +authorization**, in the [0.1.0 release authorization record](../release/sdk-0.1.0-release-authorization-record.md). +Read that record first; this runbook assumes its launch criteria. + +The two-key system is authoritative: the reviewed unlock change opens the +repository lock (`publishingEnabled: true`, four publishable manifests); the +protected `npm-release` environment approval remains the independent second +key, reachable only through `.github/workflows/release.yml`. + +## 0. Constants + +| Constant | Value | +|---|---| +| Fixed-group version | `0.1.0` | +| Tag | `sdk-v0.1.0` (annotated, `tagPrefix` from `release.config.json`) | +| Packages, canonical publish order | `@opencoven/sdk-core` → `@opencoven/cave-client` → `@opencoven/coven-client` → `@opencoven/sdk` | +| Excluded | `@opencoven/dev-cli` (deferred from 0.1; never packed, published, attested, or trusted-publisher-configured) | +| Release workflow | `.github/workflows/release.yml` on `OpenCoven/sdk`, `workflow_dispatch` with `mode` (`verify` / `publish`) and `version` | +| Artifact name | `opencoven-sdk-0.1.0` (immutable workflow artifact: 4 tarballs + `release-manifest.json`) | +| Registry | `registry.npmjs.org`, access `public`, dist-tag `latest` | + +## 1. Local dry-run evidence (executed during preparation — non-mutating) + +Executed on the stacked branch at the review head with Node `v24.18.1` / +pnpm `10.34.0`, output to throwaway temp directories only: + +- `corepack pnpm@10.34.0 verify:release` → exit 0, summary + `{"version":"0.1.0","publishingEnabled":true,"packages":["@opencoven/sdk-core","@opencoven/cave-client","@opencoven/coven-client","@opencoven/sdk"]}`. +- `corepack pnpm@10.34.0 release:artifacts -- --output /artifacts --version 0.1.0 --skip-build` + → exit 0; four tarballs + manifest (digests in §4). Re-run into a second + temp dir produced a **byte-identical manifest** — pnpm pack normalizes tar + entry metadata (fixed epoch, uid/gid 0), so artifact digests are + reproducible across checkouts and the digest comparison in §5.6 is exact. +- `corepack pnpm@10.34.0 pack:public --skip-build --json-file /pack-public.json` + → exit 0; four tarballs in an owned temp root (note: unlike + `release:artifacts`, this script does not accept a leading `--` separator). +- Registry-unchanged proof: `npm view version` for all four package + names → `E404` (exit 1) each, on 2026-08-30. No registry state exists. + +No tag was created, no workflow was dispatched, no registry mutation occurred. + +## 2. Rehearsal procedure (issue step 4) — each external action gated + +### 2.0 Authorization stop A — tag creation + +Requires fresh maintainer authorization in #41 naming the tag `sdk-v0.1.0` +and the exact commit. Then, from a clean checkout of the reviewed release +commit on `main` (after the unlock PR has merged upstream): + +```bash +corepack pnpm@10.34.0 install --frozen-lockfile +corepack pnpm@10.34.0 verify # must exit 0 +git diff --exit-code # clean tree +git tag -a sdk-v0.1.0 -m "OpenCoven SDK 0.1.0 first public release" +git push origin sdk-v0.1.0 +node ./scripts/verify-release-readiness.mjs --version 0.1.0 --tag sdk-v0.1.0 --require-tag +``` + +The workflow itself re-validates that the tag resolves to the exact checked-out +`HEAD` (`scripts/release-readiness.mjs`, `--require-tag`), so a tag pointing +anywhere else fails closed. + +### 2.1 Authorization stop B — verify-mode dispatch + +Requires fresh maintainer authorization naming verify mode. Dispatch from +`main` only (the preflight job refuses any other ref): + +``` +POST /repos/OpenCoven/sdk/actions/workflows/release.yml/dispatches +{"ref":"main","inputs":{"mode":"verify","version":"0.1.0"}} +``` + +The `verify` mode job: canonical `corepack pnpm@10.34.0 verify`, tag-on-HEAD +validation, clean-tree check, artifact creation, upload of the immutable +`opencoven-sdk-0.1.0` artifact. Verify mode **never publishes** — the publish +job is skipped unless `mode == 'publish'`. + +### 2.2 Registry-unchanged proof + +Immediately before and after the verify run: + +```bash +npm view @opencoven/sdk-core version # expect E404 +npm view @opencoven/cave-client version # expect E404 +npm view @opencoven/coven-client version # expect E404 +npm view @opencoven/sdk version # expect E404 +``` + +Record the four E404 results (with timestamps) in #41. Any registry hit is a +halt condition and an incident. + +### 2.3 Artifact retention + +Download the workflow artifact `opencoven-sdk-0.1.0`: four tarballs plus +`release-manifest.json` (which carries name, version, byte size, and SHA-256 +per tarball). Retention notes: + +- The workflow sets `retention-days: 7`; extend or re-upload retention per the + maintainer's approved count (issue language: "five-or-approved-count + tarballs" — the canonical system produces **four** package tarballs plus the + manifest; the authorization record §3.6 asks the maintainer to confirm the + approved count). +- Build **attestations** are produced only on the publish path + (`actions/attest-build-provenance` in the `publish` job); verify mode has no + attestation step. The rehearsal record therefore consists of the artifact + bundle + digests; attestation evidence arrives with the post-authorization + steps below. + +### 2.4 Digest comparison with the reviewed candidate + +The reviewed candidate is the manifest recorded in §4 (generated by +`release:artifacts` at the reviewed commit). Re-generate it at the final +reviewed commit before tagging and confirm the four digests still match +(docs-only deltas do not enter packed tarballs, but confirm anyway). Then: + +1. Compare the downloaded workflow artifact's `release-manifest.json` + byte-for-byte with §4. +2. Independently re-compute `sha256sum` of each downloaded tarball and compare + with the manifest entries. + +Any mismatch is a halt condition: do not proceed toward publish; investigate +the delta between the tagged commit and the reviewed candidate. + +## 3. Post-authorization continuation (issue steps 5–7) + +Each subsection starts at its own authorization stop. Execute in order; a +failure at any step halts the sequence until dispositioned. + +### 3.1 Step 5 — one-time bootstrap publish (Authorization stop C) + +Requires: maintainer authorization naming the bootstrap owner; the bootstrap +owner named in #41; launch criteria from the authorization record confirmed +(including F-1 resolution or formal waiver and registry E404 re-proof). + +1. Obtain a least-privilege npm **automation** credential protected by the + account 2FA, scoped to the new packages only. **Never** place it in + repository files, GitHub secrets for future use, workflow logs, argv, or + shell history — supply it interactively or via a prompt-reading mechanism; + `revoke-after-use` is the maintainer's duty (record the revocation + timestamp in #41). +2. Publish the **exact reviewed tarballs** from the rehearsal artifact bundle + in canonical order, without rebuilding (the bytes must be the digest-matched + set from §2.4): + + ```bash + npm publish tarballs/core/opencoven-sdk-core-0.1.0.tgz --access public --tag latest + npm publish tarballs/cave/opencoven-cave-client-0.1.0.tgz --access public --tag latest + npm publish tarballs/coven/opencoven-coven-client-0.1.0.tgz --access public --tag latest + npm publish tarballs/sdk/opencoven-sdk-0.1.0.tgz --access public --tag latest + ``` + + (Bootstrap publishes without provenance — npm trusted publishing cannot be + configured before the packages exist; provenance arrives with step 3.2's + OIDC path. This two-phase design is RELEASING.md §6–7.) +3. Acceptance checks: `npm owner ls ` shows the expected org ownership ×4; + `npm view ` name/version/contents match the reviewed manifests; the + four `latest` dist-tags equal `0.1.0`. +4. Revoke the bootstrap credential immediately; preserve the audit trail + (command transcript without any secret material, credential id, revocation + proof) in #41. + +### 3.2 Step 6 — trusted publishing (Authorization stop D) + +Requires: maintainer authorization for trusted-publisher configuration. + +For each of the four packages, configure npm trusted publishing to: +repository `OpenCoven/sdk`, workflow `.github/workflows/release.yml`, +GitHub environment `npm-release`. Acceptance: all four packages report the +expected trusted-publisher identity; normal publication uses OIDC only +(`id-token: write` + `--provenance`) with **no** `NPM_TOKEN`/`NODE_AUTH_TOKEN` +fallback (the publish script rejects tokens by construction, and no such +secret may be created). + +### 3.3 Step 7 — registry/provenance validation (Authorization stop E) + +Requires: maintainer authorization to run the validation sequence (read-only +except any corrective action). + +- Versions and `latest` dist-tags = `0.1.0` ×4. +- Registry manifests: names, `AGPL-3.0-only OR MIT` license, changelogs, + export maps, dependency ranges (exact `0.1.0` internal ranges), engines + `>=24.18.0 <25`. +- Registry tarball SHA-256 values match the reviewed `release-manifest.json`. +- npm provenance and GitHub build attestations present and bound to + `OpenCoven/sdk` + `release.yml` + `npm-release`. +- Clean consumer validation: blank-consumer `npm install` of the four + published packages, ESM and type imports of every public entrypoint + (the packed-consumer fixtures in `scripts/verify-package.mjs` define the + expected surface). The global CLI check is **not applicable** — + `@opencoven/dev-cli` is deferred from 0.1. +- Record the workflow run, commit, tag, manifest, attestations, and results in + the release notes and #41; link evidence from #31 (which closes only after + validation evidence is linked). + +## 4. Reviewed candidate artifact digests + +Generated by `corepack pnpm@10.34.0 release:artifacts -- --output +--version 0.1.0 --skip-build` at the review head of the stacked unlock branch +(docs-only commits after this point do not affect packed bytes; re-run at the +final reviewed commit to confirm equality before tagging): + +```json +{ + "schemaVersion": 1, + "version": "0.1.0", + "packages": [ + { + "name": "@opencoven/sdk-core", + "version": "0.1.0", + "file": "tarballs/core/opencoven-sdk-core-0.1.0.tgz", + "size": 33431, + "sha256": "99e1daec61c53cf9ff2dc05cc359dc6d26eb44568488518eedbe12a6950d0aa5" + }, + { + "name": "@opencoven/cave-client", + "version": "0.1.0", + "file": "tarballs/cave/opencoven-cave-client-0.1.0.tgz", + "size": 81625, + "sha256": "541e0f497a168c843f9a15aff716cb88709556c2cda6cf3a19502519fb4ce5c1" + }, + { + "name": "@opencoven/coven-client", + "version": "0.1.0", + "file": "tarballs/coven/opencoven-coven-client-0.1.0.tgz", + "size": 33005, + "sha256": "2b2a0ea92924199f32c9950ae361b66f0eab0db7d368e2d22cfb3d8cfc6799fa" + }, + { + "name": "@opencoven/sdk", + "version": "0.1.0", + "file": "tarballs/sdk/opencoven-sdk-0.1.0.tgz", + "size": 15817, + "sha256": "eb869de25003f4c6143b925915de4c09ab21cb7beb02f191c0e85c60cb702124" + } + ] +} +``` + +## 5. Master authorization sentence + +When a maintainer authorizes the full sequence, the single sentence to record +in #41 is: + +> Authorized for OpenCoven/sdk#41: execute the SDK 0.1.0 first-public-release +> sequence exactly as written in +> `docs/workflows/first-release-rehearsal-runbook.md` — annotate and push tag +> `sdk-v0.1.0` at the reviewed commit, run the release workflow in verify +> mode, complete the one-time bootstrap publish with a named least-privilege +> credential revoked after use, configure npm trusted publishing for the four +> packages to `OpenCoven/sdk` + `release.yml` + the protected `npm-release` +> environment, and run registry/provenance validation — with the launch +> criteria confirmed and each step's stop re-confirmed. + +Absent that sentence (or an equally explicit one scoped to a specific step), +nothing in §2–§3 may be executed. + +## 6. Rollback and incident references + +npm versions are immutable; rollback follows RELEASING.md §10 (deprecate, +dist-tag move only after review, corrective release) and §11 (suspend the +`npm-release` environment, revoke credentials, preserve evidence, advisory, +postmortem before restoring publication). The rollback owner proposal and its +required confirmation are in the authorization record §1 and §3.7. diff --git a/packages/cave/CHANGELOG.md b/packages/cave/CHANGELOG.md index 8c56074..b3453d1 100644 --- a/packages/cave/CHANGELOG.md +++ b/packages/cave/CHANGELOG.md @@ -54,3 +54,10 @@ exposing strict HPKE discovery v2 authority metadata with browser Web Crypto. - Freeze the packed declaration, runtime exports, and package export map in the repository API baseline. +- Add explicit managed-native credential custody for narrow native pairing, + credential status, and forget transports. Managed bridges retain pairing + secrets and bearers outside JavaScript while the SDK validates all exposed + non-secret results. +- Add browser-safe managed Cave and core subpaths plus a native + discovery-source adapter that validates owner-checked discovery bytes and + metadata in the SDK. diff --git a/packages/cave/package.json b/packages/cave/package.json index a913a37..fc5f30e 100644 --- a/packages/cave/package.json +++ b/packages/cave/package.json @@ -1,7 +1,6 @@ { "name": "@opencoven/cave-client", "version": "0.1.0", - "private": true, "description": "Transport-neutral constrained Cave client", "type": "module", "sideEffects": false, diff --git a/packages/core/CHANGELOG.md b/packages/core/CHANGELOG.md index 473cd96..49b5d32 100644 --- a/packages/core/CHANGELOG.md +++ b/packages/core/CHANGELOG.md @@ -19,3 +19,10 @@ - Add a bounded versioned diagnostic report builder that emits only approved environment, phase, capability, version, instance-suffix, health timestamp, and safe error facts. +- Add explicit managed-native credential custody for narrow native pairing, + credential status, and forget transports. Managed bridges retain pairing + secrets and bearers outside JavaScript while the SDK validates all exposed + non-secret results. +- Add browser-safe managed Cave and core subpaths plus a native + discovery-source adapter that validates owner-checked discovery bytes and + metadata in the SDK. diff --git a/packages/core/package.json b/packages/core/package.json index edadc93..82fdd7b 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,7 +1,6 @@ { "name": "@opencoven/sdk-core", "version": "0.1.0", - "private": true, "description": "Transport-neutral OpenCoven SDK primitives", "type": "module", "sideEffects": false, diff --git a/packages/coven/package.json b/packages/coven/package.json index 89c263e..27e2f6a 100644 --- a/packages/coven/package.json +++ b/packages/coven/package.json @@ -1,7 +1,6 @@ { "name": "@opencoven/coven-client", "version": "0.1.0", - "private": true, "description": "Transport-neutral constrained Coven client", "type": "module", "sideEffects": false, diff --git a/packages/sdk/package.json b/packages/sdk/package.json index e4c6d23..57a4a7b 100644 --- a/packages/sdk/package.json +++ b/packages/sdk/package.json @@ -1,7 +1,6 @@ { "name": "@opencoven/sdk", "version": "0.1.0", - "private": true, "description": "Optional OpenCoven client coordinator", "type": "module", "sideEffects": false, diff --git a/release.config.json b/release.config.json index d71f889..c5ad7f7 100644 --- a/release.config.json +++ b/release.config.json @@ -1,6 +1,6 @@ { "schemaVersion": 2, - "publishingEnabled": false, + "publishingEnabled": true, "tagPrefix": "sdk-v", "npmAccess": "public", "npmDistTag": "latest", diff --git a/tests/package-manifests.spec.ts b/tests/package-manifests.spec.ts index a97f4fe..3bb46be 100644 --- a/tests/package-manifests.spec.ts +++ b/tests/package-manifests.spec.ts @@ -219,16 +219,19 @@ describe('workspace package manifests', () => { expect(vitestConfig).toContain('fileParallelism: false'); }); - test('keeps every release package unpublished until an intentional release change', () => { + test('marks the four release packages publishable behind the authorization gate', () => { expect(rootManifest.pnpm?.overrides?.esbuild).toMatch(EXACT_VERSION); + // The reviewed 0.1.0 release-unlock change intentionally unprivatizes the + // four release packages. The fail-closed lifecycle gate stays in place, and + // the private CLI remains outside the release inventory. for (const { manifestPath } of PUBLIC_PACKAGES) { const manifest = JSON.parse(readFileSync(resolve(workspaceRoot, manifestPath), 'utf8')) as { private?: boolean; scripts?: Record; }; - expect(manifest.private).toBe(true); + expect(manifest.private ?? false).toBe(false); expect(manifest.scripts?.prepublishOnly).toBe( 'node ../../scripts/require-release-authorization.mjs', ); diff --git a/tests/release-policy.spec.ts b/tests/release-policy.spec.ts index a53fe99..628ddc3 100644 --- a/tests/release-policy.spec.ts +++ b/tests/release-policy.spec.ts @@ -28,7 +28,7 @@ describe('release and support policies', () => { expect(security).toContain('redact'); }); - test('documents bootstrap, trusted publishing, rollback, and incidents', () => { + test('documents the unlock state, bootstrap, trusted publishing, rollback, and incidents', () => { const releasing = readDocument('RELEASING.md'); expect(releasing).toContain('First-publish bootstrap'); @@ -37,19 +37,19 @@ describe('release and support policies', () => { expect(releasing).toContain('deprecate'); expect(releasing).toContain('incident response'); expect(releasing).toContain('revoke'); - expect(releasing).toContain('does not publish packages'); + expect(releasing).toContain('opens the repository publication lock'); expect(releasing).toContain('creates four tarballs'); expect(releasing).toContain( '`@opencoven/dev-cli` is not part of the 0.1 release group', ); }); - test('links policies without claiming publication readiness', () => { + test('links policies while stating the reviewed unlock and remaining activation gates', () => { const readme = readDocument('README.md'); expect(readme).toContain('[Support policy](SUPPORT.md)'); expect(readme).toContain('[Security policy](SECURITY.md)'); expect(readme).toContain('[Release process](RELEASING.md)'); - expect(readme).toContain('not published'); + expect(readme).toContain('nothing is published by that change alone'); }); }); diff --git a/tests/release-readiness.spec.ts b/tests/release-readiness.spec.ts index 9279a78..28a1159 100644 --- a/tests/release-readiness.spec.ts +++ b/tests/release-readiness.spec.ts @@ -20,6 +20,7 @@ import type { NativeConformancePlatforms, ReleaseConfig, } from '../scripts/release-readiness.d.mts'; +import { createReleaseArtifacts } from '../scripts/create-release-artifacts.mjs'; import { createNpmPublishArgs, publishReleaseArtifacts, @@ -28,6 +29,7 @@ import { PUBLIC_PACKAGES } from '../scripts/repository-metadata.mjs'; const workspaceRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..'); const fixtures: string[] = []; +const artifactRoots: string[] = []; interface MutablePackageManifest { dependencies: Record; @@ -153,21 +155,45 @@ afterEach(() => { for (const fixture of fixtures.splice(0)) { rmSync(fixture, { recursive: true, force: true }); } + for (const artifactRoot of artifactRoots.splice(0)) { + rmSync(artifactRoot, { recursive: true, force: true }); + } }); +function createUnlockedArtifactRoot(): string { + const outputRoot = mkdtempSync( + resolve(tmpdir(), 'opencoven-release-readiness-unlock-'), + ); + artifactRoots.push(outputRoot); + createReleaseArtifacts({ + root: workspaceRoot, + outputRoot, + build: false, + }); + return outputRoot; +} + describe('release readiness contract', () => { - test('keeps publication disabled while packages are private', () => { + test('reflects the reviewed unlock of the repository publication lock', () => { const config = readReleaseConfig(workspaceRoot); - expect(config.publishingEnabled).toBe(false); - expect(() => + // The 0.1.0 release-unlock change opens the repository lock deliberately. + // Publication still requires the second, independent deployment lock: the + // protected `npm-release` environment approval, reached only through the + // release workflow's tag-verified publish job. + expect(config.publishingEnabled).toBe(true); + expect( validateReleaseReadiness({ root: workspaceRoot, mode: 'publish', version: '0.1.0', tag: 'sdk-v0.1.0', }), - ).toThrow('Release publishing is disabled by release.config.json'); + ).toEqual({ + version: '0.1.0', + publishingEnabled: true, + packages: PUBLIC_PACKAGES.map(({ packageName }) => packageName), + }); }); test('requires fixed versions and exact internal ranges', () => { @@ -345,21 +371,25 @@ describe('release readiness contract', () => { test('enforces package privacy on both sides of the publishing lock', () => { const fixture = createReleaseFixture(); - updateJson( - resolve(fixture, 'packages/core/package.json'), - (manifest) => { - manifest.private = false; - }, - ); + const configPath = resolve(fixture, 'release.config.json'); + // The reviewed base state is unlocked and publishable. Closing the lock + // while the manifests stay publishable must fail closed. + updateJson(configPath, (config) => { + config.publishingEnabled = false; + }); expect(() => validateReleaseReadiness({ root: fixture })).toThrow( '@opencoven/sdk-core must remain private while publishing is disabled', ); - updateJson( - resolve(fixture, 'release.config.json'), - (config) => { - config.publishingEnabled = true; + // Re-opening the lock while any release manifest is private must fail. + updateJson(configPath, (config) => { + config.publishingEnabled = true; + }); + updateJson( + resolve(fixture, 'packages/cave/package.json'), + (manifest) => { + manifest.private = true; }, ); expect(() => validateReleaseReadiness({ root: fixture })).toThrow( @@ -380,10 +410,10 @@ describe('release readiness contract', () => { ).toThrow('Release tag is required'); }); - test('returns the canonical locked release summary', () => { + test('returns the canonical unlocked release summary', () => { expect(validateReleaseReadiness({ root: workspaceRoot })).toEqual({ version: '0.1.0', - publishingEnabled: false, + publishingEnabled: true, packages: PUBLIC_PACKAGES.map(({ packageName }) => packageName), }); }); @@ -406,20 +436,64 @@ describe('release readiness contract', () => { ]); }); - test('never invokes npm while publication is locked', () => { + test('keeps npm uninvoked until the reviewed release artifacts verify', () => { expect(() => publishReleaseArtifacts({ root: workspaceRoot, - artifactRoot: '/tmp/missing-artifacts', + artifactRoot: resolve( + tmpdir(), + 'opencoven-release-readiness-missing-artifacts', + ), version: '0.1.0', env: { OPENCOVEN_RELEASE_AUTHORIZATION: 'publish' }, execute: () => { - throw new Error('must not execute while locked'); + throw new Error('must not execute before artifacts verify'); }, }), - ).toThrow('Release publishing is disabled by release.config.json'); + ).toThrow(/release-manifest\.json/); }); + test('forbids token-based npm authentication on the publish path', () => { + const outputRoot = createUnlockedArtifactRoot(); + + for (const tokenVariable of ['NPM_TOKEN', 'NODE_AUTH_TOKEN']) { + expect(() => + publishReleaseArtifacts({ + root: workspaceRoot, + artifactRoot: outputRoot, + version: '0.1.0', + env: { + OPENCOVEN_RELEASE_AUTHORIZATION: 'publish', + [tokenVariable]: 'synthetic-not-a-real-credential', + }, + execute: () => { + throw new Error('npm must not run'); + }, + }), + ).toThrow('Token-based npm authentication is forbidden for regular releases'); + } + }, 30_000); + + test('reaches the publish step only after artifact verification and authorization', () => { + const outputRoot = createUnlockedArtifactRoot(); + + // The repository lock is open, so the only remaining independent control is + // the protected `npm-release` environment approval in the release workflow. + // This spy proves the npm invocation is reached — and can only be reached — + // after readiness, artifact digests, and the authorization env var pass. + expect(() => + publishReleaseArtifacts({ + root: workspaceRoot, + artifactRoot: outputRoot, + version: '0.1.0', + env: { OPENCOVEN_RELEASE_AUTHORIZATION: 'publish' }, + execute: () => { + throw new Error('publish step reached with verified artifacts'); + }, + }), + ).toThrow('publish step reached with verified artifacts'); + }, 30_000); + test('requires the release workflow identity and a tag on HEAD', () => { const fixture = createReleaseFixture(); rmSync(resolve(fixture, '.github/workflows/release.yml'));