Skip to content

Commit ec1a35f

Browse files
authored
Merge pull request #464 from AdaWorldAPI/feat/hollow-wire-failure-modes-catalogue
doc(knowledge): hollow-wire failure-modes catalogue — DRAFT-INERT / sealed-but-shadowed / feature-flag mismatch
2 parents 0a56e48 + 5caabb0 commit ec1a35f

1 file changed

Lines changed: 110 additions & 0 deletions

File tree

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
# Hollow-wire failure modes — DRAFT-INERT, sealed-but-shadowed, and the silent default
2+
3+
**READ BY:** integration-lead, truth-architect, code-reviewer, anyone wiring a new emitter / sink / adapter between cognitive-shader-driver and the lance-graph hot path.
4+
5+
**Status:** preventive documentation, generalized from bardioc B0 + B1-step2 brutal-fix rounds.
6+
7+
**Companion to:** `.claude/knowledge/lab-vs-canonical-surface.md` (the discipline rule). This doc names the failure mode the discipline rule prevents.
8+
9+
---
10+
11+
## 1. The pattern in one sentence
12+
13+
A new emitter / sink / adapter is type-correctly defined, mock-tested, and exported, but never plugged into the production hot path — the wiring looks complete; payloads are hollow. The build is green; the system produces nothing.
14+
15+
## 2. Three symptoms
16+
17+
### Symptom A — DRAFT-INERT
18+
19+
The new type has a clear "DRAFT" or "INERT" annotation in its docstring. Maintainers know it's not wired yet. CI passes because the OLD path is still in use. End-to-end fixtures produce default values (empty `Vec`, `None`, `0`) from the new path; the OLD path keeps producing real data via a shadow leg.
20+
21+
Canonical bardioc example: `services/java-ogit/.../AuditWriter.java` declared "DRAFT/INERT" in Javadoc; the entire ClickHouse wire flush path was commented out. Brutal-fix 01 caught it pre-merge. The whole audit-write subsystem looked wired (type-checks, tests against mocks) but no data ever reached ClickHouse.
22+
23+
### Symptom B — sealed-but-shadowed
24+
25+
The new types are exported and importable. Library users reach for them and assume they're hot. Internally, the boot path / `main` loop / supervision tree still wires the OLD stub instance. The new type sits sealed in the module — present, exported, never instantiated where it matters.
26+
27+
Canonical bardioc example: substrate-b returned empty `Vec<Span>` because `cognitive-shader-driver::lab` defined `LanceShaderSink` (fully implemented) but the boot path imported the local stub. `KanbanShaderSink` was fully implemented but nothing imported it. `dual_emitter_with_lance` exists but is never called. Three independent brutal rounds converged on this.
28+
29+
### Symptom C — feature-flag mismatch
30+
31+
A feature flag (`feature = "x-canonical"` or `[features] x = ["dep:foo"]`) ostensibly switches the canonical path on. The factory function is feature-gated. The factory is never called from the boot path — even with the feature on, the OLD path runs. Cargo check passes both with and without the feature; observed behavior is identical.
32+
33+
## 3. The triple-smell that names hollow wire
34+
35+
When all three are true at once, the wire is hollow:
36+
37+
1. `cargo check` passes.
38+
2. End-to-end fixtures produce **silent default values** (empty `Vec`, `None`, `0`, all zeros).
39+
3. `git diff` against any sibling consumer that has a real wire shows zero divergences in the new path.
40+
41+
That triple is the smell. The build is green, the test suite is green, and yet the output is hollow. The CI gate cannot catch this because the new path is structurally consistent; only an integration-fixture that asserts **non-default observable output** can.
42+
43+
## 4. Why it happens
44+
45+
Architecture changes ship in stages: (a) define new types, (b) migrate one site, (c) "complete migration" lands in a follow-up PR that never lands. The follow-up sits in a branch labeled "post-merge cleanup" forever.
46+
47+
Specific cognitive failure modes:
48+
49+
- **Reviewer focuses on the new code.** The reviewer reads the new file, the new traits, the new tests. The call sites that still target the OLD code don't appear in the diff because they didn't change.
50+
- **Tests target the new modules in isolation.** Unit tests pass because they construct the new type directly. Integration tests pass because they pass mocks. The boot path is never test-touched.
51+
- **Agents (Claude Code or human) write the "wire" but not the "boot integration".** The instruction was "add the new emitter" — interpreted as "create the type", not "make it run".
52+
- **Feature flags are aspirational.** The flag promises a switch; the switch is never thrown.
53+
54+
## 5. The detection checklist (bardioc brutal pattern)
55+
56+
For every new emitter / sink / adapter, ask three questions:
57+
58+
1. **Where is `use ...`?** Find every file that imports the new type. If zero, hollow.
59+
2. **Where is the construction site?** Find every `T::new(...)` or `T::default()`. If zero non-test sites, hollow.
60+
3. **Where is the boot-path activation?** Find the path from `main` / `Application::start` / supervision tree to the construction site. If the boot path still constructs the OLD type, hollow.
61+
62+
If any of those three is missing or stuck at OLD, file the case and escalate. For deferred-to-next-round disclosures: track until closed. If still in the same wire after 2 review cycles, escalate from "deferred" to "blocking".
63+
64+
## 6. How to fix it
65+
66+
Six-step recovery, in order:
67+
68+
1. **Name the call sites.** Enumerate every place the OLD path is constructed; produce a TODO list.
69+
2. **Migrate one canonical site first.** Pick the highest-traffic site; flip the construction; assert non-default output via fixture.
70+
3. **Add an integration test that DIES on default output.** If the new path returns empty Vec, the test fails. Not a unit test on the new type; an integration test on the boot path.
71+
4. **Delete the OLD type after the last call site is migrated.** Don't deprecate; delete. Compiler-driven migration prevents stragglers.
72+
5. **Audit feature-flag claims.** Every `feature = "x"` that "enables" something must have a test that proves the feature actually changes observed behavior.
73+
6. **Track to closure.** Add the migration to a tracking doc (e.g., `LATEST_STATE.md` "Pending migrations" section); status moves through `Queued → In progress → Migrated → Verified → OLD-deleted`.
74+
75+
## 7. Relationship to `lab-vs-canonical-surface.md`
76+
77+
The lab-vs-canonical discipline says: the canonical consumer surface is `UnifiedStep` via `OrchestrationBridge`; the REST / gRPC server + per-op Wire DTOs are LAB-ONLY scaffolding. Hollow-wire is the failure mode that occurs when a consumer wires the LAB transport (it compiles, types export, mocks pass) but never plugs the canonical bridge into the hot path. The lab transport is exported, importable, sometimes even mock-tested — and the hot path runs through the old stub.
78+
79+
Hollow-wire is most likely to occur exactly at the canonical-vs-lab boundary, because the lab surface is the easy path to import and the canonical bridge requires deeper integration. The discipline doc says what to do; this doc names what goes wrong when the discipline is violated, so reviewers and consumers have a vocabulary for the failure.
80+
81+
## 8. Bardioc case studies (provenance)
82+
83+
Two large-scale cases prove this is a structural risk, not a series of incidents:
84+
85+
**Case 1 (B0, S1 era):** `services/java-ogit/.../AuditWriter.java` declared DRAFT-INERT in Javadoc; the entire ClickHouse wire flush path was commented out. The audit-write subsystem looked wired (type-checked, mock-tested) but no data ever reached ClickHouse. Provenance: `bardioc/.agent-logs/brutal/b0/01-methodology.log`.
86+
87+
**Case 2 (B1-step2 + post-merge):** substrate-b returned empty `Vec<Span>` because it used a local stub not the `substrate-b-telemetry` path-dep. `KanbanShaderSink` was fully implemented but nothing imported it. `dual_emitter_with_lance` exists but is never called. Three independent brutals converged on this. Provenance: `bardioc/.agent-logs/brutal/b1-step2/{01,02,03}/` + post-merge sweep 03.
88+
89+
Treated as a **structural risk** in `bardioc/.claude/TECH_DEBT.md` provenance section.
90+
91+
## 9. Knowledge Activation
92+
93+
This doc is MANDATORY READ BY: integration-lead, truth-architect, code-reviewer, and any agent wiring a new emitter / sink / adapter between cognitive-shader-driver and lance-graph.
94+
95+
It sits in `.claude/knowledge/` alongside `lab-vs-canonical-surface.md`:
96+
- `lab-vs-canonical-surface.md` is the **discipline** (the rule).
97+
- `hollow-wire-failure-modes.md` is the **failure mode** (what goes wrong when the discipline is violated).
98+
99+
Together they bound the canonical-vs-lab surface: the discipline tells you what to do; this catalogue tells you the shape of the bug that surfaces when you don't.
100+
101+
---
102+
103+
## Cross-references
104+
105+
- `.claude/knowledge/lab-vs-canonical-surface.md` — the discipline this doc complements
106+
- `cognitive-shader-driver/src/lib.rs:46` — the canonical-API moduledoc rule
107+
- `bardioc/.agent-logs/brutal/b0/01-methodology.log` — DRAFT-INERT discovery
108+
- `bardioc/.agent-logs/brutal/b1-step2/{01,02,03}` — sealed-but-shadowed discovery
109+
- `bardioc/.agent-logs/brutal/b1-post-merge/03-hollow-wire-followup.log` — synthesis
110+
- `bardioc/.claude/TECH_DEBT.md` — provenance + "How to read this file" sections

0 commit comments

Comments
 (0)