Repository navigation
refactor(notarization): move stage record rules into Go - #65
Merged
Merged
Conversation
Parse, validate and advance the notarization stage record in one Go module behind `queue-tool notarize record`. The pipeline scripts and the REST submit path now use it instead of restating the key set, phases and ID formats in jq. Records written by earlier runs still validate and resume, including those without prepared_run_id, and writers keep the same serialization. Writes stay atomic, bounded and fail closed. Only prepared, uploading, submitted, accepted, finished is a legal sequence. The publish job gains one step that builds queue-tool from its own checkout. The recovery observer has no Go toolchain, so its recovery.json checks stay in jq and a test keeps them aligned with the module. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Marc Went <marc@went.io>
Make the tool wiring guard follow the built path through the GITHUB_ENV export and into each consumer, and prove it against recorded mutations. Run the rules distribution-recover.sh restates through real jq and the record module over the same records, so drift from either side fails. Validate publication stages without GH_TOKEN. On the local notarytool path, keep Apple's ID in the failure message when the submitted write fails, as the REST path does. Cover failed writes around the provider call, a leftover temporary file and the real attempt-first record shape. Report a failed existence check truthfully, drop a guard that can no longer be false, and correct the handoff claims on jq differences and byte compatibility. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Marc Went <marc@went.io>
Add non-hex and prefixed values to the recovery drift corpus, so format widening fails from either side, and pin those formats in the module's own table. Extend the wiring guard to a conditional, ignorable, commented-out or cross-compiled build, a later GITHUB_ENV rewrite and a later write onto the built path. Its comment now says what it guarantees and what it does not. Echo a provider value in the failure message only when it is a whole UUID, as the REST path does. Run the source artifact verifier without GH_TOKEN, which it does not use. Test the origin binding guard, and correct the handoff claims. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Marc Went <marc@went.io>
The echo gate test never used a whole UUID, so dropping the anchors from id_pattern still passed and text around a valid ID could reach the log. Add a UUID with a suffix and one with a prefix. List what the wiring guard cannot see, including dd, rsync, a continued cp and an earlier GOOS export. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Marc Went <marc@went.io>
Dunky13
force-pushed
the
arch/notarization-record
branch
from
November 3, 2026 22:23
a20c9e3 to
3550daf
Compare
Dunky13
marked this pull request as ready for review
November 3, 2026 22:23
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.
Summary
Go now owns the notarization stage record.
internal/notaryapi/record.goparses and validates it and owns the phase transitions, and both the RESTsubmitpath and the pipeline scripts use it throughqueue-tool notarize record {get,validate,create,advance}. The duplicated stage-recordjqvalidators and writers are gone frommacos-resume.sh,macos-receipts.shanddistribution-publish.sh.This is intended to preserve behaviour. Records already written still validate and resume, including those without
prepared_run_id, and a failed preparation checkpoint still resumes. New output matches the lastjqwriters byte for byte, though a rewrite of a real production record reversesprepared_attemptandprepared_run_id, which is harmless because consumers ignore key order. Exit codes keepset -e,pipefailand the existing|| failmessages working. The differences that do exist are listed in the handoff, most notably thatmacos-resume.shnow requiresQUEUE_NOTARY_TOOLfor every operation on existing state, including local Keychain-profile use.Tool provisioning. Each job builds
queue-toolfrom its own checkout, and it never crosses jobs or architectures as an artifact.notarizemacos-15publishubuntu-24.04recoverubuntu-24.04Reported rather than worked around.
distribution-recovery.ymlhas no Go toolchain. It runs every 15 minutes withactions: writeand a 10 minute budget, so adding a toolchain there is a meaningful workflow change. Itsjqtherefore still restates the phase list, the empty-ID rule, the SHA-256 format and the UUID format forrecovery.json, which is a separate 20-key document.TestRecoveryProjectionMatchesRecordRulesruns those rules through realjqand the module'svalid()over the same corpus and fails on any disagreement it exposes, from either side. It only sees differences that corpus exposes, and it leaves out trailing-newline values, where the two differ on purpose.Wiring guard limits.
TestStageRecordToolIsBuiltBeforeEveryConsumerreads workflow text and does not interpret it. It catches a missing, commented-out, conditional or cross-compiled build, a build allowed to fail, a missing$GITHUB_ENVexport, a consumer given a different path, a later$GITHUB_ENVrewrite, and a latercp,mv, redirect ordownload-artifactonto the path. It misses, confirmed by trying them, a write through another variable,rmof the tool, and a write inside a called script. A missing tool fails closed at the first record read. A replaced binary that still behaves would go unnoticed. Whether to add Go there is a separate decision.Out of scope and unchanged: GitHub inventory pagination duplicated between
scripts/macos-checkpoint.shandscripts/distribution-common.sh.Evidence
Local results are from a host under sustained load with swap nearly exhausted. "Latest repair" is the commit after
afd42e1.go test ./internal/notaryapi(non-race)internal/toolingruns: the new and changed testsscripts/lint.shTestDistributionPublish*,TestWorkflow*and the main resume, upload and origin testsafd42e1afd42e1internal/notaryapiunder-race, inside validate job 2777c38735cinternal/tooling: 139 of 176 top-level tests, covering resume, receipts, publication, checkpoint, signing and workflowc38735cTestNotarizedHomebrew*, environment-gated)queue-toolc38735cscripts/validate.sh, job 2777c38735cCorrections. At
afd42e1the recovery drift test missed four format widenings, and the failure message echoed non-UUID values such asdeadbeef. Both are fixed here.Correction to the earlier claim on
jqand Go. I said every disagreement was a trailing newline. That was wrong, because my 835-record corpus lacked the other shapes. Independent review compared them over 1,477 unique records:jqaccepted 108 that Go rejected (trailing newline 38, duplicate keys 20, over 16 KiB 20, multiple documents 20, BOM 10), and Go accepted nothingjqrejected. No writer produces any of these.scripts/validate.shdid not pass, and neither tooling shard completed locally. No commit afterc38735creran them locally, by instruction, because of host memory pressure.go test, so web, browser and native tests, the app build, and the installer and DMG checks did not run.internal/toolingreached the 12 minute limit with no test failure reported, so the package did not complete.internal/backendreached the same limit. This branch does not touch it.internal/remotefailed three Caddy tests on wall-clock limits. It does not importnotaryapi.Attributing those limits to host load is an inference: no baseline
validate.shran under the same conditions.Hosted CI runs both tooling shards on this PR, on both architectures, and is the first complete
internal/toolingrun on this code. Watch Intel timing: eachqueue-toolexec costs about 19 ms against about 3 ms forjq, and the two slowest resume tests measured about 15% slower. The 12 minute limit applies to the test binary, recent main runs took 441 s and 451 s, and the worst test step in 12 recent runs was 9:51.Remaining verification. Real notarization cannot run here. The
Distributionworkflow (.github/workflows/distribution.yml, jobnotarize, thenpublish) first exercises this against Apple, only frommain. No release or distribution run was triggered.Merge Danger
Door: Two-way. Records stay readable in both directions, and reverting restores the
jqvalidators with no migration. Key order can differ after a rewrite, and consumers ignore it.Blast Radius: The local
notarytoolpath now prints Apple's ID, only when it is a whole UUID, if itssubmittedwrite fails, which fixes a defect that also existed at7b4b27c.queue-toolnow runs withoutGH_TOKENindistribution-publish.shanddistribution-source.sh. The main-onlyDistributionworkflow, jobsnotarizeandpublish, plus any in-flight checkpoint resuming under new automation. A defect here would stop or fail a notarization run closed rather than publish. An upload already in theuploadingphase still requires manual reconciliation, as it does today.submitnow returnsinvalid_recordwhere it returnedsubmission_refusedfor malformed but parseable records, still before any provider call. Localmacos-resume.shuse withoutQUEUE_NOTARY_TOOLnow stops.Before confirming a squash merge:
Signed-off-bytrailer. Check the selected email, including any GitHub noreply default.Marc Went <marc@went.io>and retain exactly one maintainer trailer:Signed-off-by: Marc Went <marc@went.io>. Consolidate duplicate maintainer trailers and the maintainer's noreply alias; preserve other contributors' certifications. Follow the commit policy below for GitHub CLI author selection and post-merge verification.sh scripts/merge-pr.sh PR_NUMBER REVIEWED_HEAD SQUASH_BODY_FILEto bind the reviewed head, primary author email, and single maintainer certification, then verify the actual merged commit. Green PR checks do not validate a future web squash commit's author identity.See the commit policy and squash author instructions.
🤖 Generated with Claude Code