Scope managed posture to declared owned sources - #319
Merged
Conversation
--posture managed granted edit on all of wp-content/plugins and wp-content/themes. On h44lacrosse.com that meant WooCommerce, the Stripe gateway, Data Machine, and the agent's own data-machine-code runtime were writable by an autonomous agent on a site taking real card payments. Only three paths there are the site's own, and those three are exactly what its harvest workflow captures. The managed guidance promises that edits are captured as restore points; for everything else in those directories that is false, so the agent was told a safety property that did not hold for most of the surface it was granted. An edit to WooCommerce is never recorded and is destroyed by the next update. This is the prose-vs-permission split #314 existed to kill, reintroduced one level down: the guidance enumerated ("this site's theme and plugins") while the policy generalised (two directories). The invariant: the editable set must equal the set the operator's capture records. There is no reliable way to infer which plugins a site owns, and guessing wrong on production is not an acceptable default, so it is declared: --managed-source wp-content/themes/acme --managed-source wp-content/plugins/acme-core Recorded as wp_coding_agents_managed_sources so upgrades converge. Managed now denies all three roots exactly as engineering does, then emits one allow per declared path. Declaring nothing fails closed — no editable source, and the guidance says so instead of implying a directory is editable. Rule ORDER is now load-bearing. Verified against opencode 1.18.4 source, not the docs, which several open upstream issues contradict: evaluate() is findLast over a ruleset built in config key order with no specificity sorting, so a narrower allow written after a broad deny wins. The generator and the reconciler both preserve deny-before-allow. Claude Code treats deny as absolute so an allow cannot carve an exception, and Codex has no documented precedence for overlapping filesystem entries. Both now refuse managed posture rather than emit a permission set whose behavior on a production site is unverified. The guidance section is rewritten to enumerate the declared paths by name and to mark the rest of wp-content read-only, naming commerce code and the agent's own runtime as things it must not touch. The heading no longer calls the whole category editable. Engineering output is unchanged: generated opencode.json verified byte-identical to v1.11.0.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #318.
--posture managed(shipped in v1.11.0) granted edit on all ofwp-content/plugins/andwp-content/themes/. On h44lacrosse.com that put WooCommerce, the Stripe gateway, Data Machine, and the agent's owndata-machine-coderuntime inside an autonomous agent's write surface — on a site taking real card payments.Three paths there are actually the site's own (
h44-core,h44-forms,h44-lacrosse-theme), and those three are exactly what itsharvest.ymlcaptures.Why this was worse than "too broad"
The managed guidance tells the agent its edits are captured as restore points. Outside the harvested set that is false — an edit to WooCommerce is never recorded and is erased by the next update. The agent was told a safety property that did not hold for most of what it was granted.
Root cause is the prose-vs-permission split #314 existed to eliminate, reintroduced one level down: the guidance enumerated ("this site's theme and plugins"), the policy generalised (two directories).
The invariant
A path the agent can edit but nothing records is a path where it silently loses work, invisibly, until an update overwrites it. There is no reliable way to infer which plugins a site "owns", and guessing wrong on production is not an acceptable default — so it is declared:
Recorded as
wp_coding_agents_managed_sourcesso upgrades converge. Managed now denies all three roots exactly as engineering does, then emits one allow per declared path.Fails closed. Managed with nothing declared grants no editable source, and the guidance says so explicitly rather than implying a directory is editable. Malformed declarations (outside
wp-content, or a file inside a component) are rejected with a reason.Rule order is now load-bearing
Verified against opencode 1.18.4 source, not the docs — the two disagree, which is the subject of several open upstream issues (#7029, #13872, #24335, #24999, #36765):
findLastover a ruleset built in config key order, with no specificity sorting inevaluate(Wildcard.all's length sort is used elsewhere). So a narrower allow written after a broad deny wins. Both the generator and the reconciler preserve deny-before-allow, and the tests assert the ordering rather than comparing sorted keys —sort_keys=Truewould have destroyed the property under test.Runtime support narrowed, deliberately
Managed is now
opencode-only. Claude Code treats deny as absolute so an allow cannot carve an exception out of it; Codex has no documented precedence for overlapping filesystem entries. Both now refuse managed posture with a clear error rather than emit a permission set whose behavior on a production site I could not verify. Their engineering paths are untouched.Guidance rewritten
Enumerates the declared paths by name, states the list is exhaustive, and marks the remainder of
wp-contentread-only — naming commerce/payment code and the agent's own runtime as things it must not touch, with the reason (an update erases the change anyway). The heading no longer labels the whole category editable.Verification
opencode.jsondiffed byte-for-byte against generation fromv1.11.0: identical.tests/posture.shextended: ordering (deny index < allow index), fail-closed with nothing declared, normalization rejecting bad paths, the three runtimes' managed support, prose enumerating every declared path, prose never presenting a directory as editable, and fail-closed prose.tests/*.sh: no new failures (same 7 pre-existing environment failures asmain, none inshell.yml).bash -nclean.Follow-up
Re-applying to h44lacrosse.com is an operator action, not part of this PR. That box currently has the over-broad grant live.