Task — engineering-spec@1

CI truth - CAF evals in root CI, hook-claim honesty, stub workflow sweep

doneTASK-IMP-136
module improvement · class product · priority p1 · created 2026-07-23 · shipped null
depends on none · blocks none

TASK-IMP-136: CI truth - CAF evals in root CI, hook-claim honesty, stub workflow sweep

Summary

Three CI surfaces claim enforcement that does not happen: the CAF eval suite (40 fixtures + a validator) runs in no CI because its workflow sits nested under tools/caf/.github/ where GitHub never reads it; .pre-commit-config.yaml declares an awh gate hook for a framework the repo does not use (core.hooksPath=.githooks is the real mechanism, and it runs no awh hook); and 9 auto-generated stub workflows report green on every PR while their only step is an echo. This task wires the CAF evals + caf_precommit_check.sh into a real root workflow, makes the hook claims honest (wire awh into .githooks/pre-commit, drop the dead framework config), and sweeps the 9 stubs (delete by default, implement where the declaring spec's embedded YAML is complete) with a stub-honesty check so the class cannot regrow.

Problem

Audit finding H9, all verified first-hand 2026-07-23:

  1. CAF evals are dead in CI. tools/caf/core/evals/validate.py --all validates 40 audit fixtures; the only workflow that runs it lives at tools/caf/.github/workflows/evals.yml - a nested .github directory GitHub Actions never evaluates. scripts/caf_precommit_check.sh exists and is wired into nothing. The CAF gate is a load-bearing step (ship-tasks step 29); its own regression suite runs on no machine but a developer's, voluntarily.
  2. The pre-commit claims are split-brain. .pre-commit-config.yaml tells a reader that awh-gate, payload-build, and docs-build run at commit time via the pre-commit framework. The repo's real hook path is .githooks/ (set by core.hooksPath); its pre-commit script covers payload-build and docs-build directly but never runs .pre-commit-hooks/awh-gate.sh. Result: the awh commit-time gate exists only as a claim in a file no tool reads.
  3. Nine always-green stubs. Auto-generated 2026-05-17 from task build_envelope references, each with a single echo step. Several declaring tasks (TASK-AI-013, TASK-AI-015, TASK-MEMORY-102...) are done - shipped work whose acceptance story includes a CI gate that gates nothing. A green check that checks nothing is strictly worse than a missing check: it terminates the reader's investigation at a lie.

Proposed Solution

Add .github/workflows/caf-evals-gate.yml running python3 core/evals/validate.py --all (from tools/caf/) plus bash scripts/caf_precommit_check.sh on PRs touching tools/caf/** or scripts/caf_* and on a weekly schedule (drift net). Wire .pre-commit-hooks/awh-gate.sh into .githooks/pre-commit behind the same staged-paths pattern the other blocks use, then delete .pre-commit-config.yaml (reason recorded: dead config whose every live claim is now covered by the real hook; keeping it preserves a second, contradictory hook story). Sweep the 9 stubs with a per-file disposition table in the implementation PR: default DELETE (each deletion names its declaring task in the commit body so the gap stays discoverable); implement instead where the declaring task's spec embeds canonical YAML that is complete and whose runtime dependencies exist today. Add scripts/tests/test_ci_truth.sh asserting: a root workflow invokes validate.py --all; a root workflow invokes caf_precommit_check.sh; no root workflow contains the stub placeholder marker; and .pre-commit-config.yaml either does not exist or every hook it names is invoked by .githooks/pre-commit (the regrowth guard). CHANGELOG entry records the sweep and the deleted file.

Alternatives Considered

Success Metrics

Scope

In scope: the new root CAF workflow, awh hook wiring, .pre-commit-config.yaml removal, the 9-stub sweep with disposition table, the test_ci_truth.sh regrowth guard, CHANGELOG.

Out of scope / Non-Goals

Dependencies

None blocking. TASK-IMP-128 (draft) owns the run_all.sh CI job this task deliberately does not duplicate. TASK-IMP-140 (benchmark gates) adopts this task's test_ci_truth.sh as the G14 checker - soft forward reference via related_tasks, no cycle. The stub declarers (TASK-AI-013/015/018, TASK-MEMORY-102, TASK-OBS-005, TASK-PROJ-018, TASK-REW-010) are listed in related_tasks because each deletion cites its declarer; none of their statuses change here.

AI Authorship Disclosure

1. Description (normative)

2. Acceptance criteria

3. Edge cases

Audit

§1 — Verdict summary

Six §1 clauses, six ACs, six edge cases including a security-class row and an Operator-steps row (branch protection). The heavy audit pressure was on scope honesty (the run_all.sh CI job already belongs to TASK-IMP-128), deletion discipline (never-delete-without-reason), and making the stub sweep a per-file recorded judgment rather than a blanket rm.

§2 — Findings (all resolved)

ISS-001 — plan bullet included a job an existing draft task already owns

The plan's T4 says "root CI job for scripts/tests/run_all.sh"; repo inspection found TASK-IMP-128 (draft, p1, authored 2026-07-20) specifies exactly that job with its own test file. Duplicating it here would create two specs claiming one deliverable — the two-tasks-one-name corruption the id-allocation contract exists to prevent. Resolved: scoped out in Non-Goals with the reference; recorded as a plan-vs-repo adjustment in source_decisions; related_tasks carries TASK-IMP-128.

ISS-002 — stub deletion originally lacked the per-file judgment record

A blanket "delete the 9 stubs" violates the never-delete-without-stated-reason rule and erases the trail from each declaring task (several done) to its unbuilt gate. Resolved: clause 1.4 requires a 9-row disposition table in the implementation PR, deletion commits naming file + declaring task, and an implement-override rule bounded by two testable conditions (embedded YAML complete AND dependencies exist).

ISS-003 — the regrowth guard was missing from the first draft

Deleting today's stubs leaves nothing preventing tomorrow's — the generator pattern ("auto-generated from task build_envelope references") could re-emit them. Resolved: clause 1.5(c) makes the placeholder marker itself a test failure; edge case names this as the regrowth guard and states the honest future path (real YAML or nothing).

ISS-004 — awh hook wiring could reintroduce the documented SIGPIPE pitfall

.githooks/pre-commit's header documents the exact git diff --cached --name-only | grep -Eq failure (grep -q SIGPIPE under pipefail silently skipping blocks). A wiring instruction that didn't name the idiom would invite the regression the hook's own comments warn about. Resolved: clause 1.2 mandates the matches() herestring idiom by name; AC 2 asserts no pipeline form is used.

ISS-005 — .pre-commit-config.yaml removal needed a veto path

File deletion is review-sensitive; a reviewer may prefer keeping the file for contributors who run the framework standalone. Resolved: clause 1.3 records the authored default (remove) plus the explicit fallback state (non-authoritative header) so the review gate decides between two specified states rather than an unspecified middle.

ISS-006 — branch-protection interaction was unaddressed

Deleting a workflow whose check name is required by branch protection wedges every PR — a server-side effect no repo file shows. Resolved: edge case adds Operator steps (query protection via gh api, confirm none of the 9 names is required, remove first if so) per ship-tasks' in-task operator-guideline rule.

ISS-007 — CAF eval sharding could quietly subset the fixture corpus

"Shard if slow" without a floor invites running 10 of 40 fixtures and calling it green. Resolved: edge case pins "MUST NOT subset silently: all 40 run per gate invocation".

§3 — TRACE-006 semantic sufficiency (per clause)

ClauseVerb demandCited test assertsVerdict
1.1 MUST run both commands on PR paths + schedule; failures fail the jobworkflow content (commands, triggers, schedule) + a real broken-fixture failureAC 1: asserts file content AND the scratch-branch negative runsufficient
1.2 MUST invoke awh-gate via matches() idiominvocation present + idiom form + non-trigger on unrelated stagingAC 2: asserts all threesufficient
1.3 MUST remove the dead config (with recorded reason)file absence + CHANGELOG namingAC 3: asserts bothsufficient
1.4 MUST disposition every stub; none survive unchangedzero placeholder markers + per-deletion naming vs the committed tableAC 4: asserts both halvessufficient after revision (ISS-002)
1.5 MUST assert the four truths offline; auto-registered by globgreen under run_all discovery + each assert's negative pathAC 5: self-test mode exercises all four negativessufficient after revision (ISS-003)
1.6 MUST record the sweep in CHANGELOGfour named items present in top entryAC 6: asserts the four substringssufficient

§4 — Resolution

Seven findings — one scope-truth, six material — all resolved in the audited revision. Score = 10/10.

Status transition draft -> ready_to_implement is authorised by this verdict per STATUS-REFERENCE.md §1.1. The two human-acceptance gates in /ship-tasks are unchanged — this audit clears the spec-correctness gate only.


End of TASK-IMP-136 audit.