BOHICA-LABS / BOHICA-LABS/vsdd-factory
process-gap(implementer+architect): impl adds public API not documented in api-surface — reverse of phantom-API-drift (#399)
- Dominant language
- Rust
- Stars
- 2
- Forks
- 1
- Avg merge
- 6h 43m
- Merged PRs (30d)
- 29
Description
## Summary
Companion to #399 (spec cites symbols the code doesn't ship). This is the REVERSE direction — same class of drift, opposite polarity: **implementation grows new public API surface (methods, constants, signals) during a fix-and-remediate loop and never propagates that surface to the api-surface (or equivalent architectural interface doc)**. Adversary passes clean because no test or spec assertion breaks; the drift surfaces only when a later fresh-context pass reads api-surface and the shipped code side-by-side, or when a downstream story tries to consume the undocumented API.
#399 and #327 solve *spec-to-code* citation-integrity. The reverse (code-to-spec API-surface coverage) has no gate.
## Concrete evidence (this session, ftc-blue Wave 3 Batch 1)
STORY-10.10.003 (`archive_lobby.tscn` hub) went through 3 adversary passes plus 3 fix bursts. During Fix Phase 3, the implementer added three new public methods on `ZoneManager` to unbreak encapsulation findings from Pass 2:
- `set_pending_spawn(v: Vector2) -> void`
- `consume_pending_spawn() -> Variant`
- `transition_to_zone(zone_id, spawn_hint)` (stub form pending E-005)
None appeared in `.factory/specs/api-surface.md §6` (ZoneManager). The api-surface changelog still boasts *\"9 public methods\"* — now stale by 2 additions the fix burst itself introduced. Pass 3 adversary flagged 3 IMPORTANT findings (I-1, I-2, I-3) covering:
- New methods absent from `§6`
- `cancel_background_preload` **signature drift**: api-surface declares 0-param, impl accepts 1-param `zone_id`
- Constant value drift: api-surface declares `ZONE_HUB = \"zone_hub_archive_lobby\"`, impl uses `\"archive_lobby\"`
All three surfaced only at Pass 3 (2 fix bursts after the drift was introduced). Adversary Pass 2 CLEAN on all three; convergence would have shipped stale api-surface.
A second cross-story instance in the same batch: STORY-14.14.001 story-writer v1.4 introduced a new anchor citation form (`§FragmentSystem` in AC-003 text + code comment citations) — **without verifying that section exists in api-surface**. Forward-direction anchor-existence check (already covered by #327 for AC-to-BC PC-level anchors) was absent for anchor-to-api-surface section resolution.
## Why existing gates miss it
- **#399** requires the adversary to grep every symbol cited in the *spec* against the *code*. It doesn't require the reverse.
- **#327** requires AC citations to resolve to real BC anchors. It doesn't verify AC citations to api-surface sections.
- **#322** (post-remediation self-check) proposes a diff-scoped binding sweep; that would catch value drift but not \"new public API added, not documented anywhere\" because there's no prior binding to preserve.
- **#287** (9-site BC-version propagation) enumerates propagation for BC/ADR bumps, not for silently-grown public interface surface.
The gap is: fix bursts commonly grow the public surface as a legitimate response to encapsulation findings (\"don't write private field directly; expose a setter\"), but nothing forces the implementer or architect to update the interface doc in the same burst.
## Proposed remediation
Two independent gates (either works; both is better):
### 1. Implementer-side reverse citation sweep (workflow-level, cheap)
Amend the implementer agent prompt with a **bidirectional citation check** on every commit that touches a public symbol:
> For every commit that adds/renames/changes signature of a public method, constant, signal, or exported field on a class documented in api-surface (or the target's equivalent interface doc): verify api-surface § documents the added/changed surface. If not, either (a) update api-surface in the same commit, (b) note the gap in the commit message with an explicit \"NEEDS-SPEC-UPDATE\" tag, or (c) revert the surface expansion in favor of an existing surface. The default choice is (a).
This is the direct dual of L-W3-XX (spec-diff re-read mandate) applied to the reverse direction.
### 2. Deterministic pre-adversary lint (CI-gateable)
Extend the citation-integrity lint proposed in #327 and #399:
- Parse api-surface.md § sections into a `{class → public_symbol_set}` map.
- Parse the corresponding `.gd`/`.rs` file's public interface (public fn/method/constant/signal).
- **Bidirectional set-equality assertion**: public symbols in code == public symbols in api-surface, per class. Fail commit if divergent.
This is orthogonal to #248 (bidirectional save-schema lint) but same shape at a different layer — that one is BC↔ADR-0006; this one is code↔api-surface.
### 3. Story-writer forward citation-existence check (extension of #327)
Story-writer prompt amendment: before writing any AC citation of the form `(traces to § in api-surface)` or embedding `§` in AC text, verify `§` exists in api-surface at the current version. Symmetric to #327's BC-PC verification.
## Relationship to existing issues
- **#399** — spec cites phantom code symbols. This issue: code has phantom symbols not in spec. Same drift class, mirror polarity.
- **#327** — AC citations to BC anchors not resolved. This issue: AC/impl citations to api-surface sections not resolved.
- **#322** — remediation diffs get no targeted re-review. Would catch value drift (#322 Item 1); would not catch API surface expansion (no prior binding).
- **#287** — 9-site BC-version propagation. Doesn't cover code→spec API surface propagation.
- **#248** — bidirectional lint at BC↔ADR-0006 layer. Same pattern, different layer.
## Severity
**MEDIUM-HIGH**. This session:
- STORY-10.10.003: 3 IMPORTANT + 1 HIGH findings at Pass 3 all reduced to \"public API grew in fix bursts without api-surface update.\"
- STORY-14.14.001: 1 HIGH finding at Pass 3 (§FragmentSystem anchor doesn't exist in api-surface) from the forward-direction analogue.
Cost per instance: 1–2 additional adversary passes + a targeted architect burst per story. Compound cost across a wave: ~1 wave-day.
The specific asymmetry that makes this MEDIUM-HIGH rather than LOW: **fix bursts triggered by encapsulation findings systematically grow public API**. This isn't a random one-off; it's a structural pattern of the pipeline itself. Every \"stop writing that private field directly, expose a setter\" adversary finding creates one new public method that lands without api-surface propagation, unless the implementer briefing explicitly requires it.
## Provenance
Wave 3 Batch 1 cycle-001 on ftc-blue (Godot 4.7 project). Pass 3 adversary findings for STORY-10.10.003 (H-1, I-1, I-2, I-3) and STORY-14.14.001 (F-HIGH-P3-01). Not private-project content per #261 — this describes framework-level pattern, not domain content.
Contributor guide
Assessment
This issue has not been assessed yet.