Task — engineering-spec@1

Uninstall MUST preserve .cyberos/config.yaml, the override home

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

TASK-IMP-129: Uninstall MUST preserve .cyberos/config.yaml, the override home

Summary

Three shipped surfaces tell the operator that durable overrides belong in .cyberos/config.yaml, and install.sh scaffolds it exactly once and never clobbers it. uninstall.sh then removes it with the rest of .cyberos/. The one file the system promises is durable does not survive the operation most likely to be followed by a reinstall.

Problem

gates.env is machine-owned by design. install.sh:299 regenerates it on every install from autodetection, and install.sh:326 says so plainly - when regeneration changes the file it points the operator at the durable alternative: "durable overrides belong in .cyberos/config.yaml". That design is coherent and this task does not disturb it.

The durable alternative is not durable. Three surfaces name config.yaml as the override home:

uninstall.sh contains zero occurrences of config.yaml. It removes .cyberos/ wholesale, keeping only the BRAIN store under CYBEROS_UNINSTALL_KEEP_BRAIN. An operator who follows the instruction printed by install and by the gate runner loses their overrides at the next uninstall, and the reinstall scaffolds a fresh commented-out file as though they had never configured anything.

This is the same principle TASK-IMP-126 §1.4 already established - "None of 1.1-1.3 may remove an operator file" - scoped there to .mcp.json, unmarked skill dirs, and foreign hook lines. config.yaml is operator content by construction: install writes it once and never again, so every subsequent byte in it is the operator's.

Observed on this repo on 2026-07-20: an uninstall/install cycle regenerated gates.env with an empty TEST_CMD, because autodetect reported ecosystem "unknown" for a repo whose suite is bash scripts/tests/run_all.sh rather than an npm script. That silently reverted a same-day fix that had made the machine gates non-vacuous, and the gates would have reported GREEN while running no tests. Node repos were unaffected - autodetect populated build, lint and test correctly there - so the failure is specific to repos whose test entrypoint autodetect cannot name, which is exactly the population that most needs a durable override.

Proposed Solution

Add config.yaml to the set uninstall preserves, alongside the BRAIN store, and report it in the "kept on purpose" banner so the operator can see what survived. Separately, teach autodetect to recognise a shell test entrypoint so a bash-suite repo gets a populated gates.env rather than a silent empty one.

Alternatives Considered

Success Metrics

Scope

In scope: the uninstall preserve set and its banner, the autodetect rule for a shell test entrypoint, and arms in test_install_hygiene.sh.

Out of scope / Non-Goals

Dependencies

None blocking. Extends the operator-file principle established by TASK-IMP-126 §1.4 to a file that task did not enumerate.

Corroboration from TASK-IMP-122 §1.5. That task, reasoning about the fingerprint cone rather than about uninstall, independently classifies config.yaml as install-generated-or-operator-owned and declares exempt:config.yaml so its contents never enter rules_sha. Two tasks reaching the same classification from unrelated directions is the strongest available evidence that the file is operator content; this task is the one that makes the classification survive an uninstall.

Boundary against TASK-IMP-121 (done). That task made uninstall leave the repo as it found it for .agents markers, .gitignore, the pre-commit hook, and the five native channel parents. It never reaches inside .cyberos/, because everything there was assumed to be machine-owned and disposable. config.yaml is the counterexample that assumption missed. This task does not revisit any surface 121 settled.

AI Authorship Disclosure

1. Description (normative)

2. Acceptance criteria

3. Edge cases

Audit

§1 — Verdict summary

Four §1 clauses, four ACs, six edge cases including one security-class row. Machine floor clean after one title-length fix. This spec scored lowest pre-revision of the batch because its founding premise was wrong and had to be replaced before anything else could be judged — see ISS-001, which is the most consequential finding in this batch. TRACE-006 compared per clause: all four cited tests meet their clause's verb.

§2 — Findings (all resolved)

ISS-001 — the task's original premise was false and would have shipped a wrong fix

