enhance(#3717): make the edge probe's shape cues language-aware via an optional text_en field (#4156)

* test(#3717): add failing-first coverage for text_en language-aware classification

Adds unit tests for the not-yet-implemented text_en field on Requirement
(fallback selection, empty/whitespace/non-string rejection, shapes-override
precedence), a SHAPE_CUES/VALID_SHAPES parity guard (RULESET.GENERATIVE-FIX),
and workflow-prose contract tests asserting spec-phase.md Step 5.5 documents
populating text_en for response_language projects. All new tests are RED
until src/edge-probe.cts and the workflow docs are updated.

* feat(#3717): make edge-probe shape classification read an optional text_en field

Requirement gains an optional text_en; classifyShape's own signature stays
untouched (a locked, directly-tested export), and the text_en ?? text
selection is pushed to proposeEdges' single call site instead. text_en is
validated fail-closed: an empty or whitespace-only value throws rather than
silently winning the ?? fallback and degrading classification to zero shapes.

This makes the #2773 doc-only translation convention an explicit,
validatable field instead of an invisible instruction, per the approved
Form-1 scope on #3717.

* docs(#3717): document the text_en field across spec-phase, reference and how-to docs

Updates Step 5.5's response_language instructions, the edge-probe reference
Inputs contract, the FEATURES.md fragment, and the non-English how-to guide
to describe the new text_en field: text keeps the requirement's own wording
in all cases, text_en (when populated) is the engine-only English rendering
the classifier prefers.

* docs(#3717): record the text_en locked-surface change in CONTEXT.md and ADR-550

Updates the Edge Probe Module glossary entry to describe the text_en field
and its fail-closed validation, and appends an ADR-550 amendment recording
why this is additive and does not re-open the #652 LLM-classifier rejection
(text_en is a plain field read by the existing deterministic regex
classifier, not a new model-dependent surface).

* docs(#3717): add changeset fragment and regenerate FEATURES.md

pr:0 placeholder — backfilled with the real PR number after the PR opens.

* docs(#3717): attribute the text_en machine check to engine-level validation, not prose tests

Code-review (Spec axis) finding: the workflow-prose contract tests and the
ADR-550 amendment overclaimed themselves as "the machine check the #2773
doc-only stopgap lacked." That check is actually engine-level
(validateRequirement/classifyShape, covered in tests/edge-probe.test.cjs) —
the prose tests are the same style of assertion #2773 already used. Reworded
both to attribute the claim correctly.

* fix(#3717): rewrap spec-phase.md so the id-unchanged sentence stays on one line

The #3717 rewrite of Step 5.5's response_language paragraph moved a line
break so "requirement `id`s" ended one physical line and "are never
translated" started the next. The pre-existing #2773 regression test
(tests/edge-probe-spec-phase-contract.test.cjs) asserts id + "never
translated" on the SAME line (no \n in between, matching git's own
line-oriented prose), so the reflow silently broke it. Rewrapped so the
sentence lands on one line again, verified against every #2773/#3717
regex assertion in that test file.

Emitted-Drift-Ack-Growth: spec-phase.md — #3717 adds text_en documentation to Step 5.5 (response_language paragraph + REQS_JSON heredoc comment); this growth is this PR's own diff, not incidental drift.

* chore(#3717): backfill changeset PR number

pr:0 -> pr:4156 now that the PR exists.

---------

Co-authored-by: sim <sim@local>
This commit is contained in:
Tom Boucher
2026-09-01 21:39:53 -04:00
committed by GitHub
parent 4dfc46bbe7
commit bf4485ada2
11 changed files with 286 additions and 44 deletions

View File

@@ -197,3 +197,42 @@ prohibition-enforcement verify-time seam.)
this addendum adds to 550's decision record; the other two are cross-references to existing
decisions, collected so the probe family's full "alternatives considered" set is readable in
one place.
## Amendment (2026-09-01, #3717): `text_en` field — durable fix for the #2773 language stopgap
**What changed.** `Requirement` (the Edge Probe Module's locked input shape) gains an optional
`text_en: string` field. `classifyShape`'s own signature is unchanged (still `(text: string) =>
Shape[]`, a directly-tested export); the `text_en ?? text` selection happens once, at
`proposeEdges`' single call site. `validateRequirement` fail-closes on a present-but-empty or
whitespace-only `text_en` (an unvalidated `''` would otherwise win `??` silently, since nullish
coalescing does not treat `''` as nullish).
**Why this is a locked-surface change, not a silent one.** `text` previously carried a dual,
undocumented meaning under the #2773 stopgap: the requirement's own text for English projects,
but silently the *translation* for `response_language` projects (spec-phase Step 5.5 wrote the
English rendering into `text`, never the SPEC's own words). `text_en` removes that overload —
`text` always means the requirement's own text, in whatever language the SPEC uses; `text_en`,
when present, is the engine-only English rendering the classifier prefers.
**Not a re-open of the #652 rejection (see the Addendum above, `docs/adr/550-spec-phase-probe-contract.md:172-177`).** That addendum rejects a *standalone, model-dependent* LLM
classifier surface. `text_en` is a plain optional string field read by the existing
deterministic regex classifier — no new model-dependent surface, no change to the taxonomy or
`SHAPE_CUES`'s cue-matching mechanism. The maintainer's approval on #3717 confirmed this reading
explicitly and scoped the change to Form 1 only (the `text_en` field); per-language `SHAPE_CUES`
tables (a form that WAS considered — a `lang` hint + a cue-table-per-language) were evaluated and
declined as an unwarranted maintenance burden for a solo-maintainer project. `en` remains the
only `SHAPE_CUES` vocabulary.
**Acceptance bar carried into `tests/edge-probe.test.cjs` / `tests/edge-probe-spec-phase-contract.test.cjs`:** a `SHAPE_CUES`/`VALID_SHAPES` parity assertion (`RULESET.GENERATIVE-FIX`), a fixture
pair proving a non-English requirement with `text_en` classifies identically to its English
equivalent, boundary coverage for `text_en`'s absence/presence/empty-string cases, and a
workflow-prose contract test — in the same style as the existing #2773 Step 5.5 assertions —
confirming `spec-phase.md` documents populating `text_en` for `response_language` projects.
The actual machine check the #2773 doc-only stopgap lacked is engine-level: `text_en` is now
a real, validated `Requirement` field (`validateRequirement` fail-closes on an empty value)
that `classifyShape`/`proposeEdges` demonstrably prefer over `text` — a property #2773's
prose-only convention had no way to enforce.
**Explicitly out of scope (unchanged from #2773).** The ADR-857 §98 / Decision 7b recall gap — a
requirement carrying no shape cue in *any* language, including English, still classifies to
zero shapes — is untouched. The authored `shapes` override remains the remedy there.