test(cli): project-tree journeys + Phase 5 docs

Phase 5 C5. Three `pic ... --dir` tree journeys:
  * a group + a nested app apply atomically as one tree (the app's route binds
    the group's same-apply `shared` script); the group node's script is
    created; re-plan is all-noop (idempotent),
  * one invalid node aborts the whole tree — the valid group node's script is
    NOT created (true all-or-nothing),
  * a `pic groups reparent` between `pic plan --dir` and `pic apply --dir` trips
    the bound-plan (StateMoved) check via the tree's structure version.

Docs: `groups-and-project-tool.md` §11.5 status ( shipped — single-repo
nested tree apply; multi-repo single-owner + per-env approval gating deferred
per §11.1) + CLAUDE.md current-focus.

Gates: fmt, clippy -D warnings, cargo test --workspace, check-versioning (50
migrations — Phase 5 added none), schema snapshot unchanged, full journey suite
108/108. (Pre-existing flake `queue_e2e::queue_receive_acks_on_success` — a
racy immediate `count==0` after the async ack; flaky at the pre-Phase-5
baseline too, unrelated to this work.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
MechaCat02
2026-06-25 22:43:20 +02:00
parent 1662d7806a
commit 3ef3038eb7
4 changed files with 287 additions and 1 deletions

View File

@@ -986,6 +986,37 @@ Resolved items now live inline next to their topic. What genuinely remains:
> delete itself stays `RESTRICT`).
5. **Project tool maps onto groups.** Nested manifests, attach point, single-owner, server-computed
tree plan, per-env approval gating.
> **Status (Phase 5): ✅ shipped — single-repo nested tree apply, atomic.** A directory tree of
> `picloud.toml` manifests (each declaring an `[app]` or `[group]` node) applies as ONE
> server-computed plan in ONE Postgres transaction. Per the §11.1 review, the **multi-repo
> single-owner / attach-point / takeover** layer (§7) is **deferred** for the solo-dev / single-repo
> start, as is **per-env approval-policy gating** (`[project.environments]`); env overlays
> (`picloud.<env>.toml`) already exist. Groups must **pre-exist** (`pic groups create`) — a manifest
> owns each node's *content*, not the tree *shape* (declarative group create/reparent is a later
> add).
>
> Shipped surface:
> - **Engine:** the reconcile engine generalized from app-only to an `ApplyOwner { App | Group }`
> (a group node has only scripts + vars; routes/triggers/secret-values stay app-only). The in-tx
> reconcile is `reconcile_node_tx`, shared by single-node and tree apply. `apply_tree` locks every
> node key (sorted), reconciles **groups first** (recording each group's `name → id`), then apps —
> so an app route/trigger can bind a group script **created in the same transaction** (resolved
> nearest-ancestor-wins across the in-tree index + pre-existing ancestors; the pool resolver can't
> see uncommitted rows). One commit, one post-commit route refresh.
> - **Bound plan covers structure:** the tree token folds every node's `state_token` **plus every
> in-scope group's `structure_version`**, so a reparent (or content edit) between plan and apply
> trips `StateMoved` — the §4.2 "content **and** tree-structure version" check.
> - **API:** `POST /api/v1/admin/{groups/{id},tree}/{plan,apply}`; per-node capability gating (the
> actor must hold the read/write caps for **every** node touched).
> - **CLI:** a `[group]` manifest kind; `pic plan/apply` on one node (app or group); `pic
> plan/apply --dir <root>` discovers + applies the whole tree, with a single `<tree>` bound token.
>
> Live- and journey-validated: a group + nested app apply atomically (app route binds the group's
> same-apply script); re-plan is all-noop; one invalid node aborts the whole tree (nothing written);
> a `pic groups reparent` between plan and apply is refused. The §5.1 materialized effective-view +
> fan-out invalidation was **not** built — group config/scripts resolve **live** (Phase 3/4),
> so the tree apply needs no cache-invalidation protocol.
6. **(Much later) group-level collections/topics** — the v1.3 cross-app data-sharing problem, with a
real shared-scope authz model. Optionally, trigger/route **templates** (§4.5) if cardinality
demands.