invoke_service::resolve and enqueue_async performed no authz check —
no AppInvoke capability existed. Same-app isolation was preserved
(cross-app guards work), but within one app an anonymous public-HTTP
script could trigger any other script (e.g. an admin-only worker that
hits secrets/files/external HTTP). Worse: invoke_async runs the
callee with principal: None, so the callee could hold capabilities
the original public caller shouldn't.
- Add Capability::AppInvoke(AppId). app_id() / scope_for_capability
(script:write) / role_satisfies (editor+) are all updated.
- InvokeServiceImpl gains an optional `authz: Option<Arc<dyn AuthzRepo>>`
+ a `with_authz` builder. When set, resolve() runs script_gate on
AppInvoke before doing the cross-app id check.
- picloud/src/lib.rs wires it: `InvokeServiceImpl::new(...).with_authz(...)`.
- Anonymous callers (cx.principal == None) continue to skip the check
via script_gate, preserving the public-HTTP convention.
Existing 5 invoke_service unit tests still pass (the tests use the
authz-less constructor, so the gate is a no-op there).
AUDIT.md anchor: F-S-012.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>