The task was commissioned as "uninstall destroys operator gate configuration in gates.env", from a directly observed regression: an uninstall/install cycle on this repo blanked TEST_CMD and silently reverted a same-day non-vacuous-gates fix. The observation was correct; the diagnosis was not. Reading install.sh:324-326 before drafting showed gates.env is machine-owned by design under TASK-CUO-207, regenerated on every install on purpose, with the operator explicitly directed elsewhere for durable overrides. A spec written to the original premise would have required uninstall to preserve a file the system deliberately regenerates, contradicting a shipped task and making install's own regeneration message a lie. Material: would have failed at review against TASK-CUO-207. Resolved: reframed to the actual defect — config.yaml, the file all three surfaces name as the durable alternative, is itself deleted by uninstall. The correction is recorded in source_decisions rather than silently absorbed.

ISS-002 — the "documented home" claim needed more than one citation to stand

The reframed premise rests on the system telling the operator that config.yaml is durable. One citation would make that an interpretation. Resolved: three independent surfaces cited and read at HEAD — install.sh:326 (directs the operator there), install.sh:328 (scaffolds once, never clobbers), run-gates.sh:24-25 and :75 (reads it as the override layer and instructs the operator to use it). A never-clobber promise is only meaningful if the file persists, which is the contradiction.

ISS-003 — the deletion claim was asserted where it could be measured

"Uninstall removes it" is the load-bearing fact. Resolved: stated as a grep count (zero occurrences of config.yaml in uninstall.sh) plus the preserve-list citation at :4 showing only the BRAIN store is kept, so the claim is checkable by a reader without running an uninstall.

ISS-004 — XCHAIN: two adjacent tasks were undeclared

TASK-IMP-121 (done) settled uninstall's repo-restoration for .agents, .gitignore, hooks and native channel parents; TASK-IMP-122 (p1, on_hold) independently classifies config.yaml as operator-owned via exempt:config.yaml. Neither was in related_tasks. Material: unrecorded cross-task constraints, and in 122's case a corroboration that materially strengthens this task's premise. Resolved: both added, with Dependencies stating the 121 boundary (121 never reaches inside .cyberos/, because everything there was assumed disposable — this file is the counterexample) and the 122 corroboration (two tasks reaching the same classification from unrelated directions).

ISS-005 — §1.4 was needed to stop the fix over-preserving

A clause requiring preservation, unbounded, invites an implementation that keeps more of .cyberos/ than intended and quietly ends the machine-owned discipline TASK-CUO-207 established. Resolved: §1.4 binds the inverse — gates.env MUST still be regenerated and everything outside the documented preserve set MUST still be removed — traced by AC 4.

ISS-006 — the autodetect half needed to be scoped so it cannot re-rank existing cases

§1.3 asks autodetect to recognise a shell test entrypoint. Unbounded, an implementer could reasonably make the shell path win over an npm script, changing behaviour for every node repo in the fleet — none of which have the defect. Resolved: §3 edge case requires existing precedence to be preserved; 1.3 fills a gap and MUST NOT re-rank cases autodetect already resolves.

ISS-007 — "unedited config.yaml" is not distinguishable from "reset to defaults"

An implementation that preserves only modified config files would look like a reasonable optimisation and would silently discard a file an operator deliberately reset. Resolved: §3 edge case requires preservation regardless of edit state, naming the reason — guessing reintroduces the silent-loss failure this task exists to fix.

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

ClauseVerb demandsCited test assertsVerdict
1.1 MUST preserve AND MUST report in bannertwo observables — file survives, and is named in outputAC 1: asserts file present after uninstall AND named in banner outputsufficient on both halves
1.2 override MUST still be in effectbehavioural evidence after a full cycle, not mere file presenceAC 2: asserts run-gates.sh resolves the operator's command, not the autodetected defaultsufficient — resolution is stronger than presence
1.3 MUST populate TEST_CMD for a shell entrypointa non-empty value where "unknown" is produced todayAC 3: asserts non-empty TEST_CMD on a shell-only fixturesufficient
1.4 MUST still remove machine-owned pathsregeneration of gates.env and absence of other machine filesAC 4: asserts gates.env freshly regenerated and no other machine path survivedsufficient on both halves

§4 — Resolution

Seven findings, all material, all resolved in the audited revision. Machine floor clean. 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 downstream are unchanged and remain recorded human verdicts.


End of TASK-IMP-129 audit.