Skip to content

feat: project the declared owned sources to a readable manifest - #338

Merged
chubes4 merged 1 commit into
mainfrom
owned-sources-manifest
Aug 5, 2026
Merged

feat: project the declared owned sources to a readable manifest#338
chubes4 merged 1 commit into
mainfrom
owned-sources-manifest

Conversation

@chubes4

@chubes4 chubes4 commented Aug 5, 2026

Copy link
Copy Markdown
Member

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 to www-data:640 itself. So it can never run WP-CLI and never reach the database:

$ su h44harvest -c 'wp --path=/var/www/h44lacrosse.com option get wp_coding_agents_owned_sources'
PHP Warning: file_get_contents(/var/www/h44lacrosse.com/wp-config.php): Failed to open stream: Permission denied

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:

/var/lib/wp-coding-agents/<domain>/owned-sources

One wp-content-relative path per line, rewritten on every upgrade, readable by a shell with no database access.

Outside the site rootSITE_PATH is 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 — cat the manifest over the SSH it already has, instead of wp option get it cannot run.

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.
@chubes4
chubes4 merged commit d77305f into main Aug 5, 2026
42 checks passed
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.

1 participant