Skip to content

feat(rfc-008): P4d S5 — install.mjs --uninstall-enforcement [--purge-config]#416

Merged
lantiscooperdev merged 1 commit into
mainfrom
feat/rfc-008-p4d-s5-uninstall
Jun 21, 2026
Merged

feat(rfc-008): P4d S5 — install.mjs --uninstall-enforcement [--purge-config]#416
lantiscooperdev merged 1 commit into
mainfrom
feat/rfc-008-p4d-s5-uninstall

Conversation

@lantisprime

Copy link
Copy Markdown
Owner

RFC-008 P4d S5 — per-project enforcement uninstall

Adds install.mjs --uninstall-enforcement [--purge-config], reversing the per-project enforcement install delta (RFC-008 P4d, decouple enforcement from the substrate).

What it does

  • Prunes the 9 enforcement hook registrations from <project>/.claude/settings.json (canonical exact-match + legacy relative spellings; foreign same-basename hooks are warned and left — REQ-5).
  • Deletes the enforcement-only files: the gates + engine + classifier + markers + the hooks/lib/*.sh closure, and the enforcement-only hooks/patterns + hooks/plugins dirs (wholesale — core never creates them).
  • Preserves the core bp1 set (bp1-*.mjs + closure libs + bp1-approval-check.sh H1 + bp1-sweep-on-session.sh H2), the operator-owned enforce-config.json (unless --purge-config), and the global substrate (~/.claude, ~/.episodic-memory).

Correctness model

Primary proof is the single-mock core-state delta E2E: in ONE project, core-install → snapshot → --install-enforcement--uninstall-enforcement → re-snapshot deep-equals the core baseline (.claude tree + parsed settings). It auto-catches over-removal (bp1), under-removal (the .sh libs), and residual empty event keys / patterns+plugins dirs without trusting the hand-derived removal list.

One new manifest export (enforcementHookLibBasenames) is readdir-coupled to install's 5_lib deploy so the deploy and removal sets agree by construction (Rule 14).

Safety

  • assertContained (realpath both sides + path.relative, not bare startsWith) fails closed on a symlinked hook/dir escaping the project tree, including the sibling-prefix case (.claude/hooks-backup/).
  • Atomic on malformed settings.json: parse first; on SyntaxError the whole op aborts (no delete, no write).
  • Idempotent: nothing-installed run + double run both exit 0 with no change.

Review (Rule 18)

  • Plan reviewed to consensus pre-impl (negative-scenario-planner + second-opinion, 3 rounds, ACCEPT).
  • Implementation reviewed via second-opinion (--provider claude-subagent): overall ACCEPT, 0 blocking. Findings dispositioned — F1 (operator command bundled in the same hooks[] entry as a gate was collaterally deleted by whole-entry removal; reproduced on disk) fixed inline with a command-granularity prune + regression test; F2 .sh-lib drift guard added as a co-located machine check; F3/F6 help-text clarifications; F4 explicit bp1 H1 assertion; F5 version-skew → RFC-008 P4d: uninstall-enforcement version-skew under-removal (review F5) #414; OD-2 --uninstall-second-opinionRFC-008 P4d: add --uninstall-second-opinion (OD-2) #415.

Tests

  • tests/test-uninstall-enforcement.mjs14/14 (core-state delta, registrations removed, files removed, bp1 preserved, switch preserved, purge, no-global-touch, foreign-hook preserved, idempotent, containment fail-closed, malformed-settings atomic, cwd-independent, F1 shared-entry, F2 drift guard).
  • Regression green: contract-deploy 36, lib-closure 6, activation-scoping 13, enforcement-scope 17, s4-gate-e2e 5, bp1-wiring 23.

Follow-ups: #414 (F5 version-skew), #415 (OD-2). OD-3 (branch-protection required-check) lands with S8.

🤖 Generated with Claude Code

…config]

Reverse the per-project enforcement install delta: prune the 9 enforcement hook
registrations and delete the enforcement-only files (gates + engine + classifier +
markers + the hooks/lib/*.sh closure) and the enforcement-only hooks/patterns +
hooks/plugins dirs, while PRESERVING the core bp1 set, the operator-owned
enforce-config.json (unless --purge-config), and the global substrate. Never
touches ~/.claude or ~/.episodic-memory.

Correctness proof = the REQ-12 core-state delta E2E: in ONE mock project,
core-install -> snapshot -> --install-enforcement -> --uninstall-enforcement ->
re-snapshot deep-equals the core baseline (tree + parsed settings). Auto-catches
over-removal (bp1), under-removal (.sh libs), and residual empty event keys /
patterns+plugins dirs without trusting the hand-derived removal list.

One new manifest export (enforcementHookLibBasenames), readdir-coupled to install's
5_lib deploy so the deploy and removal sets agree by construction (Rule 14).

Reviewed via second-opinion (claude-subagent), overall ACCEPT, 0 blocking. Findings
dispositioned: F1 (operator command bundled in the same hooks[] entry as a gate was
collaterally deleted by whole-entry removal) FIXED inline with a command-granularity
prune + regression test; F2 .sh-lib drift guard added as a co-located machine check;
F3/F6 help-text clarifications; F4 explicit bp1 H1 assertion; F5 (version-skew) doc
note + tracking issue.

Tests: tests/test-uninstall-enforcement.mjs 14/14 (incl. core-state delta,
containment fail-closed, malformed-settings atomicity, F1 shared-entry, F2 drift
guard). Regression green: contract-deploy 36, lib-closure 6, activation-scoping 13,
enforcement-scope 17, s4-gate-e2e 5, bp1-wiring 23.

RFC-008 P4d S5. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@lantiscooperdev lantiscooperdev left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code review — RFC-008 P4d S5 (--uninstall-enforcement)

Reviewed the removal-set algebra, registration prune, containment, atomicity, and the test coverage against the diff and the live install/uninstall flow.

Verified sound

  • Removal algebrarootRemove/libRemove = (enforcement sets) MINUS (core bp1 sets), placed in the correct subdir (root vs lib/); enforcement-only patterns/+plugins/ removed wholesale (core never creates them). bp1 .mjs, bp1-approval-check.sh, bp1-sweep-on-session.sh, and bp1-shared .mjs libs are correctly excluded.
  • Registration prune — canonical exact-match strings are byte-identical to what install writes (shellQuote(abs) for gates, node <quoted abs> for SessionEnd); stop-gate's Stop + SubagentStop both handled; legacy-relative spellings resolved + removed; foreign same-basename hooks warned and left.
  • ContainmentassertContained realpaths both sides and uses path.relative, so symlink-escape and the .claude/hooks-backup/ sibling-prefix both fail closed with the escape target intact.
  • Atomicity — parse-first; SyntaxError aborts before any delete/write; settings written before file deletion, so a containment throw never orphans registrations against deleted files.
  • No global touch — all paths rooted at projectDir; the before/after global snapshot is byte-identical.

Findings dispositioned (from the second-opinion claude-subagent pass)

  • F1 (medium): whole-entry removeHookEntryByCommand would collaterally delete an operator command bundled in the same hooks[] entry as a gate (reproduced on disk). Fixed inline here via the command-granularity removeHookCommandFromEvent (drops an entry only when it empties it), covered by t_operator_command_in_shared_entry_preserved. Good resolution — keeps the shared install-time helper's whole-entry semantics intact.
  • F2 → co-located drift guard t_sh_libs_not_sourced_by_core (Rule 14). F4 → explicit bp1 H1 assertion. F3/F6 → help-text. F5/OD-2#414 / #415.

Tests

test-uninstall-enforcement.mjs 14/14 incl. the core+enforce+uninstall ≡ core delta; regression suites green (contract-deploy/lib-closure/activation-scoping/enforcement-scope/s4-gate-e2e/bp1-wiring).

No blocking issues. The core-state delta E2E is the right correctness anchor and it holds. Recommend merge after maintainer approval.

@lantiscooperdev lantiscooperdev merged commit a40d907 into main Jun 21, 2026
3 checks passed
@lantisprime lantisprime deleted the feat/rfc-008-p4d-s5-uninstall branch June 21, 2026 07:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants