{"slug": "a-real-deployment-exposed-two-contract-defects-before-i-froze-the-contract", "title": "A Real Deployment Exposed Two Contract Defects Before I Froze the Contract", "summary": "Developer Shiki Yusuke built release-evidence, a tool that records source and build provenance, seals artifact digests, and appends deployment events to connect CI builds to production deployments. A real deployment to GitHub Pages exposed two contract defects before the contract was frozen: an assumption that every target had a preview environment, and a circular digest dependency between the release bundle and the manifest. The contract was updated to handle direct production deployments with a preview_skipped flag and to remove the circular dependency.", "body_md": "A green CI run does not prove that the artifact built from reviewed source matches what production later exposes.\n\nThe build job may have produced one directory while the deploy job selected another. A file may have been added after review. The live manifest and representative file bytes may match the digests sealed at build time—or they may not.\n\nI built [release-evidence](https://github.com/shiki-yusuke/release-evidence) and its [v0 contract](https://github.com/shiki-yusuke/ai-agent-skills-playbook/tree/main/contracts/release-evidence/v0) to connect those claims. It records source and build provenance, seals artifact digests, appends deployment events, and checks a bounded part of the live production target.\n\nBut I did not freeze the first reviewed version of the contract.\n\nDeployment contracts are unusually good at lying while every synthetic fixture stays green. A\n\nschema can be internally consistent and still describe a topology that does not exist. Two digest\n\nfields can both look reasonable until an adapter has to decide which one to compute first.\n\nKeeping the contract in draft until a real adapter exercised it exposed two defects.\n\nThe first target was a static dashboard deployed through GitHub Pages.\n\nThe evidence chain needed to support a narrower statement:\n\n``` php\nreviewed source\n  -> one built artifact\n  -> that artifact deployed to production\n  -> live manifest read back\n     + representative file bytes spot-checked\n```\n\nThe draft contract used a sealed release bundle and an append-only event ledger. The bundle held\n\nthe source commit and tree, toolchain evidence, and artifact digests. The ledger appended events\n\nsuch as `prepared`\n\n, `deployed`\n\n, and `verified`\n\n; current state was derived by folding those events,\n\nnot by editing a mutable status row.\n\nThere were schemas, semantic checks, and accepted and rejected fixtures. The draft passed review.\n\nThe pull request still said that v0 would remain unfrozen until a real adapter had exercised it.\n\nThe first transition graph expected a preview environment before production.\n\nThis scheduled GitHub Pages deployment has no real preview tier. The workflow builds the static\n\ndashboard and deploys it directly to the production Pages site.\n\nI could have labeled a CI workspace “preview” and made the ledger satisfy the graph. That would\n\nhave been worse than an explicit gap: an evidence system would be changing the description of\n\nreality to pass its own validator.\n\nThe contract changed instead.\n\nA direct `prepared -> deployed|production`\n\ntransition is now valid only when it carries\n\n`preview_skipped: true`\n\nand a closed reason code. The same flag is rejected on the normal preview\n\npath. The absence of a preview is an explicit property of the target, not a silently missing event.\n\nGitHub Pages was not defective. The contract's assumption that every target had a preview was.\n\nThe static site exposes a `release-manifest.json`\n\ncontaining a digest for each deployed path. A\n\nread-back can fetch the manifest and compare a live file with the value it records.\n\nThe first design also made the release bundle carry the byte digest of that manifest, while the\n\nmanifest carried the digest of the bundle:\n\n```\nbundle\n  contains sha256(release-manifest.json bytes)\n\nrelease-manifest.json\n  contains sha256(bundle)\n```\n\nThe bundle required the finished manifest. The manifest required the finished bundle. Neither\n\nvalue could be computed first.\n\nThe cycle looks obvious when written as two arrows. It was much less obvious while reviewing the\n\ntwo record shapes separately. It surfaced when the real adapter had to generate files in an actual\n\norder.\n\nThe deployed manifest now contains only its schema version and content map:\n\n```\n{\n  \"schema_version\": \"release-evidence/v0\",\n  \"content\": {\n    \"index.html\": \"sha256:...\"\n  }\n}\n```\n\nIt does not repeat the enclosing bundle digest. During production read-back, the adapter\n\ncanonicalizes the fetched `content`\n\n, computes its SHA-256 digest, compares that value with the\n\nstatic-site artifact in the sealed bundle, and spot-checks live file bytes against the manifest.\n\nRemoving the mutual reference made the evidence flow one-directional.\n\nOn August 22, 2026, the corrected adapter ran in the real workflow.\n\n[Workflow run 32572501427](https://github.com/shiki-yusuke/agent-metrics-harvester/actions/runs/32572501427)\n\nbuilt the dashboard, deployed it to GitHub Pages, fetched the live manifest, compared its content\n\nmap, spot-checked a representative live file, and persisted the evidence ledger.\n\nThe ledger contains this sequence:\n\n``` php\nprepared\n  -> deployed | production | preview_skipped=true\n  -> verified | production\n```\n\nRecomputing the digest from the live manifest's canonicalized content produced the same value as\n\nthe artifact sealed in the bundle:\n\n```\nsha256:35e4b7ee5a17ca64f0851e54d83385f2c478b4d04e3210a2e08639fecd3b6d0c\n```\n\nOnly after that receipt existed did the contract replace its synthetic dashboard fixture with the\n\nreal deployed bundle and freeze `release-evidence/v0`\n\n.\n\nThe order mattered:\n\n``` php\nreview the draft\n  -> let a real adapter break it\n  -> repair the contract\n  -> verify a production read-back\n  -> freeze\n```\n\nFreezing first would have forced the adapter either to invent a preview or immediately violate the\n\ncontract I had just declared stable.\n\nAfter the first production exercise succeeded, I found another issue in the adapter. A later\n\nrelease should derive `previous_release_id`\n\nfrom the last verified production event. The first\n\nimplementation still needed that repeat-run chain logic.\n\nI keep this separate from the two contract defects. It did not change the transition model or the\n\ndigest definition. It was an adapter defect revealed by asking what the second release must do.\n\nAfter that fix reached main, scheduled\n\n[run 32615972524](https://github.com/shiki-yusuke/agent-metrics-harvester/actions/runs/32615972524)\n\ncreated a second production release from commit `6f0b5c8`\n\n, which includes\n\n[PR #7](https://github.com/shiki-yusuke/agent-metrics-harvester/pull/7).\n\nThe new bundle records the link explicitly:\n\n```\nrelease_id:\n  agent-metrics-dashboard@32615972524-1\n\nprevious_release_id:\n  agent-metrics-dashboard@32572501427-1\n```\n\nThe first release was the preceding verified production release; its sealed bundle digest was\n\n`sha256:63392d1a635e68f670f07dcb717ad7e1437f8282be82a1dd8c0ab2c6a664c9e1`\n\n.\n\nThe second release recorded `prepared`\n\nat `2026-08-23T03:41:48.031Z`\n\n, `deployed | production`\n\nat `2026-08-23T03:42:20.467Z`\n\n, and `verified | production`\n\nat\n\n`2026-08-23T03:42:20.659Z`\n\n.\n\nIts ledger persisted the same three-event sequence under one sealed bundle digest:\n\n``` php\nprepared\n  -> deployed | production | preview_skipped=true\n  -> verified | production\n```\n\nThe sealed bundle digest is\n\n`sha256:7dabd0a36be4308cb1c70387d1097dcb37c7fde3be3aee991f8287371bc661df`\n\n.\n\nRecomputing the static-site digest from the live manifest content produced\n\n`sha256:ac69214db9909b1f1d97bd1f806214034172e0dac780e1c1470434f158b70d49`\n\n,\n\nand the live `index.html`\n\nbytes matched the manifest entry\n\n`sha256:ce99db76efd55bace6b50bd9ba88caa76e1dbef841c76e87dcb07b7d88380c9e`\n\n.\n\nAuditing the persisted collection against the frozen v0 contract reported two bundles, six\n\nevents, and no problems. The repeat-run link is now observed in production. The order still\n\nmatters: the first success did not prove that future linkage until a second release exercised it.\n\nTwo subsequent scheduled runs created production releases from the same PR #7 merge commit.\n\n| Run | Release ID | Previous verified release | Sealed bundle digest |\n|---|---|---|---|\n|\n\n`agent-metrics-dashboard@32687508604-1`\n\n`agent-metrics-dashboard@32615972524-1`\n\n`sha256:fd2ee5b9ef6d531b1c377257a60ced576a949964be186fd9b1c6337278b2a8a4`\n\n`agent-metrics-dashboard@32806015116-1`\n\n`agent-metrics-dashboard@32687508604-1`\n\n`sha256:94d9f3ee15e2238eb84dcae6dbe26bb4f69afd434ce8a1491be636e24d40b8e0`\n\nThe first follow-up chain recorded `prepared`\n\nat `2026-08-24T03:45:20.910Z`\n\n, `deployed |`\n\nat\n\nproduction`2026-08-24T03:45:54.808Z`\n\n, and `verified | production`\n\nat\n\n`2026-08-24T03:45:55.135Z`\n\n. The second recorded the same sequence at\n\n`2026-08-25T03:40:22.482Z`\n\n, `2026-08-25T03:40:53.785Z`\n\n, and\n\n`2026-08-25T03:40:53.929Z`\n\n. Within each chain, all three events carry the same sealed bundle\n\ndigest shown in the table. The Actions logs also record `read-back verified`\n\nagainst the\n\ncorresponding static-site artifact digest.\n\nThese runs add evidence that the `previous_release_id`\n\nchain and read-back continued to operate.\n\nThey do not prove that every future deployment will succeed, that every deployment is safe, or\n\nthat an external user adopted the tool.\n\nThe integrity level of `release-evidence/v0`\n\nis `digest_only`\n\n.\n\nThere is no signature, SLSA, or SBOM claim. GitHub Actions credentials, pinned actions, the tool\n\ncheckout, and the production host remain inside the trust boundary.\n\nA successful read-back shows that the live manifest's content map matched the static-site\n\nartifact digest sealed in the bundle, and that a representative live file matched its manifest\n\nentry at verification time.\n\nIt does not show that every production file was fetched and compared byte for byte, or that the\n\nhost cannot change afterward.\n\n```\ncontent integrity\n!= semantic correctness\n\ndigest match\n!= complete supply-chain security\n\nsuccessful read-back\n!= future correctness\n```\n\nThe v0 ledger assumes one writer. The first adapter is specific to a GitHub Pages/static-site\n\ndeployment. It is not a universal release database or an automatic promotion system.\n\nStating those limits does not weaken the receipt. It prevents a content-digest proof from silently\n\nturning into a broader supply-chain claim.\n\nSchema review and synthetic fixtures were necessary. They remain valuable regression guards after\n\nthe fixes.\n\nThey were not enough to justify freezing a deployment contract.\n\nReal targets have topology that diagrams omit. Artifacts have generation order that isolated\n\nschemas hide. Production read-back crosses boundaries that a local build never touches.\n\nThe point was not to delay stability. It was to keep the contract breakable until the last cheap\n\nmoment to discover that it described something impossible.\n\nThe real adapter found a preview environment that did not exist and a digest cycle that could not\n\nbe computed. Both were contract problems, not cosmetic implementation details.\n\nBefore freezing a deployment-evidence schema, I now want one answer from the production path:\n\ncan an honest adapter actually satisfy this contract without inventing state?\n\nFor `release-evidence/v0`\n\n, asking that question before the freeze is what found the two defects.\n\nAI-assistance disclosureI used ChatGPT and Codex to help structure, edit, and translate this article. I reviewed the release IDs, digests, timestamps, implementation details, and claim boundaries against the recorded evidence before publication.", "url": "https://wpnews.pro/news/a-real-deployment-exposed-two-contract-defects-before-i-froze-the-contract", "canonical_source": "https://dev.to/shikiyusuke/a-real-deployment-exposed-two-contract-defects-before-i-froze-the-contract-2pao", "published_at": "2026-08-26 10:14:59+00:00", "updated_at": "2026-08-26 10:43:56.064405+00:00", "lang": "en", "topics": ["developer-tools", "mlops"], "entities": ["Shiki Yusuke", "release-evidence", "GitHub Pages"], "alternates": {"html": "https://wpnews.pro/news/a-real-deployment-exposed-two-contract-defects-before-i-froze-the-contract", "markdown": "https://wpnews.pro/news/a-real-deployment-exposed-two-contract-defects-before-i-froze-the-contract.md", "text": "https://wpnews.pro/news/a-real-deployment-exposed-two-contract-defects-before-i-froze-the-contract.txt", "jsonld": "https://wpnews.pro/news/a-real-deployment-exposed-two-contract-defects-before-i-froze-the-contract.jsonld"}}