Task — engineering-spec@1

A declared cone is a promise nothing checks

doneTASK-IMP-119
module improvement · class product · priority p1 · created · shipped null
depends on none · blocks none

TASK-IMP-119 - a declared cone is a promise nothing checks

Summary

Audit a task's ACTUAL writes against its DECLARED cone at the implementing -> ready_to_review flip, and report every escape. batch-select plans parallelism from cones; nothing verifies them afterwards, so the safety argument for every swarm rests on an unchecked input.

Problem

batch-select proves two tasks are independent by intersecting their cones (new_files ∪ modified_files ∪ service). That proof is only as true as the cones. Nothing compares a cone to what the task actually wrote.

Measured on the 2026-07-17 batch (TASK-IMP-110 + TASK-IMP-114), three files escaped BOTH declared cones:

fileownerwhy it escaped
tools/install/docs-tools/workflow-improve.mjs110the cone declared a SKILL.md and a test; §1.4 says "the tool MUST NOT write" and AC 1-6 are shell-tested behaviour. Prose cannot satisfy them. The executable was never declared.
tools/install/tests/test_full_sdp_payload.sh110hard-asserts 53 vendored skills; 110's DECLARED build.sh edit makes it 54. An undeclared file broken by a declared change.
tools/docs-site/tests/test_render_status_hub.sh114AC 3 and AC 5 both cite it; modified_files omits it.

None collided, so the batch was safe by luck rather than by proof. test_batch_economics.sh (114, inside tools/install) would have raced TASK-IMP-106 (service: tools/install) had 106 been in the batch - and batch-select could not have known, because 114's cone said modules/cuo.

This is the third level of one defect, all found the same day:

  1. TASK-IMP-104: declared install.sh, edited two more files inside its service. Fix: fold service into the cone.
  2. This afternoon: an undeclared cone is the EMPTY SET, which intersects nothing, so a silent spec was provably independent of everything and joined every batch. Fix: fail closed, ship alone.
  3. This task: a DECLARED cone that does not match the writes. Nothing looks.

Levels 1 and 2 widened the net. This one closes the loop: it checks the input the whole mechanism trusts.

Proposed Solution

docs-tools/cone-audit.mjs: given a task id and a base ref, diff git diff --name-only <base>..HEAD against the task's declared cone and report every path outside it. REPORT, not refuse - a write discovered mid-implementation is often a real finding (all three of today's were), and the remedy is to amend the cone, which is a spec edit and therefore a human's call.

Wire it into ship-tasks at the implementing -> ready_to_review flip, where the diff exists and the spec is still open for amendment.

Alternatives Considered

Success Metrics

AI Authorship Disclosure

Dependencies

None. batch-select (TASK-IMP-104, v2.8.0) already parses cones; relUnderRoot already exists in docs-tools. This adds a reader, no new guard and no new rule.

Scope

In scope: docs-tools/cone-audit.mjs, its suite, the build.sh vendor entry, and the ship-tasks §11a wiring note at the implementing -> ready_to_review flip.

Out of scope / Non-Goals

1. Clauses

1.1 cone-audit.mjs <task-id> [--base <ref>] MUST read the task's declared cone from frontmatter (new_files ∪ modified_files ∪ service) and the actual writes from git diff --name-only, and report every written path not inside the cone. Test: t01_escape_is_named

1.2 A path is INSIDE the cone if it equals a declared entry or is nested under one. This MUST match batch-select's own containment rule exactly - two tools disagreeing about what a cone contains is worse than neither existing. Test: t02_containment_matches_batch_select

1.3 An UNDECLARED cone MUST report every write as an escape, not zero. Empty is not "contains everything" any more than it is "contains nothing" - it is unknown, and batch-select already refuses it. Test: t03_undeclared_cone_escapes_everything

1.4 (none) MUST be filtered from the cone, matching batch-select:51. A literal placeholder is not a path. Test: t04_placeholder_is_not_a_path

1.5 The tool MUST NOT write, refuse, or flip anything. It reports and exits 0 with escapes, 2 on usage. An escape is a FINDING for a human, not a failure. Test: t05_reports_never_refuses

1.6 Deterministic: same repo state + same args = byte-identical output. No wall clock in the artefact. Test: t06_deterministic

1.7 A path outside the repo root, or an unreadable spec, MUST be refused and named - never silently skipped. Test: t07_guard_refuses_and_names

3. Edge case matrix

#CategoryTriggerExpectedTest
1NULL/EMPTYtask wrote nothingzero escapes, exit 0t01
2NULL/EMPTYcone declared, diff emptyzero escapest01
3NULL/EMPTYcone entirely absentevery write is an escapet03
4BOUNDSwrite exactly equals a declared fileinsidet02
5BOUNDSwrite nested under a declared serviceinsidet02
6BOUNDSdeclared file, write is its PARENT direscape - narrower does not cover widert02
7MALFORMED(none) in new_filesfiltered, not treated as a patht04
8MALFORMEDcone entry with a trailing slashnormalised, matches batch-selectt02
9MALFORMEDrenamed file (git R status)both old and new path consideredt01
10CONCURRENTrun mid-swarm while a sibling writesreports only THIS task's diff vs baset06
11SECURITYtask-id resolves outside the repoREFUSED, named, not executedt07
12SECURITYspec path is a symlink out of the corpusREFUSEDt07
13DEGRADATIONgit absent / base ref missingrefuse and say so; never report zero escapest07
14DEGRADATIONspec unreadablerefuse; unreadable is not "clean"t07

4. Out of scope / non-goals

See "## Scope -> ### Out of scope / Non-Goals" above.

Acceptance criteria

Audit

Audit - TASK-IMP-119

Machine floor first per TASK-IMP-084: 0 errors. TRACE-001 info is the ## 1. Clauses heading shape, as with 117/118.

Findings

ISS-001 (info, accepted): TRACE-001 heading shape. 1.1-1.7 each cite a test; AC1-AC7 each cite back.

ISS-002 (accepted, load-bearing): AC7 requires the tool to name EXACTLY the three files that escaped on 2026-07-17 and no others. The cone data in §Problem is measured - the parent ran the diff against both declared cones mechanically - so this AC is checkable against a real commit range, not a fixture. A tool that cannot find the case that motivated it is decoration; this AC is what stops that.

ISS-003 (accepted, and the sharpest choice here): 1.5 REPORTS rather than refuses. That looks weaker than the fail-closed posture used everywhere else today (the version guard, relUnderRoot, batch-select on undeclared cones). It is the right call for a different reason: all three of 2026-07-17`"'s escapes were CORRECT discoveries about WRONG specs. A hard block would have stopped three good implementations to enforce three bad cones. §Alternatives records that a refusal is revisitable once the escape RATE is known - and makes that a §11d question, which is honest about what evidence the decision needs.

ISS-004 (accepted): §Alternatives rejects deriving the cone from the diff, on the grounds that a cone which cannot be wrong makes batch-select"s