reconcile(z890): node onto main + CHIT/tokenism/guard fixes found along the way - #2184
Conversation
Aggregation Model A (submodule) reconciling combined PMOVES-pinokio fork with distributed per-service launchers (crush, claude-code) + forked plugin/code. Captures the closed-CLI constraint (claude-code integration via extension surface + Agent SDK, not a binary fork). Design approved verbally; pending written-spec review before writing-plans. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
auto-cli = Pinokio's built-in CLI launcher generator (use installable variant for claude-code/crush). Submodule recurse handled by explicit git submodule update --init --recursive in each launcher install.js (no Pinokio-clone dependency). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
5 atomic tasks (code fork+claude repoint, pmoves-claude-code launcher/scaffold, PMOVES-pinokio assembly, meta-repo pbnj submodule, install repoint+validation). Self-review fix: pmoves-claude-code launcher at repo root so its api submodule places pinokio.js correctly. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Respects PMOVES-crush's nested launcher structure; keeps version-pinning. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… guards git-rm is a reversible git-index removal, categorically different from a recursive filesystem delete. Adds (?<!git\s) to the four recursive/force delete patterns (patterns.yaml) and the DELETE matcher (bash-tool-damage-control.py), mirroring the existing docker/podman exclusion. Verified: git-index removals pass; real recursive filesystem deletes and no-delete-path deletes still block. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…d onto main) 5 pmoves-* skills (legal-assist, folder-monitor, email-organizer, yt-monitor, n8n-archon-bridge), pmoves/launcher/, launcher_profile_select.py, and the profile .env.example (placeholders only). Node-local .env + .hermes stay untracked. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Add topic domain predicate (_is_topic_target) for pmoves/contracts/topics.json and register it in DOMAIN_PATTERNS alongside main's existing compose/schema/dockerfile domains (schema was already merged upstream; dockerfile untouched). - Append 7 local trail lines (schema + topic grants) to known-roads.jsonl, additive only, no overlap with main's existing entries. z890-coordinator.yaml needed no change: main already has the Intel Core Ultra 7 265K hardware fix (commit 0f578bb), superseding the stale AMD Ryzen values in the local WIP snapshot. z890-infra.room.fabric.json left uncommitted: the local WIP version fails validation against main's current room schema ('stage' is a required property, missing from the WIP file). Not forced — needs a human decision on the correct stage value.
…TH (CHIT signing) Build context/dockerfile path and pmoves.tools packaging were already fixed on main via #2070/#2125. The remaining gap: docker-compose.yml's TOKENISM_IMAGE default pointed at an orphan tag (ghcr.io/powerfulmoves/pmoves-tokenism:pmoves-latest) that no CI workflow ever builds. pmoves/images.yaml defines the real matrix entry (name: pmoves-tokenism-simulator, context: pmoves, dockerfile: services/tokenism-simulator/Dockerfile) which build-images.yml tags as :edge. Repoint the default to that name/tag. Also add explicit ENV PYTHONPATH=/app to the Dockerfile, mirroring the hi-rag-gateway-v2 house pattern, so the pmoves.tools.* / services.common.* imports resolve independent of invocation cwd.
…crets-audit) Line 63 read the CHIT signing secret via os.getenv while the rest of the file already uses the get_secret helper. get_secret is a strict superset (env var first, then KEY_FILE indirection), so behavior is preserved and Docker secret file-mounting now works. Unblocks the secrets-audit hard-gate in secrets-funnel. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
📝 WalkthroughWalkthroughThis PR updates damage-control rules, adds Known Roads topic validation, introduces a Z890 launcher profile and selector, wires new room workflows and skill specifications, documents combined Pinokio customization, and adjusts service credential and container defaults. ChangesDamage-control and Known Roads
Launcher profiles and Z890 workflows
Combined Pinokio customization
Runtime and image updates
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant LauncherCLI
participant LauncherProfile
participant HermesConfig
participant SecretTemplate
LauncherCLI->>LauncherProfile: Load and validate profile
LauncherCLI->>HermesConfig: Merge Hermes overrides
LauncherCLI->>SecretTemplate: Render blank secret entries
LauncherCLI->>HermesConfig: Write configuration and backup
Possibly related PRs
Suggested labels: Suggested reviewers: 🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Docker Hardening ValidationHardening Validation ReportValidated: Tue Jul 21 17:26:14 UTC 2026Services CheckedPMOVES.AI Docker Hardening Validation[INFO] Checking: pmoves/docker-compose.hardened.yml [INFO] Validating: hi-rag-gateway-v2 [INFO] Validating: extract-worker [INFO] Validating: langextract [INFO] Validating: presign [INFO] Validating: render-webhook [INFO] Validating: retrieval-eval [INFO] Validating: pdf-ingest [INFO] Validating: jellyfin-bridge [INFO] Validating: invidious-companion-proxy [INFO] Validating: ffmpeg-whisper [INFO] Validating: media-video [INFO] Validating: media-audio [INFO] Validating: hi-rag-gateway-v2-gpu [INFO] Validating: hi-rag-gateway-gpu [INFO] Validating: deepresearch [INFO] Validating: supaserch [INFO] Validating: publisher-discord [INFO] Validating: mesh-agent [INFO] Validating: nats-echo-req [INFO] Validating: nats-echo-res [INFO] Validating: comfy-watcher [INFO] Validating: grayjay-plugin-host [INFO] Validating: agent-zero [INFO] Validating: p7-room-orchestrator [INFO] Validating: archon [INFO] Validating: channel-monitor [INFO] Validating: pmoves-yt [INFO] Validating: notebook-sync [INFO] Validating: supabase_service_role_key [INFO] Validating: supabase_jwt_secret [INFO] Validating: p7_control_token ====================================== |
🔏 CHIT-aware change — control-body routingThis PR touches a CHIT-aware service (ports 8086/8087/8103/8106/8113/9224 surface). Before a Control-Body ACK:
Advisory routing only — the blocking contract check is CHIT Contract. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: eab9edb883
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…argets, dry-run mkdir + verified CodeQL suppressions - room manifest: mark legal/email/n8n panels status:planned until routes exist (Codex) - launcher profile: up-archon->up-archon-native (valid target); drop skills that arent make targets (Codex) - launcher_profile_select.py: dry-run no longer creates the Hermes profile dir; lgtm suppressions for env_content writes/logs (KEY NAMES only, values blank) (Codex + CodeQL) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Docker Hardening ValidationHardening Validation ReportValidated: Wed Jul 22 00:48:15 UTC 2026Services CheckedPMOVES.AI Docker Hardening Validation[INFO] Checking: pmoves/docker-compose.hardened.yml [INFO] Validating: hi-rag-gateway-v2 [INFO] Validating: extract-worker [INFO] Validating: langextract [INFO] Validating: presign [INFO] Validating: render-webhook [INFO] Validating: retrieval-eval [INFO] Validating: pdf-ingest [INFO] Validating: jellyfin-bridge [INFO] Validating: invidious-companion-proxy [INFO] Validating: ffmpeg-whisper [INFO] Validating: media-video [INFO] Validating: media-audio [INFO] Validating: hi-rag-gateway-v2-gpu [INFO] Validating: hi-rag-gateway-gpu [INFO] Validating: deepresearch [INFO] Validating: supaserch [INFO] Validating: publisher-discord [INFO] Validating: mesh-agent [INFO] Validating: nats-echo-req [INFO] Validating: nats-echo-res [INFO] Validating: comfy-watcher [INFO] Validating: grayjay-plugin-host [INFO] Validating: agent-zero [INFO] Validating: p7-room-orchestrator [INFO] Validating: archon [INFO] Validating: channel-monitor [INFO] Validating: pmoves-yt [INFO] Validating: notebook-sync [INFO] Validating: supabase_service_role_key [INFO] Validating: supabase_jwt_secret [INFO] Validating: p7_control_token ====================================== |
There was a problem hiding this comment.
Actionable comments posted: 14
🧹 Nitpick comments (2)
docs/superpowers/specs/2026-07-20-pmoves-combined-pinokio-customization-design.md (1)
90-95: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDefine staged documentation obligations.
Because this is a design-only spec and does not introduce service endpoints yet, do not add premature catalog entries. Instead, state which implementation stages must update
.claude/context/nats-subjects.mdand.claude/context/services-catalog.md, and referencepmoves/docs/AGENTS/PMOVES_AGENT_TOPOLOGY.mdfor the required topology diagrams.Based on learnings, design-only specs must define explicit per-stage catalog obligations rather than documenting unbuilt endpoints.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@docs/superpowers/specs/2026-07-20-pmoves-combined-pinokio-customization-design.md` around lines 90 - 95, Update the “Testing / validation” or related documentation section to define explicit per-stage obligations for updating .claude/context/nats-subjects.md and .claude/context/services-catalog.md, without adding catalog entries for this design-only spec. State that stages introducing or changing NATS subjects or services must update the corresponding context document, and require topology diagrams to follow pmoves/docs/AGENTS/PMOVES_AGENT_TOPOLOGY.md.Sources: Coding guidelines, Learnings
pmoves/config/rooms/z890-infra.room.fabric.json (1)
487-498: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winNew NATS subjects not reflected in the topology doc.
ops.email.triage.completed.v1andmedia.transcript.ready.v1are newly introduced here. As per coding guidelines, NATS event topology should be documented in.claude/context/nats-subjects.md(not included in this changeset).🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@pmoves/config/rooms/z890-infra.room.fabric.json` around lines 487 - 498, Update .claude/context/nats-subjects.md to document the newly allowed NATS subjects ops.email.triage.completed.v1 and media.transcript.ready.v1, matching the existing topology documentation format and their publish configuration in the policies section.Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.claude/hooks/damage-control/known_roads.py:
- Around line 81-99: Update _is_topic_target so topics.json is authorized only
when its immediate parent directory is contracts, while preserving the existing
PMOVES-owned tree requirement. Replace the broad contracts-segment check and add
negative coverage for nested and otherwise unrelated contracts paths.
In @.claude/hooks/damage-control/patterns.yaml:
- Around line 27-44: The `git rm` exemption is overly broad because both
enforcement layers match raw shell text and allow destructive deletions through
wrappers or inconsistent option forms. In
`.claude/hooks/damage-control/patterns.yaml` lines 27-44, replace the
negative-lookbehind patterns with command-aware matching that preserves
protection for filesystem `rm` while applying an explicit policy to `git rm`; in
`.claude/hooks/damage-control/bash-tool-damage-control.py` lines 80-82, apply
the same parsed-command policy to `READ_ONLY_BLOCKED` and `NO_DELETE_BLOCKED`.
In `@docs/superpowers/plans/2026-07-20-combined-pinokio-customization.md`:
- Around line 132-137: Remove the hardcoded PMOVES_NODE_ID value from the
bootstrap settings in the “Minimal PMOVES bootstrap config” step. Leave the
setting unset so each installation can obtain its node identity from runtime
environment or secret management.
- Around line 78-84: Update each “Commit + push + PR” step to use an atomic
topic branch rather than pushing directly to PMOVES.AI-Edition-Hardened: create
and push a uniquely named topic branch, open a PR targeting the hardened branch,
and document smoke-test commands/results plus reviewer notes before merge. Apply
this consistently to all three referenced plan sections.
- Around line 233-236: Update Step 4’s recursive submodule verification to
inspect every entry rather than filtering with grep pbnj. Make the check fail
when any recursive status line begins with -, +, or U, while accepting
initialized, conflict-free nested submodules.
In
`@docs/superpowers/specs/2026-07-20-pmoves-combined-pinokio-customization-design.md`:
- Around line 48-62: Update the topology Markdown code fence in the documented
repository layout near “PMOVES-pinokio” to specify the text language, preserving
the diagram content unchanged.
- Around line 42-44: The repository topology must explicitly distinguish Phase-1
direct submodules from thin in-tree launcher entries. In
docs/superpowers/specs/2026-07-20-pmoves-combined-pinokio-customization-design.md
lines 42-44, state whether plugin/code is included in Phase 1 and reflect the
finalized submodule/entry model across topology diagrams and .gitmodules
expectations. In
docs/superpowers/plans/2026-07-20-combined-pinokio-customization.md lines
165-194, update validation commands to verify sources/pmoves-crush as the actual
submodule while treating api/pmoves-crush as the thin launcher entry.
In `@pmoves/config/rooms/z890-infra.room.fabric.json`:
- Around line 3-6: Update the stage value in the Z890 Infra Fabric Room
configuration to match the Z890 launcher profile’s expected stage, while leaving
the version, display_name, and description unchanged.
- Line 486: Update the launcher_refs entry to use the documented up-archon
launcher name instead of up-archon-native, keeping the other launcher references
unchanged.
In `@pmoves/docker-compose.yml`:
- Line 3808: Update the TOKENISM_IMAGE default in docker-compose.ui.yml to
ghcr.io/powerfulmoves/pmoves-tokenism-simulator:edge, matching the default used
by the tokenism service in docker-compose.yml.
In `@pmoves/launcher/profiles/pmoves-hermes-z890.json`:
- Around line 9-13: Update the room.stage value in the z890-infra room
configuration within the pmoves-hermes-z890 profile to match the room manifest’s
rehearsal stage. Leave the room_id and suits entries unchanged.
- Around line 54-60: Align the Archon launcher reference in the profile with the
documented Make target: update the launcher_refs entry from up-archon-native to
up-archon, and keep the profile consistent with the corresponding room manifest.
In `@pmoves/skills/pmoves-n8n-archon-bridge/SKILL.md`:
- Around line 66-78: Update the local-running and next-implementation
documentation to use the configured make target up-archon-native instead of
up-archon, matching the launcher_refs entries in the referenced configuration
files. Preserve the existing command structure and surrounding n8n instructions.
In `@pmoves/tools/launcher_profile_select.py`:
- Around line 67-82: Update deep_merge so force only overwrites keys explicitly
present in overlay; preserve base values for keys absent from the launcher
profile, including when force=True. Continue recursively merging shared
dictionaries and applying existing force/empty-value behavior only to
overlay-provided keys.
---
Nitpick comments:
In
`@docs/superpowers/specs/2026-07-20-pmoves-combined-pinokio-customization-design.md`:
- Around line 90-95: Update the “Testing / validation” or related documentation
section to define explicit per-stage obligations for updating
.claude/context/nats-subjects.md and .claude/context/services-catalog.md,
without adding catalog entries for this design-only spec. State that stages
introducing or changing NATS subjects or services must update the corresponding
context document, and require topology diagrams to follow
pmoves/docs/AGENTS/PMOVES_AGENT_TOPOLOGY.md.
In `@pmoves/config/rooms/z890-infra.room.fabric.json`:
- Around line 487-498: Update .claude/context/nats-subjects.md to document the
newly allowed NATS subjects ops.email.triage.completed.v1 and
media.transcript.ready.v1, matching the existing topology documentation format
and their publish configuration in the policies section.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: f0784133-bb8a-475e-9e66-094df62e0419
📒 Files selected for processing (19)
.claude/hooks/damage-control/bash-tool-damage-control.py.claude/hooks/damage-control/known-roads.jsonl.claude/hooks/damage-control/known_roads.py.claude/hooks/damage-control/patterns.yamldocs/superpowers/plans/2026-07-20-combined-pinokio-customization.mddocs/superpowers/specs/2026-07-20-pmoves-combined-pinokio-customization-design.mdpmoves/config/profiles/pmoves-hermes-z890.env.examplepmoves/config/rooms/z890-infra.room.fabric.jsonpmoves/docker-compose.ymlpmoves/launcher/README.mdpmoves/launcher/profiles/pmoves-hermes-z890.jsonpmoves/services/evo-controller/app.pypmoves/services/tokenism-simulator/Dockerfilepmoves/skills/pmoves-email-organizer/SKILL.mdpmoves/skills/pmoves-folder-monitor/SKILL.mdpmoves/skills/pmoves-legal-assist/SKILL.mdpmoves/skills/pmoves-n8n-archon-bridge/SKILL.mdpmoves/skills/pmoves-yt-monitor/SKILL.mdpmoves/tools/launcher_profile_select.py
| def _is_topic_target(normalized_fwd: str) -> bool: | ||
| """topic domain: the PMOVES NATS subject registry pmoves/contracts/topics.json. | ||
|
|
||
| topics.json is readOnly because a change to the subject contract ripples to | ||
| every publisher/subscriber and the shared `events.publish` topic validator. | ||
| It is NOT a *.schema.json, so the schema domain does not cover it; this domain | ||
| opens ONLY that one file under a `contracts` segment in a PMOVES-owned tree, | ||
| and — like schema — still requires a provable reason (pr:/issue:/handoff:). | ||
| """ | ||
| basename = os.path.basename(normalized_fwd).lower() | ||
| if basename != "topics.json": | ||
| return False | ||
| parts = normalized_fwd.lower().split("/") | ||
| if not any( | ||
| p == "pmoves" or p.startswith("pmoves-") or p.startswith("pmoves.") | ||
| for p in parts | ||
| ): | ||
| return False | ||
| return "contracts" in parts |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
Require topics.json to be the direct child of contracts.
return "contracts" in parts also authorizes paths such as pmoves/contracts/untrusted/topics.json and pmoves/untrusted/contracts/topics.json. Since this predicate controls a Known Road bypass, those paths could receive topic-domain authorization incorrectly.
Proposed fix
- return "contracts" in parts
+ return any(
+ part == "contracts"
+ and index + 1 < len(parts)
+ and parts[index + 1] == "topics.json"
+ for index, part in enumerate(parts)
+ )Add negative tests for nested and unrelated contracts paths.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.claude/hooks/damage-control/known_roads.py around lines 81 - 99, Update
_is_topic_target so topics.json is authorized only when its immediate parent
directory is contracts, while preserving the existing PMOVES-owned tree
requirement. Replace the broad contracts-segment check and add negative coverage
for nested and otherwise unrelated contracts paths.
| # Negative lookbehinds exclude `docker rm`/`podman rm`/`git rm` — targeted | ||
| # container removal and reversible git-index removal are not mass filesystem | ||
| # destruction. `git rm` stages a tracked-file removal that git can recover; | ||
| # it is categorically different from `rm -rf`. The dangerous container forms | ||
| # (`docker rm $(docker ps -aq)`, `docker ps -q | xargs docker rm`, and | ||
| # their podman equivalents) are caught by dedicated hard-block rules | ||
| # further down the file ("Container sweep hard-blocks" section). | ||
| - pattern: '(?<!docker\s)(?<!podman\s)\brm\s+(-[^\s]*)*-[rRf]' | ||
| reason: rm with recursive or force flags (filesystem rm, not docker/podman) | ||
| - pattern: '(?<!docker\s)(?<!podman\s)(?<!git\s)\brm\s+(-[^\s]*)*-[rRf]' | ||
| reason: rm with recursive or force flags (filesystem rm, not docker/podman/git) | ||
|
|
||
| - pattern: '(?<!docker\s)(?<!podman\s)\brm\s+-[rRf]' | ||
| reason: rm with recursive or force flags (filesystem rm, not docker/podman) | ||
| - pattern: '(?<!docker\s)(?<!podman\s)(?<!git\s)\brm\s+-[rRf]' | ||
| reason: rm with recursive or force flags (filesystem rm, not docker/podman/git) | ||
|
|
||
| - pattern: '(?<!docker\s)(?<!podman\s)\brm\s+--recursive' | ||
| reason: rm with --recursive flag (filesystem rm, not docker/podman) | ||
| - pattern: '(?<!docker\s)(?<!podman\s)(?<!git\s)\brm\s+--recursive' | ||
| reason: rm with --recursive flag (filesystem rm, not docker/podman/git) | ||
|
|
||
| - pattern: '(?<!docker\s)(?<!podman\s)\brm\s+--force' | ||
| reason: rm with --force flag (filesystem rm, not docker/podman) | ||
| - pattern: '(?<!docker\s)(?<!podman\s)(?<!git\s)\brm\s+--force' | ||
| reason: rm with --force flag (filesystem rm, not docker/podman/git) |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift
The git rm exception is too broad in both enforcement layers.
Both layers inspect raw shell text, so wrappers can bypass deletion guards while valid Git option forms behave inconsistently. git rm also removes working-tree files and should not be treated as inherently non-destructive.
.claude/hooks/damage-control/patterns.yaml#L27-L44: replace the negative-lookbehind exception with command-aware matching and preserve protections for actual filesystem deletion..claude/hooks/damage-control/bash-tool-damage-control.py#L80-L82: apply the same parsed-command policy toREAD_ONLY_BLOCKEDandNO_DELETE_BLOCKED.
📍 Affects 2 files
.claude/hooks/damage-control/patterns.yaml#L27-L44(this comment).claude/hooks/damage-control/bash-tool-damage-control.py#L80-L82
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.claude/hooks/damage-control/patterns.yaml around lines 27 - 44, The `git
rm` exemption is overly broad because both enforcement layers match raw shell
text and allow destructive deletions through wrappers or inconsistent option
forms. In `.claude/hooks/damage-control/patterns.yaml` lines 27-44, replace the
negative-lookbehind patterns with command-aware matching that preserves
protection for filesystem `rm` while applying an explicit policy to `git rm`; in
`.claude/hooks/damage-control/bash-tool-damage-control.py` lines 80-82, apply
the same parsed-command policy to `READ_ONLY_BLOCKED` and `NO_DELETE_BLOCKED`.
| - [ ] **Step 5: Commit + push + PR** | ||
|
|
||
| ```bash | ||
| git -C ../pmoves-code-fork add claude/pinokio.js | ||
| git -C ../pmoves-code-fork commit -m "feat(claude): launch claude-code with PMOVES-claude-code config dir" | ||
| git -C ../pmoves-code-fork push origin PMOVES.AI-Edition-Hardened | ||
| ``` |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win
Route repository changes through atomic PRs.
These steps push directly to hardened branches, contradicting the plan’s atomic-PR requirement. Push a topic branch, open the PR, and include smoke-test commands/results and reviewer notes before merge.
Also applies to: 144-149, 196-201
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/superpowers/plans/2026-07-20-combined-pinokio-customization.md` around
lines 78 - 84, Update each “Commit + push + PR” step to use an atomic topic
branch rather than pushing directly to PMOVES.AI-Edition-Hardened: create and
push a uniquely named topic branch, open a PR targeting the hardened branch, and
document smoke-test commands/results plus reviewer notes before merge. Apply
this consistently to all three referenced plan sections.
Source: Coding guidelines
| - [ ] **Step 4: Minimal PMOVES bootstrap config** — `claude-config/settings.json` | ||
|
|
||
| ```json | ||
| { "$comment": "Minimal PMOVES bootstrap. Full hooks/skills/MCP substance is task #13.", | ||
| "env": { "PMOVES_NODE_ID": "z890" } } | ||
| ``` |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Do not hardcode the z890 node identity in shared config.
A shared PMOVES-claude-code checkout will be used by multiple nodes, so this makes every installation report itself as z890. Read the node ID from runtime environment/secret management or leave it unset in the committed bootstrap.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/superpowers/plans/2026-07-20-combined-pinokio-customization.md` around
lines 132 - 137, Remove the hardcoded PMOVES_NODE_ID value from the bootstrap
settings in the “Minimal PMOVES bootstrap config” step. Leave the setting unset
so each installation can obtain its node identity from runtime environment or
secret management.
Source: Coding guidelines
| - [ ] **Step 4: Verify + commit** | ||
|
|
||
| Run: `git submodule status --recursive | grep pbnj` | ||
| Expected: `pbnj` resolves with nested submodules. |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Validate nested submodules instead of filtering them out.
git submodule status --recursive | grep pbnj only proves the top-level pbnj entry appears; it does not verify nested submodule status. Check all recursive entries and fail on uninitialized or conflicted prefixes (-, +, or U).
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/superpowers/plans/2026-07-20-combined-pinokio-customization.md` around
lines 233 - 236, Update Step 4’s recursive submodule verification to inspect
every entry rather than filtering with grep pbnj. Make the check fail when any
recursive status line begins with -, +, or U, while accepting initialized,
conflict-free nested submodules.
| context: . | ||
| dockerfile: services/tokenism-simulator/Dockerfile | ||
| image: ${TOKENISM_IMAGE:-ghcr.io/powerfulmoves/pmoves-tokenism:pmoves-latest} | ||
| image: ${TOKENISM_IMAGE:-ghcr.io/powerfulmoves/pmoves-tokenism-simulator:edge} |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Synchronize the tokenism image default in the UI Compose override.
pmoves/docker-compose.ui.yml still defaults TOKENISM_IMAGE to ghcr.io/powerfulmoves/pmoves-tokenism:pmoves-latest, so UI-profile bring-up can override this change and run the old image. Update that parallel default to ghcr.io/powerfulmoves/pmoves-tokenism-simulator:edge.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@pmoves/docker-compose.yml` at line 3808, Update the TOKENISM_IMAGE default in
docker-compose.ui.yml to ghcr.io/powerfulmoves/pmoves-tokenism-simulator:edge,
matching the default used by the tokenism service in docker-compose.yml.
| "room": { | ||
| "room_id": "z890-infra.room.fabric", | ||
| "stage": "live", | ||
| "suits": ["z890-infra", "pmoves-hermes", "legal-assist"] | ||
| }, |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
Room stage mismatch: profile says "live", room manifest says "rehearsal".
This profile declares room.stage: "live" for z890-infra.room.fabric, but the room manifest itself (pmoves/config/rooms/z890-infra.room.fabric.json) declares "stage": "rehearsal". This is a cross-file contract break — a launcher applying this profile would advertise the room as live when the manifest says otherwise, which can mislead operators about readiness.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@pmoves/launcher/profiles/pmoves-hermes-z890.json` around lines 9 - 13, Update
the room.stage value in the z890-infra room configuration within the
pmoves-hermes-z890 profile to match the room manifest’s rehearsal stage. Leave
the room_id and suits entries unchanged.
| "launcher_refs": [ | ||
| "up-agents", | ||
| "up-monitoring", | ||
| "secrets-funnel", | ||
| "up-n8n", | ||
| "up-archon-native" | ||
| ], |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
up-archon-native referenced here, but the skill doc calls the Make target up-archon.
pmoves/skills/pmoves-n8n-archon-bridge/SKILL.md documents make -C pmoves up-archon, while this profile (and the room manifest) list up-archon-native. If the target names differ, the documented command will fail.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@pmoves/launcher/profiles/pmoves-hermes-z890.json` around lines 54 - 60, Align
the Archon launcher reference in the profile with the documented Make target:
update the launcher_refs entry from up-archon-native to up-archon, and keep the
profile consistent with the corresponding room manifest.
| ## Running locally | ||
|
|
||
| ```bash | ||
| # Start n8n | ||
| make -C pmoves up-n8n | ||
|
|
||
| # Start Archon | ||
| make -C pmoves up-archon | ||
|
|
||
| # Verify n8n API | ||
| curl -u "$N8N_BASIC_AUTH_USER:$N8N_BASIC_AUTH_PASSWORD" \ | ||
| http://localhost:5678/api/v1/workflows | ||
| ``` |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Documented Make target up-archon doesn't match the configured up-archon-native (see consolidated comment).
Both pmoves/launcher/profiles/pmoves-hermes-z890.json and pmoves/config/rooms/z890-infra.room.fabric.json list up-archon-native in launcher_refs, but this doc's "Running locally" section and "Next implementation steps" both reference make -C pmoves up-archon.
Also applies to: 105-111
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@pmoves/skills/pmoves-n8n-archon-bridge/SKILL.md` around lines 66 - 78, Update
the local-running and next-implementation documentation to use the configured
make target up-archon-native instead of up-archon, matching the launcher_refs
entries in the referenced configuration files. Preserve the existing command
structure and surrounding n8n instructions.
| def deep_merge(base: dict[str, Any], overlay: dict[str, Any], force: bool = False) -> dict[str, Any]: | ||
| """ | ||
| Merge overlay into base. Recursively combine dicts. For non-dict values, | ||
| preserve existing non-empty base values unless force is True. | ||
| """ | ||
| result: dict[str, Any] = {} | ||
| for key in set(base.keys()) | set(overlay.keys()): | ||
| base_val = base.get(key) | ||
| overlay_val = overlay.get(key) | ||
| if isinstance(overlay_val, dict) and isinstance(base_val, dict): | ||
| result[key] = deep_merge(base_val, overlay_val, force) | ||
| elif force or is_empty(base_val): | ||
| result[key] = overlay_val | ||
| else: | ||
| result[key] = base_val | ||
| return result |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
--force silently nulls out config keys the profile doesn't override.
overlay.get(key) returns None for keys present only in base. Since the elif force or is_empty(base_val) branch fires whenever force=True — independent of whether key is actually in overlay — any existing config.yaml key absent from the launcher profile's hermes_config_overrides gets overwritten with None on --write --force. That contradicts the documented behavior ("Overwrite existing non-empty values with launcher defaults") and can wipe unrelated Hermes settings (a backup is created, but data is still lost from the active config).
🛠️ Proposed fix
def deep_merge(base: dict[str, Any], overlay: dict[str, Any], force: bool = False) -> dict[str, Any]:
result: dict[str, Any] = {}
for key in set(base.keys()) | set(overlay.keys()):
+ if key not in overlay:
+ result[key] = base.get(key)
+ continue
base_val = base.get(key)
- overlay_val = overlay.get(key)
+ overlay_val = overlay[key]
if isinstance(overlay_val, dict) and isinstance(base_val, dict):
result[key] = deep_merge(base_val, overlay_val, force)
elif force or is_empty(base_val):
result[key] = overlay_val
else:
result[key] = base_val
return result📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| def deep_merge(base: dict[str, Any], overlay: dict[str, Any], force: bool = False) -> dict[str, Any]: | |
| """ | |
| Merge overlay into base. Recursively combine dicts. For non-dict values, | |
| preserve existing non-empty base values unless force is True. | |
| """ | |
| result: dict[str, Any] = {} | |
| for key in set(base.keys()) | set(overlay.keys()): | |
| base_val = base.get(key) | |
| overlay_val = overlay.get(key) | |
| if isinstance(overlay_val, dict) and isinstance(base_val, dict): | |
| result[key] = deep_merge(base_val, overlay_val, force) | |
| elif force or is_empty(base_val): | |
| result[key] = overlay_val | |
| else: | |
| result[key] = base_val | |
| return result | |
| def deep_merge(base: dict[str, Any], overlay: dict[str, Any], force: bool = False) -> dict[str, Any]: | |
| """ | |
| Merge overlay into base. Recursively combine dicts. For non-dict values, | |
| preserve existing non-empty base values unless force is True. | |
| """ | |
| result: dict[str, Any] = {} | |
| for key in set(base.keys()) | set(overlay.keys()): | |
| if key not in overlay: | |
| result[key] = base.get(key) | |
| continue | |
| base_val = base.get(key) | |
| overlay_val = overlay[key] | |
| if isinstance(overlay_val, dict) and isinstance(base_val, dict): | |
| result[key] = deep_merge(base_val, overlay_val, force) | |
| elif force or is_empty(base_val): | |
| result[key] = overlay_val | |
| else: | |
| result[key] = base_val | |
| return result |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@pmoves/tools/launcher_profile_select.py` around lines 67 - 82, Update
deep_merge so force only overwrites keys explicitly present in overlay; preserve
base values for keys absent from the launcher profile, including when
force=True. Continue recursively merging shared dictionaries and applying
existing force/empty-value behavior only to overlay-provided keys.
…:edge) docker-compose.yml (source) pins tokenism-simulator to pmoves-tokenism-simulator:edge (set in #2184), but docker-compose.ui.yml was never regenerated and still referenced the old pmoves-tokenism:pmoves-latest. This is the drift the 'Validate Compose Files' gate flags on main. Regenerated with the pinned ruamel.yaml==0.19.1 for byte-stable output; ui.yml is the only overlay that changed. KNOWN_ROAD=compose:regenerate-overlays Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…:edge) docker-compose.yml (source) pins tokenism-simulator to pmoves-tokenism-simulator:edge (set in #2184), but docker-compose.ui.yml was never regenerated and still referenced the old pmoves-tokenism:pmoves-latest. This is the drift the 'Validate Compose Files' gate flags on main. Regenerated with the pinned ruamel.yaml==0.19.1 for byte-stable output; ui.yml is the only overlay that changed. KNOWN_ROAD=compose:regenerate-overlays Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…:edge) docker-compose.yml (source) pins tokenism-simulator to pmoves-tokenism-simulator:edge (set in #2184), but docker-compose.ui.yml was never regenerated and still referenced the old pmoves-tokenism:pmoves-latest. This is the drift the 'Validate Compose Files' gate flags on main. Regenerated with the pinned ruamel.yaml==0.19.1 for byte-stable output; ui.yml is the only overlay that changed. KNOWN_ROAD=compose:regenerate-overlays Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…:edge) (#2196) * fix(compose): regenerate ui overlay — sync tokenism image to source (:edge) docker-compose.yml (source) pins tokenism-simulator to pmoves-tokenism-simulator:edge (set in #2184), but docker-compose.ui.yml was never regenerated and still referenced the old pmoves-tokenism:pmoves-latest. This is the drift the 'Validate Compose Files' gate flags on main. Regenerated with the pinned ruamel.yaml==0.19.1 for byte-stable output; ui.yml is the only overlay that changed. KNOWN_ROAD=compose:regenerate-overlays Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(compose): reconcile agent overlay with current main --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: Shaela Bello <slbello@uncg.edu>
…c-stage leak) (#2222) The z890 node reconcile (#2184) dropped the access block from z890-infra.room.fabric.json. With access unset, is_public_room() defaults to public, so the node-control room leaked onto the public stage catalog — and test_stage_data's curation guard has been FAILING ON MAIN since that merge (every branch rebasing onto main inherits the red python-tests). Restore the pre-#2184 value: visibility unlisted. Verified: test_stage_data 5/5, validate_room_manifests 9/9. Co-authored-by: Shaela Bello <slbello@uncg.edu> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Reconcile Z890 node onto main + fleet fixes found along the way
Reconciles the
local/pmoves-hermes-z890-confignode branch (which had forked ~2 months back with 11 local commits) cleanly onto currentmain, carrying only the intentional work and dropping stale/stopgap commits. Along the way it fixes three real bugs that live onmainand affect the whole fleet.Fleet-worthy fixes (belong on main regardless of the node)
fix(tokenism)— CHIT signing was down fleet-wide because the runningpmoves-tokenism:pmoves-latestimage is an orphan tag no CI workflow builds (ModuleNotFoundError: No module named 'pmoves'). RepointsTOKENISM_IMAGEto the real CI-built name (pmoves-tokenism-simulator:edge, whichimages.yaml+build-images.ymlactually publish) and adds explicitPYTHONPATH=/app. Verified: locally-built image passes the import acceptance test (from pmoves.tools.chit_security import sign_cgp, verify_cgp→ OK). Main's Dockerfile already packagespmoves/tools(fix(packaging): ensure service Dockerfiles package pmoves.tools #2070/fix(secrets): route remaining CHIT_PASSPHRASE reads through services.common.env #2125); the gaps were the tag + PYTHONPATH.fix(evo-controller)— line 63 read the CHIT signing secret viaos.getenvwhile the rest of the file uses theget_secrethelper.get_secretis a strict superset (env var first, then{KEY}_FILE), so behavior is preserved and Docker secret file-mounting works. Unblocks thesecrets-audithard-gate that otherwise failssecrets-funnelfrom a clean checkout.fix(damage-control)— exemptgit rmfrom the recursive-delete + no-delete guards ((?<!git\s)mirroring the existing docker/podman exclusion).git rmis a reversible index operation, categorically different fromrm -rf. Verified:git rmpasses; realrm -rfand no-delete-path deletes still block.known-roads topic domain— adds thetopicKnown-Road guard domain (main hadcompose/schema/dockerfilebut nottopic) + trail entries.Node config (z890)
z890-infra.room.fabric.jsonreconciled onto main's schema (added requiredstagefield; validates).pmoves-*skills (legal-assist, folder-monitor, email-organizer, yt-monitor, n8n-archon-bridge),pmoves/launcher/,launcher_profile_select.py, and the profile.env.example(placeholders only).Explicitly NOT included
.hermes/and the real profile.env(node-local secrets) stay untracked.Follow-ups (not in this PR)
secrets_manifest_v2.yamlis missing 5 keys main's compose requires (LOGFLARE_PUBLIC/PRIVATE_ACCESS_TOKEN,SECRET_KEY_BASE,SUPABASE_DB_USER,VAULT_ENC_KEY), so a from-scratchsecrets-funnelproduces incomplete tier files. Add them to the manifest + re-funnel (funnel sources deterministically from the machine CHIT vault, so values stay consistent).🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Bug Fixes
Documentation