"Vendor the debugging-cycle pair + chain-coverage check so no ship-referenced skill can be missing from the payload"
TASK-SKILL-116: Vendor debugging-cycle + chain-coverage check
§1 - Description
The ship workflow's 31-step skill_chain is the contract for what the payload must carry, but the vendored skill list in build.sh is a hand-maintained string that silently drifted from it. This task vendors the missing pair and adds a coverage check that fails the build whenever the two disagree.
Normative clauses:
- The vendored skill set in
build.shMUST includedebugging-cycle-authoranddebugging-cycle-audit, copied into bothcuo/skills/andplugin/skills/like the existing pairs. - A script
tools/install/check-chain-coverage.sh <payload-dir>MUST extract every skill referenced by the vendored workflow docs and verify each has a directory containing aSKILL.mdin BOTH<payload>/cuo/skills/and<payload>/plugin/skills/. Extraction is defined per doc kind:skill: <name>entries in<payload>/cuo/ship-tasks.md's skill_chain, and backtick-quoted tokens matching[a-z0-9]+(-[a-z0-9]+)*-(author|audit)in<payload>/plugin/commands/*.md(deterministic token grammar, not prose guessing). Any miss MUST exit 10 listingMISSING <skill> (referenced by <doc>)per line. - Exemptions MUST be declared in
tools/install/chain-allowlist.txt(one name + one# reasonper line; initial entries:awh-gate,caf-gate). An allowlist entry exempts its name from BOTH rule kinds - MISSING (script-backed steps with no skill dir) and UNPAIRED (intentionally single skills, e.g. the four NFR skills once TASK-CUO-209 vendors them) - the reason string says which. An allowlisted name that nothing references and no payload dir matches MUST warn (stderr) so the allowlist cannot rot silently. - The check MUST also enforce pair completeness: for every vendored
<name>-authorthere MUST be a vendored<name>-auditand vice versa; non-allowlisted violations exit 10 asUNPAIRED <skill>. build.shMUST runcheck-chain-coverage.shagainst its own output as its final step and propagate a failure - a payload that under-covers its own workflow can no longer be produced. A reduced-profile payload (zero vendored skills - the documented doc-driven floor) is exempt: the check printschain SKIP: reduced profileand exits 0; partial vendoring still fails. (Amended post-ship 2026-07-12: TASK-CUO-209 t07 surfaced that the check as first shipped broke the reduced floor.)- The check MUST be pure read-only over the payload dir (no repo access needed), so TASK-IMP-068's CI gate and TASK-IMP-069's release job get it for free via the build.
§4 - Acceptance criteria
- The pair is vendored (§1 #1) - after
build.sh,debugging-cycle-author/SKILL.mdanddebugging-cycle-audit/SKILL.mdexist under bothcuo/skills/andplugin/skills/in the payload. - Chain extraction is real parsing, not a fixed list (§1 #2) - adding a fake
skill: nonexistent-authorline to the workflow doc in a scratch payload makes the check exit 10 namingnonexistent-authorand the doc. - A dropped pair fails the build (§1 #2, #5) - removing
debugging-cycle-author debugging-cycle-auditfrom the vendored set makesbuild.shitself exit non-zero with the two MISSING lines. - Allowlist works both ways (§1 #3) -
awh-gate/caf-gateproduce no failure; an allowlist entry naming a skill no doc references produces a stderr warning and exit stays 0. - Pair completeness (§1 #4) - a scratch payload with only
repo-context-map-authorpresent exits 10 withUNPAIRED repo-context-map-author. - Read-only over the payload (§1 #6) - running the check from an empty cwd against a copied payload dir succeeds; the payload's mtimes/bytes are unchanged after a run.
§5 - Verification
# tools/install/tests/test_chain_coverage.sh
# Builds one scratch payload, then mutates copies of it per case.
t01_pair_vendored() # AC 1
t02_parses_chain_not_list() # AC 2
t03_dropped_pair_fails_build() # AC 3 (patched build.sh in a temp checkout)
t04_allowlist_both_ways() # AC 4
t05_unpaired_detected() # AC 5
t06_readonly_check() # AC 6 (sha256 of payload tree before/after)
§2 - Why this design
Deriving the vendored set FROM the chain automatically was considered and rejected: the payload legitimately carries skills no chain references yet (TASK-CUO-209 vendors the full SDP set), so the honest relation is "chain is a subset of payload", enforced by a checker, with an explicit allowlist for script-backed steps. Checking pairs (#4) in the same pass kills the sibling bug class (author without audit) at zero extra cost.
§3 - Contract
check-chain-coverage.sh <payload-dir>
exit 0 chain covered, pairs complete (prints "chain OK: N referenced, M vendored, K allowlisted")
exit 10 MISSING <skill> (referenced by <doc>) | UNPAIRED <skill> (one per line)
exit 2 payload dir or workflow doc unreadable
§6 - Implementation skeleton
Extraction: grep -Eo 'skill: *[a-z0-9-]+' <doc> | awk '{print $2}' | sort -u over the chain doc + command docs; set-compare against ls <payload>/cuo/skills/; suffix swap for pair check. build.sh gains the pair in its set string and the final check invocation.
§7 - Dependencies
None upstream. Blocks TASK-CUO-209 (the expanded vendored set lands behind this checker). Related TASK-IMP-068 (gate runs the build, so this check rides along) and TASK-SKILL-118 (parity of pair CONTENTS; this task guarantees pair PRESENCE).
§8 - Example payloads
$ bash tools/install/check-chain-coverage.sh dist/cyberos
MISSING debugging-cycle-author (referenced by cuo/ship-tasks.md)
MISSING debugging-cycle-audit (referenced by cuo/ship-tasks.md)
$ echo $?
10
§9 - Open questions
None blocking. When TASK-CUO-209 vendors additional workflow docs, clause #2's doc list grows by construction (it scans plugin/commands/*.md and the vendored cuo workflow file set, not a hardcoded pair).
§10 - Failure modes inventory
- Workflow doc renames its
skill:key or format - extraction finds zero references; the check MUST treat "0 referenced skills" as exit 2 (structure changed under it), never as a pass. - Skill dir exists but is empty - presence test is
SKILL.mdinside the dir, not the dir itself. - Allowlist typo (
awhgate) - the referencedawh-gateis then unmatched -> exit 10; typo cannot cause silent skips. - Case drift (
Debugging-Cycle-Author) - names are compared lowercase-exact; a mismatch is a MISSING, surfacing the drift. - Payload built by an older build.sh (no check embedded) - CI (TASK-IMP-068) runs the current checkout's check against the fresh build, so stale build scripts cannot bypass it on main.
§11 - Implementation notes
Keep the output grep-stable (MISSING /UNPAIRED prefixes); TASK-CUO-209's expansion test keys on them. The allowlist file ships in the repo, not the payload - the check runs where the build runs.
End of TASK-SKILL-116.
Audit
TASK-SKILL-116 audit
§1 - Verdict summary
Audited for extraction determinism (the checker must parse, not guess), allowlist semantics, and forward-compatibility with TASK-CUO-209's expanded set. Two contract holes closed (prose-fuzzy extraction; allowlist covering only one rule kind). Traceability closes over t01-t06 in tools/install/tests/test_chain_coverage.sh.
§2 - Findings (all resolved)
ISS-001 command-doc extraction was prose-fuzzy
"Every skill named by plugin/commands/*.md" had no grammar - a checker cannot grep intent. Resolved: §1 #2 defines the deterministic backtick token grammar <name>-(author|audit) for command docs, skill: keys for the chain doc.
ISS-002 allowlist exempted MISSING but not UNPAIRED
TASK-CUO-209 vendors four intentionally single NFR skills; the pair rule would have failed the build the day they land. Resolved: §1 #3 allowlist entries exempt both rule kinds with a reason string; §1 #4 scopes UNPAIRED to non-allowlisted names.
ISS-003 zero-reference degeneracy
A workflow-doc format change yielding zero extracted skills would pass vacuously. Resolved: §10 #1 makes 0 references exit 2 (structure changed), never a pass.
ISS-004 empty-dir false positive
A skill directory without SKILL.md counted as present. Resolved: presence = SKILL.md inside the dir (§10 #2).
ISS-005 side-effect-free guarantee missing
CI reuse (TASK-IMP-068) requires read-only behavior; nothing said so. Resolved: §1 #6 + AC 6 hash-tree-before/after assertion.
ISS-006 build-integration failure path untested
Clause #5 (build fails on violation) had no test. Resolved: AC 3 / t03 run a patched checkout whose set drops the pair and assert build.sh itself fails.
§3 - Resolution
All six findings addressed as cited. The task now kills the bug class (hardcoded set drifting from the chain) rather than the single instance. Score = 10/10.
End of TASK-SKILL-116 audit.
§10 - Post-implementation gates (2026-07-12, ship run)
- §10.4 coverage gate: PASS - t01-t06 green on fresh testing-phase rerun; TASK-IMP-068 suite green as regression. Report: docs/tasks/.workflow/TASK-SKILL-116/coverage-and-review.md.
- TRACE-004 closure: PASS - every §1 clause's cited test passed (table in the bundled artefact).
- §10.5 awh gate: N/A (no sealed goldenset for this tooling path - declared). §10.6 caf gate: N/A (no audit-profile); floor = bash -n clean + both suites green.
- HITL gate 1: APPROVED by Stephen Cheng 2026-07-12. HITL gate 2: ACCEPTED same date via explicit operator pre-authorization at the review gate; gates stayed green.
- Live proof: payload now vendors 22 skills; build prints
chain OK: 24 referenced, 22 vendored, 2 allowlistedand the commit hook ran both checks green.
TASK-SKILL-116 shipped 2026-07-12.
§11 - Post-ship amendment (2026-07-12, surfaced by TASK-CUO-209 t07)
Field finding: the chain-coverage check failed REDUCED-profile builds (zero vendored skills = every chain reference "missing"), breaking the documented doc-driven floor. Amendment: zero-vendored payloads skip with chain SKIP: reduced profile (exit 0); partial vendoring still fails - the drift case the check exists for. §1 #5 amended; t07_reduced_profile_skips added to the suite.