feat(project-tool): per-env approval-policy gating (§4.2/§6)
Salvaged from the superseded feat/hierarchies-extension-points branch (M5),
re-ported onto main's evolved tree-apply engine. Fills the §7/§11.1 "per-env
approval-policy gating" that main's design doc still lists as intended but
unbuilt.
A project's root manifest declares which environments require explicit
approval; `pic apply --dir --env <e>` to a confirm-required env needs an
explicit `--approve <e>` (a blanket `--yes` does NOT cover it). The approval
is admin-gated (admin on every declared node) and audited, and the policy
folds into the bound-plan token.
- manifest: `[project]` block → ManifestProject { environments[{name,confirm}] },
root-manifest-only (rejected elsewhere by build_tree).
- discover: build_tree emits `project` into the bundle from the root manifest.
- apply_service: TreeBundle.project + ProjectPolicy; policy folds into the
state_token; plan surfaces approvals_required; ApplyError::ApprovalRequired
→ 409.
- apply_api: enforce_env_approval (after authz_tree) — refuses a gated env not
in approved_envs, requires AppAdmin/GroupAdmin on every declared node on
approval, audits. Server re-derives policy from the bundle (authoritative).
- CLI: --approve (repeatable); resolve_approvals refuses a gated env
non-interactively, prompts on a TTY; plan renders gated envs. Single-node
apply --file refuses a confirm-required env and directs to --dir.
- approval journey + manifest/ProjectPolicy unit tests. No migration (policy
lives in the manifest, like takeover/blast-radius).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1124,10 +1124,16 @@ Resolved items now live inline next to their topic. What genuinely remains:
|
||||
> `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).
|
||||
> start. **Per-env approval-policy gating shipped (`[project.environments]`, M5, 2026-07-05)** — the
|
||||
> root manifest declares which environments are confirm-required; `pic apply --dir --env <e>` to such
|
||||
> an env needs an explicit `--approve <e>` (a blanket `--yes` does NOT cover it), the approval is
|
||||
> admin-gated (the actor needs admin on every declared node, not just write) + audited, and the policy
|
||||
> folds into the bound-plan token. The server re-derives the policy from the bundle (authoritative);
|
||||
> single-node `apply --file --env <e>` refuses a gated env (can't carry the admin-gated approval) and
|
||||
> directs to `--dir`. Like `--takeover`/blast-radius the policy lives in the manifest (not persisted),
|
||||
> an accepted trust boundary. 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 }`
|
||||
|
||||
Reference in New Issue
Block a user