fix(modules): validate group module create per kind (Phase 4b review)

Adversarial-review finding: `create_group_script` validated every source
with `validate()` regardless of kind, so an imperatively-created group
module (`pic scripts deploy --group g --name kv --kind module`) skipped the
two gates every other module-write path enforces (app create/update in
api.rs, declarative apply in apply_service): the stricter `validate_module`
shape check and the `RESERVED_MODULE_NAMES` guard. A malformed-shape group
module was accepted at create (only failing later at import-resolve time)
and a reserved name (`kv`, `log`, …) slipped through.

Branch on kind to mirror the app path. Adds a journey asserting a reserved
group-module name is rejected.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
MechaCat02
2026-06-29 19:32:34 +02:00
parent 52da8a8704
commit 4e25a142bd
2 changed files with 50 additions and 4 deletions

View File

@@ -137,6 +137,37 @@ fn group_module_is_lexically_sealed_under_inheritance() {
);
}
#[ignore = "needs DATABASE_URL pointing at a running Postgres"]
#[test]
fn reserved_group_module_name_is_rejected() {
// Parity with app modules (api.rs): a group `module` named after a built-in
// SDK namespace is refused at create — not silently accepted to fail later.
let Some(fx) = common::fixture_or_skip() else {
return;
};
let env = common::admin_env(fx);
let group = common::unique_slug("gm-rsv");
let _g = GroupGuard::new(&env.url, &env.token, &group);
common::pic_as(&env)
.args(["groups", "create", &group])
.assert()
.success();
let dir = manifest_dir();
fs::write(dir.path().join("scripts/kv.rhai"), r#"fn x() { 1 }"#).unwrap();
let out = common::pic_as(&env)
.args(["scripts", "deploy"])
.arg(dir.path().join("scripts/kv.rhai"))
.args(["--group", &group, "--name", "kv", "--kind", "module"])
.output()
.expect("deploy");
assert!(
!out.status.success(),
"a reserved module name must be rejected for a group module too"
);
}
#[ignore = "needs DATABASE_URL pointing at a running Postgres"]
#[test]
fn dangling_import_is_rejected_by_plan() {