feat(modules): enable group modules + dangling-import plan check (Phase 4b C4)
Lift the Phase-4-lite restrictions now that import resolution is lexical: - `create_group_script` (group_scripts_api): allow `kind = module` and group scripts with `import`s; record the import edges. Module-shape and sandbox-ceiling validation stay. - The declarative apply path already reconciles group modules generically (`BundleScript.kind` + `owner.script_owner()` flow through `reconcile_node_tx`); no change needed beyond stale-comment fixes. Adds the §5.5 dangling-import plan check (single-node plan/apply): `check_imports_resolve` rejects an `import "<m>"` that resolves to neither a bundle-local module nor a module reachable lexically up the node's chain — caught at plan time instead of as a runtime 404. Roots resolution at the node's owner (an app node reaches ancestor group modules; a group node walks its own ancestry). ApplyService gains an injected `ModuleSource`. The tree path opts out (a node may import a module created by another node in the same uncommitted tx); the runtime check is the backstop there. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -97,20 +97,10 @@ async fn create_group_script(
|
||||
)
|
||||
.await?;
|
||||
|
||||
// Phase 4-lite: endpoint-only, self-contained.
|
||||
if input.kind == ScriptKind::Module {
|
||||
return Err(GroupScriptsApiError::Invalid(
|
||||
"group modules are not supported yet (Phase 4b); create an endpoint script".into(),
|
||||
));
|
||||
}
|
||||
// Phase 4b: group modules + imports are allowed. Imports resolve
|
||||
// lexically from this group's chain at runtime (§5.5); the recorded
|
||||
// edges feed the declarative dangling-import plan check.
|
||||
let validated = s.validator.validate(&input.source)?;
|
||||
if !validated.imports.is_empty() {
|
||||
return Err(GroupScriptsApiError::Invalid(format!(
|
||||
"group scripts must be self-contained in Phase 4-lite; \
|
||||
remove the import(s): {}",
|
||||
validated.imports.join(", ")
|
||||
)));
|
||||
}
|
||||
s.sandbox_ceiling
|
||||
.check(&input.sandbox)
|
||||
.map_err(|e| GroupScriptsApiError::Invalid(e.to_string()))?;
|
||||
@@ -123,7 +113,7 @@ async fn create_group_script(
|
||||
name: input.name,
|
||||
description: input.description,
|
||||
source: input.source,
|
||||
kind: ScriptKind::Endpoint,
|
||||
kind: input.kind,
|
||||
timeout_seconds: input.timeout_seconds,
|
||||
memory_limit_mb: input.memory_limit_mb,
|
||||
sandbox: if input.sandbox.is_empty() {
|
||||
@@ -132,8 +122,7 @@ async fn create_group_script(
|
||||
Some(input.sandbox)
|
||||
},
|
||||
enabled: true,
|
||||
// Self-contained — no import edges (enforced above).
|
||||
imports: Vec::new(),
|
||||
imports: validated.imports,
|
||||
})
|
||||
.await?;
|
||||
Ok((StatusCode::CREATED, Json(created)))
|
||||
|
||||
Reference in New Issue
Block a user