feat(cli): §3 M3 — per-env approval gating ([project.environments])

A `[project.environments]` block maps an env name to `{ confirm = bool }`
(`ManifestProject.environments`, client-side only — `skip_serializing`).
`pic apply --env <e>` is refused before any request when `<e>` is
confirm-required unless it is explicitly `--approve <e>`d; a blanket `--yes`
does NOT cover a gated env (§4.2 "CI must opt in per environment"); an unlisted
or `confirm = false` env applies freely. `require_env_approval` gates both the
single (`run`) and tree (`run_tree`) apply paths; `--approve <env>` is a
repeatable flag. Per-env value overlays (`picloud.<env>.toml`) already merge
client-side, so this is the confirm-policy gate only.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
MechaCat02
2026-07-07 20:28:28 +02:00
parent b8eced9d91
commit 62710b5d81
3 changed files with 53 additions and 1 deletions

View File

@@ -283,6 +283,22 @@ pub struct ManifestProject {
/// that subtree. Absent = instance root = no ceiling (the default).
#[serde(default, skip_serializing_if = "Option::is_none")]
pub parent_group: Option<String>,
/// §3 M3 per-env approval policy: `[project.environments]` maps an env name
/// to its policy. A `confirm = true` env requires an explicit `--approve
/// <env>` on `pic apply --env <env>` (a blanket `--yes` does NOT cover it —
/// §4.2 "CI must opt in per environment"). Client-side gating only; never
/// sent to the server.
#[serde(default, skip_serializing)]
pub environments: std::collections::BTreeMap<String, EnvPolicy>,
}
/// §3 M3: the apply-gating policy for one environment.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct EnvPolicy {
/// Require an explicit `--approve <env>` to apply to this env.
#[serde(default)]
pub confirm: bool,
}
/// The store kind of a shared collection (§11.6).