Build log · Part 4 of 6
Knowledge system: doctrine as a queryable graph
1,941 nodes and 1,265 edges, the authored/derived split, a drift check that emitted 88 false positives, the full tenet catalogue, and four failures in self-instrumentation
An agent session begins with no memory of prior sessions. Documentation describing the system is stale by the time it is read. The knowledge system addressed both by storing the operation’s own doctrine as data.
Data is the truth; documents are renders. The operation’s goals, mission and doctrine live as ontology data. Markdown is an ephemeral human-readable render, never the source of truth.
Node model
rp_ontology_nodes held 1,941 rows across 17 kinds, with 1,265 edges in rp_ontology_edges. The
node total moves between nightly regenerations as derived rows are added and pruned; 1,941 is the
count at teardown.
CREATE TABLE rp_ontology_nodes (
id uuid PRIMARY KEY,
kind text NOT NULL, -- tenet | goal | procedure | decision | code_unit | …
subkind text,
key text NOT NULL, -- stable slug: 'deterministic-first'
parent_id uuid,
name text,
one_liner text,
authored_lifecycle text, -- declared intent
derived_state text, -- computed from ground truth
state_drifts boolean GENERATED ALWAYS AS (…) STORED, -- see below
source text,
created_by text,
created_at timestamptz,
updated_at timestamptz
);
Distribution at teardown:
| kind | count | kind | count |
|---|---|---|---|
backlog_item | 1,165 | tenet | 28 |
data_entity | 245 | module | 21 |
session | 132 | endpoint | 11 |
message | 114 | repo | 11 |
memory_note | 72 | procedure | 9 |
code_unit | 67 | goal | 9 |
decision | 38 | doc | 9 |
Code, data model, decisions, rules, work history and accumulated memory occupied one namespace and
one edge table. A CLI (rp-ontology) read it; rp-ontology <kind>:<key> returned a single node.
Three tables carried the whole model — nodes, edges, and rp_ontology_facets, which held the
variable-shape content that would otherwise have become columns. A facet is
(node_id, kind, layer, content jsonb), unique on that triple, with kind constrained to
strategy, code or data at first and governance added later. 1,949 facets existed at
teardown: 1,555 derived and 394 authored.
Every row in all three tables carried a layer column, constrained to derived or authored.
The migration that created them states the split directly:
-- layer='derived' — regenerated from ground truth, never authored;
-- written by scripts/regen_ontology_derived.py
-- layer='authored' — intent/judgment, small & policed (~10%);
-- written by scripts/seed_ontology_authored.py.
--
-- Drift = a predicate asserted at one layer with no support at the other.
The kind CHECK constraint was widened as the model grew; migration 018 added code_unit so that
individual jobs, collectors, services and post-processors could carry documentation.
The edge model
1,265 edges spanned nine of the twelve predicates the CHECK constraint permits. implements,
deployed_at and documents were declared and never populated.
| predicate | edges | authored | derived |
|---|---|---|---|
part_of | 308 | 288 | 20 |
references | 229 | 0 | 229 |
writes_to | 210 | 125 | 85 |
reads_from | 184 | 137 | 47 |
depends_on | 123 | 0 | 123 |
changed | 113 | 0 | 113 |
touched | 89 | 0 | 89 |
governs | 5 | 5 | 0 |
supersedes | 4 | 3 | 1 |
Four predicates existed only as derived rows. references came from two derivers: 128 edges read
straight off pg_constraint — the schema’s own foreign keys, table to table — and the remainder
from backtick-quoted table names found in decision and doc bodies, intersected against the known
data_entity key set to suppress false matches. depends_on mirrored the backlog.depends_on
array. changed resolved each commit SHA a session recorded in rp_session_events to the files it
touched, then mapped those files to doc, decision, module and data_entity nodes. touched was the
weaker sibling: a substring scan of session-event bodies for table names of six characters or more.
Edges carried their own provenance. source names the mechanism; attributes.confidence grades it.
| confidence | edges | meaning |
|---|---|---|
| — | 702 | authored, or emitted before the attribute existed |
heuristic | 322 | source grep or text match |
measured | 128 | read from pg_constraint |
derived | 113 | resolved from a git commit |
The most common source values show what the graph was actually built from: authored parent assignment (262), the checked-in manifest ontology/authored/things.json (247), pg_constraint
(128), backlog.depends_on (123), rp_session_events commit shas (113), rp_session_events body match (89), and source greps of the engine (79), ops (43) and globe (8) repos. Decision documents
contributed a long tail — fifteen edges from 2026-04-11-scaling-architecture.md, thirteen from
2026-05-30-drone-corpus-data-model.md, and single edges from a dozen archived decisions.
Authored and derived edges of the same predicate connect different node kinds, because they answer different questions:
| predicate | layer | from → to | edges |
|---|---|---|---|
reads_from | authored | code_unit → data_entity | 137 |
writes_to | authored | code_unit → data_entity | 110 |
writes_to | derived | repo → data_entity | 85 |
reads_from | derived | repo → data_entity | 47 |
writes_to | authored | subsystem → data_entity | 15 |
Authored writes_to says which machine is supposed to write a table. Derived writes_to says which
repository contains an INSERT against it. The two never share endpoints. That mismatch later broke
the drift check.
Degree was concentrated. Counting edge endpoints, subsystem:infra appeared on 117,
repo:robotics-press-engine on 88, subsystem:corpus on 81, data_entity:companies on 48,
data_entity:attack_events and data_entity:signals on 34 each. Against that, 1,387 nodes carried
no edge at all:
| kind | isolated | kind | isolated |
|---|---|---|---|
backlog_item | 1,056 | endpoint | 11 |
message | 114 | code_unit | 9 |
session | 76 | procedure | 8 |
memory_note | 72 | decision | 6 |
tenet | 20 | doc | 3 |
module | 12 |
Backlog rows became connected only when a dependency was declared; 123 depends_on edges across
1,165 rows. Sessions became connected only when their commits resolved. Memory notes had no deriver
that could connect them to anything, so all 72 stood alone. The cross-linking was real for code and
data and thin for everything the system wrote about itself.
The hierarchy that did exist was explicit. Nine goals formed a tree under goal:north-star, with
four product layers beneath goal:ship-data-product. Five tenets sat under a parent tenet — four
under provenance-not-verification (justify-not-prove, lineage-chain-must-hold,
output-is-a-projection, provenance-born-at-collection) and ground-answers-in-source-of-truth
under data-is-truth-docs-are-render. One procedure, drone-defense-coverage, was declared
part_of the tenet corpus-coverage-doctrine.
The derived layer
scripts/regen_ontology_derived.py recomputed everything a script can know. It read
information_schema for tables and views, systemctl --user for services, the filesystem for repos
and modules, pg_constraint for foreign keys, rp_sessions and rp_session_events for history,
backlog for work, rp_messages for cross-repo traffic, decisions/ for the ratified record, and
~/.claude/projects/…/memory/*.md for durable notes. It wrote only layer='derived' rows.
Liveness came from measurement, not assertion. A repo is live if its directory exists. A service
is live if systemd reports it active, dead if failed. A data_entity is live if a count(*)
returns more than zero rows and dormant if it returns zero. A module is dead if its directory
holds no .py files. Each carried a facet with the evidence — row count, column count, primary-key
type, last commit SHA, file count, lines of code.
Idempotence was enforced by pruning against the run’s start timestamp:
DELETE FROM rp_ontology_edges WHERE layer='derived' AND generated_at < %s;
DELETE FROM rp_ontology_facets WHERE layer='derived' AND generated_at < %s;
DELETE FROM rp_ontology_nodes n
WHERE n.created_by='regen_derived' AND n.updated_at < %s
AND NOT EXISTS (SELECT 1 FROM rp_ontology_facets f
WHERE f.node_id=n.id AND f.layer='authored');
Anything the run did not refresh had disappeared from ground truth and was deleted. A node with an authored facet was exempt, so hand-written documentation survived a table being dropped and recreated.
The deriver also stood down where the authored layer had taken ownership. load_authored_governance_keys
returned every (kind, key) carrying an authored governance facet whose authority was not
render; the decision, doc and memory-note derivers skipped those keys rather than shadowing them
with a competing derived facet.
Three scripts ran in sequence from regen_ontology.sh — the derived pass, then
seed_ontology_authored.py, then seed_thing_docs.py and derive_data_docs.py to fill authored
and gap-filled documentation. Cron ran regen_reference.sh at 06:15 UTC, regen_ontology.sh at
06:20, and harvest_authorship.sh at 06:25. All three entries are #FROZEN# in the crontab now.
The final run logged:
regen ontology derived layer
────────────────────────────────────────────────────────────
authored-gov: 65 (owned by authored layer; skipped)
repos: 11
endpoints: 11
systemctl list-units failed: Command '['systemctl', '--user', …]' returned non-zero exit status 1.
services: 0
modules: 21
data_entities: 207
decisions: 19
docs: 9
memory_notes: 72
sessions: 132
backlog_items: 1165
messages: 114
edges:
reads_from: 47 (website/globe/ops source grep)
writes_to: 85 (repo→table source grep · 2 invariant flags)
references: 134 fk (pg_constraint table→table)
changed: 113 (session→node from git commit shas)
pruned: 0 nodes · 0 facets · 44 edges
The services line records the shutdown: systemctl --user returned non-zero, zero service nodes
were refreshed, and no service nodes remain in the kind distribution. The deriver reported the
failure and continued. Node totals move between runs as sessions, messages and backlog rows
accumulate. The per-deriver counts are upserts attempted, not distinct rows written — 134 foreign-key
upserts collapsed onto 128 distinct edges, because two constraints between the same pair of tables
produce one edge.
Drift detection
state_drifts boolean GENERATED ALWAYS AS (
kind IN ('service','module','data_entity','endpoint','subsystem')
AND authored_lifecycle IS NOT NULL AND derived_state IS NOT NULL
AND (
(authored_lifecycle = 'intended_live' AND derived_state IN ('dormant','dead'))
OR (authored_lifecycle = 'intended_dormant' AND derived_state = 'live')
OR (authored_lifecycle = 'intended_dead' AND derived_state = 'live')
)
) STORED
authored_lifecycle records declared intent. derived_state is recomputed nightly from ground
truth — whether a code_unit’s file still exists, whether a data_entity’s table is still present,
whether a decision has been superseded. state_drifts holds the delta, and holds it as a stored
generated column rather than in application code, so no writer can forget to update it.
rp-ontology --drift reads it. The same question asked of a markdown file has no mechanical answer.
The predicate is deliberately asymmetric. A thing declared dead that is still running is drift. A
thing declared dead that is merely idle is not — intended_dead against derived_state='dormant'
returns false. Five data_entity nodes sat in exactly that position at teardown, including
pub_events, pub_newsletter_sends and intel_article_ideas: authored as intended-dead, present
in the schema with zero rows, and correctly not reported.
rp_ontology_drift is a view over three arms — state drift from the generated column, authored
edges with no derived support, and derived edges with no authored counterpart, described in the view
as “undocumented reality”. Of 209 nodes carrying an authored_lifecycle, 147 were intended_live,
35 intended_dormant, 17 intended_dead and 10 experimental. Where both an intent and a measured
state existed, they agreed: 24 intended_live nodes measured live, 25 intended_dormant measured
dormant, and no intended_live node measured anything other than live.
A worked case: the writes_to grain error
Backlog #921 added the derived edge set — 145 foreign-key edges from pg_constraint and 79
repo-to-table writes_to edges from a source grep. The drift view went from approximately zero rows
to 88, and every one of the new rows was a writes_to edge. Migration 017 records the diagnosis:
-- The #921 derived edges inflated rp_ontology_drift from ~0 to ~88 rows, almost
-- all false orphans, and every one of them was a `writes_to` edge:
-- * authored writes_to is SUBSYSTEM-grain (subsystem -[writes_to]-> data_entity),
-- authored intent "the collection subsystem writes signals".
-- * derived writes_to is REPO-grain (repo -[writes_to]-> data_entity), found by
-- source-grepping INSERT/UPDATE statements per repo.
-- These are two intentionally-different grains. They can NEVER match on
-- (from_node,to_node), so the orphan-edge checks false-flag every authored
-- subsystem edge (no derived support) AND every derived repo edge (no authored
-- counterpart). That's ~88 rows of pure noise that drowns real drift.
The orphan checks compared endpoint identity. Two derivers at different grains can never produce
matching endpoints, so each new derived edge manufactured two drift rows. The fix in #924 dropped
writes_to from both orphan arms, leaving governs, documents, deployed_at and implements,
which are authored and derived at the same grain. The backlog resolution reads: “Drift 88 -> 0.”
The signal the check had been trying to carry was moved to the edge itself. derive_writes_to_edges
stamps attributes.invariant_violation = true when a repository other than the engine writes a
canonical table, which is the canonical-write-invariant tenet expressed as a query. Two edges
carried the flag at teardown, both from the ops dashboard: robotics-press-ops writing
recurring_tasks and backlog. The #921 resolution records the same finding on first run: “ops
dashboard writes canonical tables (recurring_tasks/content_types/artifacts/interactions) from API
routes = real Rule-1 drift, flagged for triage.”
An earlier correction, migration 009, added an eligibility gate: a predicate counts as drift-eligible
only if at least one derived edge of that kind exists system-wide. Without it, every authored
governs edge would have been reported as drift for as long as no governs deriver had been
written. Its comment states the rule: “the substrate doesn’t pretend ‘every authored row is drift’ —
it stays silent until there’s an actual disagreement to surface.”
Drift stood at 0 rows at teardown.
The authored layer
394 of the 1,949 facets were authored. 65 carried kind='governance' — 28 tenets, 19 decisions,
nine goals, nine procedures — and 86 authored facets carried an explicit authority field:
source-of-truth (71), historical (12), experimental (3). The field is what let the deriver
know which nodes it must not overwrite. 386 of 394 authored facets carried a reviewed_at
timestamp.
The nine goals form the strategy tree: north-star over build-the-graph, ship-data-product,
projection-layer and operate-the-factory, with four product layers under ship-data-product.
The nine procedures cover work classification, dispatch routing, editorial and research policy,
disclosure gating and operator email. The eight subsystems — collection, corpus, enrichment, infra,
publication, research, revenue, web — are the grain at which decisions were declared to govern
anything; five governs edges existed, all sourced to a single section of the as-built
documentation.
The tenet catalogue
28 tenets, verbatim from rp_ontology_nodes.name and one_liner. Every one of them was injected
into context at session start.
| tenet | one-liner |
|---|---|
| Data is the truth; documents are renders | The operation’s goals, mission, and doctrine live as ontology data. Markdown is an ephemeral human-readable render, never the source of truth. |
| Ground answers in the source of truth | Query the data before you assert — never answer about the operation from memory or a stale briefing. |
| Provenance, not verification | We stand behind the provenance & confidence of every assertion, not the current truth of a value. |
| Provenance is born at collection | Carried through, never reconstructed at publish time. |
| The lineage chain must hold | click/event -> agent run -> search query -> source URL, as a gate. |
| An output is a projection | Check the lineage, not the output. Every artifact inherits the lineage of its data. |
| Justify, not prove | Calibrated confidence + attribution + as-of + documented bias + explicit gaps. |
| Canonical write invariant | Canonical (the graph) is written only through engine’s data layer — a code-path discipline, not a repo boundary. |
| No untracked AI work | Every AI-driven action leaves a structured trace: cost for metered runtimes, provenance for subscription runtimes. |
| Deterministic-first; LLM only the gap | Never spend an LLM call on a fact the deterministic path already produced. |
| Entity resolution is one pattern | Capture raw; deterministic-first; LLM-fallback only on misses; every edge provenance-tagged. |
| Scope-time discipline; entities are stable nouns | Gatekeep what earns a row/type at scope time, not by filtering a mixed population at query time. |
| The corpus is one fusion graph | Three sub-graphs (commercial/conflict/drone-system) fuse at drone-system + company nodes; the fusion edges are the moat. |
| The graph is the job — render intelligence, not content | We render intelligence products from the graph; we no longer run a content operation. |
| Corpus coverage doctrine — the universal dataset areas | Every coverage area builds the same four universal datasets, on a three-tier cadence, rendered from the graph — never a content schedule. |
| Actor narrative-arc framework | Prioritize coverage angle by where an actor sits in its story arc: Emergence → Breakout → Peak → Maturation → Renewal/Decline. |
| Analyst voice — quantitative, global, calibrated, sourced | We are analysts, not journalists: quantitative, global, uncertainty-acknowledging, work-showing — across every rendered surface. |
| Closure integrity | Mark a row done only if its deliverable exists in the system at closing time. |
| The engine self-heals | A solo operator must never be the recovery mechanism. |
| Agents are rows that earn their keep | Self-hosted on the VPS, minimal, stubbed cheaply before real prompts + budget. |
| Judgment = operator + HQ Claude | Not a cron of autonomous officers. |
| HQ is the single run surface | Strategy + running the engine both happen in robotics-press-hq; app repos are toolsets. |
| Trust boundary on inbound | Never read inbound email bodies in the main loop; quarantine first. |
| Vertical-agnostic platform, a product lineup | The foundation is general; Drone Intelligence is the first product line. |
| Vertical logic in the product layer, never the foundation | Drone/security-specific logic (attack_events, CARVER/DRES) is a product-layer concern. |
| Bet on the product, not the platform | If the drone vertical cools, ship a new product line on the same foundation. |
| Detection/informational only, never mitigation | The product describes; it does not advise on defeating drones. |
| Export sequencing: EAR99 first, CARVER gated, AECA §38 company-ending | Ship the EAR99-side layers; gate CARVER behind a DDTC CJ; never touch USML/AECA territory. |
Eleven of the 28 govern how work is done; the rest govern what the product is and what it may say.
All 28 carried authored_lifecycle='intended_live'. None carried a derived_state, because the
generated drift column excludes tenet from the kinds it evaluates — a tenet has no mechanical
ground truth to measure against. Whether the operation followed its own doctrine was not a
computable question, and the substrate does not claim otherwise.
Injection at session start
The tenet set was not documentation an agent could choose to read. session-start.py queried the
live table at boot and injected the current tenets into context before the first token, alongside
service health, drift status, the priority-ordered backlog, and the prior session’s handoff.
The backlog entry carried an explicit instruction: the backlog is the source of truth for what to do and in what order — read it from the data, not from this briefing. A briefing is accurate at write time and stale afterward; it orients but does not instruct.
CLAUDE.md was reduced to a routing table — “when you need X, run this query” — rather than a copy
of the system description. The file states its own role: this file is the map, not the territory;
it points, it doesn’t copy.
Phase 5 of the governance-as-data epic, backlog #1030, would have closed the loop by generating
CLAUDE.md itself from goals, tenets and procedures. It was classified T2/S4/docs and reached
status ready. So did the other four rows of the epic — #946, #1028, #1029 and #1031, the last of
which would have added closeout review-stamps and a tenet self-verify. None were executed.
Backlog classification
Work items carried three mandatory axes, enforced at the database level.
| axis | values |
|---|---|
| tier | T1 Durability · T2 Leverage · T3 Capability |
| severity | S1 burning · S2 · S3 · S4 nice-to-have |
| category | bug data platform pipeline feature content ops docs |
-- pg_get_constraintdef, reformatted for width; no clause omitted
CONSTRAINT backlog_open_classified CHECK (
status <> ALL (ARRAY['proposed'::sprint_task_status,
'backlog'::sprint_task_status,
'planned'::sprint_task_status,
'ready'::sprint_task_status,
'in_progress'::sprint_task_status,
'in_review'::sprint_task_status,
'changes_requested'::sprint_task_status])
OR (tier IS NOT NULL AND severity IS NOT NULL
AND category IS NOT NULL AND category <> 'code'::text)
)
An open row without all three is rejected by the database. Classification happens at creation, when the cost of answering “why does this matter” is lowest.
Ordering was strict tier dominance: all T1 before any T2 before any T3, severity ordering
within tier, integer priority as tiebreaker.
Closure had its own rule:
Closure integrity. Done is a claim about outcomes, not decisions. Never close with unpushed commits. Re-scoped work uses
deferredwith a successor ref, notdone. Every closed row has resolution, files, session_number, resolved_at, and a verification artifact.
The force_close path verified commit SHAs against origin before permitting a done transition.
The rule held less completely than it reads. Of 1,188 rows, 311 terminated as deferred. Of those,
197 name a successor reference that resolves to another row; 68 carry no resolution text at all.
The constraint that enforced classification at creation had no counterpart enforcing a successor at
deferral, so the rule was followed where a human wrote a resolution and skipped where a bulk sweep
did not.
The resolution text was the artifact that made a closed row readable later. #924’s is one line — “Migration 017: dropped writes_to … Drift 88 -> 0.” #864’s runs to five sentences and records both what improved and what was renegotiated: “Q5 metric REDEFINED (committing-sessions denominator) and Q9 dead-vs-dormant labels accepted by operator”. A closed row that records only its own closure is not recoverable evidence; these carry the argument.
Memory notes
72 markdown files in the agent harness’s memory directory became 72 memory_note nodes. Each file
carries frontmatter — name, description, metadata.type — and the deriver lifted name and
description into name and one_liner. The index file MEMORY.md was skipped by name, and
states the arrangement: “Do not maintain a hand-written index here; it would drift.”
Subkind came from splitting the filename on underscore, which produced four real classes and one artifact:
| subkind | notes |
|---|---|
| — (hyphenated filename) | 27 |
feedback | 21 |
reference | 12 |
project | 10 |
user | 1 |
as | 1 |
The last is as_built_audit_location.md, whose name split at the first underscore. A parser with no
schema behind it will classify whatever it is given.
The classes divide by what the fact is about. feedback_* notes record operator corrections —
“Confidence-or-ask, never write hypothetical policy”; “HQ Claude is the orchestrator, never the
executor”; “Frame rules positively, not negatively”. reference_* notes record environment facts
that cost a session to discover — that unprefixed table names like jobs and llm_traces are views
over eng_* base tables; that llm_traces.parent_id is a UUID column so integer entity ids must be
mapped through uuid5; that the OpenRouter key expires periodically and returns 401 rather than 402,
taking the engine’s LLM path down while the account and credit stay intact. project_* notes record
standing decisions. The hyphenated remainder, written later, record operating traps:
main-branch-push-is-permission-gated, gh-pr-edit-fails-use-api,
dispatch-apply-auto-applies-migrations, website-deploys-via-wrangler-only.
Several notes exist because a session lost time to something no table recorded. One is explicit
about the shape of the failure: codex-timeout-salvage-pattern — “Codex dispatches can hang in
their verification phase after finishing the work — check the worktree for commits/files before
re-dispatching; salvage beats retry.”
Two notes contradict other parts of the record, and were not reconciled.
reference_decision_surfaces_authority states that the rp_decisions table was roughly ten days
stale and missing the drone pivot, so decisions/*.md should be trusted instead. Three notes give
three different figures for the OpenRouter daily cap. Notes were appended, never audited; nothing
compared one against another.
Session continuity
Sessions wrote a handoff into rp_sessions.next_up at close, retrieved by the next session’s
briefing. 87 of 133 recorded sessions wrote a summary longer than 300 characters; 86 exceeded 500. Where a handoff existed and was not
empty, it averaged 2,625 characters; the longest ran to 5,366.
The handoff carried what the structured data could not: which failure modes had been observed but not yet fixed, which claims were unverified, what the operator was waiting on. Git history records what changed; the handoff recorded what the next session needed to know that no table held.
The letters converged on a four-part form — a through-line, the traps, what the operator is blocking on, and ledger hygiene. Session 364’s, in full for the first two parts and abbreviated after:
Through-line: the operation now optimizes revenue-per-visitor over traffic volume (scale-gate verdict: $39 self-serve misses base-case year-1; commissions/sponsorship primary) and runs a detection-first beachhead with a hard trust gate before any external outreach.
Traps the data won’t tell you:
- Website: routes table sits at exactly 100 rules — new static top-level surfaces must be on-demand SSR (pattern:
/pricing.astro) or evict-checked […]- Edge cache lies during deploy verification — fresh 404s and stale 200s persist for minutes; always re-verify with cache-busted URLs before diagnosing.
- The 17 queued posts in
intel_social_queueare pre-pivot warfare voice: purge, don’t re-voice (amendment A4). […]- The 21-article keep-list (#846 input) intentionally keeps the sourced warfare dailies — the purge criterion is UNSOURCED, not off-topic; don’t re-litigate.
Operator-waiting (week-0 gate […]): CF AI-crawler flip is a zone-dashboard action (API token lacks scope […]) · Stripe keys (deliberately parked; commissions don’t block on it) · Semrush unit top-up (unblocks #1162) · social account creation + Buffer credentials.
Ledger hygiene: an older open session (seq=363) predates this one and was never closed — close or investigate on wake.
Every category in that letter is a fact no schema held. “Edge cache lies during deploy verification” is a false-negative mode in the verification procedure itself. “Don’t re-litigate” marks a settled argument, which is the specific thing a stateless successor is most likely to reopen. The operator-waiting list names work that stalls silently: nothing errors, nothing alarms, the item never advances. Session 351’s letter names that property outright — “Operator-waiting (will stall silently)”.
The letters also carried the ledger’s own defects. Session 364 reports an unclosed session
predating it; session 351 reports two more open from weeks earlier. The gap in rp_sessions was
visible to the agents writing the handoffs and was reported in prose rather than caught by a check.
Self-documentation coverage
Backlog #766 established the second layer of the no-untracked-ai-work tenet. Metered API calls
already produced a cost row per call. Subscription runtimes — Claude Code, Codex — bill flat and
produce no per-call record, so the work itself had to leave a trace. harvest_authorship.sh walked
git log across eleven repositories nightly, classified each commit by its co-author trailer, and
upserted one row per (repo, commit_sha) into rp_authorship_log.
_CLAUDE_RE = re.compile(r"co-authored-by:\s*(Claude[^\n<]*)", re.I)
_CODEX_RE = re.compile(r"co-authored-by:\s*[^\n<]*codex", re.I)
_OFFICER_RE = re.compile(r"co-authored-by:\s*[^\n<]*officer", re.I)
4,640 commits were harvested, spanning 2026-02-04 to 2026-07-24, across 20,028 file changes.
| runtime | commits | lines added | lines removed |
|---|---|---|---|
human | 3,667 | 1,653,027 | 797,565 |
claude-code | 972 | 776,221 | 106,086 |
codex | 1 | 1 | 1 |
Classification rests entirely on a commit trailer. A commit without one is recorded as human,
whatever produced it, and the single codex row against a dispatch system that ran many builds
shows the trailer was usually absent. The captured model string is whatever text followed the
trailer: Claude Opus 4.6 (1M context) 267 times, Claude Opus 4.7 (1M context) 213, Claude Opus 4.6 166, Claude Opus 4.8 (1M context) 149, Claude Fable 5 132. One row carries the model string
Claude commit marker, harvested daily into rp_authorship_log. — a line of documentation that
matched the regex and was stored as a model name.
A separate mechanism covered documentation rather than authorship. After an operator instruction in
session 311 — recorded in the memory note as “the system is documented properly or shut down” —
every code machine and data table was required to carry an authored facet of the form
{summary, does, why, reads_from, writes_to}, sourced from a checked-in manifest
(ontology/authored/things/*.json) and loaded by seed_thing_docs.py. The note records the result:
“Coverage: 65/65 code, 229/229 data (100%).”
scripts/thing_doc_coverage.py was written as the gate. Its docstring states the intent — “exits
non-zero when --strict and any code thing is undocumented, so an undocumented machine can’t
silently slip onto the board”. Run against the live database at teardown:
thing-doc coverage
code : 67/88 documented (76%)
data : 240/240 documented (100%)
21 undocumented.
The 21 gaps are all machines added after the documentation pass — job_directory_freshness_slo,
job_directory_reverify, tool_directory_budget, job_weekly_public_digest and the rest of the
directory-era job set. The data side held at 100% because derive_data_docs.py gap-fills schema
docs automatically; the code side had no such fallback and depended on a human-written manifest
entry per machine.
The gate was wired into refresh_telemetry.sh, hourly, on this line:
$RUN scripts/thing_doc_coverage.py --data >> "$LOG" 2>&1
--strict is not passed. The check ran every hour for weeks, printed the falling number into a log,
and exited zero every time.
Competency questions
The ontology was specified by the questions it had to answer, and
scripts/verify_competency_questions.py ran all twelve against the live substrate as a regression
check. Backlog #864 records the starting position — five passing, five partial, two failing — and
the state at close, twelve passing. At teardown:
✓ Q1 Operation at-a-glance 1941 nodes, 209 authored
✓ Q2 Drift now 0 total (0 state)
✓ Q3 Inspect a node attack_events: 3 facets, 34 edges
✓ Q4 Subsystem depth avg 9.0 fields per subsystem strategy facet (target ≥5)
✓ Q5 Session provenance 49/51 committing sessions (96%) have provenance · 56/132 of all
✓ Q6 Decision lineage 1 derived supersedes edges
✓ Q7 Intent-vs-reality (state) 0 state-drift rows (0 = currently clean)
✓ Q8 Unowned data_entities 0/245 (0%) have no authored owner
◐ Q9 Empty junctions classified 7 empty tables have no authored lifecycle
✓ Q10 Blast radius (reads_from) 184 reads_from edges (ops/globe wired; website is static SSG)
✓ Q11 Authority on docs/entities 86 authored facets carry an 'authority' field
✓ Q12 Stale-authored hit list 386 authored facets reviewed (0 = closeout-reconcile not wired)
✓ 11 ◐ 1 ✗ 0 (target: minimize ◐+✗)
Q9 regressed from passing to partial in the eleven weeks after #864 closed: seven tables reached zero rows without anyone declaring whether that was intended. Q5’s own denominator is the honest one — 56 of 132 session nodes carry provenance edges in absolute terms; 49 of 51 do among sessions that committed anything. The metric was redefined during #864 to measure the sessions it could measure, and the resolution says so.
Instrumentation failure
rp_sessions held 133 rows. The seq column ranged from 56 to 375.
Sessions ran, performed work, and did not write a row. No mechanism checked the sequence for gaps, so the absence was never surfaced. The operation ran under a standing rule requiring every AI action to leave a structured trace, and did not detect that its own session ledger was recording a minority of sessions.
A second instance: scripts/mine_retrospective.py, run on 2026-07-13, reported “Metered cost
booked to work: $0.18.” The actual figure was $997.48. The generator joined cost_usd through
backlog rather than through eng_llm_traces; almost no spend was ever attributed to a backlog
row, so it summed a near-empty column and emitted the result. The number sat in a generated report
for eleven days.
A third: the freshness SLO alarm did not fire on a breach affecting three of four coverage areas, because it derived from a telemetry table that had itself gone stale.
A fourth is the coverage gate above. The check existed, was correct, ran hourly, and was invoked without the flag that would have made it fail. Code documentation fell from 100% to 76% across the directory build with the gate green throughout.
In all four the monitoring is a component of the system, subject to the same failure modes, with
nothing positioned outside it. Two mechanisms cover the first three: a gap detector over any
sequence expected to be contiguous, and an independent second path to any generated aggregate —
SELECT SUM(cost_usd) FROM eng_llm_traces executes in under a second and contradicts the $0.18
figure directly. The fourth is a different question: whether the checks are running in enforcing
mode, which no single monitor can ask about itself.
state_drifts is the one measurement in the system with none of that exposure, and by construction
rather than by vigilance. As a generated column, the disagreement between intent and reality is
computed by the storage engine on write; it cannot be skipped by a writer, and cannot report success
while doing nothing. Both false-positive corrections it needed — migration 009’s eligibility gate and
migration 017’s grain fix — were visible as an implausible number in a count that was published
daily.
Next: Part 5 — Cost.