Skip to content

fix(lib,tests): mu-plugin perms self-heal + effective-prompt .actual cleanup#136

Merged
chubes4 merged 2 commits into
mainfrom
fix/133-134-mu-plugin-perms-and-test-artifacts
May 17, 2026
Merged

fix(lib,tests): mu-plugin perms self-heal + effective-prompt .actual cleanup#136
chubes4 merged 2 commits into
mainfrom
fix/133-134-mu-plugin-perms-and-test-artifacts

Conversation

@chubes4
Copy link
Copy Markdown
Member

@chubes4 chubes4 commented May 17, 2026

Summary

Two paper-cut bugs blocking real workflows on the extrachill.com VPS.

#133 — mu-plugin perms (lib/cli-channel.sh, lib/runtime-signature.sh)

cli_channel_ensure_mu_plugin_file / cli_channel_register / cli_channel_unregister and the runtime-signature equivalents wrote their mu-plugin files using the caller's umask. Root cron/systemd contexts default to umask 0077, producing mode 0600 files that PHP-FPM (www-data) cannot read — every WordPress request then logs "Permission denied" from wp-settings.php and the datamachine_code_cli_channels / datamachine_code_worktree_runtime_signatures filters return empty arrays, breaking dispatch-message and worktree origin attribution respectively.

Implementation chosen: chmod 0644 after every write path. This integrates most cleanly with the existing cat > "$file" and mktemp + mv patterns the helpers already use, requires no restructuring around install -m 0644, and keeps the umask of the calling context untouched (no risk of leaking umask 022 into other helpers that share the bash process).

Three write paths covered in both helpers:

  1. Initial scaffold (cat > $filechmod 0644)
  2. Register replacing/inserting a block (mktemp creates at 0600, mv preserves, then chmod 0644)
  3. Unregister removing a block (same mktemp + mv path)

The chmod after mktemp + mv is what gives us self-heal: a legacy 0600 file from a pre-#133 install gets upgraded the next time the helper touches it.

#134.actual snapshot files (tests/effective-prompt/run.mjs)

The effective-prompt harness writes .actual diff-intermediate files alongside the canonical snapshots when a scenario fails, but never cleans them up. They accumulated in tests/effective-prompt/__snapshots__/ and dirtied the working tree, which blocks homeboy release wp-coding-agents on preflight.working_tree.

Two-part fix matching the issue's recommendation:

  1. tests/effective-prompt/__snapshots__/.gitignore ignoring *.actual. Canonical .baseline.txt / .filtered.txt / .raw.txt snapshots stay tracked; only the diff intermediates become invisible to git.
  2. Cleanup in run.mjs: after the main loop with zero failures, unlink every *.actual in __snapshots__/. On any failure, leave them in place for reviewer inspection.

Snapshot mismatches still surface as visible test failures. The harness already prints <label> snapshot drift — run with --update to refresh in the FAIL block and exits non-zero. The .gitignore only hides the intermediate copies of the new content, not the failure signal itself. Confirmed end-to-end on this branch: drifted snapshots produced loud FAIL output with .actual files left on disk for inspection but absent from git status.

Verification

bash -n on modified scripts

$ bash -n lib/cli-channel.sh && bash -n lib/runtime-signature.sh \
    && bash -n tests/cli-channel-perms.sh && bash -n tests/runtime-signature.sh
(clean)

Perms test (umask 077 + self-heal scenarios)

$ bash tests/cli-channel-perms.sh
==> register kimaki (fresh scaffold, umask 077)
  ok   fresh scaffold lands as 0644 under umask 077
==> simulate legacy 0600 file and verify self-heal on next register
  ok   mu-plugin self-healed to 0644 after sibling register
==> unregister opencode-telegram
  ok   mu-plugin mode 0644 after unregister

OK: all cli-channel perms assertions passed

$ bash tests/runtime-signature.sh
==> register kimaki (fresh scaffold, umask 077)
  ok   mu-plugin created
  ok   scaffold parses with php -l
  ok   mu-plugin mode 0644 after fresh write under umask 077
  ok   kimaki block present
==> re-register kimaki with same signature (idempotent)
  ok   file unchanged on re-register
==> simulate legacy 0600 file and verify self-heal on next register
  ok   two-runtime file parses with php -l
  ok   mu-plugin mode self-healed to 0644 after sibling register
  ok   both runtime blocks present
==> re-register kimaki with mutated signature
  ok   kimaki block updated
  ok   opencode block preserved across kimaki mutation
