Files
PiCloud/crates/picloud-cli/tests/cli.rs
MechaCat02 4dcb8d1136 feat(hierarchies): extension points — opt-in dynamic module resolution (§5.5)
M1 of the remaining-hierarchies work. An extension point lets a PARENT/group
node opt a module name out of sealed lexical resolution: a parent script's
`import "x"` then resolves against the INHERITING app's module (with a declared
default body as fallback) instead of the parent's sealed chain — so each
descendant app supplies its own `x`. Only the declaring parent can open the
inversion; a descendant can never make a parent's sealed import dynamic or
hijack one (the "is this an extension point" decision keys on the importer's
trusted defining node, never a script-passed value).

Resolver (executor-core):
- `ModuleSource` gains `resolve_extension_point(origin, name)` and a
  chain-constrained `resolve_by_id(origin, id)`. The resolve seam checks for an
  ext point on the importer's chain first; on a hit it resolves against
  `App(cx.app_id)`, else the declared default, else ModuleNotFound. The Postgres
  query returns Some only when the NEAREST declaration of the name is an ext
  point (a peer/nearer concrete module shadows it). A group origin can't reach
  app-owned rows — the trust boundary holds. Cache stays id-keyed (no bleed).

Apply (manager-core, mirrors the `vars` pattern):
- migration 0051: owner-polymorphic `extension_points` table (group XOR app),
  optional `default_script_id` FK ON DELETE SET NULL, per-owner LOWER(name)
  unique indexes.
- Bundle/Plan/CurrentState gain extension_points; `diff_extension_points`
  (name-based, default change = Update), reconcile (upsert after scripts so the
  default resolves) + prune, `validate_bundle` (module-name shape; default must
  be a local module), `check_imports_resolve` (a declared/on-chain ext point
  satisfies an import), and the state_token fold. Writes gate on the
  script-write capability (AppWriteScript / GroupScriptsWrite).
- GET /apps|groups/{id}/extension-points so `pic pull` round-trips them — a
  re-applied pulled manifest is all-NoOp, so `--prune` can't silently drop one.

CLI: `[[extension_points]]` manifest table; plan/apply build + render; pull
exports them.

Tests: 5 resolver units (inversion, default fallback, no-provider error,
no-hijack of a sealed import, no cross-tenant cache bleed), 2 diff/state-token
units, 2 journeys (per-app resolution + default fallback via invoke; pull
round-trip). Schema golden re-blessed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 21:35:29 +02:00

46 lines
952 B
Rust

//! Integration-test binary for the `pic` CLI.
//!
//! Every `#[test]` in this binary routes through `common::fixture()`, a
//! `LazyLock` that spawns picloud once on a private port and reuses it
//! across all journey modules. Mirrors the dashboard Playwright suite,
//! which spins backend + Vite up once for 63 specs.
//!
//! Gated on `DATABASE_URL`. To run:
//!
//! docker compose up -d postgres
//! DATABASE_URL=postgres://picloud:picloud@127.0.0.1:15432/picloud \
//! cargo test -p picloud-cli --test cli -- --include-ignored
mod common;
mod admins;
mod api_keys;
mod apply;
mod apps;
mod auth;
mod config;
mod dead_letters;
mod email_queue;
mod enabled;
mod env_overlay;
mod extension_points;
mod group_modules;
mod group_scripts;
mod group_secrets;
mod groups;
mod init;
mod invoke;
mod logs;
mod output;
mod plan;
mod prune;
mod pull;
mod roles;
mod routes;
mod scripts;
mod secrets;
mod staleness;
mod tree;
mod triggers;
mod vars;