Peregrine · build report
The season is recorded from v4 and invents nothing. Seven authoring locks now run in the exporter and every one is proved against a fixture. Halloran's Books is drafted, lints clean, and is held back from export.
Case 1 carries traces only: no beat, no method memo, no canon document. These three are the whole of what recurs. Each is a proposal, and none is in the corpus as canon.
31 March is invented. v4 fixes no calendar anywhere, and burn night's hour-by-hour timeline is itself marked assumed. If the season wants a different month the date moves and nothing else does.
2214 must not collide with SP-14 or the credential format. The corpus already carries "Standing Procedure 14" and a credential written P-[n]. An account number ending in 14 would rhyme with both, which is why this one does not.
Corrigan appearing in case 1 is the point and also the risk. A player who meets him as a helpful account holder will remember him. That is what a trace is for. It also means his name is spent — he cannot arrive fresh later.
Screened against all eight suspect casts (40 names), all 29 con aliases, the rejected map, the safe pool, and a word-level sweep of all 822 entries.
No name clashes with either cast. Not one of the seven reservations collides with a suspect name or a con alias. No existing name was changed.
| Name | Role | Cast clash | Word sweep over 822 entries | |
|---|---|---|---|---|
| Miles | Corrigan, given | none | Exact word in 18 entries — it is a unit of distance, and Prime Suspect case 1 measures a route in them ("the café is 1.2 miles from the museum"). Also inside smiles. | substring hazard |
| Evan | Tobin, given | none | Inside relevance, relevant, irrelevant, grievance — 41 entries, and relevance is a literal field name on every Prime Suspect clue. | substring hazard |
| Wren | the runner | none | Inside wrench — 47 entries, and The Wrench is a con whose id is literally wrench. | substring hazard |
| LANTERN | designation | none | Inside lanternfish, in one reading passage. | minor |
| Corrigan | the dead man | none | clean | clear |
| Starling | agent name | none | clean | clear |
| Tobin | the body | none | clean | clear |
| Nico / Varela | her brother | none | clean | clear |
| Francesca | the handler, given | none | clean. Already in the safe suspect pool, so reserving it takes it out of that pool. | clear |
| Falconer | the handler, surname | none | clean | clear |
| Halloran | the pharmacy | none | clean | clear |
| Narcotizan | the sedative | none | clean | clear |
None of them is a reason to rename anybody. All three names are safe in prose, because nobody reading "1.2 miles" thinks of Miles Corrigan.
They are a reason for every check that reads these names to match on word boundaries and never as a substring. A naive substring check for Miles reports a hit on arithmetic in 18 entries; for Evan, on a field name in 41; for Wren, on a con id in 47. The writing-rule checks below all match on word boundaries, and the reason is written into the code so the next author cannot loosen it by accident.
All seven live in export-machine.py and run from build(), so they fire on a dry run, on --write, and on the --verify hook. Each skips while the documents it reads are unwritten, and each failure names the entries involved and exits 1.
| Rule | What it enforces | Proved by breaking |
|---|---|---|
| One sedative, named in three places | Narcotizan names the same sedative in the pharmacy log, the tox supplement and Wren's memo. The rapid intake says ethanol and must not name the sedative — only the confirmatory supplement does. | caught a log that drops the name; caught a rapid intake that names it |
| The two death files | The original and the operational copy differ in IDENTIFICATION and in nothing else. The SP-14 stamp is excluded from the comparison. |
caught a second differing field; caught the two agreeing on IDENTIFICATION; allowed a stamp-only difference |
| Swelling beside no injury | bruising and trauma never appear. swelling never appears without no injury noted beside it. | caught "facial bruising"; caught swelling standing alone |
| Attended, never entered | The copy says attended the residence. Peregrine and entered never share a line. | caught "Agent Peregrine entered the study" |
| The clavicle plate, two documents | It appears on the intake sheet and the identity page, and on no other document. The match is immutable because the Agency never read those two against each other. | caught a third document naming it |
| The sling, shared then named | It appears in the neighbour's statement and the clerk's note, and neither names Corrigan beside it. Only the roster card makes the sling his. | caught the neighbour naming Corrigan |
| No bird before the roster card | Starling appears in no document at or before the Act III roster card. | caught the Act I memo using the bird |
$ python3 tools/export-machine.py peregrine
season locks: 0 checked, 7 waiting on unwritten documents
waiting: one sedative, named in three places — pharmacy-log, tox-supplement, wrens-memo, intake-sheet
waiting: the two death files — death-file-original, death-file-copy
…
Against the clean fixture with every document written, the same code reports 7 checked, 0 waiting and exits 0. Silent today because the season record is staged rather than committed; a guide with no season key produces no output at all, which is why stellar and every other guide are untouched.
The death-file comparison needs a field map, not prose. If the two documents are written as prose with no fields, the check reports waiting and names both entries, instead of comparing two blobs and reporting a pass it did not earn. I got this wrong on the first pass — it read the map off the season record rather than off the written entry, which made two deliberately broken fixtures come back green. That is fixed and both now fail as they should.
A guide-level record for guide.json, beside casting_pool. Not an entry. Staged as a note; the full JSON to paste is at docs/Peregrine-Plot/season-record.proposed.json.
| Section | Contents |
|---|---|
| cast + naming_rule | Nine roles. Agents are birds, the handler is the falconer, sources are designations, civilians are civilians. |
| in_world_rules | All five, verbatim, including SP-14 and "Corrigan wrote rules 3 and 4. That is the hole he lives in." |
| night | All thirteen timeline rows, each with what happens and what the paper says. Marked assumed: true as a whole, as v4 marks it. |
| death_files | The two identification lines, the nine identical-and-unread details, the Docket verb, and the stamp recorded as chrome. |
| act_turns | All five, each with its plot and its SAT reading move. |
| proof_documents | #1–#8 plus the Docket line. Each carries act, delivery, reading move and status: "unwritten". Sixteen documents are addressable once #6's four enclosures and #8's three parts are counted. |
| credential_thread | One use in the hour, two after the burn, the Docket's verb, and the badge going into the sequel. |
| authoring_locks | All ten. |
assumed: trueThe agent name Starling · the body's name and designation · Nico Varela · the handler going face-on at the Act III split · Halloran's · the whole night timeline · hints and routing passing to the ally after the burn · and the sequel thread behind why the Agency wanted Tobin.
Each document and part gets a key, an entry_id (null until written) and a status. They carry no story; they exist so a check has something to resolve. Without them the seven rules above have no way to name a document.
The original death file is numbered twice. v4 lists it as #7 and also describes it arriving inside #6. The record indexes it inside #6, where the checks read it, and keeps a #7 row so v4's numbering survives. Delete the #7 row if the duplication is unwanted; nothing reads it.
The floor plan still says Vale. Panel 5's route legend reads "Vale's route (in, ~8:00)". v4 renamed the body to Evan Tobin specifically to clear the Theo Vale / Idris Vale collision, so the image is one revision behind its own source.
Before She's Back. The desk is Francesca Falconer's, and v4 states it directly. The staged note supersedes the "Before He's Back" note in the earlier batch; if that one has not been committed, decline its rename and take this one.
Unchanged as instructed: the flat 100 points per file stands, and it still matches Casing the Room's flat 100 per mark. The verb row is untouched — allocate remains, with your ruling on open still outstanding. The record id stays casing-the-room.
The reskin line you approved reads "Ten files on his desk, three minutes." The desk is hers. I staged the line verbatim and flagged it rather than rewriting an approved line unasked. Recommendation: "Ten files on her desk, three minutes. Read what you can and get out."
Halloran's Pharmacy closes its March quarter $1,464 short and nothing is missing from the shelves. The money left through house-account postings, $122 a week for twelve weeks. Five counters; one took it. The ordinary step that turns the case is confirming a cash account's entries with its holder — and that step is the visit.
| Thread | Mode | What it establishes | Clears |
|---|---|---|---|
| E01 · Takings Against Deposits | numeric_multiple_choice | $47,180 recorded against $45,716 banked over twelve weeks — the gap has a rate, $122 a week. | — sustains |
| E02 · The Closing Roster | numeric_multiple_choice | The gap lands in all twelve weeks; only three counters closed in all twelve. | S01, S05 |
| E03 · A Cash Account, Confirmed | claim_choice | The visit. The holder of account 2214 confirms four payments of $86; the ledger posts four receipts of $65. Cash reached the counter that the ledger never recorded — and posting takes the ledger. | S02 |
| E04 · The Posting Window | numeric_multiple_choice | Every disputed posting is timestamped 6:40–6:55 p.m.; one counter is off the floor at six. | S04 |
| E05 · The Reconciliations | numeric_multiple_choice | Twelve weekly reconciliations, signed by three different counters; two signed none at all. | S01, S04 |
9 case(s) checked, 8 published — the draft is checked and held back.CaseValidator: 0 errors. Run through the same validator PrimeSuspectCaseLintTests uses, via a temporary harness that decoded the projected draft without putting it in the bundle. The harness is deleted.target_min_moves 3, and 3 distinct investigation orders, which clears the "admits only one order" assertion.rule-id-present on all five threads. All 65 shipped threads carry the same warning, so the draft matches the corpus it joins.A single-digit answer is dangerous. account-privacy substring-matches the correct choice text against the clue's own prose, so an answer of "4" collided with the "4" inside "6:40". E04's account now spells the window out and the digits live only in the prompt.
interval_comparison is the vehicle track-width mode. It has a model behind it and will not accept a question that supplies no measured interval. E04 became numeric_multiple_choice.
It is ps-case101-hallorans-books.json, not an edit to ps-case001.json. The corpus's own mechanism for this is validation_status: draft, which the exporter checks and holds back, and the exporter's comment says so in as many words: promotion is a corpus edit.
case_id is CASE101 rather than CASE001 so an accidental publication sorts last instead of colliding with The Calder Cipher — CaseLadder reads the digits out of the id. Promoting it means taking CASE001 and retiring or renumbering the Calder Cipher, which is a decision rather than an edit.
No bird, no LANTERN, no Tobin, no death, no drink. Nobody dies and nothing is stolen — it is an audit. Corrigan is a witness and never appears on the board. There is no till-variance thread, because a variance band wide enough to hide $24 a night would clear nobody and would teach the wrong read.
All three repos were on their default branch and level with upstream before any change. Unrelated dirty files were left alone: the modified .claude/context/blueprints/*.md, the deleted session signals, and the earlier untracked meeting notes.
docs/Peregrine-Plot/ — the v4 solved case and the floor plan — is untracked in git. It is the single source for everything above and exists in one place on one machine. I did not commit files I did not author, but this one is worth committing.