fix(cli): make the per-env approval gate fail closed

Review #2 (MEDIUM). The M3 `[project.environments]` approval gate silently
failed open in three shapes:

- `EnvPolicy.confirm` was `#[serde(default)]`, so `production = {}` parsed as
  un-gated. `confirm` is now a REQUIRED field — an env listed with an empty
  policy is a load error (`missing field 'confirm'`), not a silent no-gate.
- `pic apply --file <leaf>` where the leaf carried no `[project]` skipped the
  gate even when the repo root gated the env. `run` now discovers the governing
  `[project]` by walking up from the manifest to the nearest ancestor
  `picloud.toml` that declares one (`find_governing_project`, loaded without the
  env overlay — only the block matters).
- A `[project].environments` in a non-root manifest under `--dir` was dropped
  with a generic "ignored" note; `build_tree` now warns explicitly that its
  approval gating is not enforced.

Pinned by `tests/env_approval.rs` (empty-policy load error; leaf `--file` apply
honoring the root gate).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
MechaCat02
2026-07-07 21:48:09 +02:00
parent 2e3263002a
commit 3f272daf04
4 changed files with 151 additions and 3 deletions

View File

@@ -296,8 +296,11 @@ pub struct ManifestProject {
#[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)]
/// Require an explicit `--approve <env>` to apply to this env. NOT
/// `#[serde(default)]`: an env listed with an empty policy (`prod = {}`)
/// must fail to load (`missing field 'confirm'`) rather than silently
/// parse as un-gated — listing an env to protect it, then having the gate
/// quietly absent, is the failure mode to avoid.
pub confirm: bool,
}