feat: project the declared owned sources to a readable manifest - #338
Merged
Conversation
Owned mode rests on one invariant: the editable set equals the set the operator's out-of-band capture records. A path the agent may edit but nothing captures is where work silently disappears, and on a live site it stays invisible until an update overwrites it. Nothing enforced that, because the two lists lived in different places — this install records the editable set in a wp option, and the capture mechanism kept its own hand-maintained copy. h44lacrosse.com had exactly that: three components declared in the option and the same three hardcoded twice in its harvest workflow, held in agreement by nothing but memory. The obvious fix is for capture to read the option, and it cannot. It runs as a deliberately read-only identity that cannot read wp-config.php — wp-coding-agents hardens that file to www-data:640 itself — so it can never run WP-CLI and never reach the database. Measured: `wp option get` as h44's harvest user fails with "Failed to open stream: Permission denied". I found that out by landing the read-the-option version and watching it fail closed, having tested the SSH with a root key instead of the harvest key. Testing as the wrong principal is how a permissions assumption survives verification. Granting that identity database access would dismantle a good boundary to work around it. Instead the option stays authoritative and gains a projection: one wp-content-relative path per line, rewritten on every upgrade, that a shell with no database access can cat. Deliberately outside the site root. SITE_PATH is the nginx web root — verified on h44 — so a file written there is fetchable over HTTP, and the install's component layout is not something to publish for a local reader's convenience. Deliberately 0644: the reader is an unprivileged identity that is not us, and a mode that keeps it out defeats the only reason the file exists. Leaving owned mode removes it. A stale manifest is worse than none: it keeps asserting an editable set the permission layer has stopped granting, and a capture reading it would go on harvesting components the agent can no longer touch, reporting health for a relationship that ended.
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.
Implements the missing half of #336 — the part that lets out-of-band capture derive its component list instead of duplicating it.
The invariant, and why nothing held it
Owned mode rests on: the editable set equals the set the operator's capture records. A path the agent may edit but nothing captures is where work silently disappears — invisible on a live site until an update overwrites it.
The two lists lived in different places. h44lacrosse.com had three components declared in
wp_coding_agents_owned_sources, and the same three hardcoded twice in its harvest workflow, kept in agreement by nothing but memory.Why capture can't just read the option
Because it runs as a deliberately read-only identity that cannot read
wp-config.php— a file wp-coding-agents hardens towww-data:640itself. So it can never run WP-CLI and never reach the database:I learned this the direct way: I landed a read-the-option version on h44 and watched it fail closed, having tested the SSH with a root key rather than the harvest key. Testing as the wrong principal is how a permissions assumption survives verification. Reverted within minutes; the daily harvest never missed a window.
Granting that identity database access would dismantle a good boundary to work around it.
The projection
The option stays authoritative and gains a derived artifact:
One wp-content-relative path per line, rewritten on every upgrade, readable by a shell with no database access.
Outside the site root —
SITE_PATHis the nginx web root (verified on h44:root /var/www/h44lacrosse.com;), so a file written there is fetchable over HTTP. The component layout isn't something to publish for a local reader's convenience.Mode 0644 — the reader is an unprivileged identity that is not us. A mode that keeps it out defeats the only reason the file exists.
Removed when the install leaves owned mode. A stale manifest is worse than none: it keeps asserting an editable set the permission layer has stopped granting, and a capture reading it would harvest components the agent can no longer touch — reporting health for a relationship that ended.
Coverage
Added to
tests/source-mode.sh: written and formatted correctly, world-readable, outside the site root, blank lines stripped, no trailing newline artifact, dry-run touches nothing, local installs write nothing, and leaving owned mode removes it.Next
With this landed and released, h44's harvest change relands unchanged except for its source —
catthe manifest over the SSH it already has, instead ofwp option getit cannot run.