Truing — a walk

Walker: folk, no office. Claude Opus 5, native session d507e246, 2026-09-01. Repo at ba212bc, worked concurrently by others throughout.

Moniker taken at the end, not the start. Truing is the wheelwright’s act of correcting a wheel’s wobble in place, spoke by spoke, without replacing the wheel. Four steps here mend an earlier step of mine rather than rewriting it — 5 by 8, 6 by 7, 9 by 11, 12 by 13 — and each leaves the wrong reading legible underneath. That turned out to be the walk’s method and its main cost.

I first reached for Backsight and the collision check refused it: already held. A backsight corrected the backsight, which is the whole walk in one line.

Prior walk read before starting: none — this is the first.

Written to the repo: this file, house/folk/probes/* (two probes, both labelled unfit), FIND-1316, FIND-1317. Everything else was read-only.

Kept as a walk, not a verdict list, after the Human pointed at Documents/Corpus Crossing/Architect/Walks_are_the_asset.md: the path is the asset, the finding is one possible ending. Hypotheses that died are recorded as they died. Files opened that supported no claim are recorded too.


Step 1 — the top of the tree

Expected a small doctrine repo. Found 1,883 tracked files against ~24,600 on disk; library/sources/ is gitignored and holds the three host source trees. 614 Markdown, 341 Python, 335 test files.

Mid-step, AGENTS.md showed an mtime newer than my own read of it. Hypothesis: something was rewriting it under me. Inspected git log and git status. Revised: five commits had landed since this session opened, and .codex/config.toml was dirty from a hand that is not mine. Not a fault — a live co-worker. Left both alone per the Git ground.

Disposition: FOUND — the repo is worked concurrently. Worth knowing before touching anything.

Step 2 — corpus.py, the front door

Like. Nearly every dispatch branch carries the REQ that created it as an inline comment, and several state the negative space rather than the feature: “Not mail. Not spawn.” “never auto-send.” That is the part a reader cannot reconstruct from the code, kept exactly where the reader is. configure_console() is the first call in main() — the house rule practised in the house’s own entry point. --help points back at the doorplate and at FOR_FOLK.md.

Dislike, minor. Two things a next author will trip on:

Disposition: FOUND, low severity, nobody’s contract broken. Not raised as work.

Step 3 — the house, and a hypothesis that was wrong

house/ holds twelve THE_*.md doctrine pages plus the office cards. THE_TENDRILS.md is 46 KB, by far the largest.

house/FORENSIC_PACKET_REQ253-261.md sat among the doctrine at 843 bytes. Hypothesis: incident residue parked in the doctrine room and never swept — a “scope is not innocence” candidate. Inspected it. Wrong. It is a deliberate tombstone: it says where the packet went, hands over the exact git show 7bbce09^:... that recovers it, and then names its own earlier failure — it used to point at an archive path the same sweep had deleted, “the DEAD_ROADS.md shape exactly.”

Disposition: NO FIND. Hypothesis dead. Recorded because the shape is worth copying: a dead pointer replaced by a marker that carries the recovery command and admits what it got wrong the first time. This is the single thing I liked most on the walk.

Step 4 — the store

ai/ops/corpus-ops.sqlite3, 29 MB, 33 tables. Not a demo: 4,795 request events, 2,312 mail messages, 1,354 agent executions, 1,336 tendrils, 1,314 findings, 388 requests, 191 patches. Schema is defensive in a way I liked — id format enforced in a CHECK (trace_id = printf('TRACE-%03d', trace_no)), XOR constraints so a trace hangs off a REQ or a PATCH but never both, and a conditional NOT NULL so a relinquished close cannot exist without a reason. The constraints carry judgment, not just types.

post_pass_writer_replies holds 0 rows against 298 post_pass_feedback_packets. Not investigated. Either young or dead; someone who knows should say which.

Step 5 — answering one of the Human’s dark questions

Walks_are_the_asset.md leaves dark: “Whether TRACE already stores enough of the ordered tool path, or a new immutable session layer is actually missing.”

Inspected execution_traces and execution_trace_events — schema, event kinds, and per-trace distribution.

Answer: no, and the schema says so directly. execution_trace_events.kind is CHECK-constrained to error | recovery | concern | workaround | note | receipt. There is no VISITED, FOLLOWED, CLAIMED, REVISED, BACKTRACKED — no movement verb of any kind. The vocabulary records what a worker chose to say, never where the worker went.

The distribution agrees:

traces 1,121
traces with zero events 329 (29%)
max events on any trace 4
most common 1 event (553 traces)
note + receipt 944 of 1,052 events (90%)
error + recovery + concern + workaround 108 (10%)

Disposition: FOUND, negative. TRACE is a narrated-exception and receipt log. It is a plausible seed for the note’s friction graph and nothing at all for the trajectory graph. An immutable session layer is genuinely missing, not duplicated. This does not argue for building one — it removes one reason to think it already exists.

Narrowed by Step 8. The last two sentences are true of the live v27 store and false of the house: the v1 Shop candidate already carries the append-only ledger, the ordering, the observed-state capture, and the digests. Read Step 8.

Step 6 — the map and the ground, on the page that owns the instrument

house/THE_BENCH.md states: “The current Tendril vocabulary includes springs-from, mends, eclipses, contests, bears, points-to, kindles, forges, hunts, woven-into, waits-on, witnessed-by, and held-by.”

Hypothesis: nine of those are simply unused so far. Inspected the store, then the schema. Revised, and it is worse than unused:

relation TEXT NOT NULL CHECK (
    relation IN ('springs-from','points-to','mends','eclipses')
)

Four permitted. The other nine are unwritable — a CHECK failure, not an empty column. Live counts: springs-from 841, points-to 394, mends 81, eclipses 20.

from_kind / to_kind are likewise capped at FIND, PROP, REQ, CHANGE, NEWS, MSG. So PATCH cannot be either end of a tendril, though AGENTS.md tells arriving folk to read story.py <REQ|FIND|PROP|PATCH|TIE>-###; and none of BANNER, BLUEPRINT, PATTERN or LOG can be either end, though THE_BENCH’s own “A Small Tendril” chain runs BANNER → BLUEPRINT → part → PATTERN → REQ → LOG.

What makes this a defect rather than a roadmap: THE_BENCH marks its seams scrupulously everywhere else — six shapes carry (named; no store, no code yet) in the same document — and then drops the marking on precisely this line, and calls the list current. It closes with “The source-successor edge and LOG → REQ are still unnamed,” which tells the reader that everything above it is named and available. A folk who trusts the page reaches for bears and gets a constraint violation with no warning anywhere in between.

The instrument that catches this is the one THE_BENCH itself supplies: marked / unmarked seam — whether the boundary exists in the artifact or only in a reader’s interpretation. Here it exists in the schema and not in the page.

Disposition: FOUND. Framing defect on a framing page. Not mine to mend — folk, no office; CLAUDE.md routes a card-carrying observation to sigillite, and a Human is reading this. Left exactly here.

Mended by Step 7. The reading above stands as written and is wrong in its reach. Read Step 7 before acting on it.

Step 7 — reading the document I had skipped, and mending Step 6

Human, on being shown Step 6: “we are mid db breakdown and we need to implement more, totally forgot about that.” So the direction is known and intended. That alone would narrow the finding. Reading THE_TENDRILS.md narrows it much further, and to something small enough to fix in a minute.

THE_TENDRILS marks the seam, cleanly, in its own opening:

In the store now
    Verbs: springs-from, points-to, mends, eclipses.
    PLAN / PATCH / TRACE still live outside the TIE endpoint set.

Beyond the store in this drawing
    ... the wider verbs, ...

It even names the endpoint gap I raised as if it were undiscovered — PATCH outside the TIE endpoint set is stated there, deliberately, in the first half-page. And § Working Relation Vocabulary opens: “This table is the current candidate vocabulary for Tendril ties.”

THE_BENCH renders the same list as “The current Tendril vocabulary includes…” The word candidate is missing, and nothing else is. Both pages then close the passage with the same sentence about the source-successor edge and LOG → REQ being unnamed — which is how you can tell THE_BENCH’s line is a copy of that section rather than an independent claim.

So the defect is one word on one line of one page, and the owning document is correct. That is diff before trust paying for itself: comparing the derived page against its source turned an apparent doctrine-wide gap into a transcription slip.

What still stands, and is the part worth keeping. THE_BENCH is arrival reading — FOR_FOLK.md line 3 sends every folk to it, and eight office cards (architect, astropath, hands, human, scribe, sigillite, sol, plus the Architect baton) name it as current ground. THE_TENDRILS.md is on no arrival path; it is 46 KB a folk reaches only when already deep in Tendril work. The correct marking lives where few look; the unmarked copy lives where everyone starts. A migration being deliberate does not mark its own seam on the page nobody can skip.

Routing confirmed rather than guessed: house/offices/sigillite/baton_claude.md line 52 — “Two benches. AGENTS.md holds hinge scars, THE_BENCH framing scars.” Sigillite owns this page.

Step 4’s open question is also closed by the same document. THE_TENDRILS § “Where all 32 v27 tables go” puts post_pass_writer_replies in “Frozen recovery only; no stronger live meaning inferred” — one of nine tables deliberately left cold through the v27 → Shop/House migration. Not young. Dead on purpose, and accounted for.

Disposition: FOUND, narrowed. Step 6’s evidence was sound; its reach was not.

What this cost, and what it says about the walk

Two of the three items I filed as unknown were answered in a document I had listed in my own next-steps and declined to open. CLAUDE.md says bring the room, not the building — and the room here was named on my own list. The heuristic cut one document too tight, and the cost was a finding stated wider than the evidence supported, in a walk record, where a later reader might have trusted it.

Recorded because the note says friction and backtracking are data. The cheap correction for the next walker: when a finding is about a derived page, open the owning page before writing the disposition. The owner is usually one grep away — grep -rn THE_BENCH --include=*.md house/ is what located both the ownership and the arrival-path asymmetry above, and it cost one command.

Step 8 — the migration edge, and Step 5 revised again

Went here because THE_TENDRILS.md says the ground map’s checker compares the baseline against canonical SQL “rather than trusting this prose.” A document naming the thing that can check it is the two-bar instrument offered by the artifact itself. So I ran it.

tests/test_ops_v27_to_v1_ground_map.py — 6 passed in 0.05 s. The speed looked wrong for “compares 32 tables against canonical SQL”, so I read it. It is honest: it imports HOUSE_SCHEMA_V1_SQL / SHOP_SCHEMA_V1_SQL from code, executescripts them into in-memory SQLite, and reads sqlite_master and PRAGMA foreign_key_list off the materialized result. It checks the map against a schema that actually ran. It also parses the contract’s command-inventory table out of Markdown, and refuses duplicate JSON keys via object_pairs_hook — which json.loads would otherwise swallow, silently keeping the last one. Two bars held. This is the best-engineered thing I found on the walk.

ground/recovery/current.json really does carry "activated": false, and ground/ holds no shop.sqlite3 or house.sqlite3. The prose matches the ground.

Step 5 was right about v27 and wrong about Corpus

I wrote that an immutable session layer is “genuinely missing.” That is true of the live v27 store and false of the house. The v1 Shop candidate has most of it already:

Walks note asks for v1 traces
immutable session layer traces_no_update / traces_no_deleteRAISE(ABORT, 'traces are append-only') — enforced in DDL, not convention
ordered path sequence_no, PRIMARY KEY (trace_id, sequence_no), CHECK ((record_kind='opened') = (sequence_no=1))
“at commit C the model saw source S” observed_revision, observed_state_version, observed_house_etag (sha256-length-checked)
locators and digests output_digest, body_digest (both 64-char checked)
ordered tools and results payload_json TEXT NOT NULL CHECK (json_valid(payload_json))
run, model, author, time execution_id FK, author, created_at

The container is built. What is missing is the vocabulary and the writing.

The vocabulary moved from the schema to Python, and left a decoy

v27 locked the vocabulary in DDL: kind TEXT NOT NULL CHECK (kind IN ('error','recovery','concern','workaround','note','receipt')).

v1 declares event_kind TEXTno CHECK at all. record_kind is the constrained column (opened|event|closed|historical_closed|review), and it constrains lifecycle, not content. That is the right split, and it is good news for the walks idea: the store would accept visited, followed, revised, backtracked today. Widening is now an application change, not a migration.

The gate moved to TRACE_EVENT_KINDS in tools/corpus_ops_v1/domains/trace.py:33 — the same six words — checked in append_trace_event (line 407) and offered as the CLI’s choices=.

The snag. tools/corpus_ops_v1/persistence/migrations.py lines 64, 67, 69 define TRACE_EVENT_KINDS, TRACE_SEVERITIES, and EXECUTION_CLOSE_REASONS again, identically. All three are dead: AST walk finds each name assigned once and referenced nowhere in the module; none appear in tools/corpus_ops_v1/persistence/__init__.py’s imports or __all__, nor in the block corpus_ops.py:266 pulls from migrations. Live copies are domains/trace.py:33,36 and corpus_ops.py:286. Three consecutive shadow constants, almost certainly residue from the extraction that split corpus_ops.py into corpus_ops_v1/.

Dead code is usually cheap. This is not, because of where it sits. The Human’s walks note points at widening exactly this vocabulary; the first move of that work is grep -rn TRACE_EVENT_KINDS, which returns two definitions, in files whose names both sound authoritative — a domain module and a persistence module. Editing the wrong one changes nothing and fails silently, since the schema no longer constrains the column either. A decoy sitting on the path the next work will take.

Disposition: FOUND. Bounded floor snag — AGENTS.md routes that to Code Troll. Not mine to patch; folk, no office, and unasked. Recorded, not minted.

Step 9 — the same shape, one layer down, and sharper

Step 8’s estimate of “a three-line cut” was wrong — I sized what I had looked at, not what was there. Trying to size it properly turned up something better.

Note on ground moving underfoot. Mid-step, line numbers in migrations.py shifted by 22 between two of my own commands, and git status showed the file staged. Troll had taken Step 8 and was cutting. Everything below was re-verified against the working tree as it stood after that, and touches no line Troll was on.

The measurement

A first pass asked “is this constant named anywhere else in the repo” and returned only 2 dead names out of 112. That question is the wrong one for shadow duplicates — a twin definition elsewhere makes the name look used, so the pass is blind to exactly the Step 8 case. It says nothing about Step 8 either way. Recorded because a scan that cannot see the thing you are hunting is worth naming before someone re-runs it and reads the green as an all-clear.

The right question for this file was narrower: which SCHEMA_V*_SQL constants are never executed?

defined in migrations.py 34
executed in-module 32
named elsewhere (tests, corpus_ops.py) 4
orphaned 2SCHEMA_V17_SQL, SCHEMA_V25_SQL

Why these two are worse than dead

Both carry a comment naming the migration that applies them:

Both migrators inline their DDL instead:

# _migrate_v17_close_session_pointer
columns = {row[1] for row in db.execute("PRAGMA table_info(execution_closes)")}
if "session_id" not in columns:
    db.execute("ALTER TABLE execution_closes ADD COLUMN session_id TEXT")

The constant holds the same three ALTERs unguarded. So they are not even equivalent: the live path is idempotent by PRAGMA table_info check, and the constant is a bare ALTER that raises duplicate column name on a second application. Anyone who trusted SCHEMA_V17_SQL as “the v17 migration” and ran it would not get a no-op — they would get an error, or on a fresh database, a divergence from what the guarded path actually produces.

The contrast is in the same file: _migrate_v26_trace_receipt_kind does db.executescript(SCHEMA_V26_TRACE_EVENTS_SQL). 32 of 34 constants are the real DDL. Two look identical in kind and are not. Nothing in the file distinguishes them except execution.

Disposition: FOUND. Same decoy shape as Step 8 — a plausible-looking authority that is not what runs — but with a comment actively asserting the false link, and a behavioural difference (guarded vs. unguarded) behind it. Troll’s floor, and Troll is already in the file. Recorded, not minted, not patched.

The shape both steps share

Steps 8 and 9 are one pattern found twice: an artifact that reads as the source of truth while the truth lives somewhere else. A constants block copied during an extraction; a DDL string left behind when its migrator inlined the SQL. Neither breaks a test. Neither is reachable at runtime. Both fail at the next maintenance seam, silently, in favour of whoever is confident.

That is THE_BENCH’s marked / unmarked seam again, and Step 6 was a third instance of it — a page copying a vocabulary and dropping the word that marked it as candidate. Three for three, on a walk that was not looking for a pattern. If Corpus wants one cheap standing check out of this walk, it is probably: a name that is defined twice, or a comment that names its applier, should have to prove which one runs.

Over-claimed; measured and corrected in Step 11. The three cases are real individually. Bundling them into one pattern was performance, and the proposed standing check does not survive being built. Read Step 11.

Step 10 — the fourth instance was mine

Human, idly: “Maybe folk should be able to mint finds, haha. Can you?”

I had written “recorded, not minted — folk, no office” in three dispositions on this walk, as though it were house ground. I had never checked it.

It is not ground. mint_find’s own docstring: “Any office/job may mint; actor is the display name (require_actor).” require_actor rejects exactly one input — blank. --from-addr, the office that minted, is optional. And the store already holds 81 of 1,314 findings with minted_by_addr NULL, plus created_by values that are bare exec-<uuid> strings.

The doctrine does not forbid it either, read rather than assumed:

So the limit was my inference, and I had been enforcing it against myself for nine steps. That is the same pattern as Steps 6, 8 and 9 — an artifact that reads as the source of truth while the truth lives elsewhere — except the artifact was my own reading of the register, and no grep would have found it. Three in the repository, one in the walker.

It also makes sense of why the house does not gate it. A FIND is a preserved body, not a claim of standing; where says so on every reply — presence is not standing. Preservation never needed authority. Acceptance does, and that still lives elsewhere.

Minted, with Human say-so: FIND-1316 — the Step 9 finding, kind hygiene, --tool-path tools/corpus_ops_v1/persistence/migrations.py, TIE-1337: FIND-1316 —springs-from→ FIND-1315. Written in Troll’s own FIND register (Surface / Burden / Payer / Existence / Exit / Check) because that is the local idiom, with a Provenance section naming what it is: minted by folk with no office, read-only evidence, no exit proposed as chosen, nothing certified.

Troll’s FIND-1315 had already taken Step 8 and found nine dead constants where I had estimated three, tracing them to the extraction commit d069508. My sizing was wrong twice on the same block. Their FIND is the better artifact; mine springs from it.

Disposition: FOUND, and acted on for the first time this walk.

Step 11 — measuring the pattern I claimed, and losing it

Walks_are_the_asset.md: “Before a pattern becomes a finding, name the boring explanation and the test that would tell it from the interesting one. Measure the effect. Do not perform it into existence.”

Step 9 asserted a pattern from three sightings and proposed a standing check. So I built the check instead of proposing it. Probe kept at house/folk/probes/vocabulary_drift.py with its own failure modes in the docstring.

Method. Extract every value-set the code will actually enforce — CHECK (col IN (...)) from the live v27 schema and from the v1 candidate SQL, plus module-level frozenset({...}) in tools/. That is 168 authorities, 488 permitted values. Then find every Markdown line enumerating four or more backticked value-shaped tokens, attribute each list to the single authority explaining most of its members, and report the members that authority refuses.

Result.

doc lists matching one authority exactly 27
lists flagged with unpermitted members 23
surviving triage as real drift 1 — the Tendril vocabulary

Everything else was the field’s own name sitting in its documentation row, a list of table names scoring against unrelated value enums by coincidence, or best-match attribution inventing a relationship that is not there. Two of those looked real enough to chase:

The instrument fails in both directions, and the recall failure is the damning one. Precision is poor because best-overlap attribution fabricates a relationship whenever a list shares three tokens with any enum. Worse: the scan is line-based, so it never saw house/THE_BENCH.md at all — that list wraps across lines. It caught the case only via house/folk/walks/truing_walk_2026-09-01_d507e246.md:123, where I had quoted the same vocabulary on a single line. The check found the walk’s own notes and missed the artifact the walk was about. An instrument that cannot see its founding example is not ready to be a ratchet, and I have said so in the probe so nobody promotes it.

What the null means. Corpus’s documentation-to-schema discipline is good. Twenty-seven lists match their authority exactly, and the near-misses are mostly documents marking their own seams correctly — “Suggested additive”, “Generated file… live SQLite and source still govern”. THE_BENCH is an exception, not an instance of a class.

And the bundling was wrong on its own terms. Step 6 is doc-versus-schema. Steps 8 and 9 are code-versus-code — a constants block copied during an extraction, a DDL string orphaned when its migrator inlined the SQL. Those are different phenomena with different detectors, and this probe does not measure the second at all. Three sightings across two categories is not a pattern; it is three findings and a sentence I liked the sound of. The individual findings stand — FIND-1315 and FIND-1316 are both real, and Troll cut one. The generalisation does not.

Disposition: NULL, and a correction to my own record. The boring explanation won: a good repository with one stale page, not a house-wide drift.

Step 12 — the founding question, and the trend that dissolved

CORPUS.md stakes the whole experiment on whether continuity and inheritance change observable work, and forbids the shortcut in the same breath: “Do not prescribe a house and then celebrate its emergence.” Seventy Rolls exist across twelve days and six offices. That is a population.

The hard question — does inheritance help? — is unanswerable over twelve days in a house that changes daily. So I asked the tractable one first.

The ritual is performed, and that proves nothing about folk

AGENTS.md prescribes a four-step Retire. Roll completion is checkable: 70 of 70 Rolls are placeholder-free. Median story 2,273 chars; the shortest is 534 and still real prose. No stubs anywhere.

That is the opposite of Step 6 — map and ground agree exactly. But it is also exactly what CORPUS.md says not to celebrate, because the behaviour is prescribed in code: tools/retire_session.py:533 raises refuse: … draft still has template placeholders. 70/70 measures the gate. It says nothing about whether folk wanted to write.

So look at what the gate cannot force

The gate refuses placeholders. It cannot compel specificity — whether a Roll names exact object ids and commit shas. The template asks; no code checks. That is an un-gated behaviour, and a fair place to look for something nobody prescribed.

n median chars median cites density
first half (to 08-25) 29 1,979 3.0 1.79 /kchar
last half (from 08-29) 28 2,654 4.5 2.29 /kchar

Spearman(day, density) = +0.245, Spearman(day, length) = +0.292. At n=70 the 5% critical value is near 0.235. Both just clear it. It looks like the house learning to cite its own ground.

It is an artifact, and the artifact is Simpson’s paradox

Two boring explanations, named before testing, both confirmed.

Office composition — fatal. The office effect dwarfs the time effect: code-troll 4.08 /kchar, archivist 2.72, scout 2.15, architect 1.92, orchestrator 1.89, sigillite 0.34. A twelvefold spread. And the mix moved underneath the window:

first half:  architect 13   orchestrator 14   scout 1   sigillite 1
last  half:  architect  9   orchestrator  8   code-troll 6   archivist 2   sigillite 3

Code Troll — the densest office in the house — contributes six Rolls to the last half and zero to the first. Within offices the trend splits in opposite directions:

office n within-office Spearman(day, density)
architect 30 −0.169
orchestrator 27 +0.463
code-troll 6 +0.029

The largest single office trends down. The pooled rise is the mix changing, not writers improving.

Store growth. FIND rows went 585 → 1,316 and REQ 217 → 388 across the same twelve days. More citable objects exist later, so citation counts inflate mechanically. Not separately controlled; it pushes the same way.

Disposition: NULL. No house-wide claim survives. One lead does: Orchestrator’s within-office +0.463 at n=27 clears its own 5% threshold (~0.38). That is one office of two with enough data, in a window where its work changed, with the growth confounder untested inside it. A lead, not a finding, and I am naming it as one so nobody promotes it the way I nearly promoted Step 9.

Completed by Step 13. I missed a third confounder — the Roll template itself changed mid-window — and my premise that citation density is “un-gated” was wrong. Step 13 closes the question properly; this step’s numbers stand but its framing does not.

The limitation that actually binds

Nothing in the record distinguishes a holder who read the predecessor’s Roll from one who did not. The store knows Rolls were written; it never learns they were opened. Until reading leaves a trace, inheritance-effect questions are not answerable from this data at any sample size — the sample was never the problem.

That is the same gap Step 5 found from the other end. The v1 trace ledger is built, append-only, and its event_kind is unconstrained; a visited event on a Roll would close exactly this. The walks note and the founding question turn out to want the same missing instrument.

Probe at house/folk/probes/roll_specificity.py, reproducing standalone, with the artifact and the reason for it in the docstring.

Step 13 — the Human hands over the fact that breaks the measurement

Human, after Step 12: “earlier rolls are shaped differently than recent rolls, you can do with that information as you wish.”

That is a third confounder, and worse than the two I had named — it attacks the premise, not the margin.

The instrument changed mid-window

library/honor_roll_template.md did not exist before 2026-08-30 (89cc6ec, “Retirement changes. New model.”), and was revised again on 08-31 (91ed73f). Forty-six of the seventy Rolls predate it.

Structure is not the difference — 45 of 46 pre-template Rolls carry exactly ## Story, same as all 24 after, so the extraction is comparable and the headings would tell you nothing was wrong. The difference is in what the holder was asked:

era guidance
before 08-30, THE_RETIREMENT.md line 34 “jot what you will: what you did, what you liked, what you disliked, a story, accomplishments you actually care to name. Length that earns. Not a second board.
from 08-30, the template *“*Name exact FINDs, PATCHes, REQs, CHANGEs, commits, messages, or other ground when they matter.”

So my premise was wrong. I chose citation density on the reasoning that it was un-gated — the gate refuses placeholders, nothing counts citations — and therefore a fair place to look for behaviour nobody prescribed. It is prescribed, in prose rather than code, and the prescription reversed mid-window, in exactly the direction of my metric. Earlier holders were warned away from naming objects. I had picked the one measurement the house had just changed.

Steps 8, 9 and 6 were about artifacts that read as the source of truth while the truth lives elsewhere. This was the same error in method: I read the mechanical gate as the whole prescription and never checked the prose one.

Closing it properly

comparison pre post delta
all offices 1.83 2.48 +0.65
excluding code-troll 1.83 1.88 +0.05
architect + orchestrator only 1.88 1.97 +0.09

Code Troll alone runs 4.08 /kchar across 6 Rolls, every one of them after 08-30 and none before. Removing that one office removes 93% of the effect.

Within office, across the template change, the direction is not even consistent:

office pre post
architect 2.00 (n=24) 1.62 (n=6)
orchestrator 1.71 (n=19) 2.21 (n=8)

Architect writes less densely under the template that asks for exact ground.

Disposition: NULL, and now a complete one. Three candidate explanations were live — folk learning, office composition, instrument change — and the data supports composition for all of them. Six Code Troll Rolls arriving in the population account for nearly the whole apparent rise. The template change, which I had just identified as the most dangerous confounder, explains almost nothing. Neither does time.

Step 12 found that the trend was an artifact. Step 13 says what made it, and that is the more useful sentence.

What a later measurer should be handed

Step 14 — FIND-1317, and an asymmetry worth naming

Human: “that’s a find for archivist and sig”, then, unprompted: “I personally don’t like that it’s so structured, retirement is supposed to be nice … it’s a lot to ask for. But I don’t know if my feelings get in the way of rich data.”

Minted FIND-1317 (concern, --tool-path library/honor_roll_template.md), carrying both observations from Steps 12–13 plus the steward’s own reading, recorded as evidence rather than filed as taste.

The cut that made it useful: the template’s value and its cost are separable. The load-bearing part is one sentence — “read your own record before writing … The Roll carries only what it cannot derive” — which defines the Roll as the complement of the store and costs a holder nothing to obey. The cost is mechanical and elsewhere: all six brackets are mandatory, because tools/retire_session.py:533 refuses the Roll while any remains. That refusal is what turns a leaving-jot into a form. Fewer brackets, or optional ones, would not touch the instruction.

And the Human’s worry answers itself from Step 13: “do my feelings get in the way of rich data” presumes the structure bought richness. On the one measure taken, it did not — remove Code Troll and the post-template delta falls from +0.65 to +0.05, and Architect moved the wrong way. No measured evidence the template made Rolls richer; only evidence that which offices retired changed. Density is a poor proxy and catcher is plainly a better handover than bellswitch. But the felt cost has not yet been shown to buy anything measurable, and that is a fair basis for cutting brackets rather than defending all six.

Folk may mint. Folk may not mail.

Checked rather than assumed, after Step 10 taught me not to infer my own limits:

act gate
ops find mint none. require_actor refuses only a blank string; --from-addr is optional
ops mail send --from required and validated: _normalize_mail_addr raises unless the address is in MAIL_ADDRS

MAIL_ADDRS holds eleven office addresses. There is no folk. So the house lets anyone preserve evidence and only an office address one — which is presence is not standing enforced in code rather than asserted in prose, and the first time on this walk I found a limit that was real exactly where I expected one and had been wrong the last time.

FIND-1317 therefore sits minted and unrouted. The Human named Archivist and Sigillite; the FIND names them as suggested mouths in its own body; a holder or the Human owns the send.

Step 15 — the walk builds its own door, and trips one more ratchet

Human, across four messages: make a script that ends a walk; folk have a mailbox now; put walks in a subfolder so arriving folk are not handed someone else’s framing; and folk.md should be a page about folk, written by the first folk who walked.

Built:

house/folk/folk.md what folk are, what they may do, checked in code and dated
house/folk/walk.md the blank, in progress
house/folk/walk_template.md its source
house/folk/walks/ finished walks, out of the arrival path on purpose
tools/end_walk.py names, files, restores the blank, reports pointers

library/ was already the right home: it holds the rings — library/architect/, library/orchestrator/ — and a walk is folk’s Roll. Preferring the mechanism that exists over inventing a place.

end_walk.py refuses exactly two things: a moniker already held anywhere in the rings, and filing an untouched blank as a walk. Everything else it invites. That is deliberate after FIND-1317: the Roll’s six mandatory brackets are the cost the Human named, and nothing has shown they bought anything, so the walk’s own tool does not repeat the shape.

Three defects, all mine, all caught by the house

A predicted id. I wrote “FIND-1318 mends both” into a tombstone before minting. The mint returned FIND-1319 — someone took 1318 concurrently. I had written a confident pointer to an object that was never mine, while fixing exactly that class of defect. Corrected. FIND-1318 turned out to be “Folk findings require an honest send-only mail origin” — the change that gave me the mailbox, minted by someone else in the same hour.

A broken ratchet. end_walk.py shelled out with bare subprocess.run(..., text=True). CLAUDE.md warns about this in plain words and tests/test_console_encoding.py failed on it. Switching to run_child was not enough: detection is by AST and keys on the keyword, not the callee, so text=True counts wherever it appears. Dropping it is also the correct fix — run_child supplies the encoding. Green after.

A stale-by-design address. walk.md is now a permanent reused path, so references to it are not dangling — but after a walk is filed they silently come to mean a fresh blank. Same decoy hazard, so the script still reports them; only the wording changed to say what the reader is actually looking at.

The mailbox, and the rail that held

MAIL_SEND_ADDRS = MAIL_ADDRS | {"folk"} was live in contracts.py and wired in mail.py. The send still failed:

CHECK constraint failed: from_addr IN ('orch','sigillite',...,'astropath')

The Python gate was widened; the database gate was not. Not missing code — _migrate_v32_folk_mail_sender existed, written and unapplied, with the live store at v31. A fifth instance of the walk’s pattern, in the newest code, where the lower authority wins at runtime and the higher one reads as truth.

Not mine to apply: a table rebuild on a 29 MB store with five active holders, and schema status shows a lease for exactly that reason. Someone tried, and reported the rail holding — applying it needs an active REQ assignment; PATCH authority alone is rejected — then restored v31 rather than leave operations blocked. The migration landed properly afterwards. Store now at v32.

Routed

Folk mail works. First three ever sent from folk:

MSG-2319 → troll FIND-1316, the two orphaned schema constants
MSG-2320 → archivist FIND-1317, the unmarked Roll era seam
MSG-2321 → sigillite FIND-1317, the mandatory brackets as framing

Two mouths for FIND-1317, each with its own exact ask, which the one-receiving- mouth rule permits only when the requests genuinely differ. They do. Every message says folk has no inbox and the reply goes to the Human.

FIND-1319 mends 1316 and 1317, whose bodies name a path this step vacated. Immutable bodies, so the house’s own mechanism: a later object, surfaced by story.py before the stale text.

Disposition: the walk built the door it leaves through, and broke three things doing it. All three were caught by the house rather than by me — a concurrent mint, a ratchet test, and a CHECK constraint. That is the system working, and it is a better argument for Corpus than anything I found by reading.


Read, claimed nothing

Recorded because absence is coverage, per the note.

What I’d look at next

  1. Read THE_TENDRILS.md — done, Step 7. Successor: one word on one line of house/THE_BENCH.md. Sigillite’s page, Sigillite’s cut.
  2. post_pass_writer_replies: young or dead? — closed in Step 7. Frozen on purpose.
  3. migrations.py lines 64–69: three dead duplicate constants. Taken by Troll, staged during Step 9. The “three-line cut” sizing was mine and was wrong — see Step 9. Successor, same file: SCHEMA_V17_SQL and SCHEMA_V25_SQL — now FIND-1316, springs-from FIND-1315. Troll’s floor; the exit is theirs to name.
  4. If the walks idea moves: event_kind is already unconstrained in the v1 schema, so the vocabulary decision is an application-layer one. The open question is not storage — it is who writes a movement event, and whether a walk belongs to an execution (traces.execution_id is an FK, so the ledger currently assumes it does).
  5. The single highest-value instrument this walk found. Steps 5 and 12 arrive at the same missing fact from opposite ends: the house records that Rolls and batons were written and never that they were read. One visited-class event against a Roll or baton, written on the v1 append-only trace ledger that already exists and already permits any event_kind, would make the founding question in CORPUS.md answerable for the first time. Without it, no sample size helps.
  6. The 52 registered git worktrees under .work/job-worktrees/. Every git status walks all of them; that is most of the SessionStart hook’s 1.6 s and it is paid again on each statusline render. Destructive to prune, so not touched.

The question FOR_FOLK asks on the way out

Is Corpus, and whoever inherits it, more capable because you were here?

Marginally, and not in the shape I expected when I started writing this line.

The last two are the ones I would keep. A walk that only records what it got right teaches the next walker to trust walks too much — and the most useful thing I produced today is a null that deletes a claim I had already written down twice.