Skip to content

Support a managed-hosting posture: agent edits live source, no DMC/git - #315

Merged
chubes4 merged 1 commit into
mainfrom
fix/314-managed-posture
Aug 2, 2026
Merged

Support a managed-hosting posture: agent edits live source, no DMC/git#315
chubes4 merged 1 commit into
mainfrom
fix/314-managed-posture

Conversation

@chubes4

@chubes4 chubes4 commented Aug 2, 2026

Copy link
Copy Markdown
Member

Closes #314.

The bug this starts from

h44lacrosse.com is managed agentic hosting: h44-bot edits the live theme and plugins in place, and chubes4/h44-lacrosse .github/workflows/harvest.yml captures the result into git out-of-band (homeboy harvest --check / --apply, daily + workflow_dispatch, read-only over SSH, deploy stays manual). The agent has no repo, no homeboy, and no role in that pipeline.

The install wp-coding-agents gave it says the opposite three times over. Live opencode.json:

"permission": {
  "external_directory": { "/var/lib/datamachine/workspace/**": "allow" },
  "edit": {
    "wp-content/plugins/**": "deny",
    "wp-content/themes/**": "deny",
    "wp-includes/**": "deny"
  }
}

Hard-denied on exactly the two trees it exists to edit, while granted a workspace directory that is empty (workspace list → "No repos in workspace"). The AGENTS.md prose separately told it to route changes through a workspace and pull requests.

Why a refactor rather than a flag

Posture touches five files that had independently hardcoded the same three globs:

enforcement point file
OpenCode permission.edit runtimes/opencode.sh
Claude Code permissions.deny runtimes/claude-code.sh
Codex filesystem profile runtimes/codex.sh
opencode.json reconciliation lib/repair-opencode-json.py
AGENTS.md prose lib/agents-md-guidance.sh

Bolting if posture onto each is how prose and permissions drift apart again.

What changed

lib/source-policy.sh — one answer, five consumers. Returns the root/action matrix and whether a workspace exists for the active posture. This de-duplicates policy that was already duplicated before posture existed.

guidance/ — AGENTS.md content split out of the mu-plugin mechanism, following the existing runtimes/ and bridges/_dispatch.sh pattern. A section whose prose depends on posture ships <id>.<posture>.sh; posture-neutral sections ship <id>.sh. lib/agents-md-guidance.sh is now mechanism only and no longer knows what WordPress or Homeboy are. setup.sh/upgrade.sh call guidance_sync_all.

lib/data-machine.shmanaged skips data-machine-code on setup and upgrade. Hand-deactivating never stuck because update_plugin_to_latest_tag activates. AGENTS.md composition is unaffected: data-machine core registers the composable file itself behind the same DATAMACHINE_COMPOSE_AGENTS_MD gate, so a managed install still composes correctly, just without the workspace architecture.

Posture persistence — recorded as a wp option at setup (mirroring datamachine_code_homeboy_available), so upgrade.sh converges a managed box instead of silently reverting it. --posture on either script overrides and re-records.

Managed guidance content — carries the facts the engineering variant has no reason to state: edits are live on save, work is unbacked until captured, rollback is an operator action, and the paths a capture silently skips (vendor/, node_modules/, lockfiles, manifests, build output) are destroyed by the next deploy if edited live.

Posture matrix

engineering (default) managed
wp-content/themes/, wp-content/plugins/ read-only editable
wp-includes/ read-only read-only
data-machine-code installed not installed
workspace / git / GitHub the workflow absent

Verification

  • Engineering output is unchanged. Generated opencode.json and .claude/settings.json diffed byte-for-byte against generation from origin/main (modulo tmpdir paths): identical. Codex covered by the existing tests/codex-permissions.sh.
  • tests/posture.sh (new, wired into shell.yml) pins both invariants: engineering byte-identity, and agreement between managed prose and managed permissions. Also covers an unknown posture degrading to read-only rather than to write, and a posture switch actively clearing stale Claude Code denies — leaving them behind is the exact failure mode that blocked h44-bot.
  • Full tests/*.sh run: no new failures. Seven pre-existing failures (agents-md-backup-retention, carried-claude-code-plugin, cli-channel-binary-path, datamachine-worker, homeboy-dmc-provider, homeboy-project-id, kimaki-agent-fallback) fail identically on origin/main in this environment; none are in shell.yml.
  • bash -n clean across every .sh.

Follow-up, not in this PR

Applying --posture managed to the live h44 box is an operator action and is deliberately left out.

wp-coding-agents produced exactly one shape of install: an engineering
posture where installed WordPress source is read-only reference and every
code change routes through a Data Machine Code workspace, git, and GitHub.

That is wrong for managed agentic hosting, where the agent edits the live
theme and plugins in place and changes are captured out-of-band. On
h44lacrosse.com the generated install actively blocked its own architecture:
opencode.json denied edit on wp-content/{plugins,themes} — the two trees the
agent exists to edit — while granting an empty workspace directory, and the
AGENTS.md prose told it to open pull requests it had no repo for.

Posture is a cross-cutting install-time concern, so the same three globs were
hardcoded independently in five places: both runtime permission writers, the
Codex filesystem profile, the opencode.json reconciler, and the guidance
prose. Adding a conditional to each is how they drift apart again.

- lib/source-policy.sh is the single answer all five derive from. Duplicated
  policy is the reason prose and permissions could disagree; now they cannot.
- guidance/ splits AGENTS.md content from the mu-plugin mechanism, following
  the existing runtimes/ and bridges/ dispatcher pattern. Sections whose prose
  depends on posture ship one file per variant rather than a conditional
  wrapped around a heredoc. lib/agents-md-guidance.sh no longer knows what
  WordPress or Homeboy are.
- managed skips data-machine-code entirely on both setup and upgrade. Hand
  deactivation never stuck because update_plugin_to_latest_tag reactivates.
  AGENTS.md still composes: data-machine core registers the file itself.
- The managed wordpress-source variant carries the production contract an
  agent editing live source needs — edits are live on save, work is unbacked
  until captured, and which paths a capture silently skips.
- Posture is recorded on the install so upgrades converge instead of
  reverting a managed box to engineering.

Engineering output is unchanged: opencode.json and claude-code settings.json
verified byte-identical to pre-refactor generation, and tests/posture.sh pins
both that invariant and the agreement between managed prose and permissions.
@chubes4
chubes4 merged commit 90cde4d into main Aug 2, 2026
17 checks passed
@chubes4
chubes4 deleted the fix/314-managed-posture branch August 2, 2026 15:29
chubes4 added a commit that referenced this pull request Aug 3, 2026
…e gaps

Closes #322. Supersedes #320, which fixed false claims inside a shape that was
itself wrong.

PURPOSE. The section exists so the agent is an expert on its own runtime by
reading the WordPress installed underneath it — that is what lets a small model
be competent about WordPress with no skills and no fine-tuning. Direct
Reference was the point; Read-Only was a qualifier. The managed variant added
in #315 inverted that into an editable list, a read-only list, a justification,
a production contract, and a "not source" appendix, with the reference clause
buried in the middle.

The restriction half was also redundant. lib/source-policy.sh makes the runtime
enforce it; prose enforces nothing. Restating the deny list bought no safety
while crowding out the one thing prose is uniquely good at. Both variants now
lead with what the source is FOR and name the boundary only so the agent is not
misinformed about it. The managed section is roughly half its previous length.

CORE WAS HALF-DEFINED. wp-admin and wp-includes are siblings, not nested. The
root set was wp-content/plugins, wp-content/themes, wp-includes — so on every
install ever created the agent could edit wp-admin and every root PHP file,
including wp-config.php, which holds the database credentials and the constants
that gate AGENTS.md composition. As reference the same omission pointed the
agent away from list tables, media, and the upgrade routines. Both halves fixed
by defining core once.

wp-content/mu-plugins is now denied too. It is agent governance, not a site
extension: wp-coding-agents installs the mu-plugin that GENERATES AGENTS.md
there. wp-content/uploads stays open because the agent's memory lives under it.

DECLARED EXCEPTIONS. wp-config.php has real uses (WP_DEBUG, memory limits), so
--managed-writable re-opens a denied path. It cannot reuse --managed-source:
that flag means editable AND captured, which is what makes the "your work is
recorded" promise true, and wp-config.php is not captured by a component
harvest. Conflating them would assert a safety property that does not hold —
the #318 failure. Separate category, separate prose.

LOGS. The limitation was backwards. We were strict about editing core (correct;
an update overwrites it anyway) and accidentally strict about READING the one
thing needed to recover from a fatal. OpenCode gates paths outside the site
root behind external_directory, which defaults to ask, and an autonomous agent
has nobody to ask — so the most important recovery capability in the design sat
behind an unanswerable prompt. --log-path grants read and explicitly denies
edit.

GLOB TRAP. Root files are exact literals, not globs. OpenCode's matcher turns *
into .* which spans slashes, so wp-*.php would also match
wp-content/plugins/acme/wp-thing.php and over-deny inside a site's own
component. source_policy_edit_rules now emits a dir/file kind so each runtime
can format correctly.

tests/posture.sh pins the purpose (reference framing present in both variants),
the enumerate-never-generalise rule, the writable-is-not-captured distinction,
log paths readable but not writable, uploads untouched, ordering, and the #320
guard against naming one install's stack.
chubes4 added a commit that referenced this pull request Aug 3, 2026
…-config/mu-plugins gaps (#321)

* fix: stop asserting one install's stack in managed guidance (#320)

The managed wordpress-source section is generated into the AGENTS.md of every
managed install, but it described h44lacrosse.com's stack as if it were
universal:

- "including commerce and payment code" and "the site's ability to take money"
  — there may be no store and no gateway
- "the runtime that gives you memory and tools" — that is Data Machine, this
  install's happenstance, not a property of wp-content/plugins
- a specific never-captured list (vendor, node_modules, composer.lock,
  package-lock.json, package.json) — one operator's harvest excludes copied
  verbatim, from a config this tool does not own and cannot read
- "exactly what the operator's capture records" — the intended invariant of the
  posture, which the tool has no way to verify

This is the #314/#318 failure in a third form. Those were prose claiming things
the enforced policy could not back; this is prose claiming things about the
environment that the tool never inspected. An agent told that editing a plugin
breaks the site's ability to take money, on a site with no commerce plugin, has
been handed a false model of its own install by the file that is supposed to be
its most reliable source of truth. Presenting a guessed exclude list is worse
than silence, because the agent will trust it.

Now describes the category and the reason and leaves the specifics to the
operator: plugins this site did not author, including whatever this agent
depends on; an update overwrites the change and nothing captures it, both true
regardless of what any plugin does; installed and generated files are not
authored source, so ask the operator which paths this site actually captures
rather than assume.

tests/posture.sh gains a guard asserting the rendered prose names none of
WooCommerce, Stripe, commerce, payment, money, composer.lock, package-lock,
node_modules, Data Machine, homeboy, harvest.yml, or wp-admin. Verified
non-vacuous: reintroducing a single product name fails the suite.

The two rules this file must follow are now recorded at the top of it.

* fix: restore the wordpress-source section's purpose and close the core gaps

Closes #322. Supersedes #320, which fixed false claims inside a shape that was
itself wrong.

PURPOSE. The section exists so the agent is an expert on its own runtime by
reading the WordPress installed underneath it — that is what lets a small model
be competent about WordPress with no skills and no fine-tuning. Direct
Reference was the point; Read-Only was a qualifier. The managed variant added
in #315 inverted that into an editable list, a read-only list, a justification,
a production contract, and a "not source" appendix, with the reference clause
buried in the middle.

The restriction half was also redundant. lib/source-policy.sh makes the runtime
enforce it; prose enforces nothing. Restating the deny list bought no safety
while crowding out the one thing prose is uniquely good at. Both variants now
lead with what the source is FOR and name the boundary only so the agent is not
misinformed about it. The managed section is roughly half its previous length.

CORE WAS HALF-DEFINED. wp-admin and wp-includes are siblings, not nested. The
root set was wp-content/plugins, wp-content/themes, wp-includes — so on every
install ever created the agent could edit wp-admin and every root PHP file,
including wp-config.php, which holds the database credentials and the constants
that gate AGENTS.md composition. As reference the same omission pointed the
agent away from list tables, media, and the upgrade routines. Both halves fixed
by defining core once.

wp-content/mu-plugins is now denied too. It is agent governance, not a site
extension: wp-coding-agents installs the mu-plugin that GENERATES AGENTS.md
there. wp-content/uploads stays open because the agent's memory lives under it.

DECLARED EXCEPTIONS. wp-config.php has real uses (WP_DEBUG, memory limits), so
--managed-writable re-opens a denied path. It cannot reuse --managed-source:
that flag means editable AND captured, which is what makes the "your work is
recorded" promise true, and wp-config.php is not captured by a component
harvest. Conflating them would assert a safety property that does not hold —
the #318 failure. Separate category, separate prose.

LOGS. The limitation was backwards. We were strict about editing core (correct;
an update overwrites it anyway) and accidentally strict about READING the one
thing needed to recover from a fatal. OpenCode gates paths outside the site
root behind external_directory, which defaults to ask, and an autonomous agent
has nobody to ask — so the most important recovery capability in the design sat
behind an unanswerable prompt. --log-path grants read and explicitly denies
edit.

GLOB TRAP. Root files are exact literals, not globs. OpenCode's matcher turns *
into .* which spans slashes, so wp-*.php would also match
wp-content/plugins/acme/wp-thing.php and over-deny inside a site's own
component. source_policy_edit_rules now emits a dir/file kind so each runtime
can format correctly.

tests/posture.sh pins the purpose (reference framing present in both variants),
the enumerate-never-generalise rule, the writable-is-not-captured distinction,
log paths readable but not writable, uploads untouched, ordering, and the #320
guard against naming one install's stack.
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.

Support a managed-hosting posture: agent edits live source, no DMC/git, harvest out-of-band

1 participant