Add receipt signing (schema 0.0.14) - #22
Merged
Merged
Conversation
The adopted definition calls a receipt "an authenticated record"; until now nothing made it authentic. The design went through an adversarial external review BEFORE implementation, and the review's CRITICAL finding shaped the shipped shape: the probe-running command has NO signing flag, because it executes the change's own test code and any key it can read, reviewed code can read. Signing is a separate `sign` subcommand that runs no probes and reads no tree; CI mounts the key only for that step. - schema 0.0.14: SignatureBlock (alg/public_key/audience/sig) - internal/signing: sign, verify (canonical byte-identity, mandatory subject match, pinned-key-only trust, audience bound in the preimage), PEM keygen/load with atomic exclusive creation - internal/strictjson: the strict decode contract shared with intake — also fixes the Decoder.More EOF gap the review found in merged code (a stray closing delimiter passed as end-of-input) - receipt.Canonical + ValidateConsistency: one byte-form per receipt (golden-vector frozen) and re-derivation of every computable field on both sides, so a tampered-summary receipt cannot be signed or verified - gitdiff.InputDigest now pins file kind (regular/exec/symlink) and never follows symlinks: an exec-bit flip or file-to-symlink swap changed probe behavior under an unchanged digest - render subcommand: PR markdown from the signed JSON without a second probe run; renderings state their signature is UNVERIFIED HERE - deferred with stated rationale: freshness/replay policy (verifier's CI owns it), receipt chaining (own backlog item) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015GeUG1gboWiZSnFyzQghyp
correctful receipt30 claims — ✅ 30 verified · ❌ 0 refuted · Change:
|
| Tier | Claim | Statement |
|---|---|---|
| T1-assertion | TestDetectBaseFallsBackToLocalDefaultBranch |
Detect Base Falls Back To Local Default Branch |
| T1-assertion | TestDetectBaseHonorsCIBaseRef |
Detect Base Honors CIBase Ref |
| T1-assertion | TestDetectBaseFailsLoudWithNoCandidate |
Detect Base Fails Loud With No Candidate |
| T1-assertion | TestTrackedByPattern |
Tracked By Pattern |
| T1-assertion | TestPatchCarriesCommittedAndUncommittedHunks |
Patch Carries Committed And Uncommitted Hunks |
| T1-assertion | TestResolveIncludesUntrackedButNotHiddenState |
Resolve Includes Untracked But Not Hidden State |
| T1-assertion | TestInputDigestPinsWorkingTreeContent |
Input Digest Pins Working Tree Content |
| T1-assertion | TestInputDigestPinsKindNotJustContent |
Input Digest Pins Kind Not Just Content |
| T1-assertion | TestValidateConsistencyAcceptsAssembledReceipt |
Validate Consistency Accepts Assembled Receipt |
| T1-assertion | TestValidateConsistencyRejectsTampering |
Validate Consistency Rejects Tampering |
| T1-assertion | TestCanonicalGoldenVector |
Canonical Golden Vector |
| T1-assertion | TestKeygenAndLoadRoundTrip |
Keygen And Load Round Trip |
| T1-assertion | TestKeygenRefusesExistingAndLeavesNoPartialPair |
Keygen Refuses Existing And Leaves No Partial Pair |
| T1-assertion | TestKeygenRefusesPlantedSymlink |
Keygen Refuses Planted Symlink |
| T1-assertion | TestLoadKeyRejections |
Load Key Rejections |
| T1-assertion | TestSignVerifyRoundTrip |
Sign Verify Round Trip |
| T1-assertion | TestVerifyWrongKeyFails |
Verify Wrong Key Fails |
| T1-assertion | TestVerifyAudienceBinding |
Verify Audience Binding |
| T1-assertion | TestVerifySubjectMatching |
Verify Subject Matching |
| T1-assertion | TestVerifyRejectsNonCanonicalArtifact |
Verify Rejects Non Canonical Artifact |
| T1-assertion | TestVerifyRejectsEveryByteMutation |
Verify Rejects Every Byte Mutation |
| T1-assertion | TestSignRefusesInconsistentReceipt |
Sign Refuses Inconsistent Receipt |
| T1-assertion | TestVerifyRejectsSignedInconsistency |
Verify Rejects Signed Inconsistency |
| T1-assertion | TestSignRefusesAlreadySigned |
Sign Refuses Already Signed |
| T1-assertion | TestSignRejectsControlAudience |
Sign Rejects Control Audience |
| T1-assertion | TestVerifyRejectsMalleableBase64 |
Verify Rejects Malleable Base64 |
| T1-assertion | TestGoldenSignature |
Golden Signature |
| T1-assertion | TestRFC8032Vector |
RFC8032 Vector |
| T1-assertion | TestCleanDocumentDecodes |
Clean Document Decodes |
| T1-assertion | TestStrictRejections |
Strict Rejections |
Harvest coverage: 19 files — 6 claimed · 13 scanned · 0 unread
3 spec-id mention(s) not minted as claims — the repo defines no spec-id corpus, so a reference has no possible referent
schema 0.0.14 · correctful v0.0.0-20260818031256-3f1c4dee112d 3f1c4de · exit gate: refuted claims block; the remainder informs, never fails
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Makes the word "authenticated" in the receipt definition true: an Ed25519 signature over the domain-separated canonical payload, with the trust model reviewed adversarially before a line was written.
The review's CRITICAL finding shaped the design. The draft put
-signon the main command. That command builds and executes the change's own test code — any key it can read, reviewed code can read. The shipped design has no signing flag there at all:correctful signruns no probes and reads no tree, so CI can mount the key for that step alone.What ships:
keygen/sign/verify/rendersubcommands.verifydemands the expected head SHA (a valid signature over SOME receipt is worthless to a gate), takes trust only from the caller's pinned key, and binds an audience inside the preimage so a shared CI key cannot confuse repositories.internal/strictjson— which also fixes a live bug the review found in merged code: the oldDecoder.MoreEOF check let a stray closing delimiter pass as end-of-input.Signrefuses an internally inconsistent receipt, andVerifyre-derives every computable field, so the tampered-summary attack (one refuted result,Summary.Refutedzeroed, gate reads the summary) fails even when a bypassing signer minted the signature.Deferred with stated rationale in DESIGN.md: freshness/replay policy (the verifier's CI owns freshness) and receipt chaining (its own backlog item — until then these are authenticated individual records, and the docs avoid the word "chain").
🤖 Generated with Claude Code
https://claude.ai/code/session_015GeUG1gboWiZSnFyzQghyp