feat(modules): no-provider plan check + read-only extension-points ls (§5.5 C4)

- apply: `check_extension_points_provided` (app nodes) — every EP visible on
  the app's chain (its own + inherited) must have a provider: a same-apply
  module, or one resolvable up-chain (override or default body). Else a clean
  `pic plan` error instead of a runtime failure. Single-node only (the tree
  path leans on the runtime backstop, like the dangling-import check).
- read-only surface: `extension_point_report` + `ExtensionPointInfo`;
  `GET /{apps|groups}/{id}/extension-points` (AppRead / GroupScriptsRead);
  `pic extension-points ls --app|--group` showing declared-vs-inherited + the
  resolved provider (`app override` / `inherited default` / `unset`).
- `pic pull` round-trips an app's OWN declared EPs (filtered `declared_here`),
  so pull→plan stays idempotent.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
MechaCat02
2026-06-29 20:31:14 +02:00
parent e62e073970
commit 82b4579317
8 changed files with 307 additions and 8 deletions

View File

@@ -49,6 +49,15 @@ pub async fn run(app_ident: &str, dir: &Path, force: bool, mode: OutputMode) ->
.secrets_list(VarOwnerArg::App(app_ident), None)
.await?
.secrets;
// The app's OWN declared extension points (§5.5) — inherited ones belong to
// the ancestor group's manifest, so filter to `declared_here`.
let extension_points: Vec<String> = client
.extension_points_list(crate::client::NodeKind::App, app_ident)
.await?
.into_iter()
.filter(|ep| ep.declared_here)
.map(|ep| ep.name)
.collect();
// App's OWN env-agnostic vars become the manifest `[vars]` block. Inherited
// group vars and tombstones are excluded (pull exports own rows only, §4.6);
// a value TOML can't represent (e.g. JSON null) is skipped with a warning.
@@ -224,8 +233,7 @@ pub async fn run(app_ident: &str, dir: &Path, force: bool, mode: OutputMode) ->
names: secrets.iter().map(|s| s.name.clone()).collect(),
},
vars: manifest_vars,
// Wired to the read endpoint in C4 so pull→plan round-trips EPs.
extension_points: Vec::new(),
extension_points,
};
std::fs::write(&manifest_path, manifest.to_toml()?)