feat(v1.1.9): InvokeServiceImpl + RouteTable wiring in picloud binary
manager-core/invoke_service.rs implements InvokeService over:
- ScriptRepository (for Id + Name resolution)
- RouteTable (for Path resolution via the orchestrator's matcher)
- OutboxRepo (for invoke_async)
Same-app guard runs at the service entry point: resolve_id() always
verifies resolved.app_id == cx.app_id and returns InvokeError::CrossApp
otherwise. resolve_name() reads cx.app_id when querying (so a wrong
app_id from somewhere else couldn't slip through), but defense-in-depth
won't hurt — future hardening if it surfaces.
resolve_path() runs the matcher against this app's routes only
(RouteTable::snapshot_for_app). Method assumed POST→GET fallback for
v1.1.9 — surfacing method via the SDK is a future addition.
enqueue_async() resolves the target, then writes one outbox row with
source_kind = 'invoke'. The payload carries script_id, script_name,
args, fresh execution_id, root_execution_id (inherited from caller),
trigger_depth + 1. caller principal recorded as origin_principal
(forensic only — the executing script runs with no principal). One
shot — no retry policy on the row; users wrap in retry::with() if
they want retries.
picloud/lib.rs:
- route_table construction moved BEFORE Services::new so the invoke
service can hold it. populates from route_repo as before.
- InvokeServiceImpl wired into Services::new in place of the
NoopInvokeService placeholder.
Unit tests cover: resolve by Id same-app, cross-app rejected, resolve
by Name finds + not-found, enqueue_async writes Invoke outbox row with
trigger_depth=1.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -274,11 +274,27 @@ pub async fn build_app(
|
||||
authz.clone(),
|
||||
),
|
||||
);
|
||||
// v1.1.9 function composition. Real InvokeService wired in commit 8;
|
||||
// the noop here keeps the binary compiling — without it, scripts
|
||||
// calling `invoke()` will throw a clear error.
|
||||
let invoke: Arc<dyn picloud_shared::InvokeService> =
|
||||
Arc::new(picloud_shared::NoopInvokeService);
|
||||
// Route table created early (before Services) so InvokeServiceImpl
|
||||
// can use it for path resolution. It's populated below from the
|
||||
// route_repo, then re-populated whenever the admin layer writes
|
||||
// routes.
|
||||
let route_table = Arc::new(RouteTable::new());
|
||||
let initial = route_repo.list_all().await?;
|
||||
let compiled = compile_routes(&initial)
|
||||
.map_err(|e| anyhow::anyhow!("failed to compile stored routes: {e}"))?;
|
||||
route_table.replace_all(compiled);
|
||||
|
||||
// v1.1.9 function composition. InvokeServiceImpl resolves targets
|
||||
// by Id, Name (via get_by_name), or Path (via the orchestrator's
|
||||
// RouteTable). invoke_async writes an OutboxSourceKind::Invoke row
|
||||
// that the dispatcher fires through the standard executor path.
|
||||
let invoke: Arc<dyn picloud_shared::InvokeService> = Arc::new(
|
||||
picloud_manager_core::invoke_service::InvokeServiceImpl::new(
|
||||
Arc::new(PostgresScriptRepoHandle(script_repo.clone())),
|
||||
route_table.clone(),
|
||||
outbox_repo.clone(),
|
||||
),
|
||||
);
|
||||
let services = Services::new(
|
||||
kv,
|
||||
docs,
|
||||
@@ -296,13 +312,6 @@ pub async fn build_app(
|
||||
);
|
||||
let engine = Arc::new(Engine::new(Limits::default(), services));
|
||||
|
||||
// Compile the routes table once at startup; admin writes refresh it.
|
||||
let route_table = Arc::new(RouteTable::new());
|
||||
let initial = route_repo.list_all().await?;
|
||||
let compiled = compile_routes(&initial)
|
||||
.map_err(|e| anyhow::anyhow!("failed to compile stored routes: {e}"))?;
|
||||
route_table.replace_all(compiled);
|
||||
|
||||
// Same shape for app domains (Host → app_id cache).
|
||||
let app_domain_table = Arc::new(AppDomainTable::new());
|
||||
let initial_domains = domains_repo.list_all().await?;
|
||||
|
||||
Reference in New Issue
Block a user