# Consequence-Question Retrieval — Pre-Registration v0.1 (partial — ingest-integrity and R9b sections)

**Status: partial document.** This file is being built incrementally, in the order the arc's own steps produce content that must be pre-registered before results exist. This revision covers the ingest-integrity check (replacing the earlier ad hoc version), the `gold_position` covariate, and R9b (activation-read-safety). R1–R9 verbatim, the pinned request bodies in full, and the adjudication table are added at Step 1 proper (question construction), which has not run yet. **Everything in this revision was written before this session looked at the completed Step 2 ingest's results** — the full ingest finished (`run_manifest.json` written) while this document was being drafted; its contents were deliberately not read until after this document was saved, so that the criteria below are not shaped by knowledge of the outcome they're meant to judge.

---

## Step 2 result (corrected re-ingest): primary arm VALID for closure (R)

Run after this document's ingest-integrity/v0.2 sections were written and saved. `run_cqr_ingest_v2.py` (fresh `cqr_datadir/`, no smoke promotion, no consolidation ever) completed cleanly: 13/13 docs OK, zero queue-level drops, 2954 concepts, 1050 relations, 657 engrams, 4205s elapsed. Ingest-time bridge witness (persisted, authoritative): `relations_considered=1021, edges_projected=797`. Quarantine count 385 (list carried to Step 1 per the earlier instruction).

`lift_and_verify.py` then: archived the pristine (no-consolidation, no-lift) state to `cqr_datadir_pristine/`; ran `run_consequence_lift()` exactly once on a working copy (`cqr_datadir_primary/`) — `relations_seen=1050, causal_relations=107, edges_created=378`; ran the fixed-point re-verification (lift again on a copy, compare). **Result: `FIXED_POINT_DEFECT: false` — zero diff in edge count, endpoints, or weights across the re-lift. `PRIMARY_ARM_VALID_FOR_CLOSURE_R: true`.**

**Collision witness (new, per this session's instruction) — the collision mechanism is real and recurring, but not a defect here.** Comparing the pristine (post-ingest) and primary (post-lift) snapshots: 233 of 4098 pristine edges were pairs written by more than one mechanism (bridge and/or auto-adjacency and/or entity-coref, then overwritten by the lift). Breakdown by original→surviving type: `concept_relation→consequence` (150 — the bridge's broad namespace-C connectivity, refined by the lift's causal subset), `entity_coref→consequence` (67 — a mechanism not previously implicated in this arc's collision analysis), `structural_adjacency→consequence` (16 — the same pattern as the one node traced in the tainted run, confirming it recurs even in a single clean pass). **This is not the tainted run's pathology.** The tainted run's defect was comparing two separate, differently-sized ingests (doc-1-alone vs. full-corpus) of the *same* mechanism (lift) run twice. Here, one pipeline runs once: the bridge writes first (ingest-time), the lift writes second (once, immediately after) — multiple producers legitimately sharing slot space is expected given `_edge_metadata`'s one-slot-per-pair design (confirmed last session), and the fixed-point check proves the result is stable, not partway through some further drift. Combined with the confirmed finding that `activate()`'s propagation is blind to edge type (reads only the numeric weight matrix), which type "won" at a shared slot has no retrieval consequence as long as the resulting weight is stable — which it is. Full detail: `ngramdb/scratch/cqr/lift_and_verify_results.json`.

**Gold-touching restriction on the collision witness is still pending** `gold_engram_ids` (Step 1). The primary-arm graph is otherwise ready for Step 1's gold resolution and, after that, Step 3.

---

## Ingest-integrity check (replaces the ad hoc "duplication or weight change" version)

