From bb6fff3ef7768726029b58934935e4b55e33f5c5 Mon Sep 17 00:00:00 2001 From: Raghu Betina Date: Mon, 10 Aug 2026 08:43:31 -0500 Subject: [PATCH] Promote plugin catalog to 0.1.0 Point new installs at the immutable, provenanced package after the API 0.2 discovery smoke proved one bounded create-and-push journey. Preserve the stricter qualification gaps and require the fresh Drawing Board Codespace as post-promotion proof. --- .claude-plugin/marketplace.json | 4 +- AGENTS.md | 12 +- README.md | 78 +++++---- RELEASING.md | 122 +++++++++----- .../2026-08-09-claude-plugin-0.1.0-release.md | 28 ++++ .../2026-08-09-direct-package-0.1.0-check.md | 22 +++ ...0-staging-movie-catalog-discovery-smoke.md | 68 ++++++++ test/plugin-release-order.test.mjs | 73 ++++++++- test/release-compatibility.test.mjs | 151 +++++++++++++++--- test/repository.test.mjs | 8 +- 10 files changed, 463 insertions(+), 103 deletions(-) create mode 100644 evidence/2026-08-09-claude-plugin-0.1.0-release.md create mode 100644 evidence/2026-08-09-direct-package-0.1.0-check.md create mode 100644 evidence/2026-08-10-staging-movie-catalog-discovery-smoke.md diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 43faaea..deeb032 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -9,11 +9,11 @@ "plugins": [ { "name": "firstdraft", - "version": "0.1.0-alpha.3", + "version": "0.1.0", "source": { "source": "npm", "package": "@firstdraft.com/claude-code", - "version": "0.1.0-alpha.3", + "version": "0.1.0", "registry": "https://registry.npmjs.org/" }, "displayName": "First Draft", diff --git a/AGENTS.md b/AGENTS.md index 7ed307e..8dd09c4 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -2,8 +2,10 @@ ## Release coordination -- Treat a merge to `main` as integration, not release authorization. After merging, report the exact merged SHA and - ask whether to coordinate a candidate across `firstdraft`, `cli`, and `skills` and promote it. +- Treat a merge to `main` as integration, not release authorization. A change to + `.claude-plugin/marketplace.json` is the exception: merging it changes the public catalog, so require the package, + service, and qualification gates in [`RELEASING.md`](RELEASING.md) first. After any other merge, report the exact + merged SHA and ask whether to coordinate a candidate across `firstdraft`, `cli`, and `skills` and promote it. - SemVer compatibility establishes candidate eligibility only. Record the exact SHA of every repository and the packed Claude plugin SHA-256, then follow [`RELEASING.md`](RELEASING.md). - Do not publish npm packages, deploy First Draft, or release the plugin without explicit user approval. If the user @@ -21,5 +23,11 @@ - Before pushing a `claude-v*` publication tag, verify its protection ruleset, the `npm` environment's required reviewers, the deliberately enabled `NPM_RELEASE_ENABLED` gate, and monotonic version order against npm, protected release tags, and the marketplace catalog. +- Before merging a marketplace-catalog change, require the exact promotion head's Node 24.18.0 CI job, including its + release-order rehearsal, to pass even when repository settings do not enforce it as a required check. Do not use an + administrative merge to bypass that gate. +- For plugin 0.1.0 only, `RELEASING.md` records the human-selected PAT-less discovery smoke that gates catalog + promotion and the stricter qualification boundaries it does not prove. Do not silently substitute either boundary + for the other. - The installable Claude package is assembled from the canonical Skill during packing. Do not commit a second editable copy under `packages/`. diff --git a/README.md b/README.md index 9a0ec7a..3404fa9 100644 --- a/README.md +++ b/README.md @@ -69,16 +69,29 @@ iPad support. Accounts and authentication, notifications and push, deployment, A Scaffold shapes, and gap-aware partial Compilation remain outside this boundary. Unsupported shapes fail the complete candidate closed. -The Claude plugin `@firstdraft.com/claude-code@0.1.0-alpha.3` and its bundled CLI -`@firstdraft.com/cli@0.1.0-alpha.2` are public experimental prereleases. The coordinated +A dated public-install observation records Claude plugin `@firstdraft.com/claude-code@0.1.0-alpha.3` and its bundled +CLI `@firstdraft.com/cli@0.1.0-alpha.2` as public experimental prereleases. A separate dated +[`@firstdraft.com/claude-code@0.1.0` publication observation](evidence/2026-08-09-claude-plugin-0.1.0-release.md) +binds its protected tag, successful publication workflow, registry identity, provenance presence, and exact +tarball SHA-256. It records plugin 0.1.0 under npm `next`, with npm `latest` and the public catalog still on alpha.3. +A dated [direct-package observation](evidence/2026-08-09-direct-package-0.1.0-check.md) separately records exact +plugin 0.1.0 installation, strict validation, inline discovery, and bundled CLI 0.1.0 invocation without calling +First Draft. The coordinated [`@firstdraft.com/cli@0.1.0` release](evidence/2026-08-07-cli-0.1.0-release.md) is separately published under `next`; `latest` remains alpha.2. A dated [isolated public-install observation](evidence/2026-08-06-public-claude-code-plugin-install.md) confirms Claude Code 2.1.223 could register the GitHub marketplace, install alpha.3 from npm, discover its one Skill, and run its bundled alpha.2 CLI. Plugin alpha.4 and alpha.5 were assembled as source candidates and abandoned before catalog promotion; -their source changes did not publish packages. Current registry state remains a release-time read-only check. This -source prepares ordinary plugin 0.1.0 with the coordinated published CLI 0.1.0. The public catalog still serves -alpha.3, and plugin publication and catalog promotion remain separate approval-gated actions. The 0.1.0 CLI's +their source changes did not publish packages. Current registry state remains a release-time read-only check. The +catalog manifest in this source names ordinary plugin 0.1.0 with the coordinated published CLI 0.1.0. The source +bytes and dated package observation do not establish deployment compatibility, a merge to public `main`, or a +successful fresh public install. The dated +[staging Movie Catalog discovery smoke](evidence/2026-08-10-staging-movie-catalog-discovery-smoke.md) binds the exact +compatible service, Skills, CLI, and package identities to one valid AnalysisRun, successful Compilation, and +OAuth/App-backed Publication to a fresh private repository. For plugin 0.1.0, that PAT-less observation satisfies +the narrower discovery-promotion gate selected for this catalog change. It does not claim independent +repository-byte verification, replay, a full v14 qualification, or the post-promotion template-and-Codespace path. +The 0.1.0 CLI's zero-flag `plan compile` command pushes the exact current Plan, waits for analysis of that accepted graph generation, and invokes the internal GitHub Publication lifecycle only for a valid unchanged candidate. Public `plan publish` and local-start `plan compile --output` are not commands; retained successful artifacts are materialized with @@ -92,23 +105,22 @@ After an invocation exits on an unknown outcome, unavailable status, timeout, or the same zero-flag command with unchanged Plan bytes conditionally resumes the retained singleton; there is no separate public Publication status command. An invalid projection is instead a protocol mismatch that requires coordinated CLI/service version reconciliation before replay. The controlled local harness establishes the candidate -product-Compile and progress shape only against a strict fake GitHub remote; the public-install observation did not -authenticate a model session or exercise staging. No live endpoint, staging run, or real GitHub mutation establishes this full -journey. There is no Plan GET or pull operation, complete semantic analyzer, deployment workflow, or general web or -mobile generator. +product-Compile and progress shape only against a strict fake GitHub remote; the discovery smoke separately +establishes one live create-and-push result, but it did not use a public catalog installation, template fork, or +Codespace. No evidence cited here establishes the complete public discovery journey or a full v14 qualification. +There is no Plan GET or pull operation, complete semantic analyzer, deployment workflow, or general web or mobile +generator. The required Publication progress object breaks the existing API-contract 0.1 line. Plugin 0.1.0 and CLI 0.1.0 -require service API contract `>= 0.2.0` and `< 0.3.0`. Publishing the CLI under `next` does not activate that service -contract or upgrade an installed plugin; plugin publication, service activation, and catalog promotion remain one -coordinated rollout whose service-activation phase occupies the maintenance window. Activating service contract 0.2 on -shared staging makes the alpha.2 CLI bundled in public plugin alpha.3 incompatible, and catalog promotion does not -upgrade existing installations. No interruption is approved here. A human must explicitly approve and announce the -window, affected-user notice, ordered rollout, rollback point, and completion checks before staging activation. -The serialized rollout publishes and reconciles plugin 0.1.0 under `next` first while `latest`, the public alpha.3 -catalog, and shared staging remain unchanged. Only then does the operator open the maintenance window, move staging -web and worker to the exact API 0.2 revision, promote the catalog, and use a fresh public install to close the -window. Before both service roles activate API 0.2, an exact-`next` plugin 0.1.0 install is an operator-only package -check: Plan, Compile, and every First Draft API call through it are incompatible and unsupported. +require service API contract `>= 0.2.0` and `< 0.3.0`. Publishing the CLI under `next` did not activate that service +contract or upgrade an installed plugin. At the discovery-smoke observation, staging web and worker both reported +exact API 0.2 service revision `4007fc5ef0734e2fc3e3e59714919025bd73d621`, while the public catalog still named +alpha.3. The successful smoke establishes the selected gate for promoting new catalog installs to compatible plugin +0.1.0. Because public plugin alpha.3 bundles CLI alpha.2 and defaults to shared staging, API 0.2 activation interrupts +existing alpha.3 installations until each receives the compatible 0.1.0 plugin. Catalog promotion does not update +existing installations. A fresh public template-and-Codespace discovery remains the immediate post-promotion +observation, and affected existing alpha.3 installations still require a separately verified update or an explicitly +accepted follow-up. Beginning with ordinary 0.1.0, pre-1.0 component versions use a minor bump for a breaking compatibility-line change and a patch bump for an otherwise backward-compatible change. Versions are never aliased. The npm `next` @@ -134,8 +146,8 @@ with: gh skill preview firstdraft/skills create-full-stack-app ``` -Do not install this Skill for ordinary use yet. Public experimental distribution exists, but an authenticated -public-install-to-staging journey has not established the prepared capability boundary. +Do not present this Skill as ordinary-use-ready yet. Catalog promotion enables a bounded public discovery, but an +authenticated template-and-Codespace journey has not established the prepared capability boundary. ### Claude Code plugin preview @@ -146,11 +158,13 @@ adapter and the CLI package contents packed from the exact reviewed revision nam `@firstdraft.com/cli@0.1.0` contract. Installing the plugin therefore supplies both the Skill and its compatible CLI without requiring Claude Code to install transitive npm dependencies. -The marketplace catalog uses Claude Code's documented `npm` plugin source and remains pinned to the published -`@firstdraft.com/claude-code@0.1.0-alpha.3`. The package template, installable manifest, and compatibility record in -this source prepare ordinary 0.1.0 without promoting it. A separate post-publication catalog change may point fresh -installs to 0.1.0 only after its exact registry identity has been reconciled. The installable manifest asks Claude -Code for the staging API URL and a sensitive API token. Claude stores sensitive configuration in secure storage and exports +The marketplace catalog uses Claude Code's documented `npm` plugin source and names exact +`@firstdraft.com/claude-code@0.1.0`. The package's registry identity, matching staging web and worker revision, and +the user-selected Movie Catalog discovery-promotion gate are reconciled in the dated smoke above. That observation +supports this catalog change but does not prove that the two public installation commands resolve 0.1.0 or that the +template-and-Codespace journey succeeds. The installable manifest asks Claude Code +for the staging API URL and a sensitive API token. Claude stores +sensitive configuration in secure storage and exports plugin options only to plugin subprocesses. The adapter maps those options to the CLI's environment without printing them. Users should create the token in First Draft's browser UI and enter it in Claude's configuration prompt, never paste it into an agent conversation or command line. Installed-plugin configuration is authoritative: when it @@ -187,9 +201,13 @@ claude plugin marketplace add firstdraft/skills claude plugin install firstdraft@firstdraft-skills ``` -Those exact commands succeeded for alpha.3 on 2026-08-06. They will not establish this 0.1.0 candidate until its -exact tarball is published and the catalog change is promoted. The isolated lane was not logged in, so it did not -prove model-backed Skill invocation, token onboarding, staging compatibility, or the product journey. The historical +Those exact commands succeeded for alpha.3 on 2026-08-06. The exact 0.1.0 tarball is published under npm `next`, but +success through these public catalog commands remains an observation to record after this catalog change reaches +public `main`. After that isolated install check, use the updated Drawing Board template to create a repository and +fresh Codespace, launch `claude`, make a plain-English application request, and confirm the expected fresh private +repository. The earlier isolated lane was not +logged in, so it did not prove model-backed Skill invocation, token onboarding, staging compatibility, or the +product journey. The historical 2026-08-04 source-only install report remains evidence for its recorded revision. A dated [vendored-CLI smoke](evidence/2026-08-05-claude-plugin-vendored-cli-smoke.md) records the local npm-source assembly, the rejected transitive-dependency design, and successful bare-command discovery. See diff --git a/RELEASING.md b/RELEASING.md index 81fe424..2acf856 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -3,25 +3,36 @@ This repository participates in a coordinated release with [`firstdraft/firstdraft`](https://github.com/firstdraft/firstdraft) and [`firstdraft/cli`](https://github.com/firstdraft/cli). A merge to `main` integrates source; it does not authorize a -plugin release, npm publication, or First Draft deployment. +plugin release, npm publication, or First Draft deployment. A change to `.claude-plugin/marketplace.json` is the +exception: merging it changes the public catalog and requires the ordered gates below first. ## Release identity The installable `firstdraft@firstdraft-skills` plugin is the public npm package -`@firstdraft.com/claude-code`. Version `0.1.0-alpha.3` is published. Alpha.4 and alpha.5 were assembled as source -candidates and abandoned before catalog promotion; their source changes did not publish packages. Current registry -state remains a release-time read-only check. This source supersedes those candidates. Its current candidate version -is `0.1.0`. The marketplace catalog deliberately remains pinned to alpha.3. Merging this source neither publishes -0.1.0 nor promotes the catalog; those actions are approved separately. The checkout-local `firstdraft` manifest and -private root `@firstdraft/skills@0.0.0` package are test tooling, not release identities. +`@firstdraft.com/claude-code`. A dated public-install observation records published version `0.1.0-alpha.3`. Alpha.4 +and alpha.5 were assembled as source candidates and abandoned before catalog promotion; their source changes did not +publish packages. Current registry state remains a release-time read-only check. This source supersedes those +candidates. Its release version is `0.1.0`, and the marketplace package source names that exact version. The 0.1.0 +package must be published under `next` before that catalog pointer merges. A dated +[read-only publication observation](evidence/2026-08-09-claude-plugin-0.1.0-release.md) reconciles the exact protected +tag, package digest, provenance presence, and registry dist-tags: `next` names 0.1.0 while `latest` remains alpha.3. +A separate [direct-package observation](evidence/2026-08-09-direct-package-0.1.0-check.md) records exact plugin 0.1.0 +installation, strict validation, inline discovery, and bundled CLI 0.1.0 invocation without calling First Draft. +The dated [staging Movie Catalog discovery smoke](evidence/2026-08-10-staging-movie-catalog-discovery-smoke.md) +binds the exact staging web and worker revision, compatible package constituents, and one live OAuth/App-backed +create-and-push result. For plugin 0.1.0, a human explicitly selected that bounded PAT-less smoke as the pre-catalog +promotion gate. It proves the downstream Claude-authored create-and-push path at the recorded identities; it does +not constitute full v14 qualification or prove the template, Codespace, or public-catalog journey. The +checkout-local `firstdraft` manifest and private root `@firstdraft/skills@0.0.0` package are test tooling, not release +identities. Packing deterministically assembles the plugin from the canonical `skills/create-full-stack-app` directory, the installable manifest and CLI adapter under `packages/claude-plugin`, the exact packed files from the coordinated published `@firstdraft.com/cli@0.1.0`, and the repository license. Colleagues therefore install the Skill and compatible CLI together through Claude Code rather than managing a separate global CLI or relying on transitive -installation. Public plugin alpha.3 continues to bundle the published CLI alpha.2. A -[dated release observation](evidence/2026-08-07-cli-0.1.0-release.md) records CLI 0.1.0 under `next` while `latest` -remains alpha.2; the plugin 0.1.0 candidate is not published by this source change. +installation. Existing alpha.3 installations continue to bundle the published CLI alpha.2 until explicitly updated. +A [dated release observation](evidence/2026-08-07-cli-0.1.0-release.md) records CLI 0.1.0 under `next` while `latest` +remains alpha.2; this source change cannot establish plugin publication or move either dist-tag. The always-present Publication progress object is a breaking compatibility-line change: plugin 0.1.0 and CLI 0.1.0 require service API contract `>= 0.2.0` and `< 0.3.0`. They are one coordinated rollout whose service-activation @@ -41,8 +52,9 @@ not change until those compatible package bytes are available. SHA-256, compatible service API range, exact CLI version, and Foundation Plan format. Before publication, an unpublished and unpromoted candidate is identified by its exact commit and digest and may be revised without consuming another SemVer. Once an npm version, protected release tag, or catalog version exists, that identity maps -forever to exactly one package tarball and any correction uses a new version. A compatible result establishes -candidate eligibility only; it never authorizes deployment or publication. +forever to exactly one package tarball. Correcting an identity's bytes uses a new version; repointing the catalog to +a prior immutable package is permitted for rollback. A compatible result establishes candidate eligibility only; it +never authorizes deployment or publication. Beginning with 0.1.0, current and future pre-1.0 candidates use ordinary `0.MINOR.PATCH` versions. Increment the minor component for a breaking compatibility-line change and the patch component for an otherwise @@ -58,14 +70,16 @@ move `latest`; it has no SemVer meaning and does not turn an ordinary version in `release/compatibility.json`, install both tarballs into an isolated temporary npm project, and confirm the plugin-local `firstdraft` adapter runs the exact CLI version. 3. Validate the staged plugin with the real Claude Code CLI. Exercise an ephemeral marketplace or the assembled - 0.1.0 plugin in isolated Claude state; the committed public catalog intentionally continues to exercise - alpha.3. Confirm Skill discovery, sensitive configuration handling, and CLI invocation. This is prepublication - evidence for 0.1.0. The dated public-install observation proves the prior alpha.3 package and bundled alpha.2 - CLI only. + 0.1.0 plugin in isolated Claude state without advancing the public catalog. Confirm Skill discovery, sensitive + configuration handling, and CLI invocation. This is prepublication evidence for 0.1.0. A separate catalog change + may be prepared, but it must remain unmerged until publication, deployment, and the explicitly selected promotion + gate are observed. The dated public-install observation proves the prior alpha.3 package and bundled alpha.2 CLI + only. 4. Complete the read-only cross-repository evaluator and prepare the exact service revision, rollout checks, Movie - Catalog qualification inputs, rollback point, and three-repository identity record without changing shared - staging. The breaking API 0.2 deploy and its live qualification belong to the serialized promotion sequence - below; do not deploy it while plugin 0.1.0 remains only an unpublished candidate. + Catalog smoke input, rollback point, and three-repository identity record without changing shared staging. The + breaking API 0.2 deploy and its live discovery smoke belong to the serialized promotion sequence below; do not + deploy it while plugin 0.1.0 remains only an unpublished candidate. A stricter v14 qualification may be prepared + separately, but it is not silently substituted for the selected 0.1.0 promotion gate. 5. A human decides whether to authorize the exact package-first sequence below. The protected tag, npm publication, maintenance start, service deployment, catalog promotion, and fresh public verification remain distinct approval-gated mutations or checks. Publishing and catalog promotion require new, explicit authorization. @@ -261,46 +275,78 @@ publication does not itself start the public-client outage. Do not treat this so Do not add `claude plugin details` to this no-service variant: it may call Claude `count_tokens`. Keep the temporary root until its evidence is accepted, record its cleanup separately, and never treat its absolute path as release identity. A [dated direct-package observation](evidence/2026-08-07-direct-package-alpha3-check.md) - records the same hardened procedure succeeding against public alpha.3 and its bundled CLI alpha.2. + records the same hardened procedure succeeding against public alpha.3 and its bundled CLI alpha.2. The separate + [0.1.0 direct-package observation](evidence/2026-08-09-direct-package-0.1.0-check.md) records the required exact + package check after publication without calling First Draft. 4. After package reconciliation and the isolated no-API check pass, start the announced maintenance window, notify affected users, and stop new Compile and Publication invocations. The public catalog and npm `latest` still name alpha.3 at this point. 5. Deploy the exact API 0.2 service revision to the staging web role and wait for it to report that revision. Then deploy the same exact revision to the staging worker role and wait for it to report that revision. Only after - both roles agree may the operator run the bounded Movie Catalog qualification and singleton replay through the - same isolated direct-`next` plugin 0.1.0 installation from step 3 and its bundled CLI 0.1.0. That installation - becomes supported for API operations only after both roles report the API 0.2 revision. Follow the service - runbook for migration compatibility, rollback, revision checks, and retained evidence; a web-only or worker-only - activation is not completion. -6. After both roles and the bounded qualification are verified, merge the separate marketplace-promotion change so + both roles agree may the operator run the selected 0.1.0 Movie Catalog discovery smoke. Bind the result to the + separate direct-`next` no-service check from step 3 through the exact package identity, Skills and CLI revisions, + and reproduced tarball digest. The smoke must record a Claude-authored exact Plan, valid analysis, successful + Compilation on the Standard worker, and successful OAuth/App-backed publication to a fresh private personal + repository. Record the repository result through the product response, database ledger, bounded provider job and + log evidence, and a signed-in browser observation. + + A GitHub PAT, independent clone or byte verification, generated-repository credential scan, and singleton replay + are not part of this user-selected discovery gate. Their absence must be explicit in the dated record, which must + not claim a full v14 qualification. The smoke also does not claim it installed or executed the public npm package; + the template, Codespace, and public-catalog path remains step 7. The compatible 0.1.0 constituents become supported + for API operations only after both roles report the API 0.2 revision. Follow the service runbook for migration + compatibility, rollback, revision checks, and retained evidence; a web-only or worker-only activation is not + completion. +6. After both roles and the selected 0.1.0 discovery smoke are verified, merge the separate marketplace-promotion change so `claude plugin marketplace add firstdraft/skills` resolves exact plugin 0.1.0. Never point the public catalog at - an unpublished or unverified package. Notify known existing alpha.3 installations that catalog promotion does - not update them; use only the separately verified Claude Code update procedure to move each one to 0.1.0, and do - not invent a command during the window. An installation remains incompatible with shared staging until its update + an unpublished or unverified package. Require the exact promotion head's Node 24.18.0 CI job, including its + release-order rehearsal, to pass even if repository settings do not enforce it as a required check; do not bypass + it with an administrative merge. Notify known existing alpha.3 installations that catalog promotion does not + update them; use only the separately verified Claude Code update procedure to move each one to 0.1.0, and do not + invent a command during the window. An installation remains incompatible with shared staging until its update succeeds. The fresh public path in step 7 remains required. Before ending the window, each known existing installation must either reach 0.1.0 or an authorized operator must explicitly record and accept its continuing outage as an affected-user follow-up. -7. In fresh isolated Claude state, run the exact public installation: +7. After the catalog change reaches public `main`, first run the exact public installation in fresh isolated Claude + state outside a Drawing Board: ```sh claude plugin marketplace add firstdraft/skills claude plugin install firstdraft@firstdraft-skills ``` - Start a fresh model session, confirm the Skill is discoverable, invoke its bundled CLI 0.1.0, complete staging - token onboarding without exposing the token in chat or logs, and verify the API 0.2 Publication progress - contract. End the maintenance window only after that public path succeeds and the recorded completion criteria - are met. Catalog promotion alone and the earlier direct-`next` no-API check cannot end the window. + Confirm that the catalog resolves plugin 0.1.0, the Skill is discoverable, and the bundled CLI reports 0.1.0. + Then pin the exact merged Skills SHA in the updated Drawing Board, use that template to create a repository and a + fresh Codespace, and run `claude`. Complete staging token onboarding without exposing the token in chat or logs, + make a plain-English application request, and verify that the bundled CLI 0.1.0 and API 0.2 Publication progress + contract produce the expected fresh private GitHub repository. This path does not yet cover a pull request back to + the template repository. End the maintenance window only after both the isolated public install and that template + path succeed and the recorded completion criteria are met. Catalog promotion alone and the earlier direct-`next` + no-API check cannot end the window. -If service activation or the bounded qualification in step 5 fails, follow the service runbook to restore the exact +If service activation or the selected discovery smoke in step 5 fails, follow the service runbook to restore the exact API 0.1 rollback revision to every changed role and verify that web and worker both report it. Leave npm `latest` and the public catalog at alpha.3, and end the maintenance window only after the recorded rollback criteria pass. The already-published plugin 0.1.0 identity under `next` remains immutable; a plugin-side correction uses a new SemVer. +If the fresh public path in step 7 fails after catalog promotion, keep the maintenance window open and stop new +Compile and Publication invocations. Reconcile the catalog merge and installation result read-only before another +mutation. If recovery requires the API 0.1 service, restore both service roles to the exact rollback revision and +repoint the catalog to the already-published `0.1.0-alpha.3` package, then verify both roles and a fresh alpha.3 +install before ending the window. No ordering can make clients compatible during every instant of that two-system +rollback, so keep new operations stopped until both sides agree. Repointing is one reviewable source change that +updates `.claude-plugin/marketplace.json`, its exact catalog assertions in `test/repository.test.mjs` and +`test/release-compatibility.test.mjs`, and the catalog-state prose and assertions in `README.md` and `RELEASING.md`. +The catalog change does not downgrade existing installations. Before ending the window, each known installation +moved to 0.1.0 during this rollout must return to a supported alpha.3 installation through a separately verified +Claude Code procedure, or an authorized operator must record and accept its continuing outage as an affected-user +follow-up. Selecting a prior immutable catalog package is not reusing that SemVer for different bytes; the +forward-only correction rule applies when publishing changed bytes. + If any external mutation has an ambiguous result, stop and inspect the registry, Git ref, or deployment read-only. -Do not retry until its identity is known. Corrections to an existing npm, protected-tag, or catalog release identity -are forward-only and use a new SemVer; an unpublished and unpromoted candidate may instead be revised at a new exact -commit and digest. +Do not retry until its identity is known. Publishing changed bytes for an existing npm, protected-tag, or catalog +release identity is forbidden; publish a new SemVer instead. Repointing the catalog to a prior immutable package for +rollback is allowed. An unpublished and unpromoted candidate may instead be revised at a new exact commit and digest. The plugin package is published by pushing protected tag `claude-v$package_version`. That tag triggers `.github/workflows/publish.yml`, which rechecks the source commit, verifies the exact CLI release already exists in diff --git a/evidence/2026-08-09-claude-plugin-0.1.0-release.md b/evidence/2026-08-09-claude-plugin-0.1.0-release.md new file mode 100644 index 0000000..a0691c8 --- /dev/null +++ b/evidence/2026-08-09-claude-plugin-0.1.0-release.md @@ -0,0 +1,28 @@ +# Claude plugin 0.1.0 publication — 2026-08-09 + +One read-only reconciliation observed the exact `@firstdraft.com/claude-code@0.1.0` package after its protected-tag +publication workflow completed. The immutable source and publication identities were: + +- source commit `b3e53a240aaf79a776538e9b1410689d8a4e79ee`; +- annotated tag `claude-v0.1.0`, whose tag object is `ddbc7456647a62bf2dc13b2b897cadbf4e486344` and whose + peeled commit is that exact source commit; +- successful GitHub Actions publication run + [`31321014564`](https://github.com/firstdraft/skills/actions/runs/31321014564); +- package `@firstdraft.com/claude-code@0.1.0`, published by the registry at `2026-08-09T15:25:30.197Z`; +- registry integrity + `sha512-0vbQeP1zjAZ2VROidvx60OH8tVYPZW5PYBx35B9sxKwhWepidxq9PFXSuxEw5/MNWD7iA3TWmnQRzZDIgX9vSg==`; +- registry SHA-1 `31e8b1f338a5019debc061bfdce1eb5950e96cb1`; and +- independently streamed tarball SHA-256 `02fad6cd2207f3d2ab7598f0aa67825520ebc5b807294e0c241774ee3ac6a89d`, + matching `release/compatibility.json` at the tagged source commit. + +Both the workflow's `Verify tag and source commit` and `Verify approved release` steps succeeded. Each step asserts +that the triggering ref is a protected tag and that its peeled commit is the exact event and first-parent `main` +commit before continuing. An isolated public-registry install followed by `npm audit signatures` reported one +verified registry signature and one verified attestation for this package. Its `next` dist-tag named `0.1.0`, while +`latest` remained `0.1.0-alpha.3`. Freshly fetched `origin/main` still pointed the public Claude marketplace catalog +at plugin `0.1.0-alpha.3`, so this observation did not promote or otherwise change the catalog. + +This establishes only the package, tag, workflow, provenance-presence, digest, and dist-tag identities above. It did +not install the package through Claude Code, authenticate a model or First Draft user, call staging, inspect a +deployed service revision, exercise Compilation or GitHub Publication, qualify Movie Catalog, move `latest`, merge a +catalog change, or verify the two-command public installation path for 0.1.0. diff --git a/evidence/2026-08-09-direct-package-0.1.0-check.md b/evidence/2026-08-09-direct-package-0.1.0-check.md new file mode 100644 index 0000000..3415475 --- /dev/null +++ b/evidence/2026-08-09-direct-package-0.1.0-check.md @@ -0,0 +1,22 @@ +# Direct npm plugin 0.1.0 check — 2026-08-09 + +One isolated no-service check installed exact public package `@firstdraft.com/claude-code@0.1.0` with Node +v24.18.0, npm 11.16.0, and Claude Code 2.1.224. The retained npm result reported one added package. The check used +fresh temporary state; this record omits its run-local absolute path because that path is not a release identity. + +The retained outputs established all of the following: + +- package `@firstdraft.com/claude-code@0.1.0` contained installable plugin `firstdraft@0.1.0` and canonical Skill + `skills/create-full-stack-app/SKILL.md`; +- `claude plugin validate --strict ` completed successfully; +- Claude's inline plugin listing selected one enabled session-scoped `firstdraft@inline` entry at exact version + `0.1.0`, with its installation path canonically matching the isolated plugin root; +- the plugin's bundled `firstdraft --version` returned exact `0.1.0` with empty stderr; and +- `npm audit signatures` reported one verified registry signature and one verified attestation for the one installed + package. + +This establishes exact public-package installation, manifest and Skill presence, strict plugin validation, inline +session discovery, bundled CLI identity, and registry signature and attestation presence for plugin 0.1.0. It did +not authenticate or call a model, configure or call First Draft, exercise staging, run Plan authoring, Compilation, +GitHub Publication, or singleton replay, move an npm dist-tag, change the public marketplace catalog, or prove the +two-command marketplace installation path. The bounded v14 service qualification remains a separate observation. diff --git a/evidence/2026-08-10-staging-movie-catalog-discovery-smoke.md b/evidence/2026-08-10-staging-movie-catalog-discovery-smoke.md new file mode 100644 index 0000000..0a70740 --- /dev/null +++ b/evidence/2026-08-10-staging-movie-catalog-discovery-smoke.md @@ -0,0 +1,68 @@ +# Staging Movie Catalog discovery smoke — 2026-08-10 + +**Status: passed the bounded discovery-promotion gate.** This record does not claim a completed v14 qualification. +It records one live staging product result at the narrower boundary selected for catalog promotion: a Plan authored +after a Claude Code Skill session reached valid analysis, successful Compilation, and successful OAuth/App-backed +publication to a fresh private personal GitHub repository. + +## Bound identities + +The retained compatibility result was eligible and bound these exact constituents: + +- First Draft service revision `4007fc5ef0734e2fc3e3e59714919025bd73d621`, reporting API contract `0.2.0`; +- Skills revision `b3e53a240aaf79a776538e9b1410689d8a4e79ee` and plugin + `@firstdraft.com/claude-code@0.1.0`, with tarball SHA-256 + `02fad6cd2207f3d2ab7598f0aa67825520ebc5b807294e0c241774ee3ac6a89d`; and +- CLI revision `d37d8b6775a0b97ce10bd651485bd308fed1dda2` at version `0.1.0`. + +The staging web and worker roles both reported the exact service revision above before the run. The retained +database preflight reported all 53 available migrations applied with none pending. A Claude Code 2.1.222 session +using the exact Skills revision prepared a 1,572-byte Foundation Plan with SHA-256 +`831f5d960416c7c3f01f0a75b417f5d4330abf68062527b52ce8528f0b7ef37a`. The operator, not the model session, +invoked the authenticated network Compile with the existing First Draft API token. + +## Product result + +The API result, post-publication database ledger, Render job observation, bounded service logs, returned repository +URL, and signed-in browser observation agreed on the following result: + +- exactly one AnalysisRun reached `valid`, one Compilation reached `succeeded`, and one Publication reached + `succeeded` for the Plan SHA-256 above; +- the Compilation ledger recorded a 194-file, 543,112-byte artifact; +- Standard Render job `job-d9smqin10e5c73a6m72g` ran the exact Compilation and reached `succeeded`; +- publication attempt 1 was a definite `github.name_conflict` rejection, attempt 2 created the next repository + candidate successfully, and attempt 3 published the artifact successfully; +- the CLI returned a fresh personal-repository URL, retained only in the private operator evidence; +- a signed-in browser showed the returned repository as private on `main`, with the generated Rails and iOS tree and one + First Draft commit; and +- the complete retained 270-record web-and-worker log window contained no matched exit, out-of-memory, restart, or + stopping event. + +These observations establish one bounded live product create-and-push result through the exact compatible +constituents. Together with the separate +[package publication](2026-08-09-claude-plugin-0.1.0-release.md) and +[direct-package check](2026-08-09-direct-package-0.1.0-check.md), they are sufficient for the user-selected catalog +promotion decision: expose plugin 0.1.0 through the public catalog so the template → Codespace → Claude → +plain-English request → fresh GitHub repository discovery path can be tried as a public installation. + +## Deliberately unproved boundaries + +No GitHub PAT was created or used. Consequently, this smoke did not run the PAT-dependent verifier and does not +establish any of the following: + +- an independent clone, ref, commit, tree, blob, mode, size, or byte-for-byte artifact comparison; +- an independent GitHub Actions or Dependabot inspection; +- a generated-repository credential-category scan; +- singleton replay, stable identity under replay, or replay attempt and queue-job counts; or +- the service runbook's full v14 qualification. + +The browser view is visual confirmation at the repository-shape boundary, not a substitute for those checks. The +recorded artifact metadata, tree identity, and commit identity came from the First Draft database ledger and were +not independently reconciled through GitHub. + +This run also did not begin with a template fork or Codespace and did not install plugin 0.1.0 through the public +marketplace catalog. It therefore does not yet prove the complete user-selected discovery path, a fresh public +installation, representative-user usability, generated-app execution, deployment, arbitrary application support, +or production readiness. Those are later observations; the template-and-Codespace path is the immediate +post-promotion check. The stricter PAT-backed verification and replay remain available as separate qualification +work and are not silently recast as completed by this discovery smoke. diff --git a/test/plugin-release-order.test.mjs b/test/plugin-release-order.test.mjs index d6dff98..60ec89f 100644 --- a/test/plugin-release-order.test.mjs +++ b/test/plugin-release-order.test.mjs @@ -1,6 +1,8 @@ import assert from "node:assert/strict"; -import { readFile } from "node:fs/promises"; +import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises"; import { fileURLToPath } from "node:url"; +import { tmpdir } from "node:os"; +import path from "node:path"; import test from "node:test"; import { @@ -147,13 +149,21 @@ test("prospective release order rejects incoherent current identities", () => { ); }); -test("release-order reconciliation reads npm, fetched tags, and the catalog", async () => { +test("catalog reconciliation reads npm, fetched tags, and the catalog", async () => { const invocations = []; + const compatibilitySource = await readFile( + new URL("../release/compatibility.json", import.meta.url), + "utf8", + ); const result = await checkPluginReleaseOrder({ + requireCurrentTag: false, root: fileURLToPath(new URL("../", import.meta.url)), spawn(command, arguments_, options) { invocations.push([command, arguments_, options]); if (command === "git") { + if (arguments_[0] === "show") { + return { status: 0, stderr: "", stdout: compatibilitySource }; + } return { status: 0, stderr: "", @@ -163,15 +173,16 @@ test("release-order reconciliation reads npm, fetched tags, and the catalog", as return { status: 0, stderr: "", - stdout: JSON.stringify(candidate.publishedVersions), + stdout: JSON.stringify([...candidate.publishedVersions, "0.1.0"]), }; }, }); assert.equal(result.candidateVersion, "0.1.0"); - assert.deepEqual(result.catalogVersions, ["0.1.0-alpha.3"]); + assert.deepEqual(result.catalogVersions, ["0.1.0"]); assert.deepEqual(result.taggedVersions, ["0.1.0-alpha.3", "0.1.0"]); - assert.equal(invocations.length, 2); + assert.equal(result.releaseState, "catalog"); + assert.equal(invocations.length, 3); assert.deepEqual(invocations[1][1], [ "for-each-ref", "--format=%(refname:strip=3)", @@ -179,6 +190,56 @@ test("release-order reconciliation reads npm, fetched tags, and the catalog", as ]); }); +test("default publish reconciliation rejects an already-promoted catalog", async (t) => { + const root = await mkdtemp(path.join(tmpdir(), "firstdraft-promoted-catalog-")); + t.after(() => rm(root, { force: true, recursive: true })); + const compatibilitySource = await readFile( + new URL("../release/compatibility.json", import.meta.url), + "utf8", + ); + const compatibility = JSON.parse(compatibilitySource); + const marketplace = JSON.parse( + await readFile( + new URL("../.claude-plugin/marketplace.json", import.meta.url), + "utf8", + ), + ); + const plugin = marketplace.plugins.find(({ name }) => name === "firstdraft"); + plugin.version = compatibility.version; + plugin.source.version = compatibility.version; + await mkdir(path.join(root, "release")); + await mkdir(path.join(root, ".claude-plugin")); + await writeFile( + path.join(root, "release", "compatibility.json"), + compatibilitySource, + ); + await writeFile( + path.join(root, ".claude-plugin", "marketplace.json"), + `${JSON.stringify(marketplace)}\n`, + ); + + await assert.rejects( + checkPluginReleaseOrder({ + root, + spawn(command, arguments_) { + if (command === "git") { + return { + status: 0, + stderr: "", + stdout: `claude-v${compatibility.version}\n`, + }; + } + return { + status: 0, + stderr: "", + stdout: JSON.stringify([compatibility.version]), + }; + }, + }), + /must be newer than authoritative published or catalog version/, + ); +}); + test("consumed release order binds compatibility bytes to the protected tag", async () => { const root = fileURLToPath(new URL("../", import.meta.url)); const compatibilitySource = await readFile( @@ -219,7 +280,7 @@ test("consumed release order binds compatibility bytes to the protected tag", as matchingInvocations, ), }); - assert.equal(result.releaseState, "published"); + assert.equal(result.releaseState, "catalog"); assert.deepEqual( matchingInvocations .filter(([command, arguments_]) => diff --git a/test/release-compatibility.test.mjs b/test/release-compatibility.test.mjs index f683d5c..c33d5d7 100644 --- a/test/release-compatibility.test.mjs +++ b/test/release-compatibility.test.mjs @@ -179,6 +179,9 @@ test("release operator and agent instructions track the candidate", async () => agents, readme, directPackageEvidence, + pluginReleaseEvidence, + directPackageReleaseEvidence, + discoverySmokeEvidence, ] = await Promise.all([ readJson("release/compatibility.json"), readJson(".claude-plugin/marketplace.json"), @@ -186,6 +189,9 @@ test("release operator and agent instructions track the candidate", async () => readText("AGENTS.md"), readText("README.md"), readText("evidence/2026-08-07-direct-package-alpha3-check.md"), + readText("evidence/2026-08-09-claude-plugin-0.1.0-release.md"), + readText("evidence/2026-08-09-direct-package-0.1.0-check.md"), + readText("evidence/2026-08-10-staging-movie-catalog-discovery-smoke.md"), ]); const catalogPlugin = marketplace.plugins.find( ({ name }) => name === "firstdraft", @@ -200,16 +206,102 @@ test("release operator and agent instructions track the candidate", async () => ), ); assert(releasing.includes(compatibility.plugin_source.package)); - assert.equal(catalogPlugin.version, "0.1.0-alpha.3"); + assert( + compareSemanticVersions(compatibility.version, catalogPlugin.version) >= 0, + "the marketplace catalog must not lead the release candidate", + ); + assert.equal(catalogPlugin.version, "0.1.0"); assert.match( releasing, - /marketplace catalog\s+deliberately remains pinned to alpha\.3/, + /release version is\s+`0\.1\.0`,\s+and the marketplace package source names that exact version/, ); assert.match( releasing, - /Alpha\.4 and alpha\.5 were assembled as\s+source\s+candidates and abandoned before catalog promotion/, + /Alpha\.4\s+and alpha\.5 were assembled as source candidates and abandoned before catalog promotion/, ); assert.match(releasing, /separate marketplace-promotion change/); + assert.match( + releasing, + /package must be published under `next` before that catalog pointer merges[\s\S]*?read-only publication observation[\s\S]*?exact protected[\s\S]*?tag, package digest, provenance presence,[\s\S]*?`next` names 0\.1\.0 while `latest` remains alpha\.3[\s\S]*?staging Movie Catalog discovery smoke[\s\S]*?pre-catalog\s+promotion gate/, + ); + assert.match( + readme, + /catalog manifest in this source names ordinary plugin 0\.1\.0[\s\S]*?source[\s\S]*?bytes and dated package observation do not establish deployment compatibility, a merge to public `main`, or a[\s\S]*?successful fresh public install/, + ); + assert.match( + pluginReleaseEvidence, + /source commit `b3e53a240aaf79a776538e9b1410689d8a4e79ee`[\s\S]*?tag object is `ddbc7456647a62bf2dc13b2b897cadbf4e486344`[\s\S]*?publication run[\s\S]*?`31321014564`[\s\S]*?tarball SHA-256 `02fad6cd2207f3d2ab7598f0aa67825520ebc5b807294e0c241774ee3ac6a89d`[\s\S]*?triggering ref is a protected tag[\s\S]*?one\s+verified registry signature and one verified attestation[\s\S]*?`next` dist-tag named `0\.1\.0`[\s\S]*?`latest` remained `0\.1\.0-alpha\.3`[\s\S]*?public Claude marketplace[\s\S]*?`0\.1\.0-alpha\.3`/, + ); + assert.match( + pluginReleaseEvidence, + /This establishes only the package, tag, workflow, provenance-presence, digest, and dist-tag identities[\s\S]*?did\s+not install the package through Claude Code[\s\S]*?call staging[\s\S]*?qualify Movie Catalog[\s\S]*?verify the two-command public installation path/, + ); + assert.match( + directPackageReleaseEvidence, + /Node\s+v24\.18\.0[\s\S]*?npm 11\.16\.0[\s\S]*?Claude Code 2\.1\.224[\s\S]*?one added package/, + ); + assert.match( + directPackageReleaseEvidence, + /@firstdraft\.com\/claude-code@0\.1\.0[\s\S]*?firstdraft@0\.1\.0[\s\S]*?skills\/create-full-stack-app\/SKILL\.md[\s\S]*?claude plugin validate --strict [\s\S]*?session-scoped `firstdraft@inline`[\s\S]*?bundled `firstdraft --version` returned exact `0\.1\.0` with empty stderr/, + ); + assert.match( + directPackageReleaseEvidence, + /one verified registry signature and one verified attestation[\s\S]*?did\s+not authenticate or call a model[\s\S]*?configure or call First Draft[\s\S]*?exercise staging[\s\S]*?GitHub Publication[\s\S]*?change the public marketplace catalog[\s\S]*?two-command marketplace installation path/, + ); + assert.match( + discoverySmokeEvidence, + /Status: passed the bounded discovery-promotion gate[\s\S]*?does not claim a completed v14 qualification/, + ); + assert.match( + discoverySmokeEvidence, + /4007fc5ef0734e2fc3e3e59714919025bd73d621[\s\S]*?b3e53a240aaf79a776538e9b1410689d8a4e79ee[\s\S]*?02fad6cd2207f3d2ab7598f0aa67825520ebc5b807294e0c241774ee3ac6a89d[\s\S]*?d37d8b6775a0b97ce10bd651485bd308fed1dda2/, + ); + assert.match( + discoverySmokeEvidence, + /Claude Code 2\.1\.222[\s\S]*?1,572-byte Foundation Plan[\s\S]*?831f5d960416c7c3f01f0a75b417f5d4330abf68062527b52ce8528f0b7ef37a[\s\S]*?exactly one AnalysisRun[\s\S]*?one Compilation[\s\S]*?one Publication[\s\S]*?job-d9smqin10e5c73a6m72g[\s\S]*?github\.name_conflict[\s\S]*?created the next repository[\s\S]*?published the artifact/, + ); + assert.match( + discoverySmokeEvidence, + /No GitHub PAT was created or used[\s\S]*?independent clone, ref, commit, tree, blob, mode, size, or byte-for-byte artifact comparison[\s\S]*?generated-repository credential-category scan[\s\S]*?singleton replay[\s\S]*?full v14 qualification/, + ); + assert.match( + discoverySmokeEvidence, + /did not begin with a template fork or Codespace[\s\S]*?did not install plugin 0\.1\.0 through the public[\s\S]*?marketplace catalog[\s\S]*?immediate\s+post-promotion check/, + ); + assert(!discoverySmokeEvidence.includes(repository)); + assert(!discoverySmokeEvidence.includes("demostudent27")); + assert.doesNotMatch( + discoverySmokeEvidence, + /(?:\/Users\/|\/home\/|[A-Za-z]:\\)/, + ); + assert.doesNotMatch( + discoverySmokeEvidence, + /\.firstdraft\/state\.json/, + ); + assert.doesNotMatch( + discoverySmokeEvidence, + /(?:authorization|bearer|api[_-]?key|access[_-]?token|refresh[_-]?token|client[_-]?secret|BEGIN [A-Z ]+PRIVATE KEY)/i, + ); + assert.match( + readme, + /staging Movie Catalog discovery smoke[\s\S]*?PAT-less observation satisfies[\s\S]*?narrower discovery-promotion gate[\s\S]*?does not claim independent[\s\S]*?repository-byte verification, replay, a full v14 qualification/, + ); + assert.match( + releasing, + /staging Movie Catalog discovery smoke[\s\S]*?plugin 0\.1\.0[\s\S]*?human explicitly selected[\s\S]*?bounded PAT-less smoke as the pre-catalog[\s\S]*?does\s+not constitute full v14 qualification/, + ); + assert.match( + agents, + /change to\s+`\.claude-plugin\/marketplace\.json` is the exception[\s\S]*?merging it changes the public catalog[\s\S]*?package,[\s\S]*?service,[\s\S]*?qualification gates/, + ); + assert.match( + agents, + /exact promotion head's Node 24\.18\.0 CI job[\s\S]*?release-order rehearsal[\s\S]*?repository settings do not enforce it as a required check[\s\S]*?Do not use an\s+administrative merge to bypass that gate/, + ); + assert.match( + agents, + /For plugin 0\.1\.0 only[\s\S]*?human-selected PAT-less discovery smoke[\s\S]*?stricter qualification boundaries[\s\S]*?Do not silently substitute either boundary/, + ); assert(releasing.includes("claude-v$package_version")); assert.match( releasing, @@ -241,11 +333,11 @@ test("release operator and agent instructions track the candidate", async () => ); assert.match( releasing, - /do not deploy it while plugin 0\.1\.0 remains only an unpublished candidate/, + /do not\s+deploy it while plugin 0\.1\.0 remains only an unpublished candidate/, ); assert.match( releasing, - /Notify known existing alpha\.3 installations that catalog promotion does\s+not update them[\s\S]*?separately\s+verified Claude Code update procedure/, + /Notify known existing alpha\.3 installations that catalog promotion does\s+not\s+update them[\s\S]*?separately\s+verified Claude Code update procedure/, ); assert.match( releasing, @@ -327,10 +419,12 @@ test("release operator and agent instructions track the candidate", async () => directPackageEvidence, /does not establish unpublished\s+plugin 0\.1\.0[\s\S]*?move an npm dist-tag[\s\S]*?register or change a marketplace[\s\S]*?call a model[\s\S]*?call staging[\s\S]*?mutate a First Draft service/, ); - assert.doesNotMatch( - directPackageEvidence, - /\/(?:private\/)?tmp\/firstdraft-package-first\.[A-Za-z0-9]+/, - ); + for (const source of [directPackageEvidence, directPackageReleaseEvidence]) { + assert.doesNotMatch( + source, + /\/(?:private\/)?tmp\/firstdraft-package-first(?:-[0-9]{8})?\.[A-Za-z0-9]+/, + ); + } assert.doesNotMatch( releasing, /plugin cannot be published before service activation/i, @@ -344,10 +438,17 @@ test("release operator and agent instructions track the candidate", async () => "The public catalog and npm `latest` still name alpha.3 at this point", "5. Deploy the exact API 0.2 service revision to the staging web role", "deploy the same exact revision to the staging worker role", - "the same isolated direct-`next` plugin 0.1.0 installation from step 3", - "6. After both roles and the bounded qualification are verified, merge the separate marketplace-promotion change", - "7. In fresh isolated Claude state, run the exact public installation", - "End the maintenance window only after that public path succeeds", + "selected 0.1.0 Movie Catalog discovery smoke", + "separate direct-`next` no-service check from step 3", + "A GitHub PAT, independent clone or byte verification, generated-repository credential scan, and singleton replay", + "6. After both roles and the selected 0.1.0 discovery smoke are verified, merge the separate marketplace-promotion change", + "7. After the catalog change reaches public `main`, first run the exact public installation in fresh isolated Claude state outside a Drawing Board", + "Then pin the exact merged Skills SHA in the updated Drawing Board", + "use that template to create a repository and a fresh Codespace", + "run `claude`", + "make a plain-English application request", + "expected fresh private GitHub repository", + "End the maintenance window only after both the isolated public install and that template path succeed", ]); assert.match( releasing, @@ -355,27 +456,31 @@ test("release operator and agent instructions track the candidate", async () => ); assert.match( readme, - /publishes and reconciles plugin 0\.1\.0 under `next` first[\s\S]*?open the maintenance window[\s\S]*?staging\s+web and worker[\s\S]*?promote the catalog[\s\S]*?fresh public install/, + /staging web and worker both reported[\s\S]*?4007fc5ef0734e2fc3e3e59714919025bd73d621[\s\S]*?selected gate for promoting new catalog installs[\s\S]*?public plugin alpha\.3 bundles CLI alpha\.2 and defaults to shared staging[\s\S]*?API 0\.2 activation interrupts[\s\S]*?existing alpha\.3 installations until each receives the compatible 0\.1\.0 plugin[\s\S]*?fresh public template-and-Codespace discovery[\s\S]*?immediate post-promotion[\s\S]*?observation/, ); assert.match( - readme, - /exact-`next` plugin 0\.1\.0 install is an operator-only package[\s\S]*?Plan, Compile, and every First Draft API call through it are incompatible and unsupported/, + releasing, + /An installation remains incompatible with shared staging until its update\s+succeeds[\s\S]*?Before ending the window, each known existing\s+installation must either reach 0\.1\.0 or an authorized operator must explicitly record and accept its continuing\s+outage as an affected-user follow-up/, ); assert.match( - readme, - /one\s+coordinated rollout whose service-activation phase occupies the maintenance window/, + releasing, + /Require the exact promotion head's Node 24\.18\.0 CI job[\s\S]*?release-order rehearsal[\s\S]*?repository settings do not enforce it as a required check[\s\S]*?do not bypass[\s\S]*?administrative merge/, ); assert.match( releasing, - /An installation remains incompatible with shared staging until its update\s+succeeds[\s\S]*?Before ending the window, each known existing\s+installation must either reach 0\.1\.0 or an authorized operator must explicitly record and accept its continuing\s+outage as an affected-user follow-up/, + /selected 0\.1\.0 Movie Catalog discovery smoke[\s\S]*?Claude-authored exact Plan[\s\S]*?valid analysis[\s\S]*?successful\s+Compilation on the Standard worker[\s\S]*?successful OAuth\/App-backed publication[\s\S]*?A GitHub PAT,[\s\S]*?independent clone or byte verification[\s\S]*?singleton replay[\s\S]*?not part of this user-selected discovery gate[\s\S]*?must\s+not claim a full v14 qualification/, + ); + assert.match( + releasing, + /If service activation or the selected discovery smoke in step 5 fails[\s\S]*?restore the exact\s+API 0\.1 rollback revision[\s\S]*?Leave npm `latest` and\s+the public catalog at alpha\.3[\s\S]*?plugin 0\.1\.0 identity under `next` remains immutable[\s\S]*?new SemVer/, ); assert.match( releasing, - /If service activation or the bounded qualification in step 5 fails[\s\S]*?restore the exact\s+API 0\.1 rollback revision[\s\S]*?Leave npm `latest` and\s+the public catalog at alpha\.3[\s\S]*?plugin 0\.1\.0 identity under `next` remains immutable[\s\S]*?new SemVer/, + /If the fresh public path in step 7 fails after catalog promotion[\s\S]*?restore both service roles[\s\S]*?repoint the catalog to the already-published `0\.1\.0-alpha\.3` package[\s\S]*?one reviewable source change[\s\S]*?\.claude-plugin\/marketplace\.json[\s\S]*?test\/repository\.test\.mjs[\s\S]*?test\/release-compatibility\.test\.mjs[\s\S]*?README\.md[\s\S]*?RELEASING\.md[\s\S]*?catalog change does not downgrade existing installations[\s\S]*?each known installation\s+moved to 0\.1\.0[\s\S]*?return to a supported alpha\.3 installation[\s\S]*?authorized operator must record and accept its continuing outage[\s\S]*?Selecting a prior immutable catalog package is not reusing that SemVer for different bytes/, ); assert.match( releasing, - /Corrections to an existing npm, protected-tag, or catalog release identity[\s\S]*?forward-only and use a new SemVer[\s\S]*?unpublished and unpromoted candidate may instead be revised/, + /Publishing changed bytes for an existing npm, protected-tag, or catalog\s+release identity is forbidden[\s\S]*?new SemVer[\s\S]*?Repointing the catalog to a prior immutable package for\s+rollback is allowed[\s\S]*?unpublished and unpromoted candidate may instead be revised/, ); assert.match( @@ -401,8 +506,8 @@ function assertTextOrder(source, fragments) { let previousIndex = -1; for (const fragment of fragments) { const normalizedFragment = fragment.replace(/\s+/g, " "); - const index = normalizedSource.indexOf(normalizedFragment); - assert.ok(index > previousIndex, `missing or out-of-order release step: ${fragment}`); + const index = normalizedSource.indexOf(normalizedFragment, previousIndex + 1); + assert.ok(index >= 0, `missing or out-of-order release step: ${fragment}`); previousIndex = index; } } diff --git a/test/repository.test.mjs b/test/repository.test.mjs index 151a4f6..0e0ea89 100644 --- a/test/repository.test.mjs +++ b/test/repository.test.mjs @@ -73,6 +73,8 @@ const foundationPlanServerBaseline = "35ad070beb36c66dc6480f36b33767caaed160a9"; const currentCompilerServiceBaseline = "6002be2685542fedf515879f940b97ad73b1a469"; +const discoverySmokeServiceBaseline = + "4007fc5ef0734e2fc3e3e59714919025bd73d621"; const compilationEvidenceCliBaseline = "121272cd592055354d09a4fe90e55c3ca002770c"; const compilationEvidenceCliRuntimeDigest = @@ -190,6 +192,7 @@ test("revision pins remain exhaustive across coordination surfaces", async () => freshAgentSkillBaseline, freshAgentSkillBaseline.slice(0, 7), currentCompilerServiceBaseline, + discoverySmokeServiceBaseline, ]); const skillDirectory = path.join(skillsDirectory, "create-full-stack-app"); @@ -258,6 +261,7 @@ test("revision pins remain exhaustive across coordination surfaces", async () => [ foundationPlanServerBaseline, currentCompilerServiceBaseline, + discoverySmokeServiceBaseline, compilationEvidenceCliBaseline, cliContractBaseline, previousCliContractBaseline, @@ -638,11 +642,11 @@ test("Claude Code packaging reuses the portable Skill exactly once", async () => assert.equal(marketplace.name, claudeMarketplaceName); assert.equal(marketplace.plugins.length, 1); assert.equal(marketplace.plugins[0].name, claudePluginName); - assert.equal(marketplace.plugins[0].version, "0.1.0-alpha.3"); + assert.equal(marketplace.plugins[0].version, "0.1.0"); assert.deepEqual(marketplace.plugins[0].source, { source: "npm", package: "@firstdraft.com/claude-code", - version: "0.1.0-alpha.3", + version: "0.1.0", registry: "https://registry.npmjs.org/", }); assert.equal(packageTemplate.version, "0.1.0");