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:

kindcountkindcount
backlog_item1,165tenet28
data_entity245module21
session132endpoint11
message114repo11
memory_note72procedure9
code_unit67goal9
decision38doc9

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.

predicateedgesauthoredderived
part_of30828820
references2290229
writes_to21012585
reads_from18413747
depends_on1230123
changed1130113
touched89089
governs550
supersedes431

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.

confidenceedgesmeaning
702authored, or emitted before the attribute existed
heuristic322source grep or text match
measured128read from pg_constraint
derived113resolved 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:

predicatelayerfrom → toedges
reads_fromauthoredcode_unitdata_entity137
writes_toauthoredcode_unitdata_entity110
writes_toderivedrepodata_entity85
reads_fromderivedrepodata_entity47
writes_toauthoredsubsystemdata_entity15

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:

kindisolatedkindisolated
backlog_item1,056endpoint11
message114code_unit9
session76procedure8
memory_note72decision6
tenet20doc3
module12

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.

tenetone-liner
Data is the truth; documents are rendersThe 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 truthQuery the data before you assert — never answer about the operation from memory or a stale briefing.
Provenance, not verificationWe stand behind the provenance & confidence of every assertion, not the current truth of a value.
Provenance is born at collectionCarried through, never reconstructed at publish time.
The lineage chain must holdclick/event -> agent run -> search query -> source URL, as a gate.
An output is a projectionCheck the lineage, not the output. Every artifact inherits the lineage of its data.
Justify, not proveCalibrated confidence + attribution + as-of + documented bias + explicit gaps.
Canonical write invariantCanonical (the graph) is written only through engine’s data layer — a code-path discipline, not a repo boundary.
No untracked AI workEvery AI-driven action leaves a structured trace: cost for metered runtimes, provenance for subscription runtimes.
Deterministic-first; LLM only the gapNever spend an LLM call on a fact the deterministic path already produced.
Entity resolution is one patternCapture raw; deterministic-first; LLM-fallback only on misses; every edge provenance-tagged.
Scope-time discipline; entities are stable nounsGatekeep what earns a row/type at scope time, not by filtering a mixed population at query time.
The corpus is one fusion graphThree 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 contentWe render intelligence products from the graph; we no longer run a content operation.
Corpus coverage doctrine — the universal dataset areasEvery coverage area builds the same four universal datasets, on a three-tier cadence, rendered from the graph — never a content schedule.
Actor narrative-arc frameworkPrioritize coverage angle by where an actor sits in its story arc: Emergence → Breakout → Peak → Maturation → Renewal/Decline.
Analyst voice — quantitative, global, calibrated, sourcedWe are analysts, not journalists: quantitative, global, uncertainty-acknowledging, work-showing — across every rendered surface.
Closure integrityMark a row done only if its deliverable exists in the system at closing time.
The engine self-healsA solo operator must never be the recovery mechanism.
Agents are rows that earn their keepSelf-hosted on the VPS, minimal, stubbed cheaply before real prompts + budget.
Judgment = operator + HQ ClaudeNot a cron of autonomous officers.
HQ is the single run surfaceStrategy + running the engine both happen in robotics-press-hq; app repos are toolsets.
Trust boundary on inboundNever read inbound email bodies in the main loop; quarantine first.
Vertical-agnostic platform, a product lineupThe foundation is general; Drone Intelligence is the first product line.
Vertical logic in the product layer, never the foundationDrone/security-specific logic (attack_events, CARVER/DRES) is a product-layer concern.
Bet on the product, not the platformIf the drone vertical cools, ship a new product line on the same foundation.
Detection/informational only, never mitigationThe product describes; it does not advise on defeating drones.
Export sequencing: EAR99 first, CARVER gated, AECA §38 company-endingShip 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.

axisvalues
tierT1 Durability · T2 Leverage · T3 Capability
severityS1 burning · S2 · S3 · S4 nice-to-have
categorybug 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 deferred with a successor ref, not done. 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:

subkindnotes
— (hyphenated filename)27
feedback21
reference12
project10
user1
as1

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_queue are 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.

runtimecommitslines addedlines removed
human3,6671,653,027797,565
claude-code972776,221106,086
codex111

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.