{
  "what_this_is": "Every figure in A Key Means Nothing Alone, with the tag it was taken at and the SHA-256 of that page's bytes at that tag. A figure of the past is pinned to a tag and never goes stale; a figure of the present breaks the build on the next release. That is two maintenance costs rather than none, accepted deliberately.",
  "harness": {
    "capture": "book/shots/travel.sh",
    "browser": "book/shots/shot.mjs",
    "transcripts": "book/shots/transcripts.sh",
    "all": "book/shots/capture-all.sh",
    "gate": "book/build.py --check-figures"
  },
  "rules": [
    "A figure is taken from the version its caption names \u2014 git worktree at the tag, never photographed today and captioned as the past.",
    "Never reuse a port: one capture, one port, forever.",
    "Always kill what you spawned, in a block that runs whether the capture succeeded or failed.",
    "A screenshot that looks fine, taken from a page that threw, is a figure you must not publish \u2014 pageerror is collected and fails the job.",
    "A blank check runs over every batch: the fraction of pixels that are not the modal colour, so a blank page of ANY colour is caught, not only a white one."
  ],
  "count": 14,
  "figures": [
    {
      "id": "f01-bench-two-columns",
      "page": "/bench/index.html",
      "tag": "current",
      "page_sha256": "f495703bcca4f15118b68a8c986c41803ee8620e3205bb1509b46fcf052652c3",
      "image": "shots/img/f01-bench-two-columns.png",
      "image_sha256": "cf6e30faa08a7ac3550c8792db43c6d29a73557a0ddd2c73e2487c3d9bc2c833",
      "device_scale_factor": 3,
      "ink_percent": 55.65,
      "gate": "fresh",
      "gate_note": "the recorded digest must match the live page; the build fails when it stops matching \u2014 which it will, on the next release",
      "caption": "The two columns are the same width. The limits are not a footnote to the claims \u2014 they are set beside them, at equal weight, and the generator refuses to build an entry whose right-hand column is empty.",
      "retake": "./book/shots/travel.sh current <fresh-port> book/shots/jobs/current.json"
    },
    {
      "id": "f02a-registry-at-v0.1.26",
      "page": "/registry/index.html",
      "tag": "v0.1.26",
      "page_sha256": "669a13f39dc51f664c184cce20c2b3fa74da72c2d58f3b4083f282a2bd3066f1",
      "image": "shots/img/f02a-registry-at-v0.1.26.png",
      "image_sha256": "70abca52d89cc5bb8f75805aee1ba2ee61075811ea6441502b54f77743cbe331",
      "device_scale_factor": 2,
      "ink_percent": 40.12,
      "gate": "re-derivable",
      "gate_note": "re-running travel.sh at v0.1.26 reproduces this digest; the tag does not move, so this never goes stale",
      "caption": "The register on the day it shipped. Compare the status language with the panel below: this page already says the root is a fixture.",
      "retake": "./book/shots/travel.sh v0.1.26 <fresh-port> book/shots/jobs/v0.1.26.json"
    },
    {
      "id": "f02b-registry-now",
      "page": "/registry/index.html",
      "tag": "current",
      "page_sha256": "d3c8c83246e7385498c266fee8475249fa59cc43d7a55fa2396e8085bca838ba",
      "image": "shots/img/f02b-registry-now.png",
      "image_sha256": "f399fe947ac1a6b29a0922e230add847a761113f4e8ff1b9036a67c10f4e498b",
      "device_scale_factor": 2,
      "ink_percent": 36.37,
      "gate": "fresh",
      "gate_note": "the recorded digest must match the live page; the build fails when it stops matching \u2014 which it will, on the next release",
      "caption": "The same page four days later. What changed is not the architecture but the number of places the page admits something \u2014 the corrections accumulated faster than the features.",
      "retake": "./book/shots/travel.sh current <fresh-port> book/shots/jobs/current.json"
    },
    {
      "id": "f03-six-answers",
      "page": "/registry/index.html",
      "tag": "current",
      "page_sha256": "d3c8c83246e7385498c266fee8475249fa59cc43d7a55fa2396e8085bca838ba",
      "image": "shots/img/f03-six-answers.png",
      "image_sha256": "0ce6308884bb0a14d63c1002ff981e1ac1132739e51cdedc3fed92668b4c01dc",
      "device_scale_factor": 3,
      "ink_percent": 24.14,
      "gate": "fresh",
      "gate_note": "the recorded digest must match the live page; the build fails when it stops matching \u2014 which it will, on the next release",
      "caption": "Four of the six answers are NO, and they are NO for four different reasons \u2014 revoked, expired, never accepted, identity revoked. A verifier that collapses those into one failure state is wrong on three of them.",
      "retake": "./book/shots/travel.sh current <fresh-port> book/shots/jobs/current.json"
    },
    {
      "id": "f04-identity-raw",
      "page": "/book/shots/term/t04-identity-raw.html",
      "tag": "current",
      "page_sha256": "a71eb703924bcfec3d8c6547ba0523d85a918e33af1082e2b2f8b7735243e2ea",
      "image": "shots/img/f04-identity-raw.png",
      "image_sha256": "2cf1d9d29d0a372f1d1009881aed4c10f26a9496d688ff3cde986514852103e5",
      "device_scale_factor": 3,
      "ink_percent": 3.61,
      "gate": "fresh",
      "gate_note": "the recorded digest must match the live page; the build fails when it stops matching \u2014 which it will, on the next release",
      "caption": "Read `private_key_published` before you read the signature. And note `publication_intent: deliberate` \u2014 a secret is defined by expectation, not by content, so the intention is recorded at issue, because afterwards a deliberate publication and a leak look identical.",
      "retake": "./book/shots/travel.sh current <fresh-port> book/shots/jobs/term.json"
    },
    {
      "id": "f05-validate",
      "page": "/book/shots/term/t05-validate.html",
      "tag": "current",
      "page_sha256": "1ff583569f0879bede6b1de0f8c5d10b612e1890f62be70d329f2c3fc5d6d4b5",
      "image": "shots/img/f05-validate.png",
      "image_sha256": "ab2de37b93d009e59239d12d7a8fcaebc1f7fec548b5641a98ddcc6aac807b52",
      "device_scale_factor": 3,
      "ink_percent": 6.09,
      "gate": "fresh",
      "gate_note": "the recorded digest must match the live page; the build fails when it stops matching \u2014 which it will, on the next release",
      "caption": "Six expected answers, six reproduced \u2014 and the fixture line printed for every record before any of them. The validator reads the flag first by construction, not by convention.",
      "retake": "./book/shots/travel.sh current <fresh-port> book/shots/jobs/term.json"
    },
    {
      "id": "f06-sgit-verify",
      "page": "/book/shots/term/t06-sgit-verify.html",
      "tag": "current",
      "page_sha256": "756d1878d1cdbacb1dd3e4f7b36d0e3bf776fd9153ad548cc41a5f2e0fabcb19",
      "image": "shots/img/f06-sgit-verify.png",
      "image_sha256": "8fec7649cc375c5610e89320dc26a00e40a9042ef1196b9b21bf59e2b8549e1d",
      "device_scale_factor": 3,
      "ink_percent": 6.14,
      "gate": "fresh",
      "gate_note": "the recorded digest must match the live page; the build fails when it stops matching \u2014 which it will, on the next release",
      "caption": "The signer line names a fixture. The CLI is not wrong; it is answering the only question a signature can answer \u2014 who held the private half \u2014 and on this record the answer is everybody.",
      "retake": "./book/shots/travel.sh current <fresh-port> book/shots/jobs/term.json"
    },
    {
      "id": "f07-forgery",
      "page": "/book/shots/term/t07-forgery.html",
      "tag": "current",
      "page_sha256": "b7d853249cc14deb8f49b423bbaacefb51f7337205d57282b366b7d69029823f",
      "image": "shots/img/f07-forgery.png",
      "image_sha256": "6dc7432a125ecb6b3d79d050c530a4d671f2fe3a28d6b167ab23b46edfb52485",
      "device_scale_factor": 3,
      "ink_percent": 5.08,
      "gate": "fresh",
      "gate_note": "the recorded digest must match the live page; the build fails when it stops matching \u2014 which it will, on the next release",
      "caption": "`Verified OK` on a document that says `anyone can sign this`. Nothing failed. That is the point: a signature anybody can produce conveys nothing, and the register verifies it exactly as diligently as any other.",
      "retake": "./book/shots/travel.sh current <fresh-port> book/shots/jobs/term.json"
    },
    {
      "id": "f08-refused-push",
      "page": "/book/shots/term/t08-refused-push.html",
      "tag": "current",
      "page_sha256": "2e1d58f1b48b8963aef8b27481918e49f0b90ae86667614dffd13bd0170b0ec2",
      "image": "shots/img/f08-refused-push.png",
      "image_sha256": "4385646e133a77d1b547a9d40582d590eb11510f3b2558aa4adaea3a1dd0536e",
      "device_scale_factor": 3,
      "ink_percent": 4.19,
      "gate": "fresh",
      "gate_note": "the recorded digest must match the live page; the build fails when it stops matching \u2014 which it will, on the next release",
      "caption": "The last line before the banner is git's exit code, not the agent's decision. Read the Tier line at the bottom: the control names its own weakness on its own face.",
      "retake": "./book/shots/travel.sh current <fresh-port> book/shots/jobs/term.json"
    },
    {
      "id": "f08b-amended",
      "page": "/book/shots/term/t08b-amended.html",
      "tag": "current",
      "page_sha256": "c142320f828847bdb58c9cb1ce973aba231a44e9da040d138ae3687e651ad22a",
      "image": "shots/img/f08b-amended.png",
      "image_sha256": "0ef9e653a74d73d01c52ef41d4de232c23bca48d621cdbe79ad7bb015acb3a6d",
      "device_scale_factor": 3,
      "ink_percent": 8.32,
      "gate": "fresh",
      "gate_note": "the recorded digest must match the live page; the build fails when it stops matching \u2014 which it will, on the next release",
      "caption": "The same tag, the same command, the mandate the release actually ships \u2014 and it PERMITS. The release documenting the refusal cannot contain the state that produced it, because the control refused the release carrying its own documentation until the mandate was amended.",
      "retake": "./book/shots/travel.sh current <fresh-port> book/shots/jobs/term.json"
    },
    {
      "id": "f09-tier-badges",
      "page": "/packs/grant-and-mandate/blocks.html",
      "tag": "current",
      "page_sha256": "412ee64b844da274721359c5d1a3ab227a15a2ab3967f7541ce1d00f56e7a249",
      "image": "shots/img/f09-tier-badges.png",
      "image_sha256": "65c4d485cd7e9c5c32c4f3f8a1bafa6f4aac5a6f95dc255ef7fd0ec5b4fe878e",
      "device_scale_factor": 3,
      "ink_percent": 57.89,
      "gate": "fresh",
      "gate_note": "the recorded digest must match the live page; the build fails when it stops matching \u2014 which it will, on the next release",
      "caption": "Two channels, never one: the border style carries the state as well as the colour, and the word is always present. `unknown` renders as `unknown` \u2014 never as a blank, because a gap is a fact about the floor.",
      "retake": "./book/shots/travel.sh current <fresh-port> book/shots/jobs/current.json"
    },
    {
      "id": "f10-defeated-boundary",
      "page": "/packs/grant-and-mandate/blocks.html",
      "tag": "current",
      "page_sha256": "412ee64b844da274721359c5d1a3ab227a15a2ab3967f7541ce1d00f56e7a249",
      "image": "shots/img/f10-defeated-boundary.png",
      "image_sha256": "7e433995f360d92422a6a60c7a1982dc64a50dfae07a6fe4b4ce49a428dd84b5",
      "device_scale_factor": 3,
      "ink_percent": 12.52,
      "gate": "fresh",
      "gate_note": "the recorded digest must match the live page; the build fails when it stops matching \u2014 which it will, on the next release",
      "caption": "The stored document says `boundary`. The block renders `setting`, with the defeat path attached. This is the rule working on real data that is wrong \u2014 the estate's own measurement tool produced the bad label.",
      "retake": "./book/shots/travel.sh current <fresh-port> book/shots/jobs/current.json"
    },
    {
      "id": "f11-authority-split",
      "page": "/packs/grant-and-mandate/blocks.html",
      "tag": "current",
      "page_sha256": "412ee64b844da274721359c5d1a3ab227a15a2ab3967f7541ce1d00f56e7a249",
      "image": "shots/img/f11-authority-split.png",
      "image_sha256": "1294f51e16a6da4aabf03fb08301c9b8a16e0ff28cd9f62eebadd3b7de8254a9",
      "device_scale_factor": 3,
      "ink_percent": 9.86,
      "gate": "fresh",
      "gate_note": "the recorded digest must match the live page; the build fails when it stops matching \u2014 which it will, on the next release",
      "caption": "Two indicators, never one. Averaging them into a single status is exactly how a demonstration gets mistaken for a control.",
      "retake": "./book/shots/travel.sh current <fresh-port> book/shots/jobs/current.json"
    },
    {
      "id": "f12-assess-midflow",
      "page": "/assess/index.html",
      "tag": "current",
      "page_sha256": "84882d2759dedebc445a58df118037441523d4468c48c6fafe171b61c9e4110c",
      "image": "shots/img/f12-assess-midflow.png",
      "image_sha256": "2c1ada652c3046140d764e6531eec107db488ba27551eda7eb7224c837625ac2",
      "device_scale_factor": 2,
      "ink_percent": 29.48,
      "gate": "fresh",
      "gate_note": "the recorded digest must match the live page; the build fails when it stops matching \u2014 which it will, on the next release",
      "caption": "Escalation is drawn as an edge, not written as a note \u2014 so the reader sees the path that goes around a stated control rather than reading that one exists.",
      "retake": "./book/shots/travel.sh current <fresh-port> book/shots/jobs/current.json"
    }
  ]
}
