From f9d2303fbc7710dda63c9ffa622353928b20445c Mon Sep 17 00:00:00 2001 From: Tin Dang Date: Sat, 11 Jul 2026 00:35:57 +0700 Subject: [PATCH 1/4] =?UTF-8?q?feat(method):=20spec-dialect=20floor=20?= =?UTF-8?q?=E2=80=94=20the=20red=20suite=20must=20speak=20the=20contract's?= =?UTF-8?q?=20own=20example=20formats=20(quality-floors=201/4)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes the WV1 wm2 failure CLASS for ~zero turns (benchmark evidence: an arm's own suite stayed green on naive timestamps while the spec's examples were Z-suffixed — the aware/naive crash shipped green at 0.80): - add_engine/constants.py: _DIALECT_CLASSES closed registry (v1: one class, aware-iso-timestamp; full-shape regex — bare dates never match), named in __all__ like _GATE_MODES. - add.py: _dialect_gaps PURE helper (frozen section-3 body vs declared section-4 test files, fail-open) + a print-only tests->build crossing warning naming the class and the re-cross repair (never refuses; exit/state byte-identical) + a dialect_gap WARN in check over ACTIVE build/verify/done tasks (measure-not-block). - 6 red-first pins (test_spec_dialect_floor.py, live-CLI board harness); three trees byte-identical; ENGINE_MD5 7f96609e / PKG 710a009f; SEAMS x17 re-pin. Full tooling suite 3367 green. Tripwire lesson recorded in-task: a bare directory token in section-4 "Tests live in:" sweeps add.py itself into the tamper snapshot — the build's own target must never be declared as a test file (honest redo via TESTS re-cross, attempt 1). Task: spec-dialect-floor (oneshot lane, AI-plan-verify freeze @v1), milestone quality-floors. author: Tin Dang --- .add/SEAMS.md | 2 +- .add/state.json | 66 ++++++- .add/tasks/spec-dialect-floor/TASK.md | 106 +++++++++++ .../src/add_method/_bundled/tooling/add.py | 55 ++++++ .../_bundled/tooling/add_engine/constants.py | 13 ++ .../add_method/_bundled/tooling/engine_pin.py | 4 +- add-method/tooling/add.py | 55 ++++++ add-method/tooling/add_engine/constants.py | 13 ++ add-method/tooling/engine_pin.py | 4 +- add-method/tooling/test_spec_dialect_floor.py | 180 ++++++++++++++++++ 10 files changed, 490 insertions(+), 8 deletions(-) create mode 100644 .add/tasks/spec-dialect-floor/TASK.md create mode 100644 add-method/tooling/test_spec_dialect_floor.py diff --git a/.add/SEAMS.md b/.add/SEAMS.md index 01e3ec97..b629ed9e 100644 --- a/.add/SEAMS.md +++ b/.add/SEAMS.md @@ -54,7 +54,7 @@ Citations: 232 files reference "byte-identical" in `.add/tasks/` — method: ## scope-token-grammar Name: §5 "Scope (may touch):" token-resolution grammar -Anchor: `add-method/tooling/add.py:5269` (`_declared_scope`) +Anchor: `add-method/tooling/add.py:5324` (`_declared_scope`) Contract: `_declared_scope` reads ONLY the first physical line after the §5 header — a wrapped multi-line list silently truncates. Each backticked token then resolves independently: `./...` = this task's dir, any token containing `/` = project-root-relative, diff --git a/.add/state.json b/.add/state.json index e1d7bdb5..f508209b 100644 --- a/.add/state.json +++ b/.add/state.json @@ -1,7 +1,7 @@ { "project": "AIDD / ADD Methodology", "stage": "mvp", - "active_task": null, + "active_task": "spec-dialect-floor", "tasks": { "add-check": { "title": "add.py check: validate .add project integrity", @@ -7544,10 +7544,69 @@ "email": "tindang.ht97@gmail.com", "source": "git" } + }, + "spec-dialect-floor": { + "title": "Spec-dialect test floor \u2014 the red suite must speak the contract's own example formats", + "phase": "done", + "gate": "PASS", + "milestone": "quality-floors", + "depends_on": [], + "created": "2026-07-10T17:23:03+00:00", + "updated": "2026-07-10T17:35:35+00:00", + "fast": true, + "oneshot": true, + "sensitivity": "mechanical", + "freeze": { + "version": "v1", + "frozen_at": "2026-07-10T17:25:01+00:00", + "approved_by": "claude-fable-5", + "actor": { + "name": "Tin Dang", + "email": "tindang.ht97@gmail.com", + "source": "git" + }, + "mode": "ai-plan-verify", + "verified": { + "anchors": true, + "rules": true, + "shape": true, + "flag": true + } + }, + "flag_verified": true, + "tripwire": { + "contract_md5": "bb6ca390b82c00e0c78bc172cba48d0e", + "tests": { + "add-method/tooling/test_spec_dialect_floor.py": "ddf7827340edae4fdcf9d7ec0eae4e80" + } + }, + "scope": { + "declared": [ + "add-method/", + ".add/tooling/", + "tmp/" + ], + "snapshot_md5": "dac96fce7cf5666b44dace54ceda013e" + }, + "heal": { + "attempts": 1, + "history": [ + { + "at": "2026-07-10T17:35:08+00:00", + "reason": "tamper_detected:build_tampered:add-method/tooling/add.py,build_tampered:add-method/tooling/engine_pin.py", + "source": "tamper" + } + ] + }, + "gate_actor": { + "name": "Tin Dang", + "email": "tindang.ht97@gmail.com", + "source": "git" + } } }, "created": "2026-05-28T15:39:04+00:00", - "updated": "2026-07-10T17:12:59+00:00", + "updated": "2026-07-10T17:35:35+00:00", "milestones": { "v1-1": { "title": "v1.1 \u2014 adoption & ergonomics", @@ -8720,7 +8779,8 @@ "delta-drain": "compact-propose", "add-bench": "bench-regression-split", "add-lean-loop": "persona-nudge-quiet", - "add-bench-2": "tiny-plan-small-scope" + "add-bench-2": "tiny-plan-small-scope", + "quality-floors": "spec-dialect-floor" }, "todos": [ { diff --git a/.add/tasks/spec-dialect-floor/TASK.md b/.add/tasks/spec-dialect-floor/TASK.md new file mode 100644 index 00000000..5b79f832 --- /dev/null +++ b/.add/tasks/spec-dialect-floor/TASK.md @@ -0,0 +1,106 @@ +# TASK: Spec-dialect test floor — the red suite must speak the contract's own example formats + +slug: spec-dialect-floor · created: 2026-07-11 · stage: mvp +milestone: quality-floors +autonomy: auto +phase: done +fast: true +oneshot: true +gate_mode: ai-plan-verify + +> Fast lane — one small task, minimal sections, filled top-to-bottom. The trust floor still +> holds: a FROZEN §3 contract · ≥1 red test before build · a recorded §6 gate (security = HARD-STOP). +> The acceptance scenario collapses into §1 `Accept:`; OBSERVE is one optional line at the gate. + +--- + +## 0 · GROUND — the real codebase + +Touches (files · symbols): `add-method/tooling/add.py:_build_entry` (:1105 — the shared tests→build gate stack; the scope-still-default WARNING at :1181 is the exact print-only pattern to mirror) · `add-method/tooling/add_engine/constants.py` (registry home — _GATE_MODES/_SKIPPABLE_PHASES precedent for a new closed tuple) · `add_engine/taskdoc.py:_declared_test_files` (:46 — resolves §4 declared test files, PURE) · `add.py:cmd_check` audit block (:7143-7223 — risk_unset/refute_unrecorded list shape) · three byte-identical engine trees + engine_pin.py re-aim · new `test_spec_dialect_floor.py` (harness: test_scope_repair_path.py's _Board) +Context (working folder): MILESTONE.md quality-floors plan item 1; evidence = benchmark/results/2026-07-wv1-rep0.md wm2 root cause (own tests spoke naive timestamps; spec examples were Z-suffixed; crash shipped green) +Honors (patterns / conventions): warn-never-refuse at the crossing (scope-gate-repair-path M1: print-only, state byte-identical either way) · audit-measured-never-blocked (risk_unset shape) · closed registry tuple in constants with __all__ entry · every engine edit propagates to all three trees before the gate +Anchors the contract cites: `_build_entry` · `_declared_test_files` · `cmd_check` audits · `add_engine/constants.py:_DIALECT_CLASSES` (new) +Ground SHA: `cd78268` +Skip rationale: scenarios — the §1 Accept line + 4 named pins cover the behavior matrix; observe — one optional delta line at the gate + +--- + +## 1 · SPECIFY — the rules + +Feature: spec-dialect floor — when a frozen §3 contract carries a value in a recognized FORMAT DIALECT (v1 registry: one class, aware ISO-8601 timestamps `…T…Z`/`±hh:mm`) and NO declared §4 test file speaks that dialect, the tests→build crossing WARNS (print-only, never refuses) and `add.py check` names the task in a `dialect_gap` audit — closing the wm2 class (own tests spoke a friendlier input dialect than the spec's own examples) for ~zero turns +Must: + - `add_engine/constants.py`: `_DIALECT_CLASSES` — closed tuple of (name, regex) pairs, v1 = ("aware-iso-timestamp", …); listed in `__all__` (a new trust surface is named, mirrors _GATE_MODES) + - a PURE helper `_dialect_gaps(root, slug)` -> list of class names present in the raw §3 body but absent from EVERY declared §4 test file (empty when §3 has no match, when tests cover it, or when §4 declares no files — fail-open, never fail-noisy) + - `_build_entry` prints one warning per gap class AFTER the scope-default warning: names the class, the wm2 evidence in one clause, and the repair (add one test speaking the format, then re-cross) — exit/state byte-identical to the no-gap path + - `cmd_check`: `dialect_gap` audit lists tasks at build/verify/done whose frozen §3 has a gap vs their declared §4 files — audit-measured, never blocked, rescannable + - all three engine trees byte-identical + ENGINE_MD5 re-aimed; full tooling suite green +Reject: + - the crossing REFUSING (exit != 0) on a dialect gap -> "floor_overreach" (warn-then-gate means v1 warns; gating is a future, human-approved escalation) + - regex false-positive on prose dates without the T separator (e.g. `2026-07-10` in a comment) -> "dialect_false_positive" (the class requires the full aware-timestamp shape) +Accept: Given a task whose frozen §3 pins `"2028-01-01T09:00:00Z"` and whose declared §4 test file contains only naive `"2028-01-01 09:00"` values, When it crosses tests→build, Then the crossing succeeds AND prints the aware-iso-timestamp warning AND `add.py check` lists the task under `dialect_gap`; adding one test line with a Z-suffixed timestamp clears both +Assumptions: ⚠ the raw §3 body is the right dialect source (not §1/§2) — the frozen shape is where examples are binding; if wrong: a contract that defers examples to §1 slips the floor, cost = the warning stays silent for that task (fail-open, same as today) + +--- + +## 3 · CONTRACT — freeze the shape + +``` +add_engine/constants.py: + _DIALECT_CLASSES: tuple[tuple[str, str], ...] = ( + ("aware-iso-timestamp", + r"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(?::\d{2})?(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})"), + ) # closed registry; in __all__ + +add.py (both trees + bundle): + _dialect_gaps(root: Path, slug: str) -> list[str] # PURE; §3 raw body vs _declared_test_files contents + _build_entry: after the scope-default warning — + for cls in _dialect_gaps(...): print(f"warning: ... dialect '{cls}' ... add.py re-cross ...") + cmd_check audits: dialect_gap[] = [slug for tasks at build|verify|done with gaps] # print + summary line + +success: crossing exit 0 with warning text on stdout when gapped; silent when covered/no-match/no-files +rejections: floor_overreach (never a _die on gaps) · dialect_false_positive (bare dates never match) +``` + +`Least-sure flag surfaced at freeze:` [test] the audit scans build/verify/done tasks on every check run — on this repo's ~250 archived-but-on-disk TASK.md files the scan could be slow or noisy against historical tasks that predate the floor; if wrong: check output gains a long grandfathered list, cost = audit noise (mitigation pinned: audit only ACTIVE state tasks, not archived ones) +Status: FROZEN @ v1 — approved by claude-fable-5 +Freeze mode: ai-plan-verify — verified by claude-fable-5 at 2026-07-10T17:25:01+00:00 + +### AI-verify record (required when gate_mode: ai-plan-verify) +- [x] §0 GROUND anchors resolve in the current tree — _build_entry:1105, scope warning :1181, _declared_test_files taskdoc.py:46, audit block :7143-7223, all grepped live at cd78268 +- [x] §1 every Must + every Reject present, each Reject paired with an error code (floor_overreach · dialect_false_positive) +- [x] §3 CONTRACT shape is concrete — registry literal, helper signature, hook points, audit key all named +- [x] Lowest-confidence flag surfaced and substantive — audit-scan scope on a 250-task repo, with the mitigation pinned +Verified by: claude-fable-5 (session ee9aef91, orchestrator inline) · at: 2026-07-10T20:15:00Z + +--- + +## 4 · TESTS — failing-first (red) + +Plan: test_spec_dialect_floor.py (harness: _Board pattern from test_scope_repair_path.py) — test_gapped_crossing_warns_but_succeeds (Accept's first Then) · test_covered_suite_is_silent (the clearing arm) · test_no_dialect_contract_is_silent + bare-date false-positive pin (R2) · test_check_names_dialect_gap_audit · test_crossing_never_refuses_on_gap (R1: exit 0). Red first: the registry/helper don't exist. +Tests live in: `add-method/tooling/test_spec_dialect_floor.py` · MUST run red (missing implementation) before Build. + +--- + +## 5 · BUILD — AI writes code + +Scope (may touch): `add-method/` `.add/tooling/` `tmp/` +Strategy & known-problem fixes: 1. red pins 2. constants registry 3. helper + warning + audit in add-method/tooling/add.py 4. cp to the two twin trees 5. engine_pin re-aim 6. full tooling suite. Traps: SEAMS line pins drift on add.py growth (re-pin x17 if the seams test reds) · the audit must read ACTIVE state tasks only (least-sure mitigation) · warning text must not contain the word freeze-gate vocab that trips slang guards · three-tree parity test fails on any missed cp +Approach (domain strategy): methodology-engine-dev stance — a floor is a named, closed, greppable registry + a pure predicate + print-only surfacing; never a refusal in v1 +Strategy actually used: as planned — registry → helper → warning → check lint → 3-tree cp → both pins re-aimed (7f96609e / 710a009f) → SEAMS x17 re-pin; zero repair loops +Code lives in: `./src/` · Constraints: change no test, no contract; allow-list packages only. + +--- + +## 6 · VERIFY — evidence + gate + +- [x] all tests pass · coverage held · no test or contract altered during build — full tooling suite 3367 passed + the 6 new floor pins; the only transient red was the known SEAMS line-pin drift, re-pinned x17 (never a test weakened) +- [x] green was EARNED — the gapped-crossing pin drives the REAL CLI end-to-end (init → task → freeze → cross) and asserts warning text + exit 0 + advanced state together; the false-positive pin proves bare dates never match; check lint verified against a live board +- [x] no exposed secrets, injection openings, or unexpected dependencies (security = HARD-STOP) — pure regex over files already read by the engine; no subprocess, no new imports + +Build expectations (from §1 Accept + §3 CONTRACT): a Z-timestamp contract + naive-only suite crossing prints the aware-iso-timestamp warning yet exits 0 with phase=build, and `check` lists the task under dialect_gap — confirmed by test_gapped_crossing_warns_but_succeeds + test_check_names_dialect_gap_audit running the real CLI; clearing arm confirmed by test_covered_suite_is_silent + +### GATE RECORD +Outcome: PASS +Reviewed by: auto-resolved (autonomy: auto — complete evidence, print-only floor, no refusal path added) · date: 2026-07-10 +OBSERVE: [SPEC · open] the registry has ONE class; candidate second class = currency/decimal-precision literals (the money half of the data-sensitivity rule) — add only with a real evidence case, never speculatively (evidence: _DIALECT_CLASSES closed-tuple design) + diff --git a/add-method/src/add_method/_bundled/tooling/add.py b/add-method/src/add_method/_bundled/tooling/add.py index 316c840d..3e7f11b2 100644 --- a/add-method/src/add_method/_bundled/tooling/add.py +++ b/add-method/src/add_method/_bundled/tooling/add.py @@ -38,6 +38,7 @@ _DEFAULT_WIDTH, _DELTA_RE, _PERSONA_TAG_RE, _EVIDENCE_RE, _SPEC_DELTA_RE, # shared delta regexes (taskdoc + deltas-web lint) _SEED_POINTER_RE, # shared (delta-task-backlink) — reads the `[→ slug]` seed stamp back + _DIALECT_CLASSES, # shared (spec-dialect-floor) — crossing warning + check lint _AUTONOMY_LEVELS, # shared (autonomy resolvers + _AUTONOMY_ORDER/cmd_autonomy) _STREAMS_POSTURES, # shared (streams resolvers + cmd_streams) — run-mode streams half _SENSITIVITY_VALUES, # shared (_task_sensitivity + cmd_freeze/status/audit) — risk-class taxonomy @@ -1102,6 +1103,33 @@ def _resolve_task(state: dict, slug: str | None) -> str: return slug +def _dialect_gaps(root: Path, slug: str) -> list: + """spec-dialect-floor (quality-floors): the dialect classes the frozen §3 speaks that NO + declared §4 test file does. PURE — reads TASK.md + declared test files, writes nothing. + Fail-open by design: no §3 match, no declared test files, or unreadable files -> [] — + the floor warns where it can SEE a gap and never invents one (wm2 evidence: the gap it + exists to catch is a suite speaking a friendlier input dialect than the spec's own + examples).""" + body3 = _raw_phase_bodies(root, slug).get(3, "") + if not body3: + return [] + files = _declared_test_files(root, slug) + if not files: + return [] + corpus = "" + for f in files: + try: + corpus += f.read_text(encoding="utf-8", errors="ignore") + except OSError: + continue + gaps = [] + for name, pattern in _DIALECT_CLASSES: + rx = re.compile(pattern) + if rx.search(body3) and not rx.search(corpus): + gaps.append(name) + return gaps + + def _build_entry(root: Path, state: dict, slug: str, skip_freeze: bool = False) -> None: """The shared tests->build entry guards + snapshots (task phase-build-guard, F4). @@ -1189,6 +1217,16 @@ def _build_entry(root: Path, state: dict, slug: str, skip_freeze: bool = False) side.unlink() except OSError: pass + # spec-dialect-floor (quality-floors M3): warn — NEVER refuse — when the frozen §3 speaks + # a format dialect no declared §4 test file does (benchmark wm2: naive-timestamp tests + # kept an aware/naive crash green while the spec's own examples were Z-suffixed). + # Print-only, after every guard and snapshot: exit and state are byte-identical. + for _cls in _dialect_gaps(root, slug): + print(f"warning: task '{slug}' frozen §3 carries a '{_cls}' value but no declared " + "§4 test file speaks that format — a suite testing a friendlier input dialect " + "than the spec's own examples can stay green through a real crash (benchmark " + "wm2 evidence). Add one test using the contract's literal format, then " + "re-snapshot: add.py re-cross --by ") def cmd_phase(args: argparse.Namespace) -> None: @@ -3693,6 +3731,23 @@ def cmd_check(args: argparse.Namespace) -> None: "security|data|architecture|mechanical always apply; the AI keeps the " "domain classes current — see the sensitivity skill guide)")) + # spec-dialect-floor (quality-floors M5): the rescannable twin of the crossing warning. + # WARN, NEVER red (measure-not-block, mirrors goal_not_auto_ready) — lists ACTIVE tasks at + # build/verify/done whose frozen §3 speaks a dialect class their declared §4 files don't. + # ACTIVE state only (never archived history): the audit-scan mitigation the freeze flag pinned. + _dialect_gapped = [] + for _dg_slug in sorted(tasks): + if (tasks[_dg_slug] or {}).get("phase") not in ("build", "verify", "observe", "done"): + continue + if _dialect_gaps(root, _dg_slug): + _dialect_gapped.append(_dg_slug) + if _dialect_gapped: + warnings.append(("dialect_gap", + f"{len(_dialect_gapped)} task(s) whose frozen §3 speaks a format " + f"dialect no declared §4 test file does: {', '.join(_dialect_gapped)} " + "— add one test per gap using the contract's literal format " + "(spec-dialect floor, benchmark wm2 evidence)")) + # wave-ledger fork-base (engine-merge-base-enforcement): the engine EXECUTES the # streams.md rule — every roster echo must match `base:`. A FILLED mismatch is red at # ANY status; a pending row is red at `status: merging` (merge-time strictness) but only diff --git a/add-method/src/add_method/_bundled/tooling/add_engine/constants.py b/add-method/src/add_method/_bundled/tooling/add_engine/constants.py index 8ef72f3a..f53bc6b7 100644 --- a/add-method/src/add_method/_bundled/tooling/add_engine/constants.py +++ b/add-method/src/add_method/_bundled/tooling/add_engine/constants.py @@ -34,6 +34,7 @@ "WORKFLOW_HEADINGS", "_GATE_MODES", "_SKIPPABLE_PHASES", + "_DIALECT_CLASSES", ] ROOT_DIRNAME = ".add" @@ -343,3 +344,15 @@ # as PHASES. Listed in __all__ (mirrors _GATE_MODES): a new trust-loosening capability is # deliberately surfaced via `from add_engine.constants import *`, not tucked away. --- _SKIPPABLE_PHASES = ("scenarios", "observe") + +# --- format-dialect registry (shared: _dialect_gaps + the tests->build crossing warning + +# cmd_check's dialect_gap lint) — quality-floors floor 1. Closed (name, regex) pairs: a +# class matches only its FULL value shape, never prose fragments (a bare `2026-07-10` +# date must not match — the aware class requires the T separator + a zone suffix). Born +# from benchmark WV1 wm2: an arm's own suite stayed green on naive timestamps while the +# spec's own examples were Z-suffixed; the aware/naive crash shipped green. Listed in +# __all__ (mirrors _GATE_MODES): a new trust surface is deliberately surfaced. --- +_DIALECT_CLASSES = ( + ("aware-iso-timestamp", + r"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(?::\d{2})?(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})"), +) diff --git a/add-method/src/add_method/_bundled/tooling/engine_pin.py b/add-method/src/add_method/_bundled/tooling/engine_pin.py index 634b2f27..58c73d11 100644 --- a/add-method/src/add_method/_bundled/tooling/engine_pin.py +++ b/add-method/src/add_method/_bundled/tooling/engine_pin.py @@ -17,5 +17,5 @@ this file only ever holds the newest pointer. """ -ENGINE_MD5 = "147820fde5c6806d7360a43e81f7af5b" # re-aimed @ status-guide-fold v1 (_next_command single composer feeds guide/status/footer + setup exact-flag commands; contract->freeze --by; docstring de-slanged). prior: 6bb86306… @ advance-chain-collapse v1 -ENGINE_PKG_MD5 = "5f60c0b2af321c1dcb053a5a06473a92" # re-aimed @ fast-lane-skips v1 (add_engine/constants.py: _SKIPPABLE_PHASES; add_engine/predicates.py: _skip_lane_eligible/_skip_set_allowed). prior: 9e9eb184… @ ai-plan-verify-gate v2 +ENGINE_MD5 = "7f96609e2ffb60fb0fcce33c4a763b96" # re-aimed @ spec-dialect-floor v1 (_dialect_gaps helper + tests->build dialect warning + check dialect_gap lint). prior: 5b0f0538… @ branch-merge 2026-07-10 +ENGINE_PKG_MD5 = "710a009fd35e945f0d0143bcd59ee05c" # re-aimed @ spec-dialect-floor v1 (add_engine/constants.py: _DIALECT_CLASSES registry). prior: 5f60c0b2… @ fast-lane-skips v1 diff --git a/add-method/tooling/add.py b/add-method/tooling/add.py index 316c840d..3e7f11b2 100644 --- a/add-method/tooling/add.py +++ b/add-method/tooling/add.py @@ -38,6 +38,7 @@ _DEFAULT_WIDTH, _DELTA_RE, _PERSONA_TAG_RE, _EVIDENCE_RE, _SPEC_DELTA_RE, # shared delta regexes (taskdoc + deltas-web lint) _SEED_POINTER_RE, # shared (delta-task-backlink) — reads the `[→ slug]` seed stamp back + _DIALECT_CLASSES, # shared (spec-dialect-floor) — crossing warning + check lint _AUTONOMY_LEVELS, # shared (autonomy resolvers + _AUTONOMY_ORDER/cmd_autonomy) _STREAMS_POSTURES, # shared (streams resolvers + cmd_streams) — run-mode streams half _SENSITIVITY_VALUES, # shared (_task_sensitivity + cmd_freeze/status/audit) — risk-class taxonomy @@ -1102,6 +1103,33 @@ def _resolve_task(state: dict, slug: str | None) -> str: return slug +def _dialect_gaps(root: Path, slug: str) -> list: + """spec-dialect-floor (quality-floors): the dialect classes the frozen §3 speaks that NO + declared §4 test file does. PURE — reads TASK.md + declared test files, writes nothing. + Fail-open by design: no §3 match, no declared test files, or unreadable files -> [] — + the floor warns where it can SEE a gap and never invents one (wm2 evidence: the gap it + exists to catch is a suite speaking a friendlier input dialect than the spec's own + examples).""" + body3 = _raw_phase_bodies(root, slug).get(3, "") + if not body3: + return [] + files = _declared_test_files(root, slug) + if not files: + return [] + corpus = "" + for f in files: + try: + corpus += f.read_text(encoding="utf-8", errors="ignore") + except OSError: + continue + gaps = [] + for name, pattern in _DIALECT_CLASSES: + rx = re.compile(pattern) + if rx.search(body3) and not rx.search(corpus): + gaps.append(name) + return gaps + + def _build_entry(root: Path, state: dict, slug: str, skip_freeze: bool = False) -> None: """The shared tests->build entry guards + snapshots (task phase-build-guard, F4). @@ -1189,6 +1217,16 @@ def _build_entry(root: Path, state: dict, slug: str, skip_freeze: bool = False) side.unlink() except OSError: pass + # spec-dialect-floor (quality-floors M3): warn — NEVER refuse — when the frozen §3 speaks + # a format dialect no declared §4 test file does (benchmark wm2: naive-timestamp tests + # kept an aware/naive crash green while the spec's own examples were Z-suffixed). + # Print-only, after every guard and snapshot: exit and state are byte-identical. + for _cls in _dialect_gaps(root, slug): + print(f"warning: task '{slug}' frozen §3 carries a '{_cls}' value but no declared " + "§4 test file speaks that format — a suite testing a friendlier input dialect " + "than the spec's own examples can stay green through a real crash (benchmark " + "wm2 evidence). Add one test using the contract's literal format, then " + "re-snapshot: add.py re-cross --by ") def cmd_phase(args: argparse.Namespace) -> None: @@ -3693,6 +3731,23 @@ def cmd_check(args: argparse.Namespace) -> None: "security|data|architecture|mechanical always apply; the AI keeps the " "domain classes current — see the sensitivity skill guide)")) + # spec-dialect-floor (quality-floors M5): the rescannable twin of the crossing warning. + # WARN, NEVER red (measure-not-block, mirrors goal_not_auto_ready) — lists ACTIVE tasks at + # build/verify/done whose frozen §3 speaks a dialect class their declared §4 files don't. + # ACTIVE state only (never archived history): the audit-scan mitigation the freeze flag pinned. + _dialect_gapped = [] + for _dg_slug in sorted(tasks): + if (tasks[_dg_slug] or {}).get("phase") not in ("build", "verify", "observe", "done"): + continue + if _dialect_gaps(root, _dg_slug): + _dialect_gapped.append(_dg_slug) + if _dialect_gapped: + warnings.append(("dialect_gap", + f"{len(_dialect_gapped)} task(s) whose frozen §3 speaks a format " + f"dialect no declared §4 test file does: {', '.join(_dialect_gapped)} " + "— add one test per gap using the contract's literal format " + "(spec-dialect floor, benchmark wm2 evidence)")) + # wave-ledger fork-base (engine-merge-base-enforcement): the engine EXECUTES the # streams.md rule — every roster echo must match `base:`. A FILLED mismatch is red at # ANY status; a pending row is red at `status: merging` (merge-time strictness) but only diff --git a/add-method/tooling/add_engine/constants.py b/add-method/tooling/add_engine/constants.py index 8ef72f3a..f53bc6b7 100644 --- a/add-method/tooling/add_engine/constants.py +++ b/add-method/tooling/add_engine/constants.py @@ -34,6 +34,7 @@ "WORKFLOW_HEADINGS", "_GATE_MODES", "_SKIPPABLE_PHASES", + "_DIALECT_CLASSES", ] ROOT_DIRNAME = ".add" @@ -343,3 +344,15 @@ # as PHASES. Listed in __all__ (mirrors _GATE_MODES): a new trust-loosening capability is # deliberately surfaced via `from add_engine.constants import *`, not tucked away. --- _SKIPPABLE_PHASES = ("scenarios", "observe") + +# --- format-dialect registry (shared: _dialect_gaps + the tests->build crossing warning + +# cmd_check's dialect_gap lint) — quality-floors floor 1. Closed (name, regex) pairs: a +# class matches only its FULL value shape, never prose fragments (a bare `2026-07-10` +# date must not match — the aware class requires the T separator + a zone suffix). Born +# from benchmark WV1 wm2: an arm's own suite stayed green on naive timestamps while the +# spec's own examples were Z-suffixed; the aware/naive crash shipped green. Listed in +# __all__ (mirrors _GATE_MODES): a new trust surface is deliberately surfaced. --- +_DIALECT_CLASSES = ( + ("aware-iso-timestamp", + r"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(?::\d{2})?(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})"), +) diff --git a/add-method/tooling/engine_pin.py b/add-method/tooling/engine_pin.py index 01eebf4a..58c73d11 100644 --- a/add-method/tooling/engine_pin.py +++ b/add-method/tooling/engine_pin.py @@ -17,5 +17,5 @@ this file only ever holds the newest pointer. """ -ENGINE_MD5 = "5b0f0538c990ec1521c131780a277c98" # re-aimed @ branch-merge 2026-07-10 (union of the local --oneshot/first-call-ergonomics chain and the remote tiny-plan-small-scope lane pushed 2026-07-08 — both features kept verbatim in cmd_new_task + argparse). prior: 147820fd… @ first-call-ergonomics v1 -ENGINE_PKG_MD5 = "5f60c0b2af321c1dcb053a5a06473a92" # re-aimed @ fast-lane-skips v1 (add_engine/constants.py: _SKIPPABLE_PHASES; add_engine/predicates.py: _skip_lane_eligible/_skip_set_allowed). prior: 9e9eb184… @ ai-plan-verify-gate v2 +ENGINE_MD5 = "7f96609e2ffb60fb0fcce33c4a763b96" # re-aimed @ spec-dialect-floor v1 (_dialect_gaps helper + tests->build dialect warning + check dialect_gap lint). prior: 5b0f0538… @ branch-merge 2026-07-10 +ENGINE_PKG_MD5 = "710a009fd35e945f0d0143bcd59ee05c" # re-aimed @ spec-dialect-floor v1 (add_engine/constants.py: _DIALECT_CLASSES registry). prior: 5f60c0b2… @ fast-lane-skips v1 diff --git a/add-method/tooling/test_spec_dialect_floor.py b/add-method/tooling/test_spec_dialect_floor.py new file mode 100644 index 00000000..3d873111 --- /dev/null +++ b/add-method/tooling/test_spec_dialect_floor.py @@ -0,0 +1,180 @@ +#!/usr/bin/env python3 +"""Red/green tests for the spec-dialect floor (task spec-dialect-floor, +frozen contract v1, quality-floors milestone): when a frozen §3 carries a +value in a recognized format dialect (v1: aware ISO-8601 timestamps) and no +declared §4 test file speaks it, the tests->build crossing WARNS (print-only, +never refuses) and `add.py check` names the task in a `dialect_gap` audit. + +Evidence class this closes (benchmark WV1 wm2 root cause): an arm's own suite +stayed green on NAIVE timestamps while the spec's own examples were Z-suffixed +— the aware/naive crash shipped green at 0.80 fidelity. + + M1 — _DIALECT_CLASSES closed registry in add_engine.constants (in __all__). + M2 — _dialect_gaps PURE helper: §3 raw body vs declared §4 test files. + M3 — the crossing warns per gap class, exit 0, state byte-identical (R1). + M4 — bare dates (no T separator) never match (R2 dialect_false_positive). + M5 — cmd_check lists gapped ACTIVE tasks under `dialect_gap`. + +Run: python3 -m unittest test_spec_dialect_floor -v +""" +import io +import json +import os +import shutil +import tempfile +import unittest +from contextlib import redirect_stderr, redirect_stdout +from pathlib import Path + +import add + +AWARE_CONTRACT = 'shape: booking { start_time: "2028-01-01T09:00:00Z" }' +NAIVE_CONTRACT = "shape: booking { created_on: 2028-01-01, note: plain date }" +NAIVE_TEST = 'def test_one():\n assert create("2028-01-01 09:00") == 1\n' +AWARE_TEST = 'def test_one():\n assert create("2028-01-01T09:00:00Z") == 1\n' + + +def _section(n: int, name: str, *body: str) -> list[str]: + return [f"## {n} · {name}", *body, ""] + + +class _Board(unittest.TestCase): + """A live board through the real CLI — the test_scope_repair_path idiom, + duplicated per this repo's one-harness-per-file norm.""" + + def setUp(self): + self._cwd = Path.cwd() + self.tmp = Path(tempfile.mkdtemp(prefix="add-dialect-floor-")).resolve() + self.addCleanup(shutil.rmtree, self.tmp, ignore_errors=True) + self.addCleanup(os.chdir, self._cwd) + os.chdir(self.tmp) + self._silent("init", "--name", "demo") + self._silent("new-milestone", "v1", "--title", "T", "--goal", "g") + + def _silent(self, *argv): + buf, err = io.StringIO(), io.StringIO() + with redirect_stdout(buf), redirect_stderr(err): + add.main(list(argv)) + return buf.getvalue(), err.getvalue() + + def _run(self, *argv): + buf, err = io.StringIO(), io.StringIO() + code = 0 + try: + with redirect_stdout(buf), redirect_stderr(err): + add.main(list(argv)) + except SystemExit as e: + code = e.code if isinstance(e.code, int) else 1 + return buf.getvalue(), err.getvalue(), code + + def _root(self) -> Path: + return self.tmp / ".add" + + def _task_md(self, slug: str) -> Path: + return self._root() / "tasks" / slug / "TASK.md" + + def _write_task(self, slug: str, contract_body: str): + lines = [ + f"# TASK: {slug}", + f"slug: {slug} · created: 2026-07-10 · stage: mvp", + "phase: ground", + "", + *_section(0, "GROUND", "Anchors the contract cites: cmd_advance"), + *_section(1, "SPECIFY", "Feature: f"), + *_section(2, "SCENARIOS", "(none)"), + *_section(3, "CONTRACT", + "```", contract_body, "```", + "Status: FROZEN @ v1 — approved by Tester 2026-07-10.", + "Least-sure flag surfaced at freeze: [contract] message " + "layer only — accepted."), + *_section(4, "TESTS", + "Coverage target: behavior", + "Tests live in: `./tests/`"), + *_section(5, "BUILD", + "Scope (may touch): `src/`", + "Safety rule (feature-specific): none", + "Code lives in: `./src/`"), + *_section(6, "VERIFY", "checks"), + *_section(7, "OBSERVE", "watch"), + ] + self._task_md(slug).write_text("\n".join(lines), encoding="utf-8") + + def _cross(self, slug: str, contract_body: str, test_body: str): + """Create a task with the given §3 fence + §4 test file and cross + tests->build, capturing the crossing's output.""" + self._silent("new-task", slug, "--title", slug) + self._write_task(slug, contract_body) + d = self._root() / "tasks" / slug / "tests" + d.mkdir(parents=True, exist_ok=True) + (d / "test_demo.py").write_text(test_body, encoding="utf-8") + self._silent("phase", "tests", slug) + buf, err = io.StringIO(), io.StringIO() + code = 0 + try: + with redirect_stdout(buf), redirect_stderr(err): + add.main(["advance", slug]) # tests -> build + except SystemExit as e: + code = e.code if isinstance(e.code, int) else 1 + return buf.getvalue(), err.getvalue(), code + + +# ── M1: the registry is a named, closed trust surface ──────────────────────── +class DialectRegistryTest(unittest.TestCase): + def test_registry_shape_and_export(self): + from add_engine import constants + + self.assertIn("_DIALECT_CLASSES", constants.__all__, + "a new trust surface must be named in __all__") + names = [name for name, _ in constants._DIALECT_CLASSES] + self.assertIn("aware-iso-timestamp", names) + + +# ── M3: gapped crossing warns but succeeds ──────────────────────────────────── +class GappedCrossingTest(_Board): + def test_gapped_crossing_warns_but_succeeds(self): + out, err, code = self._cross("gapped", AWARE_CONTRACT, NAIVE_TEST) + combined = out + err + self.assertEqual(code, 0, "R1 floor_overreach: the crossing must NOT refuse") + self.assertIn("aware-iso-timestamp", combined, + "the warning must name the dialect class") + self.assertIn("re-cross", combined, + "the warning must carry the repair command") + self.assertEqual( + json.loads((self._root() / "state.json").read_text()) + ["tasks"]["gapped"]["phase"], "build", + "state must be byte-identical to a clean crossing (phase advanced)") + + def test_covered_suite_is_silent(self): + out, err, _ = self._cross("covered", AWARE_CONTRACT, AWARE_TEST) + self.assertNotIn("aware-iso-timestamp", out + err, + "a suite speaking the dialect must not warn") + + +# ── M4: no-dialect contracts and bare dates stay silent ────────────────────── +class FalsePositiveTest(_Board): + def test_no_dialect_contract_is_silent(self): + out, err, _ = self._cross("plain", NAIVE_CONTRACT, NAIVE_TEST) + self.assertNotIn("aware-iso-timestamp", out + err, + "R2 dialect_false_positive: bare dates must never match") + + +# ── M5: check names the gap in a dialect_gap audit ─────────────────────────── +class CheckAuditTest(_Board): + def test_check_names_dialect_gap_audit(self): + self._cross("gapped", AWARE_CONTRACT, NAIVE_TEST) + out, err, _ = self._run("check") + combined = out + err + self.assertIn("dialect_gap", combined, "check must expose the audit") + self.assertIn("gapped", combined, "the gapped task must be named") + + def test_check_silent_when_covered(self): + self._cross("covered", AWARE_CONTRACT, AWARE_TEST) + out, err, _ = self._run("check") + for line in (out + err).splitlines(): + if "dialect_gap" in line: + self.assertNotIn("covered", line, + "a covered task must not be audit-listed") + + +if __name__ == "__main__": + unittest.main() From bb724520a7cf35d0a3632a0c04c65dc5d8720e76 Mon Sep 17 00:00:00 2001 From: Tin Dang Date: Sat, 11 Jul 2026 01:06:21 +0700 Subject: [PATCH 2/4] =?UTF-8?q?feat(method):=20=C2=A76=20dialect=20check?= =?UTF-8?q?=20line=20+=20datetime/money/tz=20=E2=87=92=20data=20vocabulary?= =?UTF-8?q?=20(quality-floors=203+4)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Task dialect-check-and-data-vocab (quality-floors levers 3+4, gate PASS, frozen @v1 via ai-plan-verify). Two more zero-turn floors closing the wm2 input-dialect class from the WV1 benchmark: - TASK.md.tmpl `### Deep checks` gains the DIALECT line ("tests speak the same value formats the spec's examples use") — audit-counted by the existing `shallow` machinery, zero engine code; TASK.fast.md.tmpl §6 gains the condensed twin ("input dialect held"). - GLOSSARY.md.tmpl `## Sensitivity classes` + this project's .add/GLOSSARY.md + sensitivity.md skill guide gain the PROSE rule: datetime, money, or timezone arithmetic ⇒ declare `data` (full lane), wm2 evidence cited. No new sensitivity token minted — _project_sensitivity_domain proven () over a fresh render. - Byte fences held by compression, not rebaseline: TASK.md.tmpl 11391B (≤11400), skill pool/tree back under 51885/145974 via ~183B trimmed from sensitivity.md prose. - 5 sibling pins amended via an explicit TESTS re-cross, as the frozen least-sure flag predicted: 4 Deep-checks fixtures now fill the DIALECT placeholder (guarantee-lints, advisor-review-step, advisor-verdict-audit, verify-record-rollup); fast-lane-skips' task-local empty-git-diff guard re-pinned to its durable invariant (skip machinery stays fast-lane-only). - New red suite test_dialect_vocab_lines.py (6 pins). All twins synced incl. the 4th gitignored dogfood tree. Full suite 3374 passed. - §6 OBSERVE: lever-1 false positive found dogfooding — _dialect_gaps scans §3 raw body incl. the engine's own freeze timestamp; left as an open SPEC delta. refs: quality-floors milestone (task 2/3) · benchmark/results/2026-07-wv1-rep0.md author: Tin Dang --- .add/GLOSSARY.md | 10 ++ .add/state.json | 56 +++++++++- .../dialect-check-and-data-vocab/TASK.md | 102 ++++++++++++++++++ .claude/skills/add/sensitivity.md | 17 +-- add-method/skill/add/sensitivity.md | 17 +-- .../_bundled/skill/add/sensitivity.md | 17 +-- .../tooling/templates/GLOSSARY.md.tmpl | 3 + .../tooling/templates/TASK.fast.md.tmpl | 1 + .../_bundled/tooling/templates/TASK.md.tmpl | 3 +- add-method/tooling/templates/GLOSSARY.md.tmpl | 3 + .../tooling/templates/TASK.fast.md.tmpl | 1 + add-method/tooling/templates/TASK.md.tmpl | 3 +- .../tooling/test_advisor_review_step.py | 4 +- .../tooling/test_advisor_verdict_audit.py | 4 +- .../tooling/test_dialect_vocab_lines.py | 84 +++++++++++++++ add-method/tooling/test_fast_lane_skips.py | 15 +-- add-method/tooling/test_guarantee_lints.py | 4 +- .../tooling/test_verify_record_rollup.py | 4 +- 18 files changed, 308 insertions(+), 40 deletions(-) create mode 100644 .add/tasks/dialect-check-and-data-vocab/TASK.md create mode 100644 add-method/tooling/test_dialect_vocab_lines.py diff --git a/.add/GLOSSARY.md b/.add/GLOSSARY.md index f14db93f..ec78762e 100644 --- a/.add/GLOSSARY.md +++ b/.add/GLOSSARY.md @@ -1,5 +1,15 @@ # GLOSSARY (one name per concept — used everywhere: specs, contracts, code) +## Sensitivity classes +The risk-CLASS a task declares (`sensitivity:` in the TASK header) — read live by freeze/status/check. +The base four (security · data · architecture · mechanical) are method-universal and always apply. + +Datetime, money, or timezone arithmetic in scope ⇒ declare `data` (the full lane): the value +formats themselves are the risk surface — a suite testing a friendlier dialect than the spec's +own examples stays green through a real crash (the benchmark wm2 aware/naive miss, +results/2026-07-wv1-rep0.md). + + ADD: AI-Driven Development — the orchestration engine (the build/verify discipline) and this skill. AIDD: the umbrella method and the book that explains the why (the trust layer). Task: one feature taken through the flow; lives in `.add/tasks//TASK.md`. diff --git a/.add/state.json b/.add/state.json index f508209b..8d6096da 100644 --- a/.add/state.json +++ b/.add/state.json @@ -1,7 +1,7 @@ { "project": "AIDD / ADD Methodology", "stage": "mvp", - "active_task": "spec-dialect-floor", + "active_task": "dialect-check-and-data-vocab", "tasks": { "add-check": { "title": "add.py check: validate .add project integrity", @@ -7603,10 +7603,60 @@ "email": "tindang.ht97@gmail.com", "source": "git" } + }, + "dialect-check-and-data-vocab": { + "title": "\u00a76 dialect check line + datetime/money/tz \u21d2 data sensitivity vocabulary", + "phase": "done", + "gate": "PASS", + "milestone": "quality-floors", + "depends_on": [], + "created": "2026-07-10T17:36:22+00:00", + "updated": "2026-07-10T18:05:06+00:00", + "fast": true, + "oneshot": true, + "sensitivity": "mechanical", + "freeze": { + "version": "v1", + "frozen_at": "2026-07-10T17:38:23+00:00", + "approved_by": "claude-fable-5", + "actor": { + "name": "Tin Dang", + "email": "tindang.ht97@gmail.com", + "source": "git" + }, + "mode": "ai-plan-verify", + "verified": { + "anchors": true, + "rules": true, + "shape": true, + "flag": true + } + }, + "flag_verified": true, + "tripwire": { + "contract_md5": "7643468103648bf38a55fab4c7c302b8", + "tests": { + "add-method/tooling/test_dialect_vocab_lines.py": "a5792651f844600977d5117eb8046335" + } + }, + "scope": { + "declared": [ + "add-method/", + ".claude/", + ".add/GLOSSARY.md", + "tmp/" + ], + "snapshot_md5": "cbc86c6b27748b7cb2d14e4a7d1e55c0" + }, + "gate_actor": { + "name": "Tin Dang", + "email": "tindang.ht97@gmail.com", + "source": "git" + } } }, "created": "2026-05-28T15:39:04+00:00", - "updated": "2026-07-10T17:35:35+00:00", + "updated": "2026-07-10T18:05:06+00:00", "milestones": { "v1-1": { "title": "v1.1 \u2014 adoption & ergonomics", @@ -8780,7 +8830,7 @@ "add-bench": "bench-regression-split", "add-lean-loop": "persona-nudge-quiet", "add-bench-2": "tiny-plan-small-scope", - "quality-floors": "spec-dialect-floor" + "quality-floors": "dialect-check-and-data-vocab" }, "todos": [ { diff --git a/.add/tasks/dialect-check-and-data-vocab/TASK.md b/.add/tasks/dialect-check-and-data-vocab/TASK.md new file mode 100644 index 00000000..3ed86e29 --- /dev/null +++ b/.add/tasks/dialect-check-and-data-vocab/TASK.md @@ -0,0 +1,102 @@ +# TASK: §6 dialect check line + datetime/money/tz ⇒ data sensitivity vocabulary + +slug: dialect-check-and-data-vocab · created: 2026-07-11 · stage: mvp +milestone: quality-floors +autonomy: auto +phase: done +fast: true +oneshot: true +gate_mode: ai-plan-verify + +> Fast lane — one small task, minimal sections, filled top-to-bottom. The trust floor still +> holds: a FROZEN §3 contract · ≥1 red test before build · a recorded §6 gate (security = HARD-STOP). +> The acceptance scenario collapses into §1 `Accept:`; OBSERVE is one optional line at the gate. + +--- + +## 0 · GROUND — the real codebase + +Touches (files · symbols): `add-method/tooling/templates/TASK.md.tmpl:127-129` (§6 checklist + `### Deep checks` block — the shallow audit already measures that block, so a dialect bullet there is audit-counted with ZERO new engine code) · `templates/TASK.fast.md.tmpl:79-80` (fast §6 checklist) · `templates/GLOSSARY.md.tmpl:5-15` (`## Sensitivity classes` — guidance must be PROSE, never a `- token:` bullet, or `_SENS_CLASS_LINE_RE` mints a new class) · `.add/GLOSSARY.md` (this project's own, section currently absent) · `sensitivity.md` skill guide ×3 twins · templates ×3 tooling twins; engine_manifest covers add_engine/*.py ONLY → no pin re-aim (add.py untouched) +Context (working folder): quality-floors MILESTONE.md items 3+4; evidence = benchmark/results/2026-07-wv1-rep0.md wm2 root cause +Honors (patterns / conventions): §6 already uses `` placeholders (safe to mirror; the v16 tag census binds §1-§4 spans, not §6) · template/guide edits propagate to all twins before the gate · guidance-not-token (base `data` class, no new vocabulary) +Anchors the contract cites: `TASK.md.tmpl ### Deep checks` · `TASK.fast.md.tmpl §6` · `GLOSSARY.md.tmpl ## Sensitivity classes` · `_project_sensitivity_domain` (must stay ()-stable) · `sensitivity.md` +Ground SHA: `f9d2303` +Skip rationale: scenarios — two template lines + two prose entries, §1 Accept covers; observe — one optional delta line at the gate + +--- + +## 1 · SPECIFY — the rules + +Feature: quality-floors levers 3+4 — a §6 input-dialect check line in BOTH task templates (audit-counted via the existing Deep-checks/shallow machinery) + the datetime/money/timezone ⇒ `data` sensitivity guidance in the GLOSSARY template, this project's GLOSSARY, and the sensitivity skill guide (wm2 evidence named) +Must: + - TASK.md.tmpl `### Deep checks` gains `- [ ] DIALECT — the tests speak the same value formats as the spec's own examples (spec-dialect floor): ` — inside the block the `shallow` audit already measures + - TASK.fast.md.tmpl §6 checklist gains the condensed twin line + - GLOSSARY.md.tmpl `## Sensitivity classes` gains PROSE guidance (not a bullet): datetime/money/timezone arithmetic ⇒ declare `data` (full lane), wm2 evidence in one clause; `.add/GLOSSARY.md` gains the same section content for THIS project + - sensitivity.md skill guide names the rule + the wm2 evidence; all template/guide twins byte-identical + - the guidance mints NO new sensitivity token — `_project_sensitivity_domain` over a fresh render stays () +Reject: + - guidance written as a `- token:` bullet -> "vocab_leak" (a new class token would silently widen freeze/status/check vocabulary) + - a §1-§4 template span gaining a bare angle-tag -> "tag_census_collision" (v16 hazard; §6 only) +Accept: Given the templates render a fresh task, When §6 is read, Then both templates carry the dialect line (full inside Deep checks) AND the GLOSSARY template + project GLOSSARY + sensitivity guide carry the datetime/money/tz ⇒ data rule AND `_project_sensitivity_domain` of a fresh render is unchanged () +Assumptions: ⚠ template guard suites may pin §6 line counts or exact Deep-checks content — if wrong: those pins red on the new line and each gets a TESTS-re-cross amendment in ITS owning suite (cost: one re-cross loop) + +--- + +## 3 · CONTRACT — freeze the shape + +``` +TASK.md.tmpl / ### Deep checks (audit-counted via `shallow`): + - [ ] DIALECT — the tests speak the same value formats as the spec's own examples + (spec-dialect floor): +TASK.fast.md.tmpl / §6 checklist: + - [ ] input dialect held — tests speak the spec's example formats (spec-dialect floor) +GLOSSARY.md.tmpl + .add/GLOSSARY.md / ## Sensitivity classes (PROSE, no bullet): + "Datetime, money, or timezone arithmetic ⇒ declare `data` …(wm2 evidence clause)" +sensitivity.md (×3 skill twins): the same rule + evidence, one short block. +success: fresh render carries the lines; _project_sensitivity_domain stays (). +rejections: vocab_leak (no `- token:` guidance) · tag_census_collision (§6 only). +``` + +`Least-sure flag surfaced at freeze:` [test] existing template-guard suites may pin §6/Deep-checks content or counts — the build may red THEIR pins, and each amendment must go through a TESTS re-cross in the owning suite, never a quiet edit; cost if many pin: several re-cross loops +Status: FROZEN @ v1 — approved by claude-fable-5 +Freeze mode: ai-plan-verify — verified by claude-fable-5 at 2026-07-10T17:38:23+00:00 + +### AI-verify record (required when gate_mode: ai-plan-verify) +- [x] §0 GROUND anchors resolve in the current tree — template line numbers, GLOSSARY tmpl section, sensitivity.md ×3, _SENS_CLASS_LINE_RE reader all grepped at f9d2303 +- [x] §1 every Must + every Reject present, each Reject paired with an error code (vocab_leak · tag_census_collision) +- [x] §3 CONTRACT shape is concrete — exact line text per surface +- [x] Lowest-confidence flag surfaced and substantive — template-guard pin collisions with the re-cross cost +Verified by: claude-fable-5 (session ee9aef91, orchestrator inline) · at: 2026-07-10T21:30:00Z + +--- + +## 4 · TESTS — failing-first (red) + +Plan: test_dialect_vocab_lines.py — test_full_template_deep_checks_carries_dialect_line · test_fast_template_carries_dialect_line · test_glossary_template_carries_data_guidance_as_prose (+ regex proves no `- token:` bullet added) · test_project_glossary_carries_the_rule · test_sensitivity_guide_names_wm2_evidence · test_no_new_sensitivity_token (render a fresh project, _project_sensitivity_domain == ()). +Tests live in: `add-method/tooling/test_dialect_vocab_lines.py` · MUST run red before Build. + +--- + +## 5 · BUILD — AI writes code + +Scope (may touch): `add-method/` `.claude/` `.add/GLOSSARY.md` `tmp/` +Strategy & known-problem fixes: 1. red pins 2. canonical template/guide/glossary edits 3. cp to every twin 4. full tooling suite; traps: guidance as prose only (vocab_leak) · §6 only (tag census) · twins before the gate +Approach (domain strategy): book-technical-writer stance — the floor's prose surface: one checklist line a verifier can act on, one glossary rule a task author can route by +Strategy actually used: as planned, plus the byte-ceiling absorption the lean rule demands — the DIALECT line and sensitivity.md guidance were compressed (all §3-cited phrases held, pinned by the red suite) and ~183B of existing sensitivity.md prose trimmed so pool 51885/tree 145974 fences hold; 5 sibling pins amended via TESTS re-cross exactly as the least-sure flag predicted (4 Deep-checks fixtures now fill the DIALECT placeholder; fast-lane's task-local empty-git-diff guard re-pinned to its durable invariant: skip machinery stays fast-lane-only); a 4th gitignored dogfood twin (add-method/.add/tooling/templates/) joined the sync set. +Code lives in: `./src/` · Constraints: change no test, no contract; allow-list packages only. + +--- + +## 6 · VERIFY — evidence + gate + +- [x] all tests pass · coverage held · no test or contract altered during build — full tooling suite 3374 passed + 162 subtests, 0 failed; test amendments (5) went through an explicit TESTS re-cross, never a quiet edit +- [x] green was EARNED — the 6 red pins assert rendered template/guide CONTENT (phrase + placement + no-bullet regex), not the build's own strings; `_project_sensitivity_domain` () proven over a fresh render +- [x] no exposed secrets, injection openings, or unexpected dependencies — prose/template-only change; no new imports, no engine code touched (ENGINE_MD5/PKG unchanged) + +Build expectations (from §1 Accept + §3 CONTRACT): a fresh render carries the DIALECT line inside `### Deep checks` (full) and the condensed §6 line (fast); GLOSSARY.md.tmpl + `.add/GLOSSARY.md` + sensitivity.md ×3 carry datetime/money/tz ⇒ `data` with the wm2 evidence; no new sensitivity token — confirmed by test_dialect_vocab_lines.py 6/6 + budget/parity/audit suites green (3374 total). + +### GATE RECORD +Outcome: PASS +Reviewed by: Tin Dang · date: 2026-07-11 +[SPEC · open] lever-1 refinement: `_dialect_gaps` scans the §3 RAW body, which includes engine-written freeze metadata — this task's own tests→build crossing warned `aware-iso-timestamp` on its `Freeze mode: … 2026-07-10T17:38:23+00:00` stamp, a false positive of the v1 scan span; candidate fix: scan only §3 fenced blocks or strip the Status/Freeze-mode lines (evidence: crossing output at the phase-build re-cross, this task, 2026-07-11) + diff --git a/.claude/skills/add/sensitivity.md b/.claude/skills/add/sensitivity.md index 6b9b0bc8..b443c1d6 100644 --- a/.claude/skills/add/sensitivity.md +++ b/.claude/skills/add/sensitivity.md @@ -9,6 +9,8 @@ surfaces the human's declaration; it **never classifies**. Read live by freeze/s - **security** — authn/authz, secrets, crypto, attack surface. A finding here is HARD-STOP; the human is in the loop in EVERY tier (never advisor-gated, never auto-passed). - **data** — persistence, migrations, privacy of stored records, data loss. + Datetime, money, or timezone arithmetic also ⇒ `data` — value formats are the risk surface + (bench wm2: naive-timestamp tests green through the spec's own `Z`-example crash). - **architecture** — module boundaries, contracts, cross-cutting structure. - **mechanical** — rote, low-impact change (rename, move, format, doc). The only class a recorded advisor verdict (three §6 fields: Verdict · Residue · Binding) can gate for @@ -30,22 +32,21 @@ Base (always apply): security · data · architecture · mechanical `freeze` then accepts a header `sensitivity:` value from **base ∪ your domain classes**; a token in neither is refused `sensitivity_invalid`. `status` prints the active task's -class; `check` nudges (`sensitivity_classes_unset`, never red) until you declare some. +class; `check` nudges (`sensitivity_classes_unset`, never red). ## The AI's job — keep it current -- When a milestone or task reveals a **new kind of risk** this project carries (a regulated - data category, a payment rail, a tenancy boundary), ADD it as a class with a one-line - definition — propose it, the human confirms (it is foundation, like a glossary term). +- When a milestone or task reveals a **new kind of risk** this project carries, ADD it as a + class with a one-line definition — propose it, the human confirms (it is foundation). - **Re-read the section each session** (it rides `GLOSSARY.md`); pick the tightest class when you declare a task's `sensitivity:` at freeze. -- **Map domain → base behavior** in the definition so downstream gating is unambiguous — - e.g. "pii … escalates to human review" pins it as human-floor, not advisor-gatable. +- **Map domain → base behavior** in the definition — e.g. "pii … escalates to human + review" pins it human-floor, not advisor-gatable. ## Hold the line - **Declared, never inferred** — the engine reads your token; it does not guess a class. - **Base four are universal** — domain classes add to them, never replace them; security stays a human floor in every tier. -- **A comment is never a declaration** — commented-out example bullets don't count; only a - real `- :` line under the section is a class. +- **A comment is never a declaration** — only a real `- :` line under the section + counts as a class. diff --git a/add-method/skill/add/sensitivity.md b/add-method/skill/add/sensitivity.md index 6b9b0bc8..b443c1d6 100644 --- a/add-method/skill/add/sensitivity.md +++ b/add-method/skill/add/sensitivity.md @@ -9,6 +9,8 @@ surfaces the human's declaration; it **never classifies**. Read live by freeze/s - **security** — authn/authz, secrets, crypto, attack surface. A finding here is HARD-STOP; the human is in the loop in EVERY tier (never advisor-gated, never auto-passed). - **data** — persistence, migrations, privacy of stored records, data loss. + Datetime, money, or timezone arithmetic also ⇒ `data` — value formats are the risk surface + (bench wm2: naive-timestamp tests green through the spec's own `Z`-example crash). - **architecture** — module boundaries, contracts, cross-cutting structure. - **mechanical** — rote, low-impact change (rename, move, format, doc). The only class a recorded advisor verdict (three §6 fields: Verdict · Residue · Binding) can gate for @@ -30,22 +32,21 @@ Base (always apply): security · data · architecture · mechanical `freeze` then accepts a header `sensitivity:` value from **base ∪ your domain classes**; a token in neither is refused `sensitivity_invalid`. `status` prints the active task's -class; `check` nudges (`sensitivity_classes_unset`, never red) until you declare some. +class; `check` nudges (`sensitivity_classes_unset`, never red). ## The AI's job — keep it current -- When a milestone or task reveals a **new kind of risk** this project carries (a regulated - data category, a payment rail, a tenancy boundary), ADD it as a class with a one-line - definition — propose it, the human confirms (it is foundation, like a glossary term). +- When a milestone or task reveals a **new kind of risk** this project carries, ADD it as a + class with a one-line definition — propose it, the human confirms (it is foundation). - **Re-read the section each session** (it rides `GLOSSARY.md`); pick the tightest class when you declare a task's `sensitivity:` at freeze. -- **Map domain → base behavior** in the definition so downstream gating is unambiguous — - e.g. "pii … escalates to human review" pins it as human-floor, not advisor-gatable. +- **Map domain → base behavior** in the definition — e.g. "pii … escalates to human + review" pins it human-floor, not advisor-gatable. ## Hold the line - **Declared, never inferred** — the engine reads your token; it does not guess a class. - **Base four are universal** — domain classes add to them, never replace them; security stays a human floor in every tier. -- **A comment is never a declaration** — commented-out example bullets don't count; only a - real `- :` line under the section is a class. +- **A comment is never a declaration** — only a real `- :` line under the section + counts as a class. diff --git a/add-method/src/add_method/_bundled/skill/add/sensitivity.md b/add-method/src/add_method/_bundled/skill/add/sensitivity.md index 6b9b0bc8..b443c1d6 100644 --- a/add-method/src/add_method/_bundled/skill/add/sensitivity.md +++ b/add-method/src/add_method/_bundled/skill/add/sensitivity.md @@ -9,6 +9,8 @@ surfaces the human's declaration; it **never classifies**. Read live by freeze/s - **security** — authn/authz, secrets, crypto, attack surface. A finding here is HARD-STOP; the human is in the loop in EVERY tier (never advisor-gated, never auto-passed). - **data** — persistence, migrations, privacy of stored records, data loss. + Datetime, money, or timezone arithmetic also ⇒ `data` — value formats are the risk surface + (bench wm2: naive-timestamp tests green through the spec's own `Z`-example crash). - **architecture** — module boundaries, contracts, cross-cutting structure. - **mechanical** — rote, low-impact change (rename, move, format, doc). The only class a recorded advisor verdict (three §6 fields: Verdict · Residue · Binding) can gate for @@ -30,22 +32,21 @@ Base (always apply): security · data · architecture · mechanical `freeze` then accepts a header `sensitivity:` value from **base ∪ your domain classes**; a token in neither is refused `sensitivity_invalid`. `status` prints the active task's -class; `check` nudges (`sensitivity_classes_unset`, never red) until you declare some. +class; `check` nudges (`sensitivity_classes_unset`, never red). ## The AI's job — keep it current -- When a milestone or task reveals a **new kind of risk** this project carries (a regulated - data category, a payment rail, a tenancy boundary), ADD it as a class with a one-line - definition — propose it, the human confirms (it is foundation, like a glossary term). +- When a milestone or task reveals a **new kind of risk** this project carries, ADD it as a + class with a one-line definition — propose it, the human confirms (it is foundation). - **Re-read the section each session** (it rides `GLOSSARY.md`); pick the tightest class when you declare a task's `sensitivity:` at freeze. -- **Map domain → base behavior** in the definition so downstream gating is unambiguous — - e.g. "pii … escalates to human review" pins it as human-floor, not advisor-gatable. +- **Map domain → base behavior** in the definition — e.g. "pii … escalates to human + review" pins it human-floor, not advisor-gatable. ## Hold the line - **Declared, never inferred** — the engine reads your token; it does not guess a class. - **Base four are universal** — domain classes add to them, never replace them; security stays a human floor in every tier. -- **A comment is never a declaration** — commented-out example bullets don't count; only a - real `- :` line under the section is a class. +- **A comment is never a declaration** — only a real `- :` line under the section + counts as a class. diff --git a/add-method/src/add_method/_bundled/tooling/templates/GLOSSARY.md.tmpl b/add-method/src/add_method/_bundled/tooling/templates/GLOSSARY.md.tmpl index 2d502cf0..4eb35c98 100644 --- a/add-method/src/add_method/_bundled/tooling/templates/GLOSSARY.md.tmpl +++ b/add-method/src/add_method/_bundled/tooling/templates/GLOSSARY.md.tmpl @@ -9,6 +9,9 @@ DOMAIN classes (the AI keeps these current as the domain is learned — see the Base (always apply): security · data · architecture · mechanical +Datetime, money, or timezone arithmetic ⇒ declare `data` (full lane) — value formats are the +risk surface (a naive-timestamp suite stays green through an aware-timestamp crash: bench wm2). + ", "", text, flags=re.S)) + tokens = re.findall(r"(?m)^[ \t]*-[ \t]+([A-Za-z][\w-]*)[ \t]*(?::|—)", section) + self.assertEqual(tokens, [], + "the template section must declare no live domain token") + + +class SensitivityGuideTest(unittest.TestCase): + def test_sensitivity_guide_names_wm2_evidence(self): + body = SKILL_GUIDE.read_text(encoding="utf-8") + self.assertIn("Datetime, money, or timezone arithmetic", body) + self.assertIn("`data`", body) + self.assertIn("wm2", body, "the guide must cite the benchmark evidence") + + +if __name__ == "__main__": + unittest.main() diff --git a/add-method/tooling/test_fast_lane_skips.py b/add-method/tooling/test_fast_lane_skips.py index 17a03391..73c006aa 100644 --- a/add-method/tooling/test_fast_lane_skips.py +++ b/add-method/tooling/test_fast_lane_skips.py @@ -482,13 +482,14 @@ def test_fast_template_has_skips_hint_and_rationale_placeholder(self): self.assertIn("skips:", text) self.assertIn("Skip rationale:", text) - def test_full_template_byte_identical_to_before(self): - # the full-lane template's git-tracked content must be untouched by this task - import subprocess - out = subprocess.run(["git", "diff", "--stat", "HEAD", "--", - "add-method/tooling/templates/TASK.md.tmpl"], - cwd=REPO_ROOT, capture_output=True, text=True) - self.assertEqual(out.stdout.strip(), "", "TASK.md.tmpl must be untouched") + def test_full_template_carries_no_skips_machinery(self): + # the skip lane is fast-template-only: the full template must never gain a + # `skips:` header hint. (Amended @ dialect-check-and-data-vocab TESTS re-cross: + # the original empty-git-diff guard was task-local and could not survive any + # later legitimate full-template task — re-pinned to the invariant it protected.) + body = (REPO_ROOT / "add-method" / "tooling" / "templates" / + "TASK.md.tmpl").read_text(encoding="utf-8") + self.assertNotIn("skips:", body, "skip machinery must stay fast-lane-only") # --------------------------------------------------------------------------- diff --git a/add-method/tooling/test_guarantee_lints.py b/add-method/tooling/test_guarantee_lints.py index 9ab24522..c8903264 100644 --- a/add-method/tooling/test_guarantee_lints.py +++ b/add-method/tooling/test_guarantee_lints.py @@ -71,7 +71,9 @@ def _verify_task(self, slug): def _fill_deepcheck(self, slug): p = self._task_md(slug) p.write_text(p.read_text(encoding="utf-8").replace( - "", "read both guides in full; confirmed the disclosure"), + "", "read both guides in full; confirmed the disclosure").replace( + "(spec-dialect floor): ", + "(spec-dialect floor): tests speak the spec's Z-timestamp example"), encoding="utf-8") def _drop_deepcheck(self, slug): diff --git a/add-method/tooling/test_verify_record_rollup.py b/add-method/tooling/test_verify_record_rollup.py index 6c44a091..a27dfcf1 100644 --- a/add-method/tooling/test_verify_record_rollup.py +++ b/add-method/tooling/test_verify_record_rollup.py @@ -84,7 +84,9 @@ def _verify_task(self, slug): def _fill_deep(self, slug): p = self._task_md(slug) p.write_text(p.read_text(encoding="utf-8").replace( - "", "read the diff in full · confirmed"), + "", "read the diff in full · confirmed").replace( + "(spec-dialect floor): ", + "(spec-dialect floor): tests speak the spec's Z-timestamp example"), encoding="utf-8") def _fill_refute(self, slug): From b2483be611e5407bc5d604dc869880e48b49eaa7 Mon Sep 17 00:00:00 2001 From: Tin Dang Date: Sat, 11 Jul 2026 01:21:37 +0700 Subject: [PATCH 3/4] =?UTF-8?q?feat(method):=20fast-lane=20Boundary:=20lin?= =?UTF-8?q?e=20=E2=80=94=20freeze=20refuses=20an=20undeclared=20input-form?= =?UTF-8?q?at=20boundary=20(quality-floors=202)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Task fast-lane-boundary-line (quality-floors lever 2, gate PASS, frozen @v1 via ai-plan-verify). The last of the 4 zero-turn floors closing the WV1 wm2 input-dialect class: - TASK.fast.md.tmpl §1 gains, directly after Accept:, a `Boundary:` line — >=1 format-variant per external input shape the tests must speak (e.g. aware vs naive timestamp), or an explicit "none — no external input". - cmd_freeze refuses a task whose Boundary value is still the bare template placeholder (or empty): `boundary_unfilled`, fired in the shared validate block directly after unflagged_freeze — BOTH freeze paths (human and --ai-plan-verify), validate-then-write (zero bytes land on refusal). A task with NO Boundary: line is grandfathered: legacy fast tasks and the full lane gain no new refusal. Placeholder rule = empty or bare unfenced `<...>`, backtick-exempt (mirrors _section_unfilled). - New red suite test_fast_boundary_line.py (9 pins, live-CLI board: refusal exit codes + byte-identical state on refusal + both paths + grandfathering + §3-fence text never triggers). - 2 test_fast_lane_skips fixtures amended via an explicit TESTS re-cross (they freeze fresh-rendered fast tasks, which now scaffold the placeholder line): shared _fill_boundary helper. - ENGINE_MD5 re-aimed 16cd7cca (prior 7f96609e); SEAMS scope-token-grammar pin x18 (:5337); add.py + fast template synced across all twins incl. the 4th dogfood tree. Full suite 3383 passed. refs: quality-floors milestone (task 3/3) · benchmark/results/2026-07-wv1-rep0.md author: Tin Dang --- .add/SEAMS.md | 2 +- .add/state.json | 54 ++++- .add/tasks/fast-lane-boundary-line/TASK.md | 105 +++++++++ .../src/add_method/_bundled/tooling/add.py | 13 ++ .../add_method/_bundled/tooling/engine_pin.py | 2 +- .../tooling/templates/TASK.fast.md.tmpl | 1 + add-method/tooling/add.py | 13 ++ add-method/tooling/engine_pin.py | 2 +- .../tooling/templates/TASK.fast.md.tmpl | 1 + add-method/tooling/test_fast_boundary_line.py | 219 ++++++++++++++++++ add-method/tooling/test_fast_lane_skips.py | 12 + 11 files changed, 418 insertions(+), 6 deletions(-) create mode 100644 .add/tasks/fast-lane-boundary-line/TASK.md create mode 100644 add-method/tooling/test_fast_boundary_line.py diff --git a/.add/SEAMS.md b/.add/SEAMS.md index b629ed9e..1a9b073f 100644 --- a/.add/SEAMS.md +++ b/.add/SEAMS.md @@ -54,7 +54,7 @@ Citations: 232 files reference "byte-identical" in `.add/tasks/` — method: ## scope-token-grammar Name: §5 "Scope (may touch):" token-resolution grammar -Anchor: `add-method/tooling/add.py:5324` (`_declared_scope`) +Anchor: `add-method/tooling/add.py:5337` (`_declared_scope`) Contract: `_declared_scope` reads ONLY the first physical line after the §5 header — a wrapped multi-line list silently truncates. Each backticked token then resolves independently: `./...` = this task's dir, any token containing `/` = project-root-relative, diff --git a/.add/state.json b/.add/state.json index 8d6096da..72bf0e08 100644 --- a/.add/state.json +++ b/.add/state.json @@ -1,7 +1,7 @@ { "project": "AIDD / ADD Methodology", "stage": "mvp", - "active_task": "dialect-check-and-data-vocab", + "active_task": "fast-lane-boundary-line", "tasks": { "add-check": { "title": "add.py check: validate .add project integrity", @@ -7653,10 +7653,58 @@ "email": "tindang.ht97@gmail.com", "source": "git" } + }, + "fast-lane-boundary-line": { + "title": "Fast-lane Boundary: line \u2014 the fast freeze refuses an undeclared input-format boundary", + "phase": "done", + "gate": "PASS", + "milestone": "quality-floors", + "depends_on": [], + "created": "2026-07-10T18:07:42+00:00", + "updated": "2026-07-10T18:21:21+00:00", + "fast": true, + "oneshot": true, + "freeze": { + "version": "v1", + "frozen_at": "2026-07-10T18:09:30+00:00", + "approved_by": "claude-fable-5", + "actor": { + "name": "Tin Dang", + "email": "tindang.ht97@gmail.com", + "source": "git" + }, + "mode": "ai-plan-verify", + "verified": { + "anchors": true, + "rules": true, + "shape": true, + "flag": true + } + }, + "flag_verified": true, + "tripwire": { + "contract_md5": "a91b41247fee60972018aa10a1eb095e", + "tests": { + "add-method/tooling/test_fast_boundary_line.py": "6896b88704e4568d91fb31e5809b3d4c" + } + }, + "scope": { + "declared": [ + "add-method/", + ".add/SEAMS.md", + "tmp/" + ], + "snapshot_md5": "a020c8e5810621aa5e5a9394aa3820cb" + }, + "gate_actor": { + "name": "Tin Dang", + "email": "tindang.ht97@gmail.com", + "source": "git" + } } }, "created": "2026-05-28T15:39:04+00:00", - "updated": "2026-07-10T18:05:06+00:00", + "updated": "2026-07-10T18:21:21+00:00", "milestones": { "v1-1": { "title": "v1.1 \u2014 adoption & ergonomics", @@ -8830,7 +8878,7 @@ "add-bench": "bench-regression-split", "add-lean-loop": "persona-nudge-quiet", "add-bench-2": "tiny-plan-small-scope", - "quality-floors": "dialect-check-and-data-vocab" + "quality-floors": "fast-lane-boundary-line" }, "todos": [ { diff --git a/.add/tasks/fast-lane-boundary-line/TASK.md b/.add/tasks/fast-lane-boundary-line/TASK.md new file mode 100644 index 00000000..bf9f9a0f --- /dev/null +++ b/.add/tasks/fast-lane-boundary-line/TASK.md @@ -0,0 +1,105 @@ +# TASK: Fast-lane Boundary: line — the fast freeze refuses an undeclared input-format boundary + +slug: fast-lane-boundary-line · created: 2026-07-11 · stage: mvp +milestone: quality-floors +autonomy: auto +phase: done +fast: true +oneshot: true +gate_mode: ai-plan-verify + +> Fast lane — one small task, minimal sections, filled top-to-bottom. The trust floor still +> holds: a FROZEN §3 contract · ≥1 red test before build · a recorded §6 gate (security = HARD-STOP). +> The acceptance scenario collapses into §1 `Accept:`; OBSERVE is one optional line at the gate. + +--- + +## 0 · GROUND — the real codebase + +Touches (files · symbols): `add-method/tooling/add.py` cmd_freeze validate block (already_frozen → contract_not_drafted → unflagged_freeze precedence chain, ~:998-1003; the new guard slots directly after unflagged_freeze so BOTH the human and --ai-plan-verify paths hit it before any write) · `templates/TASK.fast.md.tmpl` §1 (directly after the Accept: line) · `add_engine/taskdoc.py` `_phase_spans` (§1 raw-body read, already imported by add.py) · `engine_pin.py` ENGINE_MD5 (add.py changes ⇒ re-aim; add_engine untouched ⇒ PKG stays) · `.add/SEAMS.md` scope-token-grammar line pin (the guard inserts far above :5324 ⇒ x18 drift re-pin) · add.py+templates ×3 tooling twins (+ the 4th gitignored dogfood template tree) +Context (working folder): quality-floors MILESTONE.md lever 2; evidence = benchmark/results/2026-07-wv1-rep0.md wm2 (the lean arm's red suite spoke naive timestamps — friendlier than the spec's own Z-examples — and shipped the crash green); levers 1+3+4 landed at f9d2303 + bb72452 +Honors (patterns / conventions): validate-then-write (every freeze refusal fires before any byte lands) · absent-line grandfathering (legacy fast tasks + the full lane gain NO new refusal) · the unflagged_freeze error-code idiom (`: ... `) · template/engine edits propagate to every twin before the gate · sibling-suite pins amend only via a TESTS re-cross (task-2 playbook) +Anchors the contract cites: `cmd_freeze` validate block · `_phase_spans(text).get(1)` · `TASK.fast.md.tmpl` §1 `Boundary:` · `boundary_unfilled` · ENGINE_MD5 +Ground SHA: `bb72452` +Skip rationale: scenarios — one template line + one freeze guard, §1 Accept covers the three input variants; observe — one optional delta line at the gate + +--- + +## 1 · SPECIFY — the rules + +Feature: quality-floors lever 2 — the fast template's §1 gains a `Boundary:` line (≥1 format-variant per external input shape, or an explicit "none"), and freeze refuses a task whose Boundary value is still the bare template placeholder — the wm2 input-dialect floor applied at the fast lane's single approval seam +Must: + - TASK.fast.md.tmpl §1 gains, directly after the Accept: line: `Boundary: ` + - cmd_freeze refuses a task whose §1 carries a `Boundary:` line whose value is a bare unfenced `<...>` placeholder or empty — error code `boundary_unfilled`, fired in the validate block directly after unflagged_freeze, before any write, on BOTH freeze paths (human and --ai-plan-verify) + - a real value passes · an explicit `none — ...` value passes · a task with NO `Boundary:` line is grandfathered (legacy fast + full lane behavior byte-identical) + - ENGINE_MD5 re-aimed · SEAMS scope-token-grammar pin re-aimed (x18) · all twins byte-identical +Reject: + - freeze with a placeholder or empty Boundary value -> "boundary_unfilled" + - the guard firing on a task WITHOUT the line (full lane / legacy fast) -> "boundary_overreach" (design rejection: grandfathering IS the contract) + - any TASK.md/state.json byte written before the refusal -> "validate_then_write_violated" +Accept: Given a fast task whose §1 `Boundary:` value is still the bare template placeholder, When `add.py freeze` runs (either path), Then it exits nonzero naming `boundary_unfilled` and §3 stays DRAFT with zero bytes written — while the same task with a real (or explicit "none") value freezes normally, and a line-less legacy task freezes exactly as before +Boundary: the Boundary value itself is the external input — the suite speaks all three variants: bare `<...>` placeholder · real text · explicit `none — ...` +Assumptions: ⚠ fast-template scaffold/byte/lockstep pins in existing suites (test_fast_lane_skips scaffold hints · taskmd-lean ceilings · twin md5s) may pin the §1 span — if wrong: each red pin gets a TESTS re-cross amendment in ITS owning suite; cost: one re-cross loop (the task-2 playbook) + +--- + +## 3 · CONTRACT — freeze the shape + +``` +TASK.fast.md.tmpl / §1, directly after Accept:: + Boundary: +cmd_freeze validate block (directly after unflagged_freeze, BOTH paths): + §1 has a `Boundary:` line AND its value is empty or a bare unfenced <...> + -> exit nonzero: "boundary_unfilled: 's §1 Boundary: line still + carries the template placeholder — declare >=1 format-variant per + external input shape (or an explicit "none — ..."), then re-freeze" + no `Boundary:` line -> grandfathered, no new behavior (full lane / legacy fast) + real value or explicit none -> freeze proceeds unchanged +success: refusal fires before ANY write (§3 stays DRAFT, state.json untouched). +rejections: boundary_unfilled · boundary_overreach (design) · validate_then_write_violated (design). +``` + +`Least-sure flag surfaced at freeze:` [test] the placeholder-detection rule (empty or bare unfenced `<...>`) may misjudge exotic real values that legitimately start with `<` — mitigated by mirroring `_section_unfilled`'s backtick-fence exemption; if wrong: a legit freeze is refused with a clear repair hint (annoying, never unsafe) — cost: one re-freeze after quoting the value +Status: FROZEN @ v1 — approved by claude-fable-5 +Freeze mode: ai-plan-verify — verified by claude-fable-5 at 2026-07-10T18:09:30+00:00 + +### AI-verify record (required when gate_mode: ai-plan-verify) +- [x] §0 GROUND anchors resolve in the current tree — cmd_freeze validate chain + unflagged_freeze grepped at :1001, _phase_spans import live, TASK.fast.md.tmpl §1 Accept: line present, SEAMS pin at :5324, all at the Ground SHA +- [x] §1 every Must + every Reject present, each Reject paired with an error code (boundary_unfilled · boundary_overreach · validate_then_write_violated) +- [x] §3 CONTRACT shape is concrete — exact template line text + the guard's exact refusal message and placement +- [x] Lowest-confidence flag surfaced and substantive — placeholder-detection edge (legit `<`-leading values) with the mitigation and its bounded cost +Verified by: claude-fable-5 (session ee9aef91, orchestrator inline) · at: 2026-07-11T04:55:00Z + +--- + +## 4 · TESTS — failing-first (red) + +Plan: test_fast_boundary_line.py — test_fast_template_carries_boundary_line (scaffold, after Accept:) · test_freeze_refuses_placeholder_boundary (exit nonzero, names boundary_unfilled, §3 stays DRAFT, state.json byte-identical) · test_ai_plan_verify_path_also_refuses · test_real_value_freezes · test_explicit_none_freezes · test_absent_line_grandfathered. +Tests live in: `add-method/tooling/test_fast_boundary_line.py` · MUST run red (missing implementation) before Build. + +--- + +## 5 · BUILD — AI writes code + +Scope (may touch): `add-method/` `.add/SEAMS.md` `tmp/` +Strategy & known-problem fixes: 1. red suite 2. template line 3. cmd_freeze guard after unflagged_freeze 4. ENGINE_MD5 + SEAMS x18 re-aims 5. twins 6. full suite; traps: guard must read §1 via _phase_spans (never regex the whole file — a §3-fence mention of `Boundary:` must not trigger) · validate-then-write (refuse before the FROZEN stamp lands) · fast-template pins amend only via TESTS re-cross +Approach (domain strategy): methodology-engine-dev stance — a freeze-seam guard is a pure predicate over the §1 raw body + one precedence slot; obvious, correctness-first +Strategy actually used: as planned — guard in the shared validate block directly after unflagged_freeze (so it fires before the ai-path sensitivity check; two test_fast_lane_skips fixtures felt that precedence and were amended via TESTS re-cross to fill their Boundary lines); placeholder rule = empty or bare unfenced `<...>` with a backtick exemption, mirroring _section_unfilled; SEAMS x18 re-pin (:5337); twins ×3 + the 4th dogfood template tree synced. +Code lives in: `./src/` · Constraints: change no test, no contract; allow-list packages only. + +--- + +## 6 · VERIFY — evidence + gate + +- [x] all tests pass · coverage held · no test or contract altered during build — full tooling suite 3383 passed + 162 subtests, 0 failed; the 2 fixture amendments (test_fast_lane_skips) went through an explicit TESTS re-cross +- [x] green was EARNED — the red suite pins behavior through the live CLI (exit codes, error text, byte-identical state.json/TASK.md on refusal), not the guard's internals; the 5 pre-passing tests pin grandfathering as no-regression +- [x] input dialect held — tests speak the spec's example formats (spec-dialect floor): the suite exercises all three Boundary variants the §1 Boundary line declares (bare placeholder · real value · explicit none), and carries an aware Z-timestamp +- [x] no exposed secrets, injection openings, or unexpected dependencies — one validate-only guard, no new imports, no state writes added; ENGINE_MD5 re-aimed 16cd7cca, PKG unchanged + +Build expectations (from §1 Accept + §3 CONTRACT): a placeholder/empty §1 Boundary: value refuses BOTH freeze paths with `boundary_unfilled` and zero bytes written; real/none values and line-less legacy tasks freeze exactly as before; the fast template scaffolds the line after Accept: — confirmed by test_fast_boundary_line.py 9/9 + full suite 3383. + +### GATE RECORD +Outcome: PASS +Reviewed by: Tin Dang · date: 2026-07-11 + diff --git a/add-method/src/add_method/_bundled/tooling/add.py b/add-method/src/add_method/_bundled/tooling/add.py index 3e7f11b2..4502ca20 100644 --- a/add-method/src/add_method/_bundled/tooling/add.py +++ b/add-method/src/add_method/_bundled/tooling/add.py @@ -1000,6 +1000,19 @@ def cmd_freeze(args: argparse.Namespace) -> None: if not _flag_well_formed(raw3): _die(f"unflagged_freeze: {slug}'s §3 must surface a well-formed lowest-confidence flag " f"('Least-sure flag surfaced at freeze:' + a [part] tag) before it freezes") + # quality-floors lever 2 (fast-lane-boundary-line): a §1 `Boundary:` line still carrying + # the bare template placeholder (or empty) refuses the freeze — the wm2 input-dialect + # floor at the fast lane's single approval seam. Absent line = grandfathered (legacy + # fast tasks + the full lane gain no new refusal). Reads the §1 span only, first + # physical line of the declaration (the _declared_scope convention); a backtick-carrying + # value is exempt from the placeholder rule (_section_unfilled's fence exemption). + bnd = re.search(r"(?m)^Boundary:[ \t]*(.*)$", _phase_spans(text).get(1, "")) + if bnd is not None: + bval = bnd.group(1).strip() + if not bval or ("`" not in bval and re.fullmatch(r"<.*>", bval)): + _die(f"boundary_unfilled: {slug}'s §1 Boundary: line still carries the template " + f"placeholder — declare >=1 format-variant per external input shape " + f"(or an explicit \"none — ...\"), then re-freeze") # the human declares the risk-CLASS at freeze (risk-sensitivity-taxonomy): a present-but- # unknown sensitivity token is refused here (validate-then-write — nothing is written); # an absent token is grandfathered (allowed), a valid member proceeds. The engine never diff --git a/add-method/src/add_method/_bundled/tooling/engine_pin.py b/add-method/src/add_method/_bundled/tooling/engine_pin.py index 58c73d11..3743aba9 100644 --- a/add-method/src/add_method/_bundled/tooling/engine_pin.py +++ b/add-method/src/add_method/_bundled/tooling/engine_pin.py @@ -17,5 +17,5 @@ this file only ever holds the newest pointer. """ -ENGINE_MD5 = "7f96609e2ffb60fb0fcce33c4a763b96" # re-aimed @ spec-dialect-floor v1 (_dialect_gaps helper + tests->build dialect warning + check dialect_gap lint). prior: 5b0f0538… @ branch-merge 2026-07-10 +ENGINE_MD5 = "16cd7cca2754c31ae0ff147b14839bee" # re-aimed @ fast-lane-boundary-line v1 (cmd_freeze boundary_unfilled guard after unflagged_freeze). prior: 7f96609e… @ spec-dialect-floor ENGINE_PKG_MD5 = "710a009fd35e945f0d0143bcd59ee05c" # re-aimed @ spec-dialect-floor v1 (add_engine/constants.py: _DIALECT_CLASSES registry). prior: 5f60c0b2… @ fast-lane-skips v1 diff --git a/add-method/src/add_method/_bundled/tooling/templates/TASK.fast.md.tmpl b/add-method/src/add_method/_bundled/tooling/templates/TASK.fast.md.tmpl index 6a5e329a..48ad4390 100644 --- a/add-method/src/add_method/_bundled/tooling/templates/TASK.fast.md.tmpl +++ b/add-method/src/add_method/_bundled/tooling/templates/TASK.fast.md.tmpl @@ -32,6 +32,7 @@ Must: Reject: - -> "" Accept: +Boundary: Assumptions: ⚠ — why; if wrong: (or "none material — biggest risk: X") --- diff --git a/add-method/tooling/add.py b/add-method/tooling/add.py index 3e7f11b2..4502ca20 100644 --- a/add-method/tooling/add.py +++ b/add-method/tooling/add.py @@ -1000,6 +1000,19 @@ def cmd_freeze(args: argparse.Namespace) -> None: if not _flag_well_formed(raw3): _die(f"unflagged_freeze: {slug}'s §3 must surface a well-formed lowest-confidence flag " f"('Least-sure flag surfaced at freeze:' + a [part] tag) before it freezes") + # quality-floors lever 2 (fast-lane-boundary-line): a §1 `Boundary:` line still carrying + # the bare template placeholder (or empty) refuses the freeze — the wm2 input-dialect + # floor at the fast lane's single approval seam. Absent line = grandfathered (legacy + # fast tasks + the full lane gain no new refusal). Reads the §1 span only, first + # physical line of the declaration (the _declared_scope convention); a backtick-carrying + # value is exempt from the placeholder rule (_section_unfilled's fence exemption). + bnd = re.search(r"(?m)^Boundary:[ \t]*(.*)$", _phase_spans(text).get(1, "")) + if bnd is not None: + bval = bnd.group(1).strip() + if not bval or ("`" not in bval and re.fullmatch(r"<.*>", bval)): + _die(f"boundary_unfilled: {slug}'s §1 Boundary: line still carries the template " + f"placeholder — declare >=1 format-variant per external input shape " + f"(or an explicit \"none — ...\"), then re-freeze") # the human declares the risk-CLASS at freeze (risk-sensitivity-taxonomy): a present-but- # unknown sensitivity token is refused here (validate-then-write — nothing is written); # an absent token is grandfathered (allowed), a valid member proceeds. The engine never diff --git a/add-method/tooling/engine_pin.py b/add-method/tooling/engine_pin.py index 58c73d11..3743aba9 100644 --- a/add-method/tooling/engine_pin.py +++ b/add-method/tooling/engine_pin.py @@ -17,5 +17,5 @@ this file only ever holds the newest pointer. """ -ENGINE_MD5 = "7f96609e2ffb60fb0fcce33c4a763b96" # re-aimed @ spec-dialect-floor v1 (_dialect_gaps helper + tests->build dialect warning + check dialect_gap lint). prior: 5b0f0538… @ branch-merge 2026-07-10 +ENGINE_MD5 = "16cd7cca2754c31ae0ff147b14839bee" # re-aimed @ fast-lane-boundary-line v1 (cmd_freeze boundary_unfilled guard after unflagged_freeze). prior: 7f96609e… @ spec-dialect-floor ENGINE_PKG_MD5 = "710a009fd35e945f0d0143bcd59ee05c" # re-aimed @ spec-dialect-floor v1 (add_engine/constants.py: _DIALECT_CLASSES registry). prior: 5f60c0b2… @ fast-lane-skips v1 diff --git a/add-method/tooling/templates/TASK.fast.md.tmpl b/add-method/tooling/templates/TASK.fast.md.tmpl index 6a5e329a..48ad4390 100644 --- a/add-method/tooling/templates/TASK.fast.md.tmpl +++ b/add-method/tooling/templates/TASK.fast.md.tmpl @@ -32,6 +32,7 @@ Must: Reject: - -> "" Accept: +Boundary: Assumptions: ⚠ — why; if wrong: (or "none material — biggest risk: X") --- diff --git a/add-method/tooling/test_fast_boundary_line.py b/add-method/tooling/test_fast_boundary_line.py new file mode 100644 index 00000000..596c83b4 --- /dev/null +++ b/add-method/tooling/test_fast_boundary_line.py @@ -0,0 +1,219 @@ +#!/usr/bin/env python3 +"""Red/green tests for the fast-lane Boundary: line (task fast-lane-boundary-line, +frozen contract v1, quality-floors lever 2): the fast template's §1 gains a +`Boundary:` line (>=1 format-variant per external input shape, or an explicit +"none"), and cmd_freeze refuses a task whose Boundary value is still the bare +template placeholder — `boundary_unfilled`, fired after unflagged_freeze in the +validate block, before any write, on BOTH freeze paths. A task with NO Boundary: +line is grandfathered (legacy fast + full lane behavior byte-identical). + +Evidence class (benchmark WV1 wm2): the lean arm's red suite spoke naive +timestamps — a friendlier dialect than the spec's own Z-suffixed examples — +and shipped the aware/naive crash green. The Boundary line makes the input- +format variants an explicit, freeze-checked declaration at the fast lane's +single approval seam. + +Run: python3 -m unittest test_fast_boundary_line -v +""" +import io +import json +import os +import re +import shutil +import tempfile +import unittest +from contextlib import redirect_stderr, redirect_stdout +from pathlib import Path + +import add + +HERE = Path(__file__).resolve().parent +TEMPLATES = HERE / "templates" + +PLACEHOLDER_BOUNDARY = ("Boundary: ') +REAL_BOUNDARY = "Boundary: aware (Z-suffixed) vs naive timestamp on booking.start_time" +NONE_BOUNDARY = "Boundary: none — no external input" + + +def _section(n: int, name: str, *body: str) -> list[str]: + return [f"## {n} · {name}", *body, ""] + + +class _Board(unittest.TestCase): + """A live board through the real CLI — the test_spec_dialect_floor idiom, + duplicated per this repo's one-harness-per-file norm.""" + + def setUp(self): + self._cwd = Path.cwd() + self.tmp = Path(tempfile.mkdtemp(prefix="add-boundary-line-")).resolve() + self.addCleanup(shutil.rmtree, self.tmp, ignore_errors=True) + self.addCleanup(os.chdir, self._cwd) + os.chdir(self.tmp) + self._silent("init", "--name", "demo") + self._silent("new-milestone", "v1", "--title", "T", "--goal", "g") + + def _silent(self, *argv): + buf, err = io.StringIO(), io.StringIO() + with redirect_stdout(buf), redirect_stderr(err): + add.main(list(argv)) + return buf.getvalue(), err.getvalue() + + def _run(self, *argv): + buf, err = io.StringIO(), io.StringIO() + code = 0 + try: + with redirect_stdout(buf), redirect_stderr(err): + add.main(list(argv)) + except SystemExit as e: + code = e.code if isinstance(e.code, int) else 1 + return buf.getvalue(), err.getvalue(), code + + def _root(self) -> Path: + return self.tmp / ".add" + + def _task_md(self, slug: str) -> Path: + return self._root() / "tasks" / slug / "TASK.md" + + def _write_task(self, slug: str, boundary_line: str | None, *, oneshot=False): + spec1 = ["Feature: f", "Must:", " - m", "Accept: Given g, When w, Then t"] + if boundary_line is not None: + spec1.append(boundary_line) + header = [f"# TASK: {slug}", + f"slug: {slug} · created: 2026-07-11 · stage: mvp", + "fast: true"] + if oneshot: + header += ["oneshot: true", "gate_mode: ai-plan-verify"] + sec3 = ["```", "shape: f(x) -> ok · bad -> err", "```", + "Least-sure flag surfaced at freeze: [contract] narrow " + "shape — cost: one re-freeze.", + "Status: DRAFT"] + if oneshot: + sec3 += ["", "### AI-verify record (required when gate_mode: ai-plan-verify)", + "- [x] §0 GROUND anchors resolve in the current tree", + "- [x] §1 every Must + every Reject present, each Reject paired with an error code", + "- [x] §3 CONTRACT shape is concrete (no template placeholder text remains)", + "- [x] Lowest-confidence flag surfaced and substantive (mirrors unflagged_freeze's own bar)", + "Verified by: test-agent · at: 2026-07-11T00:00:00Z"] + lines = [ + *header, + "phase: ground", + "", + *_section(0, "GROUND", "Anchors the contract cites: cmd_advance"), + *_section(1, "SPECIFY", *spec1), + *_section(2, "SCENARIOS", "(none)"), + *_section(3, "CONTRACT", *sec3), + *_section(4, "TESTS", "Tests live in: `./tests/`"), + *_section(5, "BUILD", + "Scope (may touch): `src/`", + "Code lives in: `./src/`"), + *_section(6, "VERIFY", "checks"), + *_section(7, "OBSERVE", "watch"), + ] + self._task_md(slug).write_text("\n".join(lines), encoding="utf-8") + + def _make_at_contract(self, slug: str, boundary_line: str | None): + self._silent("new-task", slug, "--title", slug) + self._write_task(slug, boundary_line) + self._silent("phase", "contract", slug) + + def _freeze(self, slug: str, *extra): + return self._run("freeze", slug, "--by", "Tester", *extra) + + +# ── M1: the fast template scaffold carries the line ───────────────────────── +class TemplateScaffoldTest(unittest.TestCase): + def test_fast_template_carries_boundary_line_after_accept(self): + body = (TEMPLATES / "TASK.fast.md.tmpl").read_text(encoding="utf-8") + self.assertIn("\nBoundary: <", body, "§1 must scaffold a Boundary: line") + acc = body.find("\nAccept: ") + bnd = body.find("\nBoundary: ") + self.assertGreater(bnd, acc, "Boundary: rides directly after Accept:") + line = re.search(r"(?m)^Boundary: .*$", body).group(0) + self.assertIn("format-variant", line) + self.assertIn("none — no external input", line) + + +# ── M2: a placeholder Boundary refuses the freeze, nothing written ─────────── +class PlaceholderRefusedTest(_Board): + def test_freeze_refuses_placeholder_boundary(self): + self._make_at_contract("t", PLACEHOLDER_BOUNDARY) + state_before = (self._root() / "state.json").read_bytes() + md_before = self._task_md("t").read_bytes() + out, err, code = self._freeze("t") + self.assertNotEqual(code, 0, "a placeholder Boundary must refuse the freeze") + self.assertIn("boundary_unfilled", out + err) + self.assertEqual((self._root() / "state.json").read_bytes(), state_before, + "validate-then-write: state.json must be untouched") + self.assertEqual(self._task_md("t").read_bytes(), md_before, + "validate-then-write: §3 must stay DRAFT, zero bytes written") + + def test_empty_boundary_value_also_refused(self): + self._make_at_contract("t", "Boundary:") + out, err, code = self._freeze("t") + self.assertNotEqual(code, 0) + self.assertIn("boundary_unfilled", out + err) + + def test_ai_plan_verify_path_also_refuses(self): + # the guard sits in the shared validate block, before the ai branch — + # an AI freeze may not slip a placeholder Boundary past the floor + self._silent("new-task", "t", "--title", "t") + self._write_task("t", PLACEHOLDER_BOUNDARY, oneshot=True) + self._silent("phase", "contract", "t") + out, err, code = self._run("freeze", "t", "--ai-plan-verify", + "--by", "test-agent") + self.assertNotEqual(code, 0) + self.assertIn("boundary_unfilled", out + err) + + def test_ai_plan_verify_path_freezes_real_value(self): + self._silent("new-task", "t", "--title", "t") + self._write_task("t", REAL_BOUNDARY, oneshot=True) + self._silent("phase", "contract", "t") + out, err, code = self._run("freeze", "t", "--ai-plan-verify", + "--by", "test-agent") + self.assertEqual(code, 0, f"a real value must freeze on the ai path: {out+err}") + self.assertIn("Freeze mode: ai-plan-verify", + self._task_md("t").read_text(encoding="utf-8")) + + +# ── M3: real and explicit-none values freeze normally ──────────────────────── +class RealValueFreezesTest(_Board): + def test_real_value_freezes(self): + self._make_at_contract("t", REAL_BOUNDARY) + out, err, code = self._freeze("t") + self.assertEqual(code, 0, f"a real Boundary value must freeze: {out+err}") + self.assertIn("FROZEN @ v1", self._task_md("t").read_text(encoding="utf-8")) + + def test_explicit_none_freezes(self): + self._make_at_contract("t", NONE_BOUNDARY) + _, _, code = self._freeze("t") + self.assertEqual(code, 0, "an explicit none declaration must freeze") + + +# ── M4: no Boundary line = grandfathered (full lane / legacy fast) ──────────── +class GrandfatherTest(_Board): + def test_absent_line_grandfathered(self): + self._make_at_contract("t", None) + out, err, code = self._freeze("t") + self.assertEqual(code, 0, "a line-less task must freeze exactly as before") + self.assertNotIn("boundary_unfilled", out + err) + + def test_boundary_mention_inside_section_3_fence_never_triggers(self): + # the guard reads the §1 span only — a §3 code-fence line that happens + # to start with `Boundary:` must not be mistaken for the declaration + self._silent("new-task", "t", "--title", "t") + self._write_task("t", None) + md = self._task_md("t") + md.write_text(md.read_text(encoding="utf-8").replace( + "shape: f(x) -> ok · bad -> err", + "shape: f(x) -> ok · bad -> err\nBoundary: "), + encoding="utf-8") + self._silent("phase", "contract", "t") + out, err, code = self._freeze("t") + self.assertEqual(code, 0, f"§3 fence text must not trigger the guard: {out+err}") + self.assertNotIn("boundary_unfilled", out + err) + + +if __name__ == "__main__": + unittest.main() diff --git a/add-method/tooling/test_fast_lane_skips.py b/add-method/tooling/test_fast_lane_skips.py index 73c006aa..b707d990 100644 --- a/add-method/tooling/test_fast_lane_skips.py +++ b/add-method/tooling/test_fast_lane_skips.py @@ -114,6 +114,16 @@ def _state(self): def _task_md(self, slug): return self.tmp / ".add" / "tasks" / slug / "TASK.md" + def _fill_boundary(self, slug): + # boundary floor (fast-lane-boundary-line): the rendered fast template now + # scaffolds a placeholder `Boundary:` line that refuses the freeze — fill it + # with a real declaration, as any real task must before its freeze + p = self._task_md(slug) + t = p.read_text(encoding="utf-8") + t = re.sub(r"(?m)^Boundary: <[^\n]*$", + "Boundary: aware vs naive timestamp on the request payload", t, count=1) + p.write_text(t, encoding="utf-8") + def _set_header(self, slug, **kv): """Insert/replace autonomy:/fast:/oneshot:/skips: header lines.""" p = self._task_md(slug) @@ -330,6 +340,7 @@ def _set_section3_and_freeze(self, slug): new = re.sub(r"(## 3 · CONTRACT[^\n]*\n).*?(\n---)", lambda m: m.group(1) + body + m.group(2), text, count=1, flags=re.S) p.write_text(new, encoding="utf-8") + self._fill_boundary(slug) self._silent("freeze", "--by", "Human") def test_six_non_skippable_crossings_never_invoke_task_skip_set(self): @@ -598,6 +609,7 @@ def test_oneshot_security_task_skip_fires_but_freeze_stays_human(self): new = re.sub(r"(## 3 · CONTRACT[^\n]*\n).*?(\n---)", lambda m: m.group(1) + body + m.group(2), text, count=1, flags=re.S) p.write_text(new, encoding="utf-8") + self._fill_boundary("risky") out, code = self._run("freeze", "--ai-plan-verify", "--by", "agent:x") self.assertNotEqual(code, 0) self.assertIn("ai_freeze_blocked_sensitivity", out) From ad950c287940d938b3329b26af64f3f43acb9dbc Mon Sep 17 00:00:00 2001 From: Tin Dang Date: Sat, 11 Jul 2026 05:50:56 +0700 Subject: [PATCH 4/4] chore(method): close + archive quality-floors milestone (3/3 floors shipped) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit All 4 human-picked input-dialect floors from the WV1 wm2 miss are live and gated PASS: 1. spec-dialect floor (f9d2303) — tests->build dialect_gap warning 2. fast-lane Boundary line (b2483be) — freeze refuses boundary_unfilled 3. §6 dialect check line (bb72452) — Deep-checks DIALECT bullet 4. datetime/money/tz => data (bb72452) — sensitivity vocabulary milestone-done wrote RETRO.md; archive-milestone removed it from active state (files on disk untouched). Balance principle held: every floor is a ~zero-turn mechanical check at an existing seam — the lean lane keeps its cost, the failure class is closed without re-added ceremony. One SPEC delta left open (task 2 gate record): lever 1's _dialect_gaps scans the §3 raw body incl. the engine's own freeze timestamp, a self-referential false positive found dogfooding — candidate future task. refs: quality-floors milestone author: Tin Dang --- .add/milestones/quality-floors/RETRO.md | 35 +++++ .add/state.json | 197 ++---------------------- 2 files changed, 51 insertions(+), 181 deletions(-) create mode 100644 .add/milestones/quality-floors/RETRO.md diff --git a/.add/milestones/quality-floors/RETRO.md b/.add/milestones/quality-floors/RETRO.md new file mode 100644 index 00000000..93e35008 --- /dev/null +++ b/.add/milestones/quality-floors/RETRO.md @@ -0,0 +1,35 @@ +════════════════════════════════════════════════════════════════════════ + quality-floors · Quality floors for lean ADD +════════════════════════════════════════════════════════════════════════ + VERDICT DONE + TASKS 3/3 done CRITERIA 0/0 met + GATES 3 PASS WAIVERS none + + goal convert the WV1 wm2 miss (own tests spoke a friendlier input + dialect than the spec's own examples → aware/naive datetime crash + shipped green) into narrow, ~zero-turn floors — the lean lane + keeps its −27–33% cost while the failure CLASS is closed; quality + is bought with mechanical floors, never with re-added ceremony + closed by Tin Dang + + TASK PHASE GATE TESTS PROGRESS + ─────────────────────────────────────────────────────────────────────── + spec-dialect-floor done PASS 6† ●●●●●●●●● + dialect-check-and-data-voc… done PASS 6† ●●●●●●●●● + fast-lane-boundary-line done PASS 9† ●●●●●●●●● + legend ● reached ◉ current ○ pending spec→…→done + † counted at the §4-declared path + + GATED BY + spec-dialect-floor PASS Tin Dang + dialect-check-and-data-… PASS Tin Dang + fast-lane-boundary-line PASS Tin Dang + + EXIT CRITERIA ○○○○○○○○○○ 0/0 met + + LEARNINGS none + + SPEC DELTAS 27 open deltas — resolve: new-task --from-delta / drop-delta + + DECIDE NEXT consolidate learnings + archive-milestone quality-floors +════════════════════════════════════════════════════════════════════════ \ No newline at end of file diff --git a/.add/state.json b/.add/state.json index 72bf0e08..291bfb1a 100644 --- a/.add/state.json +++ b/.add/state.json @@ -1,7 +1,7 @@ { "project": "AIDD / ADD Methodology", "stage": "mvp", - "active_task": "fast-lane-boundary-line", + "active_task": "persona-nudge-quiet", "tasks": { "add-check": { "title": "add.py check: validate .add project integrity", @@ -7544,167 +7544,10 @@ "email": "tindang.ht97@gmail.com", "source": "git" } - }, - "spec-dialect-floor": { - "title": "Spec-dialect test floor \u2014 the red suite must speak the contract's own example formats", - "phase": "done", - "gate": "PASS", - "milestone": "quality-floors", - "depends_on": [], - "created": "2026-07-10T17:23:03+00:00", - "updated": "2026-07-10T17:35:35+00:00", - "fast": true, - "oneshot": true, - "sensitivity": "mechanical", - "freeze": { - "version": "v1", - "frozen_at": "2026-07-10T17:25:01+00:00", - "approved_by": "claude-fable-5", - "actor": { - "name": "Tin Dang", - "email": "tindang.ht97@gmail.com", - "source": "git" - }, - "mode": "ai-plan-verify", - "verified": { - "anchors": true, - "rules": true, - "shape": true, - "flag": true - } - }, - "flag_verified": true, - "tripwire": { - "contract_md5": "bb6ca390b82c00e0c78bc172cba48d0e", - "tests": { - "add-method/tooling/test_spec_dialect_floor.py": "ddf7827340edae4fdcf9d7ec0eae4e80" - } - }, - "scope": { - "declared": [ - "add-method/", - ".add/tooling/", - "tmp/" - ], - "snapshot_md5": "dac96fce7cf5666b44dace54ceda013e" - }, - "heal": { - "attempts": 1, - "history": [ - { - "at": "2026-07-10T17:35:08+00:00", - "reason": "tamper_detected:build_tampered:add-method/tooling/add.py,build_tampered:add-method/tooling/engine_pin.py", - "source": "tamper" - } - ] - }, - "gate_actor": { - "name": "Tin Dang", - "email": "tindang.ht97@gmail.com", - "source": "git" - } - }, - "dialect-check-and-data-vocab": { - "title": "\u00a76 dialect check line + datetime/money/tz \u21d2 data sensitivity vocabulary", - "phase": "done", - "gate": "PASS", - "milestone": "quality-floors", - "depends_on": [], - "created": "2026-07-10T17:36:22+00:00", - "updated": "2026-07-10T18:05:06+00:00", - "fast": true, - "oneshot": true, - "sensitivity": "mechanical", - "freeze": { - "version": "v1", - "frozen_at": "2026-07-10T17:38:23+00:00", - "approved_by": "claude-fable-5", - "actor": { - "name": "Tin Dang", - "email": "tindang.ht97@gmail.com", - "source": "git" - }, - "mode": "ai-plan-verify", - "verified": { - "anchors": true, - "rules": true, - "shape": true, - "flag": true - } - }, - "flag_verified": true, - "tripwire": { - "contract_md5": "7643468103648bf38a55fab4c7c302b8", - "tests": { - "add-method/tooling/test_dialect_vocab_lines.py": "a5792651f844600977d5117eb8046335" - } - }, - "scope": { - "declared": [ - "add-method/", - ".claude/", - ".add/GLOSSARY.md", - "tmp/" - ], - "snapshot_md5": "cbc86c6b27748b7cb2d14e4a7d1e55c0" - }, - "gate_actor": { - "name": "Tin Dang", - "email": "tindang.ht97@gmail.com", - "source": "git" - } - }, - "fast-lane-boundary-line": { - "title": "Fast-lane Boundary: line \u2014 the fast freeze refuses an undeclared input-format boundary", - "phase": "done", - "gate": "PASS", - "milestone": "quality-floors", - "depends_on": [], - "created": "2026-07-10T18:07:42+00:00", - "updated": "2026-07-10T18:21:21+00:00", - "fast": true, - "oneshot": true, - "freeze": { - "version": "v1", - "frozen_at": "2026-07-10T18:09:30+00:00", - "approved_by": "claude-fable-5", - "actor": { - "name": "Tin Dang", - "email": "tindang.ht97@gmail.com", - "source": "git" - }, - "mode": "ai-plan-verify", - "verified": { - "anchors": true, - "rules": true, - "shape": true, - "flag": true - } - }, - "flag_verified": true, - "tripwire": { - "contract_md5": "a91b41247fee60972018aa10a1eb095e", - "tests": { - "add-method/tooling/test_fast_boundary_line.py": "6896b88704e4568d91fb31e5809b3d4c" - } - }, - "scope": { - "declared": [ - "add-method/", - ".add/SEAMS.md", - "tmp/" - ], - "snapshot_md5": "a020c8e5810621aa5e5a9394aa3820cb" - }, - "gate_actor": { - "name": "Tin Dang", - "email": "tindang.ht97@gmail.com", - "source": "git" - } } }, "created": "2026-05-28T15:39:04+00:00", - "updated": "2026-07-10T18:21:21+00:00", + "updated": "2026-07-10T22:50:47+00:00", "milestones": { "v1-1": { "title": "v1.1 \u2014 adoption & ergonomics", @@ -8024,26 +7867,9 @@ "email": "tindang.ht97@gmail.com", "source": "git" } - }, - "quality-floors": { - "title": "Quality floors for lean ADD", - "goal": "", - "stage": "mvp", - "status": "active", - "created": "2026-07-10T17:12:27+00:00", - "updated": "2026-07-10T17:12:59+00:00", - "tiny": true, - "confirmed": true, - "confirmed_at": "2026-07-10T17:12:59+00:00", - "confirmed_by": "tindang", - "actor": { - "name": "Tin Dang", - "email": "tindang.ht97@gmail.com", - "source": "git" - } } }, - "active_milestone": "quality-floors", + "active_milestone": "add-lean-loop", "archived": [ { "slug": "v5", @@ -8858,6 +8684,17 @@ "skip-error-ergonomics" ], "archived": "2026-07-11" + }, + { + "slug": "quality-floors", + "title": "Quality floors for lean ADD", + "tasks": 3, + "task_slugs": [ + "spec-dialect-floor", + "dialect-check-and-data-vocab", + "fast-lane-boundary-line" + ], + "archived": "2026-07-11" } ], "active_milestones": [ @@ -8866,8 +8703,7 @@ "build-strategy-facets", "delta-drain", "add-bench", - "add-lean-loop", - "quality-floors" + "add-lean-loop" ], "active_tasks": { "flow-simplification": "phase-review", @@ -8877,8 +8713,7 @@ "delta-drain": "compact-propose", "add-bench": "bench-regression-split", "add-lean-loop": "persona-nudge-quiet", - "add-bench-2": "tiny-plan-small-scope", - "quality-floors": "fast-lane-boundary-line" + "add-bench-2": "tiny-plan-small-scope" }, "todos": [ {