fix(neo4j): internal-only Neo4j fronted by a Tailscale forwarder on a dedicated graph front; LOAD CSV blocklist; fleet test - #3201
Conversation
Docker publishes NOTHING for a container attached only to `internal: true` networks, with no error. Phase 1b of the Neo4j migration hit exactly that: the compose neo4j came up healthy on pmoves_app/bus/data, all internal, and its tailnet bind silently did not exist. The render and dry-run gates could not see it; they read the REQUESTED bindings. This test parses the Makefile's default STACK_FILES (the YAML directly, no docker or env files), merges services by name, and asserts that every published service joins at least one non-internal network. Measured fleet-wide on origin/main f9d8a82: 35 violators. Empirically, on Knuckles, 15 of 15 RUNNING violators had requested bindings and ZERO effective ones, while cipher-api, minio and presign (each also on a non-internal network) published. The 34 other than neo4j are listed in KNOWN_VIOLATORS as xfail(strict=True): recorded, not silently passed, and a fix makes the case XPASS-fail until the name is removed. A staleness test keeps the list honest. neo4j is deliberately NOT listed. This test FAILS on it at main (1 failed, 58 passed, 34 xfailed) until the compose change in this PR (it joins pmoves_external, like cipher-api) lands under its grant. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Important Review skippedAuto reviews are limited based on label configuration. 🏷️ Required labels (at least one) (1)
Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository: POWERFULMOVES/PMOVES.AI/.coderabbit.yaml Review profile: CHILL Plan: Advanced Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
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 |
Code Review SummaryThe review did not run because the selected model is no longer available. Choose another model in Kilo Code review settings: https://app.kilo.ai/code-reviews Previous Review Summaries (8 snapshots)Current summary above is authoritative. Previous snapshots are kept for context only. Previous reviewThe review did not run because the selected model is no longer available. Choose another model in Kilo Code review settings: https://app.kilo.ai/code-reviews Previous reviewThe review did not run because the selected model is no longer available. Choose another model in Kilo Code review settings: https://app.kilo.ai/code-reviews Previous reviewThe review did not run because the selected model is no longer available. Choose another model in Kilo Code review settings: https://app.kilo.ai/code-reviews Previous reviewThe review did not run because the selected model is no longer available. Choose another model in Kilo Code review settings: https://app.kilo.ai/code-reviews Previous reviewThe review did not run because the selected model is no longer available. Choose another model in Kilo Code review settings: https://app.kilo.ai/code-reviews Previous reviewThe review did not run because the selected model is no longer available. Choose another model in Kilo Code review settings: https://app.kilo.ai/code-reviews Previous reviewThe review did not run because the selected model is no longer available. Choose another model in Kilo Code review settings: https://app.kilo.ai/code-reviews Previous reviewThe review did not run because the selected model is no longer available. Choose another model in Kilo Code review settings: https://app.kilo.ai/code-reviews |
Fleet review: kilocode (kilo/z-ai/glm-5.3)reviewer: tier 2/3
reviewer chain
_ lane: fleet-review chain (self-hosted kvm4) - spark-local (when online) -> kilo-primary -> kilo-alternate _ |
…lish (#3201) Under KNOWN_ROAD compose:pr:3201, a file grant written by the operator. Phase 1b step 7: the compose neo4j came up healthy but published NOTHING. pmoves_app, pmoves_bus and pmoves_data are all internal:true, and Docker does not publish ports for a container attached only to internal networks. cipher-api publishes because it is ALSO on pmoves_external. - docker-compose.yml (source): services.neo4j adds pmoves_external, with NO alias. Measured: every Neo4j consumer already shares app/bus/data with it, and cipher-api carries no alias there either. - The networks injector was run directly, not `make compose-networks`. It added pmoves_external to neo4j's PMOVES_NETWORKS. - docker-compose.core.yml was regenerated by `make compose-split`. Checks: - compose-networks-check: in sync (109 services); - test_published_ports_need_a_non_internal_network: the neo4j case now PASSES; the 34 known violators still xfail; - with bind passthrough + safety + no-bare-compose: 134 passed, 34 xfailed. Rendered (read-only; the operator's real NEO4J_BIND carried unprinted): - networks: app/bus/data with the neo4j alias, plus pmoves_external (null); - external is internal=false; - ports 7474 and 7687 on the TAILNET address; - container_name pmoves-neo4j; image at the 5.26.30 digest; - volume pmoves_neo4j-data. No recreate here. That is a separate operator-approved step with its own dry run and effective-publish gate. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Docker Hardening ValidationHardening Validation ReportValidated: Sun Sep 27 08:04:43 UTC 2026Services CheckedPMOVES.AI compose hardening ratchet[INFO] Checking: /home/runner/work/PMOVES.AI/PMOVES.AI/pmoves/docker-compose.hardened.yml [INFO] Validating: agent-zero [INFO] Validating: archon [INFO] Validating: channel-monitor [INFO] Validating: comfy-watcher [INFO] Validating: deepresearch [INFO] Validating: extract-worker [INFO] Validating: ffmpeg-whisper [INFO] Validating: grayjay-plugin-host [INFO] Validating: hi-rag-gateway-gpu [INFO] Validating: hi-rag-gateway-v2 [INFO] Validating: hi-rag-gateway-v2-gpu [INFO] Validating: invidious-companion-proxy [INFO] Validating: jellyfin-bridge [INFO] Validating: langextract [INFO] Validating: media-audio [INFO] Validating: media-video [INFO] Validating: mesh-agent [INFO] Validating: nats-echo-req [INFO] Validating: nats-echo-res [INFO] Validating: notebook-sync [INFO] Validating: p7-room-orchestrator [INFO] Validating: pdf-ingest [INFO] Validating: pmoves-yt [INFO] Validating: presign [INFO] Validating: publisher-discord [INFO] Validating: render-webhook [INFO] Validating: retrieval-eval [INFO] Validating: supaserch =================================== |
Fleet review: kilocode (kilo/z-ai/glm-5.3)reviewer: tier 2/3
reviewer chain
_ lane: fleet-review chain (self-hosted kvm4) - spark-local (when online) -> kilo-primary -> kilo-alternate _ |
POWERFULMOVES
left a comment
There was a problem hiding this comment.
Independent review (Control Body, B850 steward), head reviewed 21526bb: request changes (0 P1, 2 P2)
The compose change is clean. Both files show zero top-level diffs, and exactly one service (neo4j) differs. It gains pmoves_external plus the matching PMOVES_NETWORKS entry. Ports, image digest and volumes are byte-identical to main, and the regenerated core.yml mirrors the source. The new test gives 59 passed, 34 xfailed; it is pure YAML parsing with no subprocess or docker.
P2-A, contract correctness: the test takes external networks' internal flag on trust
An external: true network's internal flag lives wherever docker network create ran, not in the YAML. So the assertion that pmoves_external is non-internal is vacuously true.
- 53 passing services pass ONLY via
pmoves_external, including neo4j and cipher-api. supabase-db passes only viapmoves_db_egress. - Today every sanctioned create site is non-internal, so today's verdicts are right. A future
--internalat any create site would still turn the guard green on exactly the failure it exists to catch. - Fix: assert structurally that the Makefile's create lines for these external networks carry no
--internal, or keep an explicit table of external-network flags checked against those sites.
P2-B, defense in depth: joining pmoves_external gives Neo4j outbound egress
app, bus and data are internal: true, so Neo4j had no route out. pmoves_external is a normal NAT'd bridge, so any client holding the password could make Neo4j fetch arbitrary URLs through LOAD CSV (or APOC load procedures, if present). That includes the host gateway and host-published ports: an SSRF and exfiltration channel. cipher-api already has this egress.
Operator decision: keep pmoves_external for publishing, and restrict Neo4j's outbound URL loading in this PR. The setting must be verified for Neo4j 5.26 Community, and the PR must show LOAD CSV from a URL being refused.
In-network reach added: in STACK_FILES, only pmoves-ollama and supabase-gotrue newly share a network with neo4j. In other compose files, if deployed: chit-tour, persona-room and traefik. Anything attached to pmoves_external by hand also gains reach.
P3
- The comment "No alias here" is misleading: compose gives every container its service name and container_name as DNS names on every network it joins.
- Network keys are not resolved to their
name:. For example comfyui'sapi_tiermaps to the internalpmoves_api, which is masked today. - Only STACK_FILES is scanned. Overlays using
!resetor!overridetags don'tsafe_load. Say both in the docstring.
Operator decision: keep pmoves_external, and restrict Neo4j's outbound URL
loading in this PR. On a non-internal network, a Cypher `LOAD CSV FROM
'http(s)://…'` could otherwise make Neo4j fetch from anywhere it can route to.
- NEO4J_internal_dbms_cypher__ip__blocklist=0.0.0.0/0,::/0 on the neo4j
service (docker-compose.yml; core overlay regenerated by compose-split).
- Verified on the pinned 5.26.30 Community image, with throwaway containers
only; the live container was not touched:
- strict validation RECOGNISES the key, while a bogus key is rejected;
- on a private --internal bridge WITHOUT it, LOAD CSV of
http://127.0.0.1:1/x fails with a connection error ("Couldn't load the
external resource");
- WITH it, http and https IP literals AND a resolvable hostname fail by
POLICY: "access to … is blocked via the configuration property
internal.dbms.cypher_ip_blocklist";
- re-proved with the service's full rendered environment (24 keys), whose
values were passed via the docker CLI's env, never argv or a file.
- `internal.*` means unsupported by Neo4j; the digest pin keeps it stable.
(--network none could not be used: Neo4j 5.26 exits immediately with code 3.)
Also P3: the pmoves_external comment is corrected. Compose registers the
service name `neo4j` (and container_name) on every network the service
joins, so no explicit alias is needed there.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Docker Hardening ValidationHardening Validation ReportValidated: Sun Sep 27 08:21:39 UTC 2026Services CheckedPMOVES.AI compose hardening ratchet[INFO] Checking: /home/runner/work/PMOVES.AI/PMOVES.AI/pmoves/docker-compose.hardened.yml [INFO] Validating: agent-zero [INFO] Validating: archon [INFO] Validating: channel-monitor [INFO] Validating: comfy-watcher [INFO] Validating: deepresearch [INFO] Validating: extract-worker [INFO] Validating: ffmpeg-whisper [INFO] Validating: grayjay-plugin-host [INFO] Validating: hi-rag-gateway-gpu [INFO] Validating: hi-rag-gateway-v2 [INFO] Validating: hi-rag-gateway-v2-gpu [INFO] Validating: invidious-companion-proxy [INFO] Validating: jellyfin-bridge [INFO] Validating: langextract [INFO] Validating: media-audio [INFO] Validating: media-video [INFO] Validating: mesh-agent [INFO] Validating: nats-echo-req [INFO] Validating: nats-echo-res [INFO] Validating: notebook-sync [INFO] Validating: p7-room-orchestrator [INFO] Validating: pdf-ingest [INFO] Validating: pmoves-yt [INFO] Validating: presign [INFO] Validating: publisher-discord [INFO] Validating: render-webhook [INFO] Validating: retrieval-eval [INFO] Validating: supaserch =================================== |
…te sites (#3201 P2-A, P3) Review of #3201: - P2-A: pmoves_external and pmoves_db_egress are `external: true` in compose, so compose never states whether they are internal. That is decided where the Makefile/mk CREATE them. New: EXTERNAL_NON_INTERNAL pins both, and test_external_networks_are_created_without_internal asserts that EVERY `docker network create` site for each (Makefile:725/5144/5191/5813/5815) omits --internal. A positive control proves the check catches --internal. - The published-ports rule now derives an external network's internal flag from those create sites (it no longer assumes non-internal). It FAILS if a used external network has no create site to check. - P3: network keys are resolved to their real `name:`. The docstring states the scope: only the default STACK_FILES are scanned, and !reset/!override overlays are not modelled. The parser finds 3 create sites for pmoves_external and 2 for pmoves_db_egress, none internal, and derives pmoves_api/app/data as internal from theirs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Docker Hardening ValidationHardening Validation ReportValidated: Sun Sep 27 08:22:56 UTC 2026Services CheckedPMOVES.AI compose hardening ratchet[INFO] Checking: /home/runner/work/PMOVES.AI/PMOVES.AI/pmoves/docker-compose.hardened.yml [INFO] Validating: agent-zero [INFO] Validating: archon [INFO] Validating: channel-monitor [INFO] Validating: comfy-watcher [INFO] Validating: deepresearch [INFO] Validating: extract-worker [INFO] Validating: ffmpeg-whisper [INFO] Validating: grayjay-plugin-host [INFO] Validating: hi-rag-gateway-gpu [INFO] Validating: hi-rag-gateway-v2 [INFO] Validating: hi-rag-gateway-v2-gpu [INFO] Validating: invidious-companion-proxy [INFO] Validating: jellyfin-bridge [INFO] Validating: langextract [INFO] Validating: media-audio [INFO] Validating: media-video [INFO] Validating: mesh-agent [INFO] Validating: nats-echo-req [INFO] Validating: nats-echo-res [INFO] Validating: notebook-sync [INFO] Validating: p7-room-orchestrator [INFO] Validating: pdf-ingest [INFO] Validating: pmoves-yt [INFO] Validating: presign [INFO] Validating: publisher-discord [INFO] Validating: render-webhook [INFO] Validating: retrieval-eval [INFO] Validating: supaserch =================================== |
Fleet review: kilocode (kilo/z-ai/glm-5.3)reviewer: tier 2/3 Diff reviewed in full (no repo checkout in this lane — findings are from the diff text only; the blocklist value is confirmed 1. CORRECTNESS
2. SECURITY / TOPOLOGY
3. VERDICT: REQUEST_CHANGESSound compose fix and a well-guarded fleet test, but the public test file commits internal hostnames, the load-bearing egress blocklist rests on an unsupported setting with no runtime gate, and the reviewer chain
_ lane: fleet-review chain (self-hosted kvm4) - spark-local (when online) -> kilo-primary -> kilo-alternate _ |
…view P2-B)" This reverts commit 8a09845.
… the invariants
Operator decision after the networking-doc and upstream reconciliation: neo4j
stays INTERNAL-ONLY (app/bus/data: no egress, no host port) and is fronted by
a Tailscale forwarder. That is DOCKER_NETWORK_HARDENING Rule 5 ("gateway-front
it"). The pmoves_external commit is reverted: Rule 1 says no data service on
pmoves_external.
- neo4j is added to KNOWN_VIOLATORS BY DESIGN, with the reason inline. Its
ports: stay only to carry ${NEO4J_BIND} (#3196), and are inert on
internal-only networks.
- New invariants:
- neo4j joins exactly pmoves_app, pmoves_bus and pmoves_data, all internal,
and no new network;
- neo4j has no network_mode, so it never shares a sidecar's netns.
test_published_ports_need_a_non_internal_network: 63 passed, 35 xfailed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…pth (#3201) Re-applied on its own, after the pmoves_external revert. It no longer edits any network block. NEO4J_internal_dbms_cypher__ip__blocklist=0.0.0.0/0,::/0 on the neo4j service (docker-compose.yml; core overlay regenerated). Even with neo4j internal-only it stops Cypher LOAD CSV from reaching the host gateway, the LAN or other containers. Verified on the pinned 5.26.30 Community image, with throwaway containers only (evidence in the PR body): - strict validation recognises the key, while a bogus key is rejected; - WITHOUT it, LOAD CSV of http://127.0.0.1:1/x fails with a connection error; - WITH it, http and https IP literals and a resolvable hostname fail by POLICY ("access to … is blocked via … internal.dbms.cypher_ip_blocklist"); - re-proved with the service's full rendered env. `internal.*` means unsupported by Neo4j; the digest pin keeps it stable. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Docker Hardening ValidationHardening Validation ReportValidated: Sun Sep 27 08:27:31 UTC 2026Services CheckedPMOVES.AI compose hardening ratchet[INFO] Checking: /home/runner/work/PMOVES.AI/PMOVES.AI/pmoves/docker-compose.hardened.yml [INFO] Validating: agent-zero [INFO] Validating: archon [INFO] Validating: channel-monitor [INFO] Validating: comfy-watcher [INFO] Validating: deepresearch [INFO] Validating: extract-worker [INFO] Validating: ffmpeg-whisper [INFO] Validating: grayjay-plugin-host [INFO] Validating: hi-rag-gateway-gpu [INFO] Validating: hi-rag-gateway-v2 [INFO] Validating: hi-rag-gateway-v2-gpu [INFO] Validating: invidious-companion-proxy [INFO] Validating: jellyfin-bridge [INFO] Validating: langextract [INFO] Validating: media-audio [INFO] Validating: media-video [INFO] Validating: mesh-agent [INFO] Validating: nats-echo-req [INFO] Validating: nats-echo-res [INFO] Validating: notebook-sync [INFO] Validating: p7-room-orchestrator [INFO] Validating: pdf-ingest [INFO] Validating: pmoves-yt [INFO] Validating: presign [INFO] Validating: publisher-discord [INFO] Validating: render-webhook [INFO] Validating: retrieval-eval [INFO] Validating: supaserch =================================== |
Fleet review: kilocode (kilo/z-ai/glm-5.3)reviewer: tier 2/3 PR #3201 Review (kilocode lane)1. CORRECTNESS
2. SECURITY / TOPOLOGY
3. VERDICT: REQUEST_CHANGESThe diff implements the opposite design from the title/body (option A: internal-only neo4j + blocklist; the advertised reviewer chain
_ lane: fleet-review chain (self-hosted kvm4) - spark-local (when online) -> kilo-primary -> kilo-alternate _ |
…ion A)
Operator decision: Neo4j stays INTERNAL-ONLY (no egress, no host port) and is
fronted by a Tailscale forwarder. That is DOCKER_NETWORK_HARDENING Rule 5
("There is no 'publish without egress.' Front it with a gateway").
- pmoves_graph_front: a new INTERNAL network (the *network-internal-only
anchor), subnet 172.30.9.0/24, gateway .1. It is permanent per Rule 2;
.1 to .8 are allocated in the repo and live. Joined ONLY by neo4j and the
forwarder, so a compromised forwarder reaches Neo4j and nothing else on
the data tier.
- neo4j joins pmoves_graph_front. Its other networks are unchanged
(app/bus/data, all internal). No alias: compose registers the service
name on every network it joins.
- docker-compose.neo4j-tailnet.yml is an OVERLAY, not in STACK_FILES. It
follows docker-compose.yt-egress.yml:
- image tailscale/tailscale:stable pinned by digest (the precedent uses
the moving :latest);
- userspace mode; the state dir on a named volume;
- the auth key from the secrets funnel, failing closed at compose parse;
- --advertise-tags=tag:neo4j;
- TS_ACCEPT_DNS=false, so `neo4j` resolves via Docker DNS;
- cap_drop ALL + no-new-privileges (measured to start with no caps);
- networks: pmoves_graph_front + pmoves_external (the documented
dual-attach pattern; a gateway, not a data store), NOT pmoves_data;
- no host ports, and no network_mode (Neo4j does not share its netns).
- Forwarding: config/tailscale/neo4j-tailnet-serve.json via TS_SERVE_CONFIG,
TCP 7687 to neo4j:7687 only (the 7474 browser UI is not forwarded).
- TS_DEST_IP is refused in userspace; measured on the pinned image.
- The CLI `serve --tcp` accepts only localhost targets, but a ServeConfig's
TCPForward is dialled with SystemDial (ipn/ipnlocal/serve.go) with no
localhost check. That is source-reasoned and must be proven at the live
step.
- mk/neo4j-tailnet.mk: up-neo4j-tailnet (preflight `config -q`, which trips
the overlay's own key guard without printing it), neo4j-tailnet-status,
and down-neo4j-tailnet (compose stop, then a non-forced
`docker container rm`). No recipe nests make.
- Tests:
- tests/test_neo4j_tailnet_forwarder.py makes 7 structural assertions:
digest pin; userspace and no TS_DEST_IP; serve mount; bolt-only forward;
the tag; graph_front + external only, with no netns, ports or caps; not
in STACK_FILES; no nested make.
- The fleet test's invariant is now neo4j on app/bus/data/graph_front, all
internal, and graph_front's only stack member is neo4j.
- Result: 146 passed, 35 xfailed (targeted).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Docker Hardening ValidationHardening Validation ReportValidated: Sun Sep 27 08:34:00 UTC 2026Services CheckedPMOVES.AI compose hardening ratchet[INFO] Checking: /home/runner/work/PMOVES.AI/PMOVES.AI/pmoves/docker-compose.hardened.yml [INFO] Validating: agent-zero [INFO] Validating: archon [INFO] Validating: channel-monitor [INFO] Validating: comfy-watcher [INFO] Validating: deepresearch [INFO] Validating: extract-worker [INFO] Validating: ffmpeg-whisper [INFO] Validating: grayjay-plugin-host [INFO] Validating: hi-rag-gateway-gpu [INFO] Validating: hi-rag-gateway-v2 [INFO] Validating: hi-rag-gateway-v2-gpu [INFO] Validating: invidious-companion-proxy [INFO] Validating: jellyfin-bridge [INFO] Validating: langextract [INFO] Validating: media-audio [INFO] Validating: media-video [INFO] Validating: mesh-agent [INFO] Validating: nats-echo-req [INFO] Validating: nats-echo-res [INFO] Validating: notebook-sync [INFO] Validating: p7-room-orchestrator [INFO] Validating: pdf-ingest [INFO] Validating: pmoves-yt [INFO] Validating: presign [INFO] Validating: publisher-discord [INFO] Validating: render-webhook [INFO] Validating: retrieval-eval [INFO] Validating: supaserch =================================== |
Fleet review: kilocode (kilo/z-ai/glm-5.3)reviewer: tier 2/3 Read the diff ( 1. CORRECTNESS
2. SECURITY / TOPOLOGYNot clean:
3. VERDICTREQUEST_CHANGES — the diff implements a different design than the title/body describe (internal-only neo4j + tailnet forwarder vs "publish via pmoves_external"), so the description is untrustworthy and the newly added forwarder subsystem ships with an unresolved make-target failure/recreate risk, inert reviewer chain
_ lane: fleet-review chain (self-hosted kvm4) - spark-local (when online) -> kilo-primary -> kilo-alternate _ |
POWERFULMOVES
left a comment
There was a problem hiding this comment.
Review of the option-A rework (21526bb..9b64c67): request changes (0 P1, 1 P2). The design is sound and the reverts left no residue.
Method:
- structural YAML diffs of the compose files against the merge-base;
- a client-side, read-only
config --no-interpolateof the base file plus the overlay, printing structural fields only; - the targeted structural tests: 71 passed, 35 xfailed.
P2: up-neo4j-tailnet silently recreates the live Neo4j
The forwarder has depends_on: neo4j, and this PR changes neo4j's config (the graph_front network plus the blocklist env). So the first up-neo4j-tailnet after merge would bring up the dependency and recreate pmoves-neo4j as a side effect: gracefully and on the same volume, but unannounced, and bypassing the Phase 1b dry-run and MATCH gates. This is reasoned from compose's behaviour with a diverged dependency, not measured.
Fix:
- Make neo4j's recreate an explicit runbook step, gated by a dry run that must show a Recreate of
pmoves-neo4jonly, plus creation of the graph_front network. - Give the forwarder target
--no-deps. - Add a preflight to the forwarder target that fails if
pmoves-neo4jis not yet attached topmoves_graph_front.
Verified clean
- graph_front is internal, compose-managed, declared the same way as app, bus and data, and uses a CIDR (172.30.9.0/24) found nowhere else in the repo.
- The forwarder: networks exactly
[pmoves_external, pmoves_graph_front]; no ports; no network_mode; all capabilities dropped; no-new-privileges; the serve config mounted read-only.- The serve config forwards only TCP 7687 to
neo4j:7687, with no Funnel. In userspace mode the handler lives in tailscaled's netstack on the tailnet address, so containers onpmoves_externalcannot reach Neo4j through it (reasoned from Tailscale's design, not measured). - graph_front's members are exactly neo4j and the forwarder.
- The serve config forwards only TCP 7687 to
- Residue: neo4j differs from main only by +graph_front and the blocklist env line.
pmoves_externalis gone from it. Ports, image and volumes are unchanged. - Tests: exact equality, not vacuous (91 published services parsed). External networks now resolve through
name:to their create sites.
P3
- Network registry: add rows for 172.30.8 (db_egress) and 172.30.9 (graph_front) to the subnet table in
DOCKER_NETWORK_HARDENING.md. Rule 2 makes them permanent. - A stale comment says
neo4jmust resolve on pmoves_data; it resolves on graph_front. - The auth key:
- It sits in the container environment, visible to
docker inspect. - The same variable feeds three Tailscale sidecars, so anyone holding it can enrol a node as
tag:neo4j. - Use a dedicated variable holding a one-off, pre-authorised key scoped to
tag:neo4j, and setTS_AUTH_ONCE=true.
- It sits in the container environment, visible to
- The state volume name may match the volume-reset pattern for neo4j. Rename it or document that.
- The create-site parser stops at a newline, so a multi-line
docker network createwith--internalon a continuation line would be missed. Join continuation lines. - Neo4j's dead
ports:(inert on internal-only networks) could be dropped, together withNEO4J_BIND. That is the operator's decision, already noted in the PR body.
…ey; review P3s (#3201) Review of #3201 (REQUEST-CHANGES, 1 P2). P2: `up-neo4j-tailnet` would have RECREATED the live pmoves-neo4j through `depends_on: neo4j`, because this PR changes neo4j's config (it joins pmoves_graph_front). - The target now runs `up -d --no-deps neo4j-tailnet`. - A names-only preflight REFUSES unless the running pmoves-neo4j is already attached to pmoves_graph_front, which only a deliberate, gated Neo4j recreate does (the runbook step in the PR body). - `depends_on` stays, for full-stack ordering only. - Tests: a structural check (--no-deps present, preflight before the up); and a behavioural one, running real make with build_stub_env stubs. Before the recreate it REFUSES and no compose `up` runs; after it, every `up` is --no-deps. P3: - DOCKER_NETWORK_HARDENING.md inventory rows for pmoves_db_egress (172.30.8.0/24) and pmoves_graph_front (172.30.9.0/24). Its "Six networks" line was already stale (7 listed); now nine. - The overlay comment is fixed: `neo4j` resolves on pmoves_graph_front, not pmoves_data. - The state volume is renamed to `graphfront-tailnet-state`. Any name containing "neo4j" (including the suggested pmoves_tailnet-neo4j-state) still matches volume-reset's ^pmoves_.*neo4j, so `make volume-reset SERVICE=neo4j` would have wiped the forwarder's tailnet identity. A test asserts it cannot match. - The create-site parser joins backslash-continued lines. The old regex stopped at the newline and missed an --internal on a continuation line (demonstrated). New test. - Key scoping: a DEDICATED variable, NEO4J_TAILNET_AUTHKEY (a one-off, pre-authorised key scoped to tag:neo4j), plus TS_AUTH_ONCE=true, with the :? guard kept. Registering it in the secrets manifest is an OPERATOR step: the manifest path is zero-access (even a protection check naming it was refused). Targeted: 157 passed, 35 xfailed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Docker Hardening ValidationHardening Validation ReportValidated: Sun Sep 27 08:42:33 UTC 2026Services CheckedPMOVES.AI compose hardening ratchet[INFO] Checking: /home/runner/work/PMOVES.AI/PMOVES.AI/pmoves/docker-compose.hardened.yml [INFO] Validating: agent-zero [INFO] Validating: archon [INFO] Validating: channel-monitor [INFO] Validating: comfy-watcher [INFO] Validating: deepresearch [INFO] Validating: extract-worker [INFO] Validating: ffmpeg-whisper [INFO] Validating: grayjay-plugin-host [INFO] Validating: hi-rag-gateway-gpu [INFO] Validating: hi-rag-gateway-v2 [INFO] Validating: hi-rag-gateway-v2-gpu [INFO] Validating: invidious-companion-proxy [INFO] Validating: jellyfin-bridge [INFO] Validating: langextract [INFO] Validating: media-audio [INFO] Validating: media-video [INFO] Validating: mesh-agent [INFO] Validating: nats-echo-req [INFO] Validating: nats-echo-res [INFO] Validating: notebook-sync [INFO] Validating: p7-room-orchestrator [INFO] Validating: pdf-ingest [INFO] Validating: pmoves-yt [INFO] Validating: presign [INFO] Validating: publisher-discord [INFO] Validating: render-webhook [INFO] Validating: retrieval-eval [INFO] Validating: supaserch =================================== |
Fleet review: kilocode (kilo/z-ai/glm-5.3)reviewer: tier 2/3
reviewer chain
_ lane: fleet-review chain (self-hosted kvm4) - spark-local (when online) -> kilo-primary -> kilo-alternate _ |
POWERFULMOVES
left a comment
There was a problem hiding this comment.
Re-review of 9b64c67..5889d40: approve-with-nits (the P2 is closed; no new P1 or P2)
Tests: forwarder plus fleet test files, 76 passed / 35 xfailed. The new behavioural test uses build_stub_env, and the others are structural. The steward independently re-ran the five targeted files: 151 passed / 35 xfailed.
Verified:
- The preflight fails closed on any inspect error (missing container, daemon down, permission, wrong context). The match is an exact token, so it cannot be satisfied by a similarly named network. It prints network names only. The make escaping is valid.
--no-depshas no side effect on the forwarder's own volume or network attachment. The external network must already exist, or compose fails loudly. The preflight guarantees graph_front exists.- The runbook ordering is gated: key, then grant, then the gated Neo4j recreate, then the forwarder. The forwarder's preflight enforces the order. Network creation precedes the container, so a CIDR overlap aborts with Neo4j untouched.
- The rest of the delta:
- the network registry rows are correct;
- the stale comment is fixed;
- the dedicated key uses a fail-closed guard plus
TS_AUTH_ONCE; - the renamed state volume cannot match the reset pattern;
- the continuation-joining parser has a real failing-before case;
- the 151 correction is noted in the body.
Nits, fold in before the operator runs step 4:
- Step 4: give the literal compose dry-run command rather than "
up-data-tierin dry-run form", because a make dry run of that target executes its nested make line. - Backup: name the mechanism (the Phase 1b stopped read-only volume copy, or
neo4j-backup). - Rollback: write one for steps 4 and 5: bring the forwarder down, then re-up neo4j from the pre-PR compose through the same gated path. The internal graph_front network can remain.
- The preflight message: say "could not inspect (missing, or daemon/permission error)", not "not found". Add a test for the inspect-error branch.
- A stopped-but-attached Neo4j passes the preflight. That is harmless; note it, or check
.State.Running. - Key lifetime: the
:?guard keeps the auth key as a permanent requirement, even though it is only used once. That is the operator's call.
…e inspect-failure message (#3201 nits) - The preflight now reads .State.Running together with the network names, in the same single inspect call (still no addresses). It refuses when pmoves-neo4j is stopped: --no-deps will not start it, and a forwarder in front of a stopped Neo4j fronts nothing. - An inspect failure now says "could not inspect pmoves-neo4j (missing, or daemon/permission error)" instead of "not found". It is the same exit 1. - The behavioural test covers four stub states, each checking the refusal reason and that no compose `up` ran: before the recreate; after it; recreated but stopped; inspect failing with rc 1. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Docker Hardening ValidationHardening Validation ReportValidated: Sun Sep 27 08:47:42 UTC 2026Services CheckedPMOVES.AI compose hardening ratchet[INFO] Checking: /home/runner/work/PMOVES.AI/PMOVES.AI/pmoves/docker-compose.hardened.yml [INFO] Validating: agent-zero [INFO] Validating: archon [INFO] Validating: channel-monitor [INFO] Validating: comfy-watcher [INFO] Validating: deepresearch [INFO] Validating: extract-worker [INFO] Validating: ffmpeg-whisper [INFO] Validating: grayjay-plugin-host [INFO] Validating: hi-rag-gateway-gpu [INFO] Validating: hi-rag-gateway-v2 [INFO] Validating: hi-rag-gateway-v2-gpu [INFO] Validating: invidious-companion-proxy [INFO] Validating: jellyfin-bridge [INFO] Validating: langextract [INFO] Validating: media-audio [INFO] Validating: media-video [INFO] Validating: mesh-agent [INFO] Validating: nats-echo-req [INFO] Validating: nats-echo-res [INFO] Validating: notebook-sync [INFO] Validating: p7-room-orchestrator [INFO] Validating: pdf-ingest [INFO] Validating: pmoves-yt [INFO] Validating: presign [INFO] Validating: publisher-discord [INFO] Validating: render-webhook [INFO] Validating: retrieval-eval [INFO] Validating: supaserch =================================== |
Fleet review: kilocode (kilo/z-ai/glm-5.3)reviewer: tier 2/3 I've reviewed the full diff (702 lines, all files). Here is the review. 1. CORRECTNESS
2. SECURITY / TOPOLOGY
3. VERDICTREQUEST_CHANGES — the one load-bearing behavior ( reviewer chain
_ lane: fleet-review chain (self-hosted kvm4) - spark-local (when online) -> kilo-primary -> kilo-alternate _ |
Docker Hardening ValidationHardening Validation ReportValidated: Sun Sep 27 08:56:30 UTC 2026Services CheckedPMOVES.AI compose hardening ratchet[INFO] Checking: /home/runner/work/PMOVES.AI/PMOVES.AI/pmoves/docker-compose.hardened.yml [INFO] Validating: agent-zero [INFO] Validating: archon [INFO] Validating: channel-monitor [INFO] Validating: comfy-watcher [INFO] Validating: deepresearch [INFO] Validating: extract-worker [INFO] Validating: ffmpeg-whisper [INFO] Validating: grayjay-plugin-host [INFO] Validating: hi-rag-gateway-gpu [INFO] Validating: hi-rag-gateway-v2 [INFO] Validating: hi-rag-gateway-v2-gpu [INFO] Validating: invidious-companion-proxy [INFO] Validating: jellyfin-bridge [INFO] Validating: langextract [INFO] Validating: media-audio [INFO] Validating: media-video [INFO] Validating: mesh-agent [INFO] Validating: nats-echo-req [INFO] Validating: nats-echo-res [INFO] Validating: notebook-sync [INFO] Validating: p7-room-orchestrator [INFO] Validating: pdf-ingest [INFO] Validating: pmoves-yt [INFO] Validating: presign [INFO] Validating: publisher-discord [INFO] Validating: render-webhook [INFO] Validating: retrieval-eval [INFO] Validating: supaserch =================================== |
Fleet review: kilocode (kilo/z-ai/glm-5.3)reviewer: tier 2/3 No repo checkout is available — reviewing the diff itself. Quick scans for leaks and consistency: 1. CORRECTNESS
2. SECURITY / TOPOLOGY
3. VERDICT: APPROVEDesign is least-privilege and evidence-driven, every risky action is gated (inspect preflight + reviewer chain
_ lane: fleet-review chain (self-hosted kvm4) - spark-local (when online) -> kilo-primary -> kilo-alternate _ |
Regenerated with `python pmoves/tools/topology/build_docker_matrix.py` (85 real services, 56 overlays). The diff is +20 lines, and all of it is #3201's own change: - the new neo4j-tailnet overlay; - its unmapped keys (cap_drop, graphfront-tailnet-state, neo4j-tailnet, security_opt); - pmoves_graph_front as an unmapped key in the base and main overlays. No unrelated drift. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Docker Hardening ValidationHardening Validation ReportValidated: Sun Sep 27 09:07:39 UTC 2026Services CheckedPMOVES.AI compose hardening ratchet[INFO] Checking: /home/runner/work/PMOVES.AI/PMOVES.AI/pmoves/docker-compose.hardened.yml [INFO] Validating: agent-zero [INFO] Validating: archon [INFO] Validating: channel-monitor [INFO] Validating: comfy-watcher [INFO] Validating: deepresearch [INFO] Validating: extract-worker [INFO] Validating: ffmpeg-whisper [INFO] Validating: grayjay-plugin-host [INFO] Validating: hi-rag-gateway-gpu [INFO] Validating: hi-rag-gateway-v2 [INFO] Validating: hi-rag-gateway-v2-gpu [INFO] Validating: invidious-companion-proxy [INFO] Validating: jellyfin-bridge [INFO] Validating: langextract [INFO] Validating: media-audio [INFO] Validating: media-video [INFO] Validating: mesh-agent [INFO] Validating: nats-echo-req [INFO] Validating: nats-echo-res [INFO] Validating: notebook-sync [INFO] Validating: p7-room-orchestrator [INFO] Validating: pdf-ingest [INFO] Validating: pmoves-yt [INFO] Validating: presign [INFO] Validating: publisher-discord [INFO] Validating: render-webhook [INFO] Validating: retrieval-eval [INFO] Validating: supaserch =================================== |
|
Steward checkpoint (B850-CLAUDE, restart stop point, 2026-09-27 05:13 EDT). Head |
Fleet review: kilocode-alternate (kilo/z-ai/glm-5.2)reviewer: tier 3/3 Let me verify a few key assumptions by locating the actual repo files referenced (Makefile 1. CORRECTNESS
2. SECURITY / TOPOLOGY
3. VERDICTAPPROVE — diff is correct and security-clean (no secrets/tokens; least-privilege forwarder; networks resolve via the stack and a bare-overlay run fails loud, not silent). The blocklist's strict-validation-off silent-no-op, the missing self-documenting reviewer chain
_ lane: fleet-review chain (self-hosted kvm4) - spark-local (when online) -> kilo-primary -> kilo-alternate _ |
Lane:
ops/knuckles-neo4j-from-fork(Neo4j Phase 1b follow-up). Branchops/knuckles-neo4j-external. No live change.Why
Phase 1b step 7 (2026-09-27): the compose
neo4jcame up healthy, but Docker published nothing.pmoves_app,pmoves_busandpmoves_dataare allinternal: true, and Docker does not publish ports for a container attached only to internal networks. The render and dry-run gates could not see this: they read REQUESTED bindings.Design: operator OPTION A (after the networking-doc and upstream reconciliation)
Neo4j stays internal-only (no egress, no host port), fronted by a Tailscale forwarder. This is
DOCKER_NETWORK_HARDENING.mdRule 5: "There is no 'publish without egress.' Front it with a gateway." (preferred)Rejected alternatives:
pmoves_external. It violates Rule 1 (data services never onpmoves_external).network-planes-and-package-sharing-2026-09-03.mdcalls it "a security regression".The first attempt (neo4j joins
pmoves_external, commit 21526bb) is reverted (e8ded62), as is the blocklist commit that had edited that block (a39991a). History is kept honest.Commits
pmoves_graph_front--no-deps+ graph_front preflight (P2); dedicated scoped key +TS_AUTH_ONCE; state-volume rename; registry rows; continuation-line parser.State.Running); accurate inspect-failure message; 4-state behavioural testThe forwarder:
docker-compose.neo4j-tailnet.yml(an overlay, NOT in STACK_FILES)Pattern: it reuses the repo's sidecar pattern (
docker-compose.yt-egress.yml) andTAILSCALE_FLEET_POSTURE.md.Service
neo4j-tailnet:tailscale/tailscale:stablepinned by digest (sha256:c507f3a2…, tailscale 1.102.5). The precedent uses the moving:latest, whichvalidate-composes-imagesflags.TS_USERSPACE=true, andTS_STATE_DIRon the named volumegraphfront-tailnet-state(see review fixes for why the name avoids "neo4j").TS_AUTHKEYcomes from a dedicatedNEO4J_TAILNET_AUTHKEY(:?guarded), withTS_AUTH_ONCE=true.:?guard as yt-egress).TS_EXTRA_ARGS=--advertise-tags=tag:neo4j.TS_ACCEPT_DNS=false, soneo4jresolves via Docker DNS.cap_drop: [ALL]+no-new-privileges. Measured: userspace tailscaled starts with no capabilities (throwaway, no key,--network none). yt-egress'sNET_ADMINwas for its proxy listeners, which this does not use.network_mode: Neo4j does not share its netns.Forwarding:
config/tailscale/neo4j-tailnet-serve.jsonviaTS_SERVE_CONFIGsends only TCP 7687 toneo4j:7687. The 7474 browser UI is not forwarded, because fleet AGInTs need bolt only.Networks (least privilege, per the steward's ruling):
pmoves_graph_front(NEW,internal: true,172.30.9.0/24, gateway.1): joined only byneo4jand the forwarder. A compromised forwarder reaches Neo4j and nothing else on the data tier; the forwarder is deliberately NOT onpmoves_data..1–.8are allocated (repo and live);.9was free.pmoves_externalis the one egress network, for the Tailscale control plane and DERP. This is the documented dual-attach pattern (agent-zero, archon, hi-rag-gateway-v2, flute-gateway). Rule 1 keeps DATA services offpmoves_external; the forwarder is a gateway, not a data store.Neo4j: it joins
pmoves_graph_front(internal) and gains nothing else. It stays on app/bus/data, all internal, with no alias needed (compose registers the service name on every network).Make targets (
mk/neo4j-tailnet.mk; no recipe nests make):up-neo4j-tailnet: preflight is$(DC) -f <overlay> config -q, which trips the overlay's own key guard without printing it.neo4j-tailnet-status.down-neo4j-tailnet: composestop, then a non-forceddocker container rm.Open questions: evidence
TS_DEST_IPwork in userspace? NO. Containerboot on the pinned image says "invalid configuration: TS_DEST_IP is not supported with TS_USERSPACE". It would need kernel mode (/dev/net/tun+NET_ADMIN), which departs from the repo's userspace precedent.ipn/serve.goExpandProxyTargetValue, in ourPMOVES-Tailscalefork).ServeConfigapplied viaTS_SERVE_CONFIGis dialled by tailscaled withb.dialer.SystemDial(ctx, "tcp", backDst)(ipn/ipnlocal/serve.go), with no localhost check. SoTCPForward: "neo4j:7687"resolves via Docker DNS.Rendered evidence (read-only; the shared checkout's env over this tree's compose; env values never printed)
neo4jnetworkspmoves_app, pmoves_bus, pmoves_data, pmoves_graph_frontneo4j-tailnetnetworkspmoves_external, pmoves_graph_fronttailscale/tailscale:stable@sha256:c507f3a2…network_modecap_dropsecurity_optno-new-privileges:trueTS_USERSPACETS_SERVE_CONFIG/config/serve.json(mounted ro)TS_EXTRA_ARGS--advertise-tags=tag:neo4jTS_ACCEPT_DNSpmoves_graph_front172.30.9.0/24The LOAD CSV blocklist: defense in depth (e45c8fc)
NEO4J_internal_dbms_cypher__ip__blocklist=0.0.0.0/0,::/0on neo4j. Even internal-only, it stops CypherLOAD CSVfrom reaching the host gateway, the LAN or other containers.Evidence (pinned 5.26.30 Community image; throwaway containers only; the live container untouched):
--internalbridge,LOAD CSV FROM 'http://127.0.0.1:1/x'gives "Couldn't load the external resource". That is a connection error.internal.*is unsupported by Neo4j; the digest pin keeps it stable.apoc.load.json. Otherapoc.load.*procedures and HTTP redirects are unverified.--network noneis unusable for offline Neo4j tests: Neo4j 5.26 exits immediately (code 3, "shutdown initiated by request", no error) without a network. Use a private--internalbridge.Fleet test:
tests/test_published_ports_need_a_non_internal_network.pyEvery published service in the default STACK_FILES must join at least one non-internal network.
KNOWN_VIOLATORSas strict xfail. Neo4j is listed by design (see below).pmoves_externalandpmoves_db_egressareexternal: true, so their internal flag comes from the Makefile create sites.EXTERNAL_NON_INTERNALasserts that everydocker network createsite for them omits--internal(Makefile:725/5815 for db_egress; 5144/5191/5813 for external), with a positive control.name:. Scope: STACK_FILES only;!reset/!overrideare not modelled.network_mode;pmoves_graph_frontis internal, and its only STACK member is neo4j (the forwarder joins from its overlay).Forwarder test (
tests/test_neo4j_tailnet_forwarder.py), 7 structural assertions: digest pin; userspace and noTS_DEST_IP; the serve mount; bolt-only forward; the tag; graph_front + external only, with no netns, ports or caps; not in STACK_FILES; no recipe nests make.:?guard. Their text named the auth-key variable, which the zero-access gate refuses. They are not rephrased to avoid the name. Coverage stays adequate: the compose line's:?makes compose itself refuse to start without the key.Counts (targeted): forwarder 7 + fleet + bind passthrough + neo4j safety + no-bare-compose = 146 passed, 35 xfailed, 0 failed.
Gates:
compose-networks-checkcompose-split-checkvalidate-composesReview fixes (5889d40)
P2: the forwarder never recreates Neo4j. Before this fix,
up-neo4j-tailnetwould have recreated the livepmoves-neo4jimplicitly: throughdepends_on: neo4j, compose sees neo4j's config changed by this PR, since it joinspmoves_graph_front.up -d --no-deps neo4j-tailnet.docker inspectpreflight refuses (exit 1) unless the runningpmoves-neo4jis already attached topmoves_graph_front. It prints network names only, never addresses.depends_onstays, for full-stack ordering.--no-depsis present and the preflight precedes theup;makeruns againstbuild_stub_envstubs. Before the recreate the target refuses and no composeupruns; after it, everyupcarries--no-deps.P3:
DOCKER_NETWORK_HARDENING.mdgainspmoves_db_egress(172.30.8.0/24, not internal) andpmoves_graph_front(172.30.9.0/24, internal). The "Six networks" line was already stale; it now reads nine.neo4jresolves onpmoves_graph_front, notpmoves_data.graphfront-tailnet-state. Any name containingneo4jwould still match volume-reset's(^pmoves_.*neo4j|neo4j$), and that includes the suggestedpmoves_tailnet-neo4j-state. A match meansmake volume-reset SERVICE=neo4jwould wipe the forwarder's tailnet identity. A test asserts that the name cannot match.--internalplaced on a continuation line. There is a new test for this.NEO4J_TAILNET_AUTHKEY(a one-off, pre-authorised key scoped totag:neo4j) plusTS_AUTH_ONCE=true. Registering it in the secrets manifest is an operator step: the manifest path is zero-access to agents (even a protection check naming the path was refused), so I did not attempt the edit by another route.Tests (targeted only), at 5889d40:
test_neo4j_tailnet_forwarder,test_published_ports_need_a_non_internal_network,test_neo4j_bind_passthrough,test_neo4j_container_safetyandtest_no_bare_compose_callsgive 151 passed, 35 xfailed. The commit message says 157; that figure is wrong, and 151 is the measured count. Gates:compose-networks-checkOK (109 services),validate-composesclean,compose-split-checkrc 0.Nits (ea6461e):
.State.Runningtogether with the network names, in the same single inspect call (still no addresses). It refuses whenpmoves-neo4jis stopped:--no-depswill not start it, and a forwarder in front of a stopped Neo4j fronts nothing.upran: before the recreate; after it (the only one that runsup, and with--no-deps); recreated but stopped; inspect failing with rc 1.compose-networks-checkOK (109 services);validate-composesclean;compose-split-checkrc 0.Operator decision (left as is): the overlay keeps
${NEO4J_TAILNET_AUTHKEY:?}. WithTS_AUTH_ONCE=truethe key is only used at first login, but the:?means the variable must stay set for compose to render the overlay at all; relaxing it once the state volume holds an identity is the operator's call. The main stack is unaffected, because the overlay is not inSTACK_FILES.Findings (not fixed here)
docker-image-src/5/coredb/docker-entrypoint.sh:468-473) thatNEO4JLABS_PLUGINSis still honoured in 5.x as a fallback (: ${NEO4J_PLUGINS:=${NEO4JLABS_PLUGINS}}, with a deprecation warning), and coreapocinstalls from the locallabs/jar with no download. So APOC is most likely LOADED, anddbms.security.procedures.unrestricted=apoc.*is LIVE. This corrects my earlier "may never load" reading, which looked only at the emptyplugins/of the bare image. Suggestion, not in this PR: rename toNEO4J_PLUGINS.ports:/NEO4J_BINDare INERT under option A. On internal-only networks nothing publishes. They are kept only because feat(neo4j): NEO4J_BIND pass-through (option B) + Neo4j on app/bus/data for every AGInT (compose half pending road) #3196's pass-through reads them. Dropping them (and with them feat(neo4j): NEO4J_BIND pass-through (option B) + Neo4j on app/bus/data for every AGInT (compose half pending road) #3196's pass-through) is a follow-up decision for the operator, not this PR.pmoves_app, which isinternal: true, so it may never reach the Tailscale control plane. Unmeasured.down-yt-egress, was misread by the Bash guard as a filesystem recursive/force removal and refused. The same pattern later matched this PR description's own prose describing it: the finding reproduced itself. The approved down target uses composestopplus a non-forceddocker container rm.mk/neo4j-tailnet.mk, both containing the auth-key variable NAME, were written WITHOUT refusal. A test-file heredoc with the same NAME was refused by the zero-access pattern for that variable, probably Write-tool content vs Bash command-text scanning. The two assertions were then dropped (the steward's ruling), not rephrased.grepwhose own PATTERN named the variable was refused, even though the file it checked no longer contained the name. The self-check moved to the Read tool.du;docker port/NetworkSettings.Ports);OPERATOR STEPS (not done by this PR)
tag:neo4j. This needs atagOwnersentry fortag:neo4jin the tailnet policy. Deliver it asNEO4J_TAILNET_AUTHKEYthrough the secrets funnel (make -C pmoves secrets-funnel), and registerNEO4J_TAILNET_AUTHKEYin the secrets manifest. Agents cannot do that last part because the manifest is zero-access to them. WithTS_AUTH_ONCE=true, the key is used only for the first login; after that, the state volume carries the node identity.<fleet AGInT tag>→ dsttag:neo4j,ip: tcp:7687(inpmoves/configs/tailscale-acl-policy.json, or wherever the operator manages the policy).NEO4J_BIND: keep it at a loopback value on this node (e.g.127.0.0.1via the.env.localKnown Road). No host publish is needed under option A, and a loopback value stays safe if a non-internal network is ever added.docker stop -t 60 pmoves-neo4j, then the fix(neo4j): Phase 1 stabilise: never start a second or volumeless Neo4j; one name source (compose pin pending road) #3193 Rollback A.1 log check (INFO Stopped.after the lastINFO Started.). If the stop was not clean: STOP.:ro, into a dated volume whose name does not start withpmoves_(sovolume-reset SERVICE=neo4jcannot match it). The-pre3201suffix keeps the 1b backup intact.-p pmoves --project-directory "$PWD", and the same--env-fileand-flists, re-derived on the day with theprint-dcone-liner from Phase 1b step 6. The subcommand is:Network pmoves_graph_front Creating/CreatedandContainer pmoves-neo4j Recreate/Recreated/Starting/Started. Anything else is a STOP: another container, a volume creation, or orphan removal. Do not usemake -n up-data-tieras the dry run: its nested$(MAKE) … wait-dataline executes even under-n.make -C pmoves up-data-tier DATA_SERVICES=neo4j.docker inspectlistspmoves_graph_front.cipher-apistill resolvesneo4j.make -C pmoves up-neo4j-tailnet. Its preflight refuses until step 4 has attachedpmoves-neo4jtopmoves_graph_front, and it runs with--no-deps, so it never touches Neo4j;neo4j-tailnet-status;Rollback (steps 4 and 5)
Step 5 failed, or needs undoing: run
make -C pmoves down-neo4j-tailnet. This stops the forwarder and removes it without force. Neo4j is untouched, and nothing else needs doing: the internal, now-emptypmoves_graph_frontcan remain.Step 4 failed, or needs undoing. Re-up Neo4j from the pre-PR compose, through the same gated path:
make -C pmoves down-neo4j-tailnetfirst.docker-compose.ymlis the only file in the step-6 list that this PR changes, so write its pre-merge copy to scratch:-f <scratch>/docker-compose.pre3201.ymlin place of-f docker-compose.yml. Keep--project-directory "$PWD"so relative paths resolve exactly as before. It must show ONLYContainer pmoves-neo4j Recreate/Recreated/Starting/Started; anything else is a STOP. Compose does not delete a network a service merely left, sopmoves_graph_frontstays: internal, and empty.--dry-run. This is the one literal composeupin the runbook: no make target renders a substituted compose file.pmoves_graph_front.Data damaged. That means post-counts differ from 4a, or
debug.logshows store, recovery or corruption ERRORs. Nothing starts onpmoves_neo4j-datauntil the restore has finished. Restore fromknuckles-neo4j-backup-YYYYMMDD-pre3201using #3193 Rollback B:Only then run R3 and R4.
🤖 Generated with Claude Code