feat(disk-hygiene): add read-only managed-state owner registry - #5562
Conversation
…ema and test Refs #4006 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ches Refs #4006 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…al and leaves the engine unchanged Registry-matched paths (.pulumi, .cache/chezmoi, .codex, and the rest) classify and preview exactly like neutral controls, a claimed registry owner stays report-only with no token, the token depends on snapshot and plan alone, apply stays behind --execute, tier and a fresh token, and the engine never reads the registry or runs a registry tool. The baseline policy loads identically with the registry absent. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…y as 0.30.0 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e's apply lane The registry tests proved a registry match grants no approval, but nothing failed if a new file ran a registry command or deleted outside the engine, and the engine-only naming check would have rejected the shared route while a parallel one passed. A plugin-wide scan now fails any shipped file outside the engine's apply lane that deletes, names the registry or one of its commands, references apply_plan or anchored_remove, or imports a process runner other than the engine and the telemetry emitter. Planted parallel paths are asserted to fail and the apply lane is asserted to pass. anchored_remove must have apply_plan as its only caller. managed-state-report.md no longer says the destructive command is routed through the engine's approval: the route is not built and the engine blocks an owner-claimed plan as native-managed-report-only. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
|
PR body contract — issue linkage This PR body conforms to the issue-linkage contract. Nothing to do. |
…the apply-lane scan The scan matched a registry command only as a whole string, so the Pulumi destructive command (stored with a placeholder) and a command built as an argv list passed, and a new shell script could delete without being seen. Commands are matched up to their placeholder, registry tool names as string constants are flagged outside the apply lane, hooks.json is scanned, and non-Python files fail on deletion verbs or child_process beyond the launchers' existing count. Planted shell and Python parallel paths are asserted to fail. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The file starts with a shebang, so the lint exec-bit check requires mode 100755, as for its sibling test_hygiene.py. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…te-registry Renumber the managed-state registry release to 0.31.0: origin/main already carries disk-hygiene 0.30.0 (#5542), so the entry sits above it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e commands Step 4 printed the native destructive command for an operator to copy, and the design-check row counted that as meeting "read-only and destructive are different gates". Showing the command offers it outside the tier and exact-list approval, and whether the report may show or offer one is the owner's decision (#4006). The registry keeps the string as inspectable data; the report neither shows nor runs it, and the table row states what is and is not built. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Merging origin/main left SKILL.md at 502 lines, and check-changed-skills fails at the 500-line hard cap. Fold the managed-state report pointer into the existing managed-state paragraph in section 4 instead of adding a standalone paragraph. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…te-registry Renumber disk-hygiene to 0.36.0 above main's 0.35.1. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Main's handoff_apply (#5541) also reaches anchored_remove, and its purge_directory_contents and write_text_atomic delete too, so the guard's apply-lane set and the one-caller assertions no longer described the engine. Deleting is now allowed only in apply_plan, handoff_apply, anchored_remove, purge_directory_contents and write_text_atomic (its own temporary file). A registry command or tool may appear only in apply_plan: handoff_apply takes no plan, so it has no owner claim, and the test pins that signature. Each lane has main as its only caller, anchored_remove has exactly those two, and only the handoff lane asks it to empty Git metadata. The engine is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…te-registry Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…command handoff Section 4 now says a registry match follows managed-state-report.md alone, whose step 4 shows no destructive command, and that the native-command handoff applies to managed state with no registry match. Step 5 of the report points unmatched paths at that handoff. The two documents no longer give opposite instructions for a registry match. Refs #4006 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tch case The skill's section 1 bullet and the README now say a registry match follows managed-state-report.md, so the native-command handoff text no longer reads as applying to a registry match. The report doc's step 5 returns to the owner-approved wording. Refs #4006 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s probes The report told the agent to resolve the tool on PATH and run the registry's read-only command, and its design check said step 3 runs freely. The clean skill's Bash guard denies both on every platform, and the registry test fences read-only commands as it fences destructive ones, so no shipped code runs them either. The report now says the agent runs them in the PowerShell lane, where the guard gives no decision, or the operator runs them and the report records the output, and the design-check rows and the test docstring say the same. Refs #4006 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…te-registry Renumber disk-hygiene to 0.37.0 above main's 0.36.0 (the audit skill). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
|
Claude finished @kyle-sexton's task in 2m 8s —— View job Security review (
|
…te-registry Renumbers disk-hygiene to 0.38.0 above main's 0.37.0 and keeps main's changelog entries. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Claude finished @kyle-sexton's task in 5m 5s —— View job Claude finished the code review
Ran Findings (posted inline)
What I verified directly
Not independently verifiedThis sandbox's Bash tool required interactive approval for every |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 331e26741b
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
Claude finished @kyle-sexton's task in 2m 14s —— View job Claude security review complete
No security issues found. Reviewed all 10 changed files (
No CRITICAL, IMPORTANT, or SUGGESTION findings. GitHub Actions hardening is out of scope for this lane (zizmor's advisory), and no workflow files are touched by this PR anyway. |
…stry Consult the registry from the ownership step, keep a manual step when its tool is absent, run the read-only probe by the resolved application executable, and give each registry entry a verification record (claim, basis, as-of date, recheck trigger) that the schema requires. Refs #4006 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Claude finished @kyle-sexton's task in 1m 40s —— View job Claude security review complete
Ran No security issues found. What changed since the last review, and why it's clean
Reconfirmed from the unchanged parts of the diff (still holds)
GitHub Actions hardening is out of scope for this lane (zizmor's advisory); no workflow files are touched by this PR. |
|
Claude finished @kyle-sexton's task in 3m 5s —— View job Claude finished the code review
Ran Prior findings: resolution check
New finding (posted inline)
Not independently verifiedDoc-only change; no test suite covers PowerShell invocation prose. |
…te-registry Renumbers disk-hygiene to 0.39.0 above main's 0.38.0 and keeps main's changelog entries. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Claude finished @kyle-sexton's task in 1m 1s —— View job Claude security review complete
No security issues found. Skip-gate check (per
|
|
Claude finished @kyle-sexton's task in 1m 10s —— View job Claude finished the code review
Skip gate: stopping early, no new findings posted. This trigger fired on a The only files the merge touched are Per this lane's skip gate ("this head already has a successful review from this lane that still
Posting no duplicate inline comments. No new findings from this synchronize. |
…mpound probe Refs #4006 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Claude finished @kyle-sexton's task in 1m 3s —— View job Claude finished the code review
No new findings. The new commit fully resolves the last open finding. This That commit is the fix for the one finding still open from the 19:55:47 UTC review: the resolved-executable invocation template in
Checked this against the registry data ( No correctness or maintainability issues in this commit: it's a documentation-only clarification, doesn't affect the |
|
Claude finished @kyle-sexton's task in 38s —— View job Security review (
|
…te-registry Renumbers disk-hygiene to 0.40.0 above main's 0.39.0. The deletion fence now treats run_inventory as a temporary-file writer like write_text_atomic, and lets the read-only inventory name products as /tmp producer labels while still checking it for deletions and process runners. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Claude finished @kyle-sexton's task in 2m 0s —— View job Claude finished the code review
No new findings. This The one new thing since the last pass (
No correctness or maintainability issue found in this adaptation. The Not independently verified: this sandbox's Bash tool requires interactive approval for |
|
Claude finished @kyle-sexton's task in 2m 4s —— View job Security review (
|

Refs: #4006
Summary
Adds the read-only slice of #4006: a managed-state owner registry as data (
owner-registry.jsonwith a schema), a report design for registry matches, and tests. No destructive command, credentials, elevation or deletion was added, and the engine is unchanged.The engine has two deletion lanes.
applyneeds--execute,--confirm-tierand a fresh token, then runsapply_plan.handoff-applyneeds--executeand one exact path, re-verifies it in process, then runshandoff_apply. Both remove throughanchored_remove. The registry adds no route into either: the engine blocks a plan that claims a registry owner asnative-managed-report-onlyand issues no token for it, andhandoff_applytakes no plan, so it carries no owner claim.The report neither shows nor runs a product-native destructive command; the registry keeps each as data to inspect. Whether the report may show one, or offer one behind the engine's tier and exact-list approval, is the owner's call, and building a route needs an engine change. #4006 stays open.
Managed state with no registry match still gets the native cleanup handoff under the existing
SKILL.mdtext: the documented native command and its current dry-run result. A registry match followsmanaged-state-report.mdalone (SKILL.md§4 says so), and its step 4 shows no destructive command. The owner's decision on whether a report may show a destructive command therefore covers both cases.Fix
skills/clean/reference/owner-registry.jsonandowner-registry.schema.json: six owners, each with platform-scoped path patterns and a note that a hint is not authorization. Each entry carries a required verification record (claim, basis, as-of date, recheck trigger).skills/clean/reference/managed-state-report.md: how a registry match is reported, with a table checking it against the design constraints. It says the report neither shows nor runs the destructive command and that no route for either is built. Its presence check and read-only command are tool calls or operator actions, never shipped code: the skill's Bash guard denies them, so the agent runs them in the PowerShell lane (where the guard gives no decision) or the operator runs them and the report records the output. The test fence names read-only commands as it names destructive ones, and the design-check rows and the test docstring say so.skills/clean/scripts/test_owner_registry.py(wrapperowner_registry.test.sh), 24 tests:RegistryGrantsNoApprovalTest): a registry path gets the same verdict as a neutral path, the token depends only on snapshot and plan, apply keeps its gates, and an owner-claimed plan stays report-only. These show the registry adds no approval. They do not test a destructive route, because none exists.OnlyTheEngineLanesDestroyTest,EngineSourceTest):plugins/disk-hygiene(includinghooks/hooks.json) fails any file outside the engine that deletes, names the registry, one of its commands (matched up to a placeholder) or one of its tools as a string constant, referencesapply_plan,handoff_apply,anchored_removeorpurge_directory_contents, or imports a process runner (only the engine and the telemetry emitter may). A non-Python file also fails on a deletion verb orchild_processbeyond the launchers' existing count.apply_plan,handoff_apply,anchored_remove,purge_directory_contentsandwrite_text_atomicandrun_inventorymay delete.write_text_atomicandrun_inventoryremove only their own temporary file, which a test pins. A registry command or tool may appear only inapply_plan, so a route built on the registry would sit behind the tier and token gates.apply_planandhandoff_applyeach havemainas their only caller.anchored_removehas exactly those two callers.purge_directory_contentsis called only fromanchored_remove, and onlyhandoff_applyasks for it.handoff_applytakes(snapshot, relative, vcs_evidence), so no plan and no owner claim.handoff_apply,anchored_removeorpurge_directory_contents, are asserted to fail; the two lanes, their removers and the telemetry emitter are asserted to pass.handoff_apply's own gates (exact path, in-process clear verdict) are covered bytest_hygiene.py.getattr,eval,importliband a command assembled at run time are not seen. A move or overwrite (os.replace,shutil.move) is not counted as a deletion, a non-Python file is not checked for every process it starts, and test files are not scanned.skills/clean/SKILL.md§4 (the file stays at 499 lines under the 500-line cap) states the precedence: a registry match followsmanaged-state-report.mdalone, and only managed state with no registry match gets the native-command handoff. The §1 managed-state bullet andREADME.mdpoint a registry match at §4 and the report.reference/safety-model.mdpoints to the report doc.SKILL.md§2 question 3 tells the agent to match a path against the registry first. The report gates only commands on tool presence, keepsmanual_stepwhen the tool is absent, and runs probes by the resolved application executable.plugin.json0.40.0 with a matching CHANGELOG entry above main's 0.39.0.test_owner_registry.pyis mode 100755, as its shebang and its siblingtest_hygiene.pyrequire.Verification
Run on
29643714b, which contains origin/main at52c4885a2(git merge-tree HEAD origin/mainis clean).owner_registry.test.sh: 24 tests OK. The planted Python and shell parallel paths, and the registry command planted in each engine function, are asserted inside the suite, so they run every time; this head was not re-checked by planting files in the tree.hygiene.test.sh(648 tests, 1 skipped),engine_context.test.sh(8),guard_launch_monitor.test.sh(46) and the setup skill'skill_switch_probe.test.sh(20) andpython3_alias_probe.test.sh(10): OK.scripts/affected-tests.sh --run: every shell suite it runs passes; it lists 5 Python suites (includingtest_owner_registry.py) as NOT RUN because they belong to their own lane, and they were run through their wrappers as above.scripts/run-ruff.sh checkandformatontest_owner_registry.pypass.destructive_guard.pyfed PreToolUse payloads returneddenyfor Bashdocker system df,chezmoi doctor,command -v pulumiandpulumi about, and no decision for PowerShelldocker system df,Get-Command dockerandchezmoi doctor.scripts/check-changelog-parity.sh --checkand--check-bump origin/mainpass (0.40.0 against main's 0.39.0).scripts/check-changed-skills.sh origin/mainpasses (skills/clean/SKILL.mdis 499 lines, cap 500).plugins/disk-hygienewith a shebang has index mode 100644.Related
🤖 Generated with Claude Code