**Background.** The Step 2 datadir (`ngramdb/scratch/cqr_datadir/`) was built by promoting a one-document smoke-test run (document 1, the Cloudflare postmortem) into the real datadir, then resuming ingestion for documents 2–13. This means document 1's relations were lifted via `/neural/consequence_lift`-equivalent (`store.run_consequence_lift()`) **twice**: once during the smoke test (lift #1, doc-1-only), once as part of the full run's own single post-drain consolidation+lift cycle (lift #2, all 13 docs' relations, including doc 1's again). Source tracing (this session, prior checkpoint) established `run_consequence_lift` is idempotent in graph *state* (dict-overwrite keyed by `(source_id, target_id)`, not append) but not idempotent in its own *reported stats*, and flagged a not-yet-empirically-confirmed risk: `_write_edge`'s non-reinforce path re-triggers per-node weight renormalization on every call, which could in principle shift the weights of a node's *other* edges of the same type, not just the one edge being written.

**Two checks, testing two different things:**

### Check A — cross-ingest doc-1 integrity (lift #1 → lift #2)

Compares doc-1's 121 engrams' outgoing edges at two points that are legitimately far apart in the pipeline (docs 2–13 were fully ingested and bridged in between, so *new* edges touching doc-1 nodes are expected):
- **S1** ("before"): captured from `/tmp/cqr_smoke_datadir2` (untouched since the smoke run's own shutdown at 16:48:24) — doc-1-only state, after lift #1.
- **S2** ("after"): the completed full ingest's final state, after lift #2 — `ngramdb/scratch/cqr_datadir/` once it has fully shut down.

For each of doc-1's 121 engram IDs as source: take the full outgoing-edge set (target_id, edge_type, weight) in S1 and in S2.
- **Edges present in both S1 and S2** (same `(source_id, target_id)` key) must have **identical weight**. A weight difference on a common edge = **DEFECT**.
- **Edges present in S2 but not S1** (new neighbors) are **EXPECTED**, not a defect — this is exactly the forward-bridging effect item 2(a) predicted (later documents' relations can bridge backward onto doc-1 engrams). Report the count and a sample, but do not treat as a failure.
- **Edges present in S1 but not S2** (a doc-1 edge disappearing) = **DEFECT** — nothing in this pipeline should delete an edge between two still-existing engrams.
- **Duplicate edges for the same ordered `(source_id, target_id)` pair** — checked directly against the storage structure (`_edge_metadata: dict[tuple[str,str], Edge]`, confirmed this session to be a plain dict, structurally unable to hold two values for one key under the current single-representative-edge configuration) — expected to be structurally impossible here, verified empirically anyway, not assumed. Any duplicate found = **DEFECT**.

### Check B — fixed-point check (lift #2 → lift #3)

Tests whether re-running the lift changes *anything at all* when no new relations have been added in between (the strictest form of the idempotency claim):
- **S2**: the completed full ingest's final state (as above), captured immediately, before any further action.
- **S3**: state after one additional, isolated `store.run_consequence_lift()` call against a copy of S2 (never the live/completed datadir directly, to avoid any risk to the artifact of record) — no new ingestion, no new Pass-2, nothing else happens between S2 and S3 except this one call.
- **Pass condition:** zero diff between S2 and S3 in edge count, edge endpoints, and edge weights, for the *entire* graph (not just doc-1) — this window has no legitimate reason for *any* change, unlike Check A's window.
- Any diff at all = **DEFECT**.

### Verdict rule (both checks)

**Any DEFECT under Check A or Check B = this run is invalid for the retrieval closure (R). Document 1 is re-ingested from scratch (not patched) before closure (R) can be reported.** Closure (E) (the extraction-closure question — whether any bridge/consequence edge touches a gold engram at all) is unaffected by this specific check and is evaluated independently once Step 1's probe exists.

Both checks are run and reported together, after this document was saved — not before.

---

## `gold_position` covariate (informational, no rule)

At gold resolution (Step 1's `build_probe.py`), in addition to `gold_engram_ids`, record **`gold_position`**: whether the gold passage's chunk(s) occur **before** or **after** the question passage's own chunk, in the document's linear reading order (by `chunk_index`). This matters given item 2(a)'s finding that the concept→engram bridge is forward-only in *ingestion* order — but within a single document, all chunks are ingested in one `store_document()` call in reading order, so `gold_position` here is about document-internal reading order, a different (and for this arc, more directly relevant) axis than the cross-document ingestion-order effect Check A is about.

R7's summary tables are reported once **split by `gold_position`** (before/after), in addition to the pooled numbers already specified — **informational only, no tier or rule is conditioned on this split.** It exists to let a reader see whether graph or similarity has a directional bias by reading order, without that observation being able to move the R8 verdict.

---

## R9b — activation-read safety (pinned, confirmed from source before any rank is computed)

**Claim: the pinned `/activate/text/full` request produces zero mutation of edge weights and reads zero mutable per-engram field.** Traced end-to-end this session:

- **Edge weights:** `activate_text()` → `activate()`'s only state-mutating call is `self._recent_activation_buffer.record(...)` (an in-memory activation-history buffer, not an edge weight). That buffer only becomes durable graph mutation via an explicit, separate `/neural/replay`-style endpoint (never called during Step 3) whose own sequential-edge write path is additionally dormant under `NGRAMDB_CONSEQUENCE_MODE=extracted_boost` (this arc's pinned setting). `reinforce_edge` (`engram_store.py:4992`), the actual Hebbian weight-update primitive, has exactly one real caller in the codebase (`server.py:1136`, an explicit feedback endpoint), never invoked by activation or by this arc's scripts.
- **Ranking mode reads no mutable field:** the pinned body (inherited from Q1/Arm C, `consequence_baseline_run.py`'s `arm_text_full`: `hops=2, decay=0.7, threshold=0.0, max_results=60, apply_tau_local=False`, **no `ranking` dict**) means `_apply_composite_ranking` (`engram_store.py:6676`) — the only code path that reads `engram.metadata["hebbian_weight"]` or a `created_at`-based recency score — **never executes**, because its call is gated on the caller supplying a non-empty `ranking` dict, which this arc's pinned body does not. The raw `activation_score` returned is a fresh propagation computation each call, not a cached or incrementally-updated value.
- **`activation_count`:** incremented in exactly one place (`engram_store.py:2507`), inside the store-time dedup path (`store()`, not `activate()`/`activate_text()`). Never touched by a read/query.
- **`session_recency` / tau_local, pinned off explicitly, not just by omission:** `apply_tau_local: False` is set in the pinned body (inherited from Q1/Arm C, confirmed above) and `session_recency` is not supplied (defaults to `None`). Belt-and-suspenders: even if it were supplied, the field it would read (`engram.last_activated`) is never populated anywhere in `engram_store.py` — a fact already on record from a prior session's source trace, re-confirmed here as still true (no write site found for `last_activated` in this codebase).

**Pinned for Step 3, stated as R9b:** the exact `/activate/text/full` body used for every question is
```json
{"query_text": "<verbatim question text>", "hops": 2, "decay": 0.7, "threshold": 0.0, "max_results": 60, "apply_tau_local": false}
```
No `ranking` dict, no `session_recency`, no `min_weight`, no `min_overlap`, no `cohort_weights`. `threshold: 0.0` (not the field default `0.1`) is deliberate, per this session's earlier endpoint-facts finding: it's the only way to guarantee every engram that receives any activation at all appears in the ranked list, so the "not retrieved" fallback (rank = N_engrams + 1) is reserved for engrams receiving literally zero activation, not ones merely below an arbitrary threshold. `max_results: 60` follows Q1/Arm C's own value (not `None`) — reconfirmed against this arc's own earlier finding that `max_results=None` applies no cap either, so both choices satisfy "rank the entire corpus" as long as 60 ≥ N_engrams for this datadir's actual engram count (to be checked at Step 3 setup; if N_engrams > 60, `max_results` will be widened, not left at Q1's value by inertia — a deviation to be logged if it happens, not silently absorbed).

Given the above, **no pristine-copy-per-pass protocol is needed** — the mechanism is provably inert under this exact pinned body, not inert by a promise kept manually across repeated calls. The determinism double-pass (R6/§6) is expected to produce byte-identical ranks for exactly this reason.

---

## Corpus and probe — draft adjudication table

Built by `ngramdb/scratch/cqr/build_probe.py` (R1-R5 mechanical pass) + this session's manual reading of the candidate lists it produced (`cqr_probe_candidates_v1.json`). **`gold_engram_ids` and `gold_position` are null throughout — resolved after the ingest completes**, per this session's Step-2-before-Step-1 ordering. The operator adjudicates (keep/drop/edit) before anything below is ratified; nothing here has been used for retrieval.

**Structural finding, before any pair was chosen:** of the 13 corpus documents, only 2 (`#10`, `#12`) have clean `## Decision` / `## Consequences` markdown headings — R1's other two ADRs (`#9`, `#11`) are background/spec documents without that structure, and all 8 cause-from-symptom postmortems have no section headings at all (4 are plain prose; 4 — the GitLab issue-tracker docs, `#3-6` — are bulleted Q&A templates). `build_probe.py` does not invent boundaries for the 10 non-mechanical documents; it emits every rendered line as a location-tagged candidate (with light "possible-gold"/"possible-question" heuristic tags, not decisions) and this session hand-selected a suggested pair from those candidates for the table below, explicitly marked as suggestions, not ground truth.

**Selection source, added this amendment.** Every pair now carries `selection_source`, tracked in `cqr_probe_candidates_v1.json` and repeated in the tables below: `heading-mechanical` (R2/R3 extracted directly from a real `## Decision`/`## Consequences` heading, no human judgment involved), `CC-suggested` (this session hand-picked one candidate from `build_probe.py`'s tagged line list — a starting point, not authoritative), or `pending-operator-selection` (no confident suggestion offered at all). Once the operator ratifies or overrides a pair at Step 1, its `selection_source` is updated to `operator-selected` — this happens in the JSON and in this table together, not in one without the other.

### Family: effect-from-action (mechanical, R2/R3 directly extracted)

| Doc | Question (Decision, verbatim, truncated) | Gold (Consequences, verbatim, truncated) | Jaccard | R4 | Selection source | Operator disposition |
|---|---|---|---|---|---|---|
| `#10` catalog-core-entities | Original: full `## Decision` section (18 chunks, incl. YAML). **Trimmed 2026-08-28** (operator: "trim, R4 decides") to lines 14-25 only — the intro paragraph + entity bullet list, excluding the `### Component`/`### API`/`### Resource` subsections and YAML blocks: "Backstage should eventually support the following core entities: - Components... - APIs... - Resources... For now, we'll start by only implementing support for the Component entity..." | "We will continue fleshing out support for the Component entity in the Backstage catalog." | ~~0.0267~~ **0.1316** (recomputed against the trimmed text) | included | `operator-selected` (APPROVED — keep, trimmed) | **keep, trimmed** — R4 decided: included, FINAL |
| `#12` plugin-package-structure | "We will place all plugin related code in the `plugins/` directory..." | "We will actively migrate existing packages that are part of a plugin to the `plugins/` folder..." | 0.1639 | **excluded-overlap** (fails R4 as-is; near-duplicate vocabulary between Decision and Consequences) | `heading-mechanical` | **drop** (ratified 2026-08-28) |

### Family: effect-from-action — no Decision/Consequences structure found (recommend drop, not forced)

| Doc | Why | Selection source | Operator disposition |
|---|---|---|---|
| `#9` default-catalog-file-format | Background/spec document (file-format description), no Decision or Consequences section; 132 line-level candidates exist but none is a natural fit for this family's structure | `pending-operator-selection` (no suggestion offered) | **drop** (ratified 2026-08-28) |
| `#11` entity-references | Same — background + two how-to sections ("Entity References in YAML files" / "...in URLs"), no Decision/Consequences; 49 candidates | `pending-operator-selection` (no suggestion offered) | **drop** (ratified 2026-08-28) |

### Family: cause-from-symptom (suggested pairs from line-level candidates — operator ratifies, edits, or picks a different candidate from the full list in `cqr_probe_candidates_v1.json`)

| Doc | Suggested question (impact, verbatim) | Suggested gold (root cause, verbatim) | Jaccard | R4 | Selection source | Operator disposition |
|---|---|---|---|---|---|---|
| `#1` Cloudflare | L3: "Cloudflare's network began experiencing significant failures to deliver core network traffic. This showed up to Internet users trying to access our customers' sites as an error page..." | L4: "...it was triggered by a change to one of our database systems' permissions which caused the database to output multiple entries into a 'feature file'..." | 0.0167 | included | `operator-selected` | **keep** (ratified 2026-08-28) |
| `#2` GitLab DB outage | **Changed 2026-08-28** (operator: "keep, CC proposes question text"). New: L4 "This incident caused the GitLab.com service to be unavailable for many hours. We also lost some production data that we were eventually unable to recover... Our best estimate is that it affected roughly 5,000 projects, 5,000 comments and 700 new user accounts..." | L3 unchanged: "...The outage was caused by an accidental removal of data from our primary database server." | ~~0.1724~~ **0.1311** (recomputed — see correction note below) | included | `operator-selected` (APPROVED as-is) | **keep, question text CC-proposed** — R4 included, FINAL |
| `#3` GitLab #17057 | **Changed 2026-08-28** (operator: "keep, CC proposes question text"). New: L14 "Customers trying to access GitLab.com between 2023-10-30 15:27 UTC to 16:15 UTC. Duration of 48 minutes of service disruptions." (replacing the prior thin "- All GitLab.com users." candidate) | L18 unchanged: "The root cause was database saturation on the merge_requests table caused by bulk import jobs..." | 0.0000 | included | `operator-selected` (APPROVED as-is) | **keep, question text CC-proposed** — R4 included, FINAL |
| `#4` GitLab #18490 | L6: "Customer Impact: Most GitLab services unavailable for all users" | L9: "Root cause: Manual database restart during scheduled production change and maintenance work." | 0.0000 | included | `operator-selected` | **drop** (ratified 2026-08-28 — question and gold had resolved to the identical engram, see the gold-resolution flags above) |
| `#5` GitLab #18596 | L6 unchanged: "Customer Impact: Users of GitLab.com would see slow API, Web and git responses." | **Edited 2026-08-28** (operator: "keep as edited"). New: "- Root cause: Excessive traffic to a specific endpoint from a small number of users." (trimmed — drops the unrelated "See internal note" tail that made this gold genuinely unresolvable) | 0.0526 | included | `operator-selected` | **keep as edited** — now resolves cleanly |
| `#6` GitLab #15999 | L10: "GitLab.com was unavailable on 2023-07-07 from 16:25 UTC to 18:42 UTC..." | L21: "The root cause was an out-of-sync infrastructure configuration plan (Terraform)..." | 0.0000 | included | `operator-selected` | **keep** (ratified 2026-08-28) |
| `#7` AWS S3 | **Added 2026-08-28** (operator: "add, CC proposes both texts"). CC sub-split L4 into clauses. Question (impact clause): "While these subsystems were being restarted, S3 was unable to service requests. Other AWS services in the US-EAST-1 Region that rely on S3 for storage... were also impacted while the S3 APIs were unavailable." | Gold (cause clause, same paragraph, textually *earlier*): "At 9:37AM PST, an authorized S3 team member using an established playbook executed a command which was intended to remove a small number of servers... one of the inputs to the command was entered incorrectly and a larger set of servers was removed than intended." | 0.0167 | included | `operator-selected` (APPROVED as-is) | **add** — FINAL. See the added-pair note above (atypical gold-before-question ordering; gold engram is quarantine-touched) |
| `#13` Twilio | **Edited 2026-08-28** (operator: "edit to first sentence only"). New: "Twilio experienced an incident with its billing system on July 18, 2013." — scoped to chunk 2 only after discovering this sentence also duplicates in the document's title/lede block (chunk 0); see the closing note above. | **Moved 2026-08-28** (operator: "move gold to Redis paragraph"), then **scoped 2026-08-28** (operator: "gold scoped to root-cause chunks: approved") to chunks 23,24 only, excluding the timeline-section duplicate (chunk 7). New: L28, "At 1:35 AM PDT on July 18, a loss of network connectivity caused all billing redis-slaves to simultaneously disconnect from the master..." | 0.0513 | included | `operator-selected` (APPROVED, both sides) | **keep, gold + question both scoped** — FINAL, resolved both sides |

## Gold resolution (Step 1, run 2026-08-28 against `cqr_datadir_primary/`, post-ratification)

Two independent implementations, same result. First pass (`resolve_gold.py`) opened the primary arm via a live, read-only `EngramStore`. Per the operator's follow-up instruction, re-run **offline** (`resolve_gold_v2.py`, `gold_resolution_v2.json`) — direct `msgpack.unpack` on `engrams.msgpack`/`edges.msgpack`, `_progress.jsonl` parsed for the file→document_id mapping, no `EngramStore`/`asyncio`/SDR/LSH/coactivation load at all. **Every field matched exactly between the two runs** (gold/question engram IDs, positions, quarantine intersections) — recorded here as a cross-check, not merely asserted. `documents.msgpack` does not exist in this datadir (confirmed empirically both times, `document_store.no_snapshot` on the live-store load) — no `DocumentStore`-level snapshot was ever written by this arc's scripts, only the ingest's own progress log, which is why `_progress.jsonl` carries the file→document_id mapping instead.

Method per R2: exact normalized-substring match (whitespace-collapsed, lowercased) against the primary arm's engram texts, restricted to the same document (`metadata.document_id`). Two match directions tried: passage contains one whole chunk, or a passage spanning >1 chunk with ≥2 chunks individually matching (to avoid a spurious single-chunk coincidental hit). `gold_position`: `before`/`after`/`overlapping` (gold and question share ≥1 engram) / `same-chunk-index`, by comparing `min(chunk_index)` of each resolved set. `quarantine_intersects_*`: intersection against the 100 distinct quarantined engram UIDs (`checkpoint_during_ingest.md` Item 7 — not the manifest's raw 385-record count). `selection_source` carried through unchanged from `cqr_probe_candidates_v1.json` (§ below).

**Superseded by the operator's 2026-08-28 adjudication** (verbatim: "#01 keep | #02 keep, CC proposes question text | #03 keep, CC proposes question text / #04 drop | #05 keep as edited | #06 keep | #07 add, CC proposes both texts / #09 drop | #10 trim, R4 decides | #11 drop | #12 drop | #13 move gold to Redis paragraph"). `#04`, `#09`, `#11`, `#12` are dropped — their earlier resolution numbers above are historical record only, not carried forward. `cqr_probe_candidates_v2.json` is the resulting probe file; `gold_resolution_v3.json` is the re-resolution against it (`apply_adjudication_v2.py`, offline, same method). **8 pairs survive, all 8 resolved on the gold side; 7 of 8 also resolved on the question side (`#13`'s question is unchanged and still unresolved — see below, not edited this round).**

| Doc | Disposition | Gold engrams (chunk idx) | Question engrams (chunk idx) | `gold_position` | Quarantine: gold / question |
|---|---|---|---|---|---|
| `#01` Cloudflare | keep, unchanged | 2 chunks (2,3) | 2 chunks (0,1) | after | 1/2 / 0/2 |
| `#02` GitLab DB outage | keep, question CC-proposed (L4) | 2 chunks (0,1) | 4 chunks (2,3,4,5) | before | 0/2 / 0/4 |
| `#03` GitLab #17057 | keep, question CC-proposed (L14) | 2 chunks (12,13) | 2 chunks (7,8) | after | 0/2 / 0/2 |
| `#05` GitLab #18596 | keep as edited (gold trimmed) | 1 chunk (5) | 1 chunk (4) | after | 0/1 / 0/1 |
| `#06` GitLab #15999 | keep, unchanged | 1 chunk (14) | 2 chunks (5,6) | after | 0/1 / 0/2 |
| `#10` Backstage catalog-core-entities | trimmed, R4 included | 1 chunk (21) | 2 chunks (3,4) | after | 0/1 / 0/2 (the 4 quarantine touches on the old, untrimmed question all fell in the now-excluded subsections) |
| `#07` AWS S3 | **added**, CC sub-split | 1 chunk (2) | 2 chunks (8,9) | **before** (atypical — flagged) | **1/1 (quarantine-touched)** / 0/2 |
| `#13` Twilio | gold moved to Redis paragraph | **3 chunks (7,23,24)** (flagged — see below) | **unresolved** (unchanged, still open) | — | 0/3 / — |

**Two findings from this round's resolution, disclosed rather than smoothed over:**

1. **`#07` (new pair) — gold resolves to a chunk textually *before* the question's chunk, the only "before" among the cause-from-symptom family.** This is a direct consequence of how the pair was constructed: both clauses come from the *same* run-on source paragraph (`build_probe.py` found no clean per-line split for this document), and within that paragraph the root-cause clause ("...one of the inputs to the command was entered incorrectly...") sits textually earlier than the impact clause ("...S3 was unable to service requests..."). Every other surviving pair in this family has the symptom described first and the root cause explained later (the natural incident-report structure) — `#07` inverts that by construction, not by any retrieval-relevant property. Also: the gold engram for `#07` is quarantine-touched (its relation extraction is degraded) — the only added/kept pair besides `#01` and (partially) `#10` with a quarantine-touched gold side specifically. Flagged for awareness, not treated as disqualifying.
2. **`#13` — gold now resolves to *three* chunks, not two, because of a genuine text duplication in the source document, not a chunking artifact.** The sentence "This caused all redis-slaves to reconnect and request full synchronization with the master at the same time." appears **verbatim twice** in the document: once in the early timeline bullet list (a different engram, chunk `abe26fb9…`) and once as the middle sentence of the Redis root-cause paragraph itself (chunk `0a031d7d…`, alongside chunk `d47de9e7…` for the paragraph's third sentence). The substring-match resolver correctly finds all three as literal substrings of the new gold_text — R2's rule as written doesn't distinguish "this text also happens to repeat verbatim elsewhere in the document" from "this chunk is genuinely part of the passage." **Recommended disposition: restrict `gold_engram_ids` to `{0a031d7d…, d47de9e7…}` only** (the two chunks that are actually the Redis root-cause paragraph, per the operator's own "Redis paragraph" framing) and treat `abe26fb9…` (the timeline-section duplicate) as a resolution false-positive, not a third gold chunk — but this is a judgment call about R2's substring rule, not applied unilaterally here. **`#13`'s question is also still unresolved** (unchanged this round, per the operator's instruction only covering the gold) — flagged as a still-open item; Step 3 cannot run for `#13` until either the question is edited or the pair is otherwise adjudicated.

**One correction, disclosed rather than silently substituted:** `#02`'s jaccard against the L4 question candidate recomputes to **0.1311** (included) under the pinned `jaccard()`/`tokenize()` implementation, run against the exact stored candidate text — not the **0.1724** (excluded) figure originally recorded in this document's own first-draft adjudication table for the same L3-vs-L4 pairing. The 0.1724 figure does not reproduce and its origin (a different tokenizer state, a hand-computed estimate, or a transcription difference) was not tracked down; the 0.1311 figure is the one computed here, from the authoritative script, against text pulled directly from `cqr_probe_candidates_v1.json` rather than retyped — trusted as the correct one, with the discrepancy disclosed rather than the earlier number silently dropped.

**`#10`'s quarantine touch resolves itself with the trim** — the 4 quarantined engrams that touched the old, full 18-chunk question all fell inside the now-excluded `### Component`/`### API`/`### Resource` subsections; the trimmed question (lines 14–25 only) touches zero quarantined engrams.

### Bridge reconciliation — 797 → 549, decomposed and independently recounted

`edges_projected: 797` (the ingest-time bridge witness, `run_manifest.json.witnesses_pristine.bridge`, already reported in `checkpoint_during_ingest.md` Item 7) was never expected to equal the final stored `concept_relation` count (549, `condition_c_manifest.json`) directly — confirmed from source (`engram_store.py:4898-4913`) that `edges_projected` increments once per `(source_engram, candidate_target)` write **attempt** inside the bridge's own fanout loop, not once per distinct surviving key. Full decomposition, each term independently recounted from the actual pristine/primary edge censuses (`resolve_gold_v2.py`, offline):

**797 (attempts) = 699 (distinct `concept_relation` keys actually present in the pristine, pre-lift snapshot) + 98 (same-pair collisions within the bridge's own writes).** The 98 is a derived quantity, not a directly-witnessed diff — two relations (or two candidates of the same relation) landing on the identical `(source, target)` key during ingest itself are invisible in any pristine-vs-primary comparison, since only the last writer survives to any snapshot; 98 is recoverable only arithmetically as `797 − 699`, disclosed as such rather than presented as an observed count.

**699 (pristine) − 150 (overwritten by the one `consequence_lift` call) + 0 (unexpected new `concept_relation` in primary not present in pristine) = 549 (final).** All 150 overwritten keys land on `consequence` type (0 land on any other type) — recounted directly, not taken from the earlier collision-witness prose. The 0 "unexpected new" is a structural check, not a coincidence: Item 9's writer census already established the bridge is the *only* code path in the entire source tree that ever writes `custom_edge_type="concept_relation"`, and it is ingest-time-only (never re-triggered by the lift or by anything else), so no new `concept_relation` edge can appear in primary that wasn't already in pristine. `reconciliation_check: true`. Full detail: `ngramdb/scratch/cqr/gold_resolution_v2.json.bridge_reconciliation`.

### Quarantine rate per document

Distinct quarantined engrams / total chunks, all 13 corpus documents (not just the 9 resolvable pairs — `#08` is the distractor-only document, `#07`/`#09`/`#11` are the still-pending-selection docs):

| Doc | Quarantined / total chunks | Rate |
|---|---|---|
| `#01` Cloudflare | 24/121 | 19.8% |
| `#02` GitLab DB outage | 23/153 | 15.0% |
| `#03` GitLab #17057 | 6/49 | 12.2% |
| `#04` GitLab #18490 | 0/10 | 0% |
| `#05` GitLab #18596 | 1/12 | 8.3% |
| `#06` GitLab #15999 | 1/54 | 1.9% |
| `#07` AWS S3 | 7/42 | 16.7% |
| `#08` Ory Hydra (distractor) | 8/27 | 29.6% |
| `#09` Backstage adr002 | 11/73 | 15.1% |
| `#10` Backstage adr005 | 4/24 | 16.7% |
| `#11` Backstage adr009 | 1/16 | 6.3% |
| `#12` Backstage adr011 | 4/23 | 17.4% |
| `#13` Twilio | 10/53 | 18.9% |

Sums to 100/657, matching the distinct-engram total exactly (Item 7). `#04` is the only document with zero quarantine — notable because `#04` is also the pair already flagged above for likely drop on the "question and gold collapse to the same engram" grounds; if dropped, none of the surviving resolved pairs comes from a quarantine-free document, which is useful context for interpreting any eventual retrieval result but decides nothing here.

**Status: CLOSED, 2026-08-28.** Operator approved `#02`/`#03`/`#07`'s CC-proposed texts as-is, `#10` kept (trimmed text as proposed), and `#13`'s question edited to the first-sentence trim. `#13`'s gold scoped to the two root-cause chunks (23,24), excluding the timeline-section duplicate — approved. **Final probe: `cqr_probe_candidates_v3.json`, 8 pairs (`#01, #02, #03, #05, #06, #07, #10, #13`), 4 dropped (`#04, #09, #11, #12`), all 8 resolved on both gold and question sides.** `selection_source` updated to `operator-selected` for `#02/#03/#07/#10/#13`.

**One more duplicate-text finding, surfaced while applying the `#13` question edit, disclosed before being resolved rather than silently folded in:** the approved trim ("Twilio experienced an incident with its billing system on July 18, 2013.") itself duplicates verbatim — once in the document's title/lede block (chunk 0: "Billing Incident Post-Mortem... Time to read:\nTwilio experienced an incident..."), once as the real body-opening sentence (chunk 2). Applying the identical principle just approved for `#13`'s gold (scope to the substantive occurrence, exclude the incidental duplicate), the question is scoped to **chunk 2 only** — not put to a separate confirmation round, since it is a tight, mechanical analogy to the decision already made one line above it, but flagged here explicitly so the operator can override if the analogy doesn't hold. `gold_position` for the finalized `#13` pair: **after** (gold chunks 23–24 follow question chunk 2 — the same "symptom described first, root cause explained later" structure every other surviving pair in this family has, which `#13`'s pre-scoping resolution had obscured).

`#13`'s full mechanical-vs-scoped resolution (both raw substring matches and the operator-approved scoped set) is preserved in `cqr_probe_candidates_v3.json` and `gold_resolution_final.json` for audit — `gold_engram_ids_scoped`/`question_engram_ids_scoped` are the values that carry forward to Step 3, not the raw mechanical matches.

**The probe is closed.** Step 3 (retrieval and scoring) may now proceed on operator go, subject to R9's no-further-tuning rule — no rank exists yet, and nothing above was adjusted after one did.

### Frozen probe table (n, per-family counts)

| | n | Docs |
|---|---|---|
| cause-from-symptom | 7 | `#01, #02, #03, #05, #06, #07, #13` |
| effect-from-action | 1 | `#10` |
| **Total scored** | **8** | |
| Dropped | 4 | `#04, #09, #11, #12` |
| Corpus total (R1) | 13 | (1 distractor-only, `#08`, never eligible) |

`#13`'s gold is frozen at R2c scope: chunks 23–24 only (the Redis root-cause paragraph), excluding the timeline-section duplicate (chunk 7) — operator-approved. This table, `cqr_probe_candidates_v3.json`, and `gold_resolution_v3.json` (plus `#13`'s scoped override) are the frozen inputs to Step 3; no further edits follow.

## v0.3 amendment — condition (c) attribution control (ratified 2026-08-28)

**Status: ratified.** Adds a third, deterministic, paired condition to Step 3 that isolates whether this arc's claim is about *causal-specific* structure or *generic relatedness* topology.

### 1. `cohort_weights` verified — not edge-class capable; condition (c) is offline edge surgery

Checked from source before designing anything: `cohort_weights` (`ActivateTextFullRequest`) is applied post-propagation, `activation[idx] *= cohort_weights.get(engram.persona, 1.0)` (`engram_store.py:4260`, and identically at `:4367` in the other code path) — keyed on **`engram.persona`**, a per-*engram* field, not per-edge and not per-edge-type. It cannot scale "causal classes" at query time. Moot twice over for this corpus: every engram here is ingested with `persona=""` (`run_cqr_ingest_v2.py`'s `store_document(..., persona="", ...)`), so even a persona-based reweighting couldn't differentiate anything in this dataset. **Condition (c) is therefore offline edge surgery on a copy, not a request-body knob**, per the pre-registered fallback.

### 2. Condition (c) built — stored type labels verified first, one unexpected discovery along the way

Verified exact stored type labels on the primary arm's graph (`cqr_datadir_primary`, direct `msgpack.unpack` on `edges.msgpack`) before designing the deletion rule: `entity_coref` (1583), `structural_adjacency` (1105), `consequence` (646), `concept_relation` (549), and **628 edges with `edge_type='consolidated'`, empty metadata** — present already in the *pristine* (pre-lift) snapshot, so purely ingest-time.

**Unexpected finding, disclosed rather than glossed over:** those 628 edges are **not** from `_run_consolidation_cycle` (confirmed never called — `consolidation_cycles=0` in both the log and the manifest). They come from `DocumentStore.store_document()`'s own **within-document schema detection** (`document_store.py:372`, `SchemaDetector.detect_and_create`, fires whenever a document has ≥3 chunks — true for all 13), gated by `NGRAMDB_SCHEMA_DETECTION_ENABLED`, confirmed **default `true`**, and never set to `false` in any script this arc has used. The v0.2 amendment's primary-arm description ("ingest-formed edges: Pass-2 canonicalization + the concept→engram bridge... no consolidation cycle, ever") was accurate about `_run_consolidation_cycle` specifically but did not anticipate this second, independently-gated, always-on-by-default schema mechanism — a scope gap in that amendment's wording, not a defect in the data: these edges are structurally within-document topology (schema hub ↔ constituents), the same category as `entity_coref`/`structural_adjacency`, and the fixed-point check (which covers the *entire* graph, not just the traced collision) already found this graph stable. **Kept in condition (c) as generic relatedness** — deleting them would not isolate anything causal-specific, and doing so was never the point of this control.

**Deletion rule, run** (`build_condition_c.py`): prune every edge whose `metadata.custom_edge_type` is `concept_relation` (the bridge) or `consequence` (the lift) — the two mechanisms this arc's claim is actually about — using the sanctioned `_prune_edge` primitive (`engram_store.py:4603`, keeps matrix/metadata/sidecar/index consistent, not a hand-rolled partial deletion). **Result: 1195 edges pruned (549 `concept_relation` + 646 `consequence`), 4511 → 3316 edges, deterministic** (same input, same rule, same output — no randomness in the deletion itself). Datadir: `ngramdb/scratch/cqr_datadir_condition_c/`.

### 3. Weight-collision semantics, precisely (not overwrite, not max, not sum — a third mechanism)

Traced `_write_edge`'s non-reinforce path (`engram_store.py:4750-4762`) through to `_renorm_node_type` (`engram_store.py:4519-4602`), which the non-reinforce path always calls:

- At the `(source, target)` **key level**: pure **overwrite**. `self._edge_metadata[key] = new_edge` discards whatever was there — no max, no sum, no averaging with the prior occupant. Confirmed identically for both `create_typed_edge` (bridge, lift) and `create_edge` (schema hub edges) — both funnel through the same non-reinforce `_write_edge` call.
- The **numeric weight** is not simply the caller's passed value. Two stages: (a) a per-edge clamp to `w_max` for its effective type (`engram_store.py:4759`); (b) `_renorm_node_type` recomputes an **L1 budget renormalization** across *all* of that source node's outgoing edges of the same effective type, every time any one of them is written — `intended` (the pre-budget, clamped value, stored in `metadata["_w_intended"]` only while actively scaled) times `factor = min(1, B_MAX / sum(intended))`. This is **order-invariant by design** (recomputed from the full intended multiset every time, never incrementally) and confirmed to write the **same** resulting value into both `_edge_metadata[key].weight` (what `edges.msgpack` serializes, what this session has been reading) and `_edge_matrix` (what `activate()` actually propagates on) — the two never diverge.
- **Practical consequence, verified against the data, not just asserted:** because renorm is node+type-scoped and recomputed on every write, a write to *one* edge can change the *surviving* weight of a *different*, untouched-by-name sibling edge on the same node — this is a legitimate mechanism (not a bug) that could explain some of the "5 unexplained" nodes from the tainted-run analysis, though that specific attribution was not re-opened this session (data since discarded, see the earlier note on that).
- **Confirmed: `run_consequence_lift` is the last writer in the v2 sequence, by construction, not by inspection alone.** `run_cqr_ingest_v2.py` never calls `run_consequence_lift`; `lift_and_verify.py` calls it exactly once, strictly after the ingest's own pristine snapshot is saved and archived. No write of any kind happens between the pristine snapshot and the one lift call.

**Extended multi-writer witness, built** (`extended_collision_witness.py`): for all 233 multi-writer pairs (unchanged from the prior count — same pristine/primary comparison, now with intended weights), reports original type/intended/surviving alongside new type/intended/surviving. 120 of the 233 pairs' *original* (pre-lift) weight was already renorm-scaled below its per-edge clamp; 162 of the 233 pairs' *surviving* (post-lift) weight is renorm-scaled. Example: `(e92497dc→a821ab7a)`: `concept_relation` (intended 0.5, surviving 0.5, not scaled) → `consequence` (intended 1.0, surviving 1.0, not scaled) — a clean overwrite with no renorm compression on this particular node. Another: `(7eb41f7f→57f3037b)`: `entity_coref` (intended 0.098, surviving 0.033 — already compressed to 34% of intended) → `consequence` (intended 1.0, surviving 0.455 — compressed to 45%) — both sides renorm-scaled, showing the budget cap binds on this node regardless of which mechanism writes to it. Full detail: `extended_collision_witness_results.json`. **Gold-restricted subset deferred to Step 1** (`gold_engram_ids` doesn't exist yet) — will be a filter over this same table, not a re-measurement.

### 4. Pre-registered attribution reading (fixed before any Step 3 rank exists)

Three conditions run in Step 3, paired, same questions, same scoring: **(a)** primary arm (ingest edges + one lift); **(b)** similarity baseline (unchanged, R6); **(c)** primary arm with `concept_relation`/`consequence` edges pruned (generic relatedness only).

- **Consequence attribution (the strong reading): requires (a) > (c).** If the primary arm's rank advantage survives removing the causal-specific edges, the causal mechanism isn't doing the work — reject this reading.
- **If (a) ≈ (c) *and* (a) > (b): relatedness-topology reading, stated as such, not conflated with consequence retrieval.** The graph beats similarity, but the advantage comes from generic connectivity (any edges, of any type, help), not from the causal/consequence mechanism specifically. This would still be a real finding — it just isn't the claim this arc set out to test — and must be reported in exactly those terms, not folded into a "graph beats similarity" headline that implies the causal mechanism is what did it.
- **(c) cannot modify the R8 tier under any outcome.** R8's SUPPORTED/UNSUPPORTED/INCONCLUSIVE computation is (a) vs (b) only, per the rule already fixed in §2. Condition (c) is an attribution/interpretation layer reported alongside R8, the same non-rescuing relationship the secondary consolidation arm already has to R8 — it explains *why* a tier came out the way it did, it never changes *which* tier is reported.

### Operator ratification: ratified 2026-08-28 — v0.3

Verbatim: "matrix authority for all witnesses and edits; condition (c) = primary minus concept_relation minus consequence via _prune_edge, survivor-weight identity verified bit-exact; attribution reading — consequence attribution requires (a) > (c); (a) ≈ (c) with (a) > (b) is a relatedness-topology finding; (c) cannot modify the R8 tier." Ratified separately from v0.2 per the note below (kept as two decisions, not combined) — both are now applied.

**Post-ratification verification, this session (Check 2 of the three pre-registered checks, ratification-gated):** survivor-weight identity between (a) `cqr_datadir_primary` and (c) `cqr_datadir_condition_c` was swept exhaustively (all 3316 surviving edges, not a sample) — 0 metadata-weight diffs, 0 `_edge_matrix` cell diffs, bit-identical. Paired readback (10 surviving, 10 pruned) confirmed. `_prune_edge` verified from source to never call `_renorm_node_type`, so pruning cannot perturb a sibling edge's weight — matches the empirical result. Full detail: `ngramdb/scratch/cqr/survivor_weight_identity_results.json`, `ngramdb/scratch/cqr/checkpoint_during_ingest.md` Item 8. No rebuild required.

### Changelog (v0.3)

- 2026-08-27: `cohort_weights` confirmed persona-based, not edge-class-capable (moot for this corpus regardless, uniform empty persona) — condition (c) built as offline edge surgery, deterministic, 1195 edges pruned (concept_relation + consequence), 4511→3316. Discovered and disclosed a scope gap in v0.2's primary-arm description: `NGRAMDB_SCHEMA_DETECTION_ENABLED` (default true, never set false) creates within-document schema/consolidated edges independent of `_run_consolidation_cycle` — 628 such edges exist in the "no consolidation ever" graph; kept in condition (c) as generic relatedness, not treated as a defect. Weight-collision semantics fully traced: key-level overwrite, then node+type-scoped L1 budget renormalization (order-invariant, matrix and metadata always in sync) — not max, not sum. Confirmed `run_consequence_lift` is the last writer by construction. Extended the multi-writer witness with intended-vs-surviving weights (120/233 pairs renorm-scaled on the original side, 162/233 on the surviving side). Pre-registered the attribution reading: (a)>(c) required for consequence attribution; (a)≈(c) with (a)>(b) is relatedness-topology, stated as such; (c) cannot move the R8 tier. v0.3 ratification line added beside v0.2's, left blank.

## Note carried forward (not a rule — presentation requirement for Step 4)

The findings document's precondition-witness section (§7.4 in the executing prompt's structure) reports doc-1's mentions-per-concept share (**41/141, 29.1%**, from this session's Item 4 analysis) **beside** `edges_projected` in the same table row/paragraph — not as two separately-located numbers. This is a formatting/juxtaposition instruction from the operator, recorded here so it survives to whoever drafts Step 4.

---

## v0.2 amendment — root cause, and primary/secondary graph-arm split (ratified 2026-08-28)

**Status: ratified.** Written after running Check A/B against the completed (first) ingest, which found a genuine defect (below) — this amendment is the response, not a pre-emptive design choice.

### Root cause (read-only source trace, confirmed with a concrete example from the actual data)

Check A found 6 doc-1 source nodes with an unchanged neighbor set but a drifted edge weight (e.g. `c7214efe→44c6e068`: weight 0.3→1.0). **Reclassification (this amendment): the one node actually traced (`c7214efe`) is expected recomputation, not a defect** — see below. **Corrected count: 5 unexplained + a separate 3-node "type changed" category, not 6 defects.** Traced to a **storage-model collision, not wall-clock, edge age, cycle count, or randomness** — no code path in `schema_detector.py` or the consolidation cycle (`engram_store.py` `_run_consolidation_cycle`, `~L2890-3380`) reads `datetime.now()`/`time.time()`/`random`/a cycle counter as an input to any *edge weight* decision (the one cycle-count read, `engram_store.py` `~L3083`, `self._consolidation_cycles % self._schema_extraction_frequency`, only gates *whether* schema extraction runs that cycle, never a weight value). The actual mechanism:

- `_edge_metadata` (`engram_store.py:372`) is a plain `dict[tuple[source_id, target_id], Edge]` — **one slot per ordered pair, full stop**, regardless of *why* an edge between those two engrams exists.
- `create_typed_edge` (`engram_store.py:5258`) — used by both `run_consequence_lift` and the schema-detector's hub edges — always calls `_write_edge(..., reinforce=False)` (`engram_store.py:4750-4762`), which does `self._edge_metadata[key] = new_edge`: an **unconditional overwrite**, with no check for "does a differently-sourced edge already occupy this slot." The new weight is clamped to `self._guardrail_w_max.get(etype, ...)` (`engram_store.py:4759`) before being written.
- **Confirmed with the actual pair**: in S1 (doc-1-only), `(c7214efe, 44c6e068)` held a `structural_adjacency` edge (`metadata.custom_edge_type`), weight `0.3`, created at ingest time from SDR-overlap-based auto-adjacency. In S2 (full corpus), the *same* `(source, target)` slot holds a `consequence` edge (`metadata.custom_edge_type`), weight `1.0` (the passed weight `4.0`, per `NGRAMDB_CONSEQUENCE_EDGE_WEIGHT`'s default, clamped down to the guardrail's `w_max`), `metadata.predicate="causes"`. **A causal relation that only exists once the full 13-document corpus is ingested happens to have a cause/effect concept pair that resolves, via `memories_mentioning`, to exactly these two doc-1 engrams — so `run_consequence_lift`'s `cause_engrams × effect_engrams` loop (`engram_store.py:5680-5698`) calls `create_typed_edge` on a slot doc-1's own original ingest had already populated, and silently overwrites it.**
- This is fully deterministic given the relation set at lift time — it happens *because* lift ran once against 8 relations (doc-1-only) and again against 1,052 relations (full corpus), not because of anything time- or order-dependent within a single lift call. Check B (fixed point across an isolated third lift, no new relations introduced) found **zero** diff, confirming a *single* lift against a *stable* relation set is exactly idempotent — the defect is specific to running lift twice against two different-sized relation sets that happen to collide on a shared storage slot.

**Implication:** any pipeline that (a) creates auto/structural edges at ingest time and (b) later writes semantically different edges (consequence, consolidated-schema, or a second lift's causal edges) into the *same* undifferentiated `(source, target)` slot is exposed to this collision whenever the second write's relation/concept set differs from the first's. Two-pass ingestion (this arc's smoke-promotion shortcut) makes the collision very likely; a single clean pass makes it structurally unreachable for the *lift-vs-lift* case (nothing to collide with — lift runs exactly once against the final relation set) but does **not**, on its own, remove the schema-detector's separate hub-edge collision risk, since consolidation's schema extraction and the ingest-time auto-adjacency edges are two more producers writing into the same undifferentiated slot space. This is why the amendment below drops consolidation from the primary arm entirely, rather than trying to patch the collision.

**Which specific consolidation phase changed each of the originally-6 unchanged-neighbor nodes — one explained, five partially unanswerable, disclosed rather than guessed.** `c7214efe→44c6e068` was traced to a specific mechanism (`run_consequence_lift`, confirmed by reading the edge's own `metadata.custom_edge_type="consequence"` in the raw data) and **reclassified as expected recomputation under changed corpus statistics** — the same class of event criterion (ii) already treats as expected when a node's neighbor set changes, extended to cover a slot's *role* changing rather than a brand-new slot appearing, since a real causal relation (existing only once the full corpus is ingested) legitimately claims a pair the smaller corpus's auto-adjacency had placeholder-populated. This node is no longer counted as a defect. The other 5 nodes' specific culprit (consequence_lift vs. the schema-detector's hub edges vs. the second-order correlation detector, below) was not individually traced before the source datadirs (`/tmp/cqr_smoke_datadir2`, `/tmp/cqr_S2_copy`) were deleted per instruction — re-deriving it now would require a fresh ingest reproducing the exact tainted-run conditions, which is not worth doing given the methodology is already being replaced. One of the 5 (`77d84ba0→44c6e068`, same target as the explained case, same "lands exactly on `w_max`=1.0" signature) is *suspected* to be the same mechanism but its metadata was never individually read — disclosed as a suspicion, not folded into the explained bucket without evidence. **Corrected accounting: 1 explained + 5 unexplained + 3 type-changed (a separate category) = the original 6 weight-drift nodes plus the 3 type-change nodes, not "6 defects."**

**Collision mechanism, independently verified (not just traced through one example):** `_edge_metadata` is one slot per ordered `(source_id, target_id)` pair, last-writer-wins on any non-reinforce write (`engram_store.py:4750-4762`) — confirmed already. The mechanism that would prevent this — TD-NGRAM-022's multi-relational edge sidecar, which preserves distinct types per pair instead of overwriting (`_edge_types.setdefault(key, {})[etype] = new_edge`) — is gated by `NGRAMDB_MULTI_RELATIONAL_EDGES_ENABLED`, confirmed **default `false`** (`engram_store.py:529-530`) and **never set to `true` in any script this arc has used** (`run_cqr_ingest.py`, `run_cqr_ingest_v2.py`, `launch_node.py`, the canary, or B2/Q1's own scripts) — the collision-preventing mechanism was inactive throughout, not merely unused by accident.

**Does edge type affect retrieval at all, or only weight? Confirmed from source: only weight.** `activate()`'s full body (`engram_store.py:3575`, the core hop-spreading propagation `/activate/text/full` ultimately calls) contains **zero references to `edge_type`, `custom_edge_type`, or any `EdgeType` enum member** — propagation reads only `self._edge_matrix`, a sparse numeric matrix with exactly one scalar weight per `(source, target)` cell. This is structural, not incidental: the matrix representation itself cannot hold more than one type's contribution per cell, so type-blindness in propagation isn't a missing feature, it's a direct consequence of the storage shape. **Practical implication for the tainted run's defect:** the 3 "type changed" nodes' metadata drift (e.g. `consolidated`→`semantic`) has **no effect on `/activate/text/full`'s retrieval behavior** by itself — only a coincident *weight* change would move a rank. Type does matter at **write time** — `_effective_edge_type(edge_type, metadata)` maps a custom type to a guardrail budget bucket (`W_MAX`/`B_MAX` per effective type), which is exactly the mechanism that clamped the traced collision's weight to `1.0` — but once written, the numeric cell is all propagation ever sees.

**New witness, added to `lift_and_verify.py` for the corrected re-ingest:** pairs written by more than one mechanism (pristine post-ingest snapshot vs. post-lift snapshot, any `(source, target)` key present in both with a different type or weight), reporting original type/weight and surviving type/weight for each. Restricted to gold-touching pairs once `gold_engram_ids` exists (Step 1, post-ingest) — the general (non-gold-restricted) witness is computed as soon as the corrected ingest + lift completes; the gold-restricted view is a filter applied afterward, not a separate measurement. Within-ingest collisions (auto-adjacency vs. the bridge, both write-time-triggered during Pass-2 apply, before any lift) are **not** measured by this witness — no pre-bridge snapshot exists to diff against — and are recorded as theoretically possible from source (same shared slot space) but not directly witnessed.

**Full sweep, "does any [consolidation] phase read wall-clock, edge age, cycle count, or randomness" — answerable from source regardless of the deleted data, and answered exhaustively across every edge-writing phase found, not just schema extraction:**

| Phase | Writes edges? | Wall-clock / edge-age / random / cycle-count as a *weight* input? |
|---|---|---|
| Phase 1-3 (cluster/merge/archive core, `engram_store.py` `~2890-3080`) | No `_write_edge`/`create_edge`/`create_typed_edge` call found in this span | N/A |
| Schema extraction (`schema_detector.py:642`) | Yes — `weight=1.0` hardcoded for every new schema-hub edge | No. Reinforcement decision is a pure SDR-overlap-vs-`reinforcement_threshold` comparison (`schema_detector.py:589`); the cycle-count read (`engram_store.py:3083`, `_consolidation_cycles % _schema_extraction_frequency`) only gates *whether* this phase runs this cycle, never a weight value |
| Second-order/correlation extraction (`consolidation.py:959` `run_second_order_extraction`, Phase 4) | Yes — `create_typed_edge(..., weight=candidate["strength"], ...)` (`consolidation.py:1043`) and `weight=1.0` (`consolidation.py:1053`) | No. `strength = min(1.0, len(cycles) / (min_occurrences * 2))` (`consolidation.py:842`) — a pure function of counted co-occurrences, no time/random component. Gated by the same style of cycle-count scheduling condition (`engram_store.py:3128`, `_consolidation_cycles % _second_order_frequency`) — scheduling only, not a weight input |
| Archive (Phase 5) | No edge writes — deletes/archives low-weight engrams by a fixed threshold comparison | N/A |
| Cross-engram co-activation (Phase 6) | Writes to a separate co-activation table, not `_edge_metadata` | N/A to this collision |
| Hebbian decay (Phase 7) | Not an edge-creation path (decays existing weights on a fixed schedule); `NGRAMDB_DECAY_ENABLED=false` in this arc's config regardless, confirmed inert | N/A |
| `run_consequence_lift` (`engram_store.py:5612`, not itself part of `_run_consolidation_cycle` — a separate call) | Yes — `weight = float(os.environ.get("NGRAMDB_CONSEQUENCE_EDGE_WEIGHT", "4.0"))`, one fixed value for the whole call | No. Single env-var read, same every call |
| `random.shuffle` (`engram_store.py:6250`) | Inside `_run_correlation_cycle` — a **different function**, confirmed **not called anywhere within `_run_consolidation_cycle`'s body** (checked the full `~2890-3420` span for a call site; none found) | Irrelevant to this concern |

**Conclusion, recorded as the condition on the secondary arm:** every edge-writing mechanism found (schema hub edges, second-order correlation edges, consequence lift) computes its weight as a pure, deterministic function of the concept/relation/co-occurrence data present at call time — no wall-clock, edge-age, or randomness anywhere in a weight decision, and the only cycle-count reads are scheduling gates (whether a phase runs this cycle), never weight inputs. **This means a single consolidation cycle run once against a fixed, final corpus is internally reproducible** (would produce the same output if re-run against byte-identical input, the same property Check B already demonstrated for lift alone) — satisfying the literal "is it deterministic" condition the secondary arm requires. **This is a narrower guarantee than "collision-safe," and the two should not be conflated**: determinism means re-running against the *same* state reproduces the *same* output; it says nothing about whether that one run's schema/second-order edges silently overwrite an ingest-time auto-adjacency edge or a lift-created edge at a shared `(source, target)` slot, which is the exact mechanism that produced the primary defect. The secondary arm is therefore permitted under the literal condition asked, but its own edge weights carry the same *residual* collision exposure as the tainted run did — this is disclosed explicitly in the secondary arm's own reporting (below), not treated as fully resolved by the determinism finding.

**Operator ratification: ratified 2026-08-28.** Verbatim: "primary arm = ingest-formed edges + one consequence_lift, no consolidation cycle; corrected arm description includes the 628 store-time schema-detection edges (NGRAMDB_SCHEMA_DETECTION_ENABLED default-true, within-document); secondary consolidation arm conditional on the determinism sweep (passed), cannot rescue the primary." This amendment (root cause, primary/secondary split, and the determinism finding above) is now applied.

### Primary/secondary graph-arm split

**Primary graph arm (used for every R7/R8 number): ingest-formed edges (Pass-2 canonicalization + the concept→engram bridge, both write-time-triggered during ingestion) + exactly one `run_consequence_lift()` call. No consolidation cycle, ever, on the primary arm's datadir.**

Verified against Q1 and Arm C before writing this rule (not asserted from memory): neither `consequence_baseline_run.py` (Q1) nor `b2_three_arm_runner.py` (B2, all three arms) contains any reference to consolidation or `consequence_lift` — Arm C's validated ~5× ceiling and Arm B's bridge measurement were both taken on graphs that **never ran a consolidation cycle and never ran consequence_lift**. "No consolidation cycle" for the primary arm matches this exactly. "+ one `consequence_lift` call" is an **addition beyond** what Arm C/B2 validated — necessary because this arc's claim is specifically about the *consequence* mechanism (cause→effect bridging), not the generic concept-relation bridge alone — but it is not itself unvalidated: Check B already empirically confirmed a single lift against a stable relation set is an exact fixed point (zero diff on re-run), so adding it once, after Pass-2 fully drains and before any consolidation ever touches the graph, does not reintroduce a version of the collision problem — there is nothing else writing into the same slot space at that point.

**Secondary, informational arm: a snapshot copy of the primary arm's completed datadir, with one consolidation cycle run against the copy — reported alongside the primary arm's numbers, purely as color on what consolidation *would* add (schema hubs, cross-document clustering).** This arm is gated: **it is only produced if root-cause tracing shows the consolidation cycle itself is deterministic** (no wall-clock/random/cycle-count dependence in what it writes) — confirmed true by the full sweep above (every edge-writing phase's weight is a pure function of the data present, no time/random component; cycle-count reads are scheduling gates only), so the secondary arm is permitted to be produced. **Determinism is not the same guarantee as collision-safety** (see the sweep's conclusion) — the secondary arm's own edges carry the same residual storage-slot-collision exposure the primary defect was caused by, since consolidation's schema/second-order edges write into the same undifferentiated `(source, target)` slot space as the primary arm's own ingest/lift edges. This residual risk is why the secondary arm is capped at *informational* regardless of the determinism finding, not elevated to a second measured arm. **The secondary arm can never rescue the primary arm under R8** — if the primary arm is UNSUPPORTED, the thesis closure statement is reported as such regardless of what the secondary arm shows; the secondary is reported in "what this does not establish" / a dedicated informational subsection, never blended into the pooled R7 numbers or the R8 tier computation.

### Changelog

- 2026-08-27: partial pre-registration created — ingest-integrity Check A/B (replacing the earlier single-criterion version), `gold_position` covariate, R9b activation-read-safety confirmation and pinned request body. R1–R9 verbatim, corpus/question construction, and the adjudication table to be added at Step 1. Written before this session read the just-completed Step 2 ingest's `run_manifest.json` or any post-ingest edge census.
- 2026-08-27 (v0.2 amendment, pending ratification): root cause of Check A's defect identified and confirmed with a concrete example (storage-slot collision between ingest-time auto-adjacency edges and later consequence_lift/schema edges, not wall-clock/random/cycle-count). Full sweep of every edge-writing consolidation phase (schema extraction, second-order correlation, consequence_lift) found no wall-clock/edge-age/random/cycle-count dependence in any weight decision — cycle-count is a scheduling gate only. Primary/secondary graph-arm split adopted: primary = ingest edges + one lift, no consolidation ever; secondary = one consolidation cycle on a copy, informational only, cannot rescue primary under R8, and explicitly disclosed to carry the same residual collision exposure despite being internally deterministic. Verified against Q1/Arm C's actual scripts before writing the rule. Ratification line added, left blank. Which of the 5 untraced (of 6) unchanged-neighbor nodes came from which specific mechanism is disclosed as unanswerable post-hoc (source datadirs deleted before this granularity was asked).
- 2026-08-27: `build_probe.py` run against the 13 corpus documents (R1-R5 mechanical pass). Structural finding: only 2 of 13 documents (`#10`, `#12`) have clean Decision/Consequences headings; the other 11 needed line-level candidate emission (no invented boundaries) plus this session's manual best-effort pair suggestions for the draft adjudication table above. `gold_engram_ids`/`gold_position` left null throughout, pending ingest completion. Two documents (`#9`, `#11`) recommended for drop (no Decision/Consequences structure exists); one (`#7` AWS S3) recommended for drop or manual sub-line splitting (its rendering flattens cause and impact into the same lines); one (`#2` GitLab DB outage) has a jaccard-failing primary candidate and a weaker-fit fallback, both presented for operator choice.
- 2026-08-27: reclassified the one lift-traced node as expected recomputation (not a defect) — corrected count 5 unexplained + 3 type-changed, not 6. Verified the collision mechanism independently (TD-NGRAM-022 sidecar confirmed default-off, never enabled) and confirmed from source that `activate()`'s propagation is type-blind (weight-matrix-only) — type changes alone cannot move a retrieval rank. Added the pairs-written-by->1-mechanism/surviving-type witness to `lift_and_verify.py`. Added `selection_source` to every candidate/pair. Made the ratify-then-adjudicate sequencing explicit.
- 2026-08-27: **Step 2 complete on the corrected methodology.** `run_cqr_ingest_v2.py` finished cleanly (13/13, 0 drops, 0 consolidation cycles, 1050 relations). `lift_and_verify.py` ran the one lift and the fixed-point re-verification: **zero diff, `PRIMARY_ARM_VALID_FOR_CLOSURE_R: true`.** Collision witness found 233 multi-mechanism pairs (bridge/auto-adjacency/entity-coref, then the lift) — disclosed as real and recurring but not a defect, since it's a single stable pipeline (not a two-run comparison) and propagation is provably type-blind. Ready for Step 1 gold resolution.
- 2026-08-27/28: **Three pre-registered checks run** (`ngramdb/scratch/cqr/checkpoint_during_ingest.md` Items 7–9) before ratification, as instructed: (1) v2 acceptance record — fixed point zero-diff confirmed, bridge witness 1021/797 (the authoritative ingest-time value, not `lift_and_verify_results.json`'s own 0/0 fresh-process artifact), lift 378 iterations → 646 stored `consequence` edges (reconciled: 413 new + 233 collision-overwrites = 646), mentions-per-concept 177/552 (32.1%, full corpus), drops=0, quarantine 385 records / **100 distinct engrams** (the manifest field name is misleading — it holds records, not distinct UIDs). (2) Survivor-weight identity, (c) vs (a): full sweep of all 3316 surviving edges, 0 diffs, bit-identical, confirmed against `_prune_edge`'s source-verified no-renorm behavior. (3) Writer-tagged census: both pruned types exhaustively single-writer by source (not just by label) — `consequence` only from `run_consequence_lift` (one call site, `engram_store.py:5691`), `concept_relation` only from the bridge (one call site, `engram_store.py:4905`); no ingest-time consequence writer exists anywhere in the codebase. **v0.2 and v0.3 ratified 2026-08-28** (verbatim operator text recorded at each amendment's ratification line).
- 2026-08-28: **Gold resolution run** (`resolve_gold.py`) against the ratified primary arm — new section above. 7/9 resolvable pairs resolved cleanly; 1 gold-unresolved (`#05`), 1 resolved-gold-only (`#13`); three construction findings surfaced and flagged for operator disposition (`#04` question/gold collapse to the same engram — recommend drop; `#05` gold_text fuses in an unrelated trailing fragment — recommend edit-gold; `#13` question_text fuses two segmenter-split sentences — recommend edit-question); one scope question on `#10`'s 18-chunk mechanical Decision extraction, flagged not fixed. Quarantine-confounding disclosed on 4/9 pairs. Stopped for operator adjudication — no retrieval, no ranks.
- 2026-08-28: **Re-run offline** (`resolve_gold_v2.py`, direct `msgpack.unpack`, no `EngramStore`/asyncio) per the operator's instruction — every field matched the live-store run exactly, recorded as a cross-check. Added `selection_source` to the resolution table. Bridge reconciliation computed and independently verified: `797 = 699 + 98` (699 distinct pristine `concept_relation` keys + 98 same-pair collisions within the bridge's own writes, arithmetic-only); `699 − 150 + 0 = 549` (150 overwritten by the one lift, all to `consequence` type; 0 unexpected new, structurally required by Item 9's writer census) — `reconciliation_check: true`. Quarantine rate computed per document (0%–29.6%, `#04` the only zero-quarantine document among the resolved pairs). Both new items folded into `checkpoint_during_ingest.md` Item 7 and this document. Still stopped for operator adjudication — no retrieval, no ranks.
- 2026-08-28: **Operator adjudication applied** (`apply_adjudication_v2.py` → `cqr_probe_candidates_v2.json`, re-resolved offline → `gold_resolution_v3.json`). `#04/#09/#11/#12` dropped. `#01/#06` kept unchanged. `#05` kept as edited (gold trimmed) — now resolves cleanly. `#02` (question → L4, exact text pulled from the stored candidate, not retyped) and `#03` (question → L14) got CC-proposed replacement question text per the operator's instruction; both pass R4 and resolve cleanly. `#10` trimmed to lines 14–25 of the Decision section per "trim, R4 decides" — jaccard recomputes to 0.1316, included; the trim also incidentally clears the pair's earlier quarantine touch (the 4 touched engrams were all in the now-excluded subsections). `#07` added via a CC-constructed sub-split of its one run-on paragraph into cause/impact clauses — resolves cleanly but flagged for an atypical gold-before-question ordering (an artifact of the sub-split, not a retrieval property) and a quarantine-touched gold engram. `#13` gold moved to the Redis root-cause paragraph per instruction — resolved to 3 chunks, not 2, because one sentence in that paragraph duplicates verbatim elsewhere in the document (a genuine text-duplication finding, not a chunking artifact); flagged for a disposition on whether to exclude the duplicate chunk. `#13`'s question was left unchanged (operator did not direct an edit this round) and remains unresolved — this pair cannot reach Step 3 until addressed. Disclosed a jaccard correction for `#02`: 0.1311 (included) recomputes under the pinned script against the exact stored text, not the 0.1724 (excluded) figure this document originally recorded for the same L3-vs-L4 pairing — the discrepancy's origin was not chased down, but the freshly-computed, script-verified figure is the one trusted going forward. Still stopped: three open items (final operator read-through of all CC-constructed text, `#13`'s duplicate-chunk judgment call, `#13`'s question) before this probe is closed — no retrieval, no ranks.
- 2026-08-28: **Probe CLOSED.** Operator approved `#02`/`#03`/`#07`'s CC-proposed text as-is, `#10` kept (trimmed), and `#13`'s gold scoped to the root-cause chunks (23,24), excluding the timeline duplicate. `#13`'s question edited to the first-sentence trim (operator: "edit to first sentence only") — applying it surfaced a second duplicate-text case (the trimmed sentence also appears in the document's title/lede block, chunk 0, not just the real body-opening sentence, chunk 2); scoped to chunk 2 only by the same substantive-occurrence principle just approved for gold, disclosed rather than separately re-confirmed since it is a tight mechanical analogy to the decision one line above it. `finalize_probe_v3.py` → `cqr_probe_candidates_v3.json` (final probe, 8 pairs: `#01, #02, #03, #05, #06, #07, #10, #13`; 4 dropped: `#04, #09, #11, #12`) and `gold_resolution_final.json`. All 8 pairs resolved on both gold and question sides. `selection_source` updated to `operator-selected` throughout. Step 3 (retrieval and scoring) may now proceed on operator go.
- 2026-08-28: **Probe table frozen** (n=8: 7 cause-from-symptom, 1 effect-from-action; 4 dropped) — see the frozen table above. **Secondary consolidation arm built** (`build_secondary_arm.py`): pristine → one consolidation cycle (0 clusters/merges/archives, `schemas_created:1`, engrams 657→658) → one lift (identical stats to the primary arm's lift). Zero LLM calls, asserted by full-log grep, not assumed (`checkpoint_during_ingest.md` Item 10). **Step 3 run** (`run_probe.py`): all four arms (a/b/c/secondary) plus `/activation/from_query` informational, pinned body per R9b with `max_results` omitted (R9b's own anticipated widening, triggered because N_engrams=657>60) — determinism double-pass **100% bit-identical, zero mismatches, every arm, every question**. Two bugs caught before scoring: `gold_engram_ids`, `gold_position`, and `quarantine_intersects_*` were never written back into the probe pairs by the adjudication scripts (only `#13` had a few fields patched directly) — both `run_probe.py` and `score_probe.py` were corrected to read the authoritative values from `gold_resolution_v3.json` (with `#13` using its final scoped/verified overrides) instead of trusting the probe file's stale nulls; ranking itself was unaffected since `run_probe.py`'s gold-ID fix was caught before any rank was computed, but the metadata bug (position/quarantine) was caught only in scoring and fixed there without re-running retrieval. **Scored** (`score_probe.py`): precondition witness passes (edges_projected=797>0, 6/8 gold sets causal-edge-touched — closure (E) does not trigger). **R8 TIER: UNSUPPORTED** — median(a)=77 vs median(b)=28, graph wins 1/8 (12.5%, both conditions of the UNSUPPORTED rule independently satisfied). v0.3 attribution: (a) does not beat (c) on median (77 vs 74) — relatedness-topology reading, moot regardless since R8 is already UNSUPPORTED. Sensitivity check excluding the two near-R4-boundary pairs (`#02` jaccard 0.1311, `#10` jaccard 0.1316, both near the 0.15 cutoff) confirms the tier is not an artifact of those two: n=6, graph wins 1/6, median 73 vs 43. **Decoy finding (R5):** in all 8/8 questions under both (a) and (b), the best-ranked same-document non-gold engram outranks the actual gold engram — neither mechanism isolates the specific causal antecedent over generic same-document proximity in this test. `/activation/from_query`: 0/8, gold never retrieved at all (informational). Closure statement per §7: **"the reasoning-surface claim is unsupported and the retrieval thesis is closed."** Full detail: `cqr_results_v1.json`, `cqr_scoring_v1.json`. Findings doc and STOP 2 follow.
- 2026-08-28: **Close-out bookkeeping** (post-STOP-2, ledger and markdown only, no retrieval, no scratch/datadir mutation): fork ledger F-035 closed with the tier and findings path verbatim (`fork_ledger.py close`, `check` clean); F-015 annotated with a mechanism-inheritance note via `fork_ledger.py park` (status unchanged, OPEN — `park` sets status to `OPEN` unconditionally, a true no-op here) rather than closed, since P4's own strategic disposition is explicitly left to the operator; production readiness ledger row 24 added (`MEASURED-INSUFFICIENT; RETRIEVAL THESIS CLOSED`), row 23 left unedited per the ledger's row-14a/14b convention. Commit package built for operator review; nothing staged, nothing committed by this session.
