RED TEAM — Architecture Refutation (Architect Alphonso, antithesis)
Author: Architect Alphonso — RED TEAM (dialectic antithesis)
Branch: design/naming-identity-ssot-alignment @ 3.2.0 (read-only; no commit/switch)
Date: 2026-06-16
Mandate: REFUTE the white-team thesis ("3.2.0 built the right SSOT — ExecutionContext/
resolve_action_context/branch_naming; the split-brain is NON-ADOPTION; the fix is THREAD the
context + extend the ratchet, not new construction"). Default to "this is wrong/dangerous"; concede
only where code forces it.
Governance. Directives applied: DIR-001 (Architectural Integrity — a "SSOT" that holds two contradictory answers on one object is NOT an integrity win), DIR-003 (every refutation carries code evidence at
file:line+ a hold/survive verdict), DIR-031 (Context-Aware Design — the coord/primary and action-vs-bulk boundaries are the test; over-threading collapses them), DIR-032 (Conceptual Alignment — I use the white-team's own vocabulary: fragment, substrate, op-composite, deep module, ratchet — and turn it against the design).
Method: I did not trust the white-team prose. Every counter-claim below was grep/sed/git log
verified against the code on this branch. Where the white team already conceded a point in a footnote,
I promote it to a headline and show it is larger than they scoped it.
Executive antithesis (the one-screen counter-case)
The thesis says: the right object exists, it is "partly enforced," and the work is adoption + freeze.
The code says something sharper and more damning: the fragment-bearing ExecutionContext is a
built-and-parked abstraction that has never been adopted by a single consumer on its identity, branch,
status, workspace, or prompt-source axis (exactly two fragment reads exist in the entire tree,
both of the same artifact_placement fragment — implement.py:552, agent/mission.py:727), and the
composite is mutable by construction necessity, not by transitional accident — the builder itself
mutates four substrate fields after it has frozen the fragments, so the SSOT object can already hold
two contradictory answers for the same concept (context.branch_name ≠ context.branch_ref. target_branch). That is not "the right SSOT, under-adopted." That is a half-built abstraction whose
internal invariant is already violated by its own builder, married to a correct but unrelated
static seam (branch_naming.mid8()) that is doing all the actual work.
Net verdict: the architecture is FLAWED — not fatal, but the thesis's central architectural claim is
overstated to the point of being misleading as a planning basis. The honest framing is: retire or
demote the fragment composite; double down on the static branch_naming / mid8() seam + ratchet,
which is the part that demonstrably works. Threading the composite is the more expensive, higher-
risk path the white team itself shows is unnecessary for ~6 of the ~12 sites and structurally
impossible for the dashboard.
R1 — ExecutionContext is the WRONG abstraction at the current maturity: a mutable composite whose own builder violates its single-derivation invariant
| Claim attacked | "The intended SSOT is a compute-once-thread-through Context value object … with the single-derivation rule written into the code as the contract and partly enforced by invariant" (design-verdict §1b, §5). |
| The counter-case | The composite is @dataclass (mutable) — context.py:184 — while its fragments are @dataclass(frozen=True). This is not a cosmetic transitional state. The builder mutates four substrate fields after construction: resolution.py:793-801 sets context.wp_id, context.lane, context.branch_name, context.workspace_path on the already-built object, and does NOT rebuild any fragment to match. BranchRefFragment is built at resolution.py:730 carrying target_branch only (context.py:128) — before the WP-level branch_name (the lane branch) is even known. Result: on any WP-scoped action, context.branch_name (substrate, = lane branch e.g. kitty/mission-foo-…-lane-b) and context.branch_ref.target_branch (fragment, = mission branch) are two different strings on the same "single source of truth" object. A consumer that "adopts the fragment" and one that reads the substrate get different answers — the exact split-brain the design claims to abolish, relocated inside the SSOT. |
| Code evidence | context.py:184 (@dataclass, not frozen) vs :66/:84/:117/… (frozen fragments). resolution.py:793-801 (post-construction mutation, no fragment rebuild). context.py:128 (BranchRefFragment.target_branch) vs context.py:212 (ExecutionContext.branch_name). Grep: no fragment reassignment after the mutation block; no test asserting fragment↔substrate consistency (grep branch_ref tests/ → only unrelated hits). |
| Hold or survive? | HOLDS, and it is worse than the white team's footnote. The design-verdict's caveat #1 ("immutability unfinished") frames this as "finish the freeze." But you cannot simply freeze this object — the builder's two-phase construction (resolve mission → build fragments → resolve WP → mutate substrate) requires mutability or a full redesign of the builder. Freezing forces either (a) a builder rewrite to single-phase, or (b) fragment rebuild after WP resolution. Neither is "adoption"; both are construction. The thesis mislabels a redesign as a finishing touch. |
| Severity | HIGH. A SSOT that can hold two answers is an anti-SSOT (DIR-001). Threading it before the freeze (which the squad's WP shape does — adopt now, freeze "begun by the strangler") spreads a mutable, internally-inconsistent dependency across consumers. Sequencing is backwards. |
R2 — Threading is NOT better than re-deriving here: the composite is built-and-parked (2 reads, 0 on the contested axes); the static mid8() primitive is the part that actually works
| Claim attacked | "The fix is adoption … the residual split-brain is non-adoption of that Context by consumers" (design-verdict §2, final §6); "thread the existing Context everywhere." |
| The counter-case | Steelman re-derivation and the evidence vindicates it. Across the entire codebase there are exactly two reads of any fragment off a resolved context — context.artifact_placement at implement.py:552 and agent/mission.py:727 — both the same fragment. There are zero reads of .identity, .branch_ref, .status_surface, .workspace, or .prompt_source anywhere (grep -E '(ctx\|context\|_ctx)\.(identity\|branch_ref\|…)' → nothing but those two). git log -S "context.identity" over src/specify_cli returns zero commits: the identity fragment has never been consumed since it was added (c5a10ce56). Meanwhile the thing that does work is the static, stateless branch_naming.mid8() (branch_naming.py:139) — a pure mission_id[:8], trivially testable, reusable from bulk and action contexts, honoured by the ratchet. The white team's own implementer (pedro §3) concedes 6 of ~12 sites cannot thread and must use the static seam, and that even the 2 threadable sites are an optional improvement over the static fix. So the "non-adoption" is largely consumers correctly choosing the cheaper, context-free primitive over a composite that is action-scoped, mutable, and raises on legacy data. |
| Code evidence | Fragment reads: implement.py:552, agent/mission.py:727 (only two; same fragment). git log -S "context.identity" -- src/specify_cli → empty. Bare mission_id[:8] at aggregate.py:250, doctor.py:3070/3162, sparse_checkout.py:286, dashboard/scanner.py:438, implement.py:386, mission_resolver.py:163, apply.py:745/831, retrospective_terminus.py:69 — ~12 sites, most holding no context. branch_naming.mid8() at branch_naming.py:139. |
| Hold or survive? | HOLDS as a re-prioritization, partially survives as "both can coexist." The design SURVIVES on one narrow point: at the 2 action-scoped sites that already hold a context (implement.py:386, agent/mission.py:772), consuming a fragment would delete a redundant meta.json re-read — a real, if small, win (pedro §4). But the thesis's headline — "thread the Context everywhere; the disease is non-adoption" — is refuted: the dominant correct fix is the static mid8() routing + ratchet (≥6 sites, the dashboard, and arguably the 2 threadable ones too), and the composite has earned its coldness. The white team buries this in pedro's "~5% adopted / superset-by-completion" framing; the architecture-level read is: the composite is not load-bearing; the static seam is. Lead with the seam. |
| Severity | MEDIUM-HIGH. Mis-prioritizing toward composite-threading inflates scope, raises risk (R1, R4), and gold-plates an abstraction with no demonstrated consumer demand. |
R3 — The deferral is BACKWARDS: fixing the read side while the WRITE side (#1878) authors identity leaves the authoring split-brain intact; the read side will re-diverge
| Claim attacked | "The deep coord/primary write-side work (#1878) is the next increment, deliberately deferred so this slice stays small" (OVERVIEW §6; design-verdict treats read-side adoption as the in-scope win). |
| The counter-case | Identity is authored on the write side — branch creation, worktree composition, coordination-branch durability, meta.json minting. The read-side fragments (IdentityFragment, BranchRefFragment, StatusSurfaceFragment) are projections of whatever the write side persisted. If the write side still composes names/branches via scattered, un-routed logic (#1878 — "WRITE side scattered," OVERVIEW §2 row D), then the read-side SSOT is projecting from an un-consolidated source: you can thread IdentityFragment.mid8 into every reader and the split-brain re-enters the moment a write path mints a <slug>-<mid8>-<mid8> (the #1949 double-append class) or an NNN--stripped name never created on disk (#1589). The historical failures the white team cites as motivation (#1949, #1978, #1718 — OVERVIEW §4) are all write/compose-side or topology-entry bugs, not read-projection bugs. Fixing reads first treats the symptom. The correct strangler order is author once (write SSOT) → project (read SSOT) → thread; the squad inverts it. |
| Code evidence | OVERVIEW §2 row #1878: "READ side largely consolidated; WRITE side scattered." OVERVIEW §4 failure table: #1949 (mission_branch_name double-append), #1978 (preflight strip), #1589 (NNN--strip drift) are all compose/write defects. resolution.py:730 builds branch_ref from get_feature_target_branch (a read of persisted state) — garbage-in if the write side authored a drifted branch. |
| Hold or survive? | HOLDS for the deep claim; SURVIVES for the bounded slice. It survives narrowly: the static branch_naming compose helpers (mission_dir_name, worktree_dir_name) ARE write-side and the slice routes the 3 inline composes through them (#2000) — so a sliver of write-side authoring is consolidated. But the umbrella authoring split (#1878 — is_committed HEAD checks, lifecycle emission, ref-advance) is deferred, and that is where identity durability lives. The architecture-level error: calling the read-side adoption "the fix" when the authority is authored elsewhere. The read SSOT cannot be more consistent than the write SSOT feeding it. |
| Severity | MEDIUM. Not fatal — the static compose routing is genuinely in-scope and correct — but the narrative ("read-side adoption finishes the strangler") is architecturally false; #1878 is the keystone, not the follow-on. |
R4 — Bounded-context collapse: resolve_action_context STRUCTURALLY cannot be the universal SSOT — it raises on the exact data (legacy/orphan) the dashboard exists to render
| Claim attacked | "every consumer — CLI and dashboard — accepts the resolved Context" (design-verdict §5 INTENDED); "the dashboard is the textbook specimen … half-threaded" (design-verdict §2). |
| The counter-case | The composite is action-scoped and fail-closed: resolve_action_context "raises ActionContextError on unresolvable context (no silent fallback)" — resolution.py:694-695. The dashboard's build_mission_registry is a bulk enumeration that must render legacy: and orphan: missions which have no mission_id at all (scanner.py:438: mid8 = None if is_pseudo …; _mission_record_key mints legacy:/orphan: pseudo-keys, scanner.py:405-407). Threading the action-scoped context into the dashboard would raise on the first legacy mission — converting a tolerant read model into a hard crash. So the dashboard does not "fail to adopt" an available SSOT; it correctly refuses an SSOT of the wrong cardinality and tolerance. The design-verdict spins this as "half-threaded (surface ✓, identity ✗)" — but pedro's own feasibility note (§2-3, §5 trap 3) demolishes that spin: "the dashboard is the one consumer that genuinely cannot thread the runtime context — for a legitimate architectural reason." The two white-team docs contradict each other, and the implementer is right. Calling the dashboard a "non-adoption specimen" is a DIR-031 boundary violation: it conflates a single-action context with a whole-repo read model. |
| Code evidence | resolution.py:694-695 (raises, no fallback). scanner.py:438 (None if is_pseudo), scanner.py:405-407 (legacy:/orphan: pseudo-keys with no mission_id). Grep: zero uses of resolve_action_context/ExecutionContext/IdentityFragment in src/specify_cli/dashboard/. Pedro §2-3 vs design-verdict §2 — direct contradiction. |
| Hold or survive? | HOLDS decisively. The "universal SSOT consumed by the dashboard too" framing is architecturally false. The dashboard's correct SSOT is the static mid8()/branch_naming seam, not the composite. The composite is — correctly — a single-action context; promoting it to "the consolidated API the dashboard consumes" (design-verdict §3, operator's claim) over-reaches the bounded context. |
| Severity | HIGH for the thesis's universality claim; the bounded design (action context for action callers, static seam for bulk) is sound — but that is my recommended architecture, not the thesis's "thread the Context everywhere." |
R5 — Is it a deep module or a god-object? The composite assembles 6 concerns; "deep module" is asserted, not demonstrated — and the substrate makes it a data-bag
| Claim attacked | "Keep it a deep module … select fragments per op; never expose the fragments as a sprawling public API" (design-verdict §4 guardrail 1); the doc-09 "deep module (small interface), fragments are hidden structure" framing. |
| The counter-case | A deep module (Ousterhout) is a small interface over substantial hidden logic. ExecutionContext is the inverse: a wide interface of ~17 flat substrate fields (context.py:203-220) plus 6 publicly-typed fragment fields (:225-230), all part of the public dataclass surface, with no behavior — the only methods are to_dict() (:241) and the fragments' derive/__post_init__. It is a data-bag with two parallel representations of overlapping data (substrate target_branch/branch_name/feature_dir/mission_slug duplicate fragment fields). The "deep module" claim is contradicted by the object's own shape: the interface is enormous (23 public fields), the hidden logic is nil (it holds; it does not compute — derivation lives in the builder, a separate module). Worse, the substrate is not "byte-identical preserved, fragments attached harmlessly": the substrate is the only thing read (R2), so the fragments are dead weight that every reader must not read to stay correct — the opposite of a small interface. The op-composite "select only the fragments you need" discipline (design-verdict §4) is unenforced — resolve_action_context always attaches all six fragments (resolution.py:739-744); there is no ReadContext/WriteContext selection in code, only in doc-09 aspiration. |
| Code evidence | context.py:203-220 (17 substrate fields) + :225-230 (6 fragment fields) = 23 public fields, no behavior beyond to_dict. resolution.py:739-744 (all six fragments attached unconditionally — no op-composite selection). Substrate fields duplicate fragment data (target_branch at :206 and :128; mission_slug at :204 and :96). |
| Hold or survive? | MOSTLY HOLDS; the design's guardrail survives as advice but is violated by the code. The white team's guardrail ("don't make it a god-object") is correct advice — but the current object already is a wide-interface data-bag with duplicated state and no op-selection, so the guardrail is aspirational, not a description of what exists. The doc-09 op-composite (ReadContext ≠ WriteContext) does not exist in code; there is one monolithic ExecutionContext carrying everything. The thesis cites doc-09 as if implemented; it is not. |
| Severity | MEDIUM. The object works as a substrate bag today; the danger is the thesis's plan to thread it widely, which would cement the god-object the guardrail warns against — while the op-composite that would have prevented it remains unbuilt. |
What SURVIVED my attack (honest dialectic)
I am required to concede where the code forces it. The following white-team claims hold against me:
The static
branch_namingseam is genuinely good and load-bearing.branch_naming.py—mission_branch_name_required(fail-closed),resolve_mid8,worktree_dir_name/worktree_path(emit-don't-guess),parse_mission_slug_from_branch(dual-era), enforced bytest_no_worktree_name_guess.py. This is a real compose+parse SSOT with a working ratchet. My recommendation amplifies this, not refutes it. (Survives R2.)The ratchet-as-completeness-oracle pattern is sound. A shrinking AST allow-list is a legitimate strangler oracle; extending it to bare
_id[:8]closes a real gap (OVERVIEW §"new guards"). No refutation. (Survives all angles.)The bounded-context guardrails (DIR-031) are correctly identified — identity≠path, three C-LANES-1 surfaces, strip-vs-verbatim twins must stay two functions. These are right, and the slice honours them. (Survives R4 partially — the boundaries are right; the thesis just mis-applies the composite across one of them.)
At the 2 action-scoped sites that already hold a context, fragment-consumption beats re-deriving (pedro §4) — deletes a redundant
meta.jsonre-read. A small, real win. (Survives R2 narrowly.)The #2000 / #1993 / #1971 static slice is correct and low-risk — pure compose routing, lanes-dir extraction, project-root collapse. None of this depends on the composite. My refutation strengthens the case for shipping exactly this slice and demoting the composite-threading narrative. (Survives — and is the part I endorse.)
Net architectural verdict
FLAWED (not fatal). The 3.2.0 work split into two unequal halves, and the thesis conflates them:
- The static
branch_naming/mid8()seam + ratchet is sound, working, and the real SSOT. Ship the #2000/#1993/#1971 slice and extend the ratchet. This half survives every attack. - The
ExecutionContextfragment composite is a built-and-parked, internally-inconsistent, mutable- by-necessity abstraction with two consumer reads of one fragment and zero on the contested axes. It is not "the right SSOT, under-adopted"; it is a half-built abstraction whose own builder violates its single-derivation invariant (R1), which the dashboard structurally cannot consume (R4), whose "deep-module / op-composite" pedigree is doc-only (R5), and whose adoption the white team itself shows is unnecessary for most sites (R2).
The thesis's planning error: framing the mission as "thread the existing Context + finish the freeze" treats a redesign (un-mutate the builder, build the op-composites, consolidate the write side that authors identity) as adoption. That under-sizes the work and sequences it backwards (adopt-before-freeze, read-before-write).
Recommended correction: lead with the static seam + ratchet (the proven half); demote the
composite-threading to the 2 action-scoped sites only, gated AFTER the builder is made single-phase /
immutable; and re-sequence #1878 (write-side authoring) ahead of, or alongside, read-side adoption
— because a read SSOT cannot be more consistent than the write SSOT that feeds it. The bounded design
(action-context for action callers; static mid8() for bulk/dashboard) is correct — but it is the
antithesis of "thread the Context everywhere," and the spec should say so.