==> unregister opencode
  ok   opencode block removed
  ok   kimaki block preserved across opencode unregister
  ok   post-unregister file parses with php -l
  ok   mu-plugin mode 0644 after unregister
==> apply_filters returns the expected shape
  ok   filter returns surviving kimaki signature

OK: all runtime-signature assertions passed

Regression-validity check (test fails without the fix)

Stashed the fix and re-ran tests/cli-channel-perms.sh against unpatched lib/cli-channel.sh:

==> register kimaki (fresh scaffold, umask 077)
  FAIL fresh scaffold lands as 0644 under umask 077
    got:  600
    want: 644
==> simulate legacy 0600 file and verify self-heal on next register
  FAIL mu-plugin self-healed to 0644 after sibling register
    got:  600
    want: 644
==> unregister opencode-telegram
  FAIL mu-plugin mode 0644 after unregister
    got:  600
    want: 644

FAILED: 3 assertion(s)

Test catches the exact bug from the bug report.

Effective-prompt cleanup

Ran the harness against drifted snapshots (kimaki on host has moved ahead of committed snapshots — unrelated to this PR). .actual files were written to disk on failure but were absent from git status thanks to the new .gitignore. After --update to refresh snapshots, the next clean run unlinked every .actual file and left tests/effective-prompt/__snapshots__/ containing only the six canonical .txt files. Working tree clean.

No release/version churn

  • No CHANGELOG.md edits
  • No VERSION edits
  • No homeboy release / homeboy deploy run

Closes #133, closes #134

chubes4 added 2 commits May 17, 2026 20:17
…me-signature

cli_channel_ensure_mu_plugin_file / cli_channel_register / cli_channel_unregister
and the runtime-signature equivalents wrote their mu-plugin files using the
caller's umask. Root cron/systemd contexts default to umask 0077, producing
mode 0600 files that PHP-FPM (www-data) cannot read — every WordPress
request then logs 'Permission denied' from wp-settings.php and the
datamachine_code_cli_channels / datamachine_code_worktree_runtime_signatures
filters return empty arrays, breaking dispatch-message and worktree origin
attribution respectively.

Force mode 0644 after every write path in both helpers:

  * Initial scaffold write (cat > $file)
  * Block register (mktemp + mv — mktemp creates at 0600, mv preserves)
  * Block unregister (same mktemp + mv path)

The chmod after mktemp+mv also self-heals legacy installs that already
have a 0600 file on disk — the next register/unregister upgrades it.

Add tests/cli-channel-perms.sh covering all three write paths under a
hostile umask (umask 077) plus the self-heal scenario. Extend
tests/runtime-signature.sh with the same assertions: fresh write under
umask 077 lands as 0644, sibling register self-heals a 0600 file, and
unregister preserves 0644.

Closes #133
…tive-prompt runs

tests/effective-prompt/run.mjs writes .actual diff-intermediates next to
the canonical .baseline.txt / .filtered.txt / .raw.txt snapshots whenever
a scenario fails snapshot comparison, but never removes them. They
accumulate in tests/effective-prompt/__snapshots__/ and dirty the working
tree, which blocks 'homeboy release wp-coding-agents' on its
preflight.working_tree check.

Two-part fix matching the issue's recommendation:

  1. Add tests/effective-prompt/__snapshots__/.gitignore ignoring *.actual.
     Canonical snapshots stay tracked; diff intermediates become invisible
     to git but still land on disk for reviewer inspection when a test
     fails. Snapshot drift still surfaces loudly because run.mjs prints
     'snapshot drift — run with --update to refresh' in the FAIL block and
     exits non-zero; the .gitignore only hides the intermediate copies of
     the new content, not the failure signal itself.

  2. In run.mjs, after the main loop completes with zero failures, unlink
     every *.actual file in __snapshots__/. On any failure, leave them in
     place so the reviewer can inspect the diff. This means a clean run
     produces a clean working tree even without the .gitignore, and the
     .gitignore handles the failure case.

Verified end-to-end: drifted snapshots produce a FAIL with .actual files
left on disk but absent from 'git status'; a passing run after --update
clears the .actual files entirely.

Closes #134
@chubes4
Copy link
Copy Markdown
Member Author

chubes4 commented May 17, 2026

<@532385681268408341> ready for review — two paper-cut fixes (#133 mu-plugin perms self-heal, #134 effective-prompt .actual cleanup). All scoped tests green, regression-validity check confirms perms test catches the exact bug from #133. No release/deploy run.

@chubes4 chubes4 merged commit acf2c71 into main May 17, 2026
4 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

1 participant