Skip to content

feat(release-artifact): verify at pin time, mirror offline, and lock what was verified - #267

Open
Alan-TheGentleman wants to merge 5 commits into
feat/release-artifact-decoderfrom
feat/release-artifact-sync
Open

feat(release-artifact): verify at pin time, mirror offline, and lock what was verified#267
Alan-TheGentleman wants to merge 5 commits into
feat/release-artifact-decoderfrom
feat/release-artifact-sync

Conversation

@Alan-TheGentleman

Copy link
Copy Markdown
Collaborator

Second slice of the consumer chain, stacked on #266. Owns trust-order steps 1 through 5, the network boundary; #266 owns steps 6 through 11.

Where the signature check actually belongs

scripts/gentle-ai-installer.mjs documents that the pinned sha256 literals were derived by a human from the signed checksums.txt, and install compares against that pin. So minisign verification and repo/tag binding belong to the pin-time sync job; install time inherits that trust through the lock.

Shipping a verifier and a trusted key to every end user would be a larger new trust surface for no gain, and it was rejected in design rather than skipped by accident.

The lock is what makes every PR check offline

It binds release identity, contract major, archive digest, tree digest, canonical entries and generated-output digests. Only an explicit pin-bump job downloads anything.

scripts/verify-package-files.mjs loses its hand-maintained ~60-entry contractHashes map and reconciles against the lock instead. Three failure modes each fail naming the exact file: a mirror on disk the lock does not list, a lock entry with no file, and a drifted digest.

The offline claim is proven, not asserted: the trusted key is a pending sentinel that throws before any network call, and verify-package-files.mjs --check passes reading only the lock and local files.

A defect this slice found and fixed in the previous one

Running the script end to end surfaced that lib/release-artifact.ts and its generated runtime were missing from requiredPaths in the package check. Fixed here rather than left for someone to trip over.

And a defect in this slice, caught in review

The evidence class was carried only on the in-memory sync result. The lock that actually gets checked in, reviewed and later trusted carried no indication of where it came from.

That is precisely the failure mode the spec exists to prevent. A bootstrap-derived lock has a placeholder release.commit and an archive.digestSource reading signed-checksums.txt — any reader would take it for a real signed release, and nothing in the file said otherwise.

The lock now persists evidence.class and evidence.signatureStatus, so a reader holding only the file can still refuse it as pin or final-acceptance evidence. Two tests cover it, including one asserting the checked-in lock itself is labelled bootstrap. That fix is its own commit.

Status of the checked-in lock

It is development/bootstrap evidence and says so. No gentle-ai release exists under this contract yet, so it must be regenerated by a real network --write run once the provider release lands and the real minisign key is provisioned.

Tests

1067 pass, 3 fail. Those three are the known receipt-driven-development-disabled failures in tests/native-review-parity-runtime.test.ts, verified identical on clean main before this chain started. No regressions.

One interpretation flagged

The minisign trusted-comment repo and tag convention was defined here because design did not specify an exact wire format for this boundary. It is tested, but it is an interpretation and should be confirmed against the provider's actual signing output before the first real sync.

… release artifacts

Implements design D1 steps 1-5 (the network/pin-time trust boundary):
a from-scratch Ed25519/minisign wire-format verifier (node:crypto only,
no new dependency), a checksums.txt line matcher, and a trusted-comment
repo/tag binder, orchestrated by syncGentleAiRelease() which delegates
D1 steps 6-11 and D2 extraction to lib/release-artifact.ts.

A local --bootstrap-archive skips network and signature verification
entirely and stamps every result development/bootstrap, matching the
S-vs-R evidence ledger: assertReleaseAcceptanceEvidence throws on a
bootstrap-sourced result, so it can never be relabeled release evidence.

The network path fails closed with zero network activity while the
trusted minisign public key is the pending sentinel (mirroring the
existing GENTLE_AI_PENDING_DIGEST pattern) — gentle-ai has not yet
published a signed release under this contract.
Writes the checked-in mirror files this unit introduces and the
canonical lock, produced by running scripts/sync-gentle-ai-release.mjs
--write --bootstrap-archive against a locally-built archive:

- contracts/release-artifact/v1/schemas/artifact-manifest.schema.json
- capabilities/review-integration-v2.semantic.json
- docs/gentle-ai/review-integration.md
- capabilities/gentle-ai-release.lock.json (canonical LF, 2-space,
  path-sorted entries/generated, one trailing LF; release.version
  asserted equal to INSTALLER_VERSION)

No gentle-ai release exists yet under the release-artifact contract
(tracked separately, out of scope here), so this lock is
development/bootstrap evidence, not release evidence: it pins today's
already-correct mirror bytes and the mechanism, and must be
regenerated by a real signed-release sync once one is published.
The existing contracts/review-integration/{v1,v2}/** files are
untouched byte-for-byte (git status shows zero diff on any
previously-tracked file).
…ot a hand-maintained map

Deletes the ~60-entry hand-maintained contractHashes map. Extends the
existing reconcileContractsOnDisk pattern (same exported name and
2-argument signature) to walk every full-mirror directory (contracts/,
docs/gentle-ai/, capabilities/ minus the lock itself and the future
hand-authored capabilities/native-cli-history.json) and compare against
digests read from capabilities/gentle-ai-release.lock.json via the new
mirrorDigestsFromLock/mirrorDigestDrift helpers. Each of the three
failure modes (unlisted-on-disk, listed-but-missing, digest drift)
names the exact file. assertLockReleaseVersionPin gates the lock's
release.version against the authoritative INSTALLER_VERSION pin.

Also fixes a gap surfaced by actually running this script end-to-end
for the first time since P1a landed: lib/release-artifact.ts and
runtime/release-artifact.mjs were missing from requiredPaths and the
generated-runtime reconciliation. Adds capabilities/ and
scripts/sync-gentle-ai-release.mjs to the required/packaged set, and
notes in ci.yml that the existing "Verify package contents" step now
performs this reconciliation fully offline.
The evidence class was carried only on the in-memory sync result, so the
lock that actually gets checked in, reviewed and later trusted carried no
indication of where it came from.

That is the failure mode the spec exists to prevent. A bootstrap-derived
lock has a placeholder release.commit and an archive.digestSource reading
signed-checksums.txt, which any reader would take for a real signed
release. Nothing in the file said otherwise.

The lock now records evidence.class and evidence.signatureStatus, so a
reader holding only the file on disk can still refuse it as pin or
final-acceptance evidence. The checked-in lock is relabelled accordingly.
@coderabbitai

coderabbitai Bot commented Aug 1, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: be99389b-9b18-4e3f-a9c7-f50c38c4a114

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant