feat(interceptors): extend before/after hooks to docs/files/queue/pubsub/http (§9.4 M7-M11)
Every non-KV mutating op now runs the same before/after interceptor machinery: - docs create/update/delete (+ group) — create/update honor the M4 data transform; - files create/update/delete (+ group) — allow/deny only (the blob is never surfaced to the hook, value = None); - queue enqueue (+ shared) — transform-capable; after reports the message id; - pubsub publish_durable (+ shared topic) — transform-capable; - http request — all verbs funnel through two svc.request sites; collection = method, key = url, body not surfaced. ictx threaded into the free-fn/handle paths that lacked it (http was _ictx; queue/pubsub per-app publish). validate_bundle_for generalized to a per-service allowed-ops map (kv set/delete; docs/files create/update/delete; queue enqueue; pubsub publish; http request) — unknown service/op still rejected. INVARIANT enforced + verified: every allowed (service, op) has a matching runtime hook, so no validated-but-unhooked fail-open. Pinned by a new docs-create deny journey (11 interceptor journeys green). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -38,16 +38,39 @@ pub(super) fn register(
|
||||
{
|
||||
let svc = svc.clone();
|
||||
let cx = cx.clone();
|
||||
let ictx = ictx.clone();
|
||||
module.set_native_fn(
|
||||
"publish_durable",
|
||||
move |topic: &str, message: Dynamic| -> Result<(), Box<EvalAltResult>> {
|
||||
crate::sdk::emit_budget::charge_emission("pubsub::publish_durable")?;
|
||||
let json = message_to_json(&message)?;
|
||||
let svc = svc.clone();
|
||||
let cx = cx.clone();
|
||||
// §9.4 before-op interceptor (allow/deny + M4 data-transform).
|
||||
let write_val = super::interceptor::run_before(
|
||||
&ictx,
|
||||
&cx,
|
||||
"pubsub",
|
||||
"publish",
|
||||
topic,
|
||||
"",
|
||||
Some(&json),
|
||||
)?
|
||||
.unwrap_or(json);
|
||||
let written = write_val.clone();
|
||||
let svc2 = svc.clone();
|
||||
let cx2 = cx.clone();
|
||||
block_on("pubsub", async move {
|
||||
svc.publish_durable(&cx, topic, json).await
|
||||
})
|
||||
svc2.publish_durable(&cx2, topic, write_val).await
|
||||
})?;
|
||||
super::interceptor::run_after(
|
||||
&ictx,
|
||||
&cx,
|
||||
"pubsub",
|
||||
"publish",
|
||||
topic,
|
||||
"",
|
||||
Some(&written),
|
||||
Json::Null,
|
||||
)
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -112,9 +135,7 @@ pub struct GroupTopicHandle {
|
||||
namespace: String,
|
||||
service: Arc<dyn picloud_shared::GroupPubsubService>,
|
||||
cx: Arc<SdkCallCx>,
|
||||
// §9.4 M1 plumbing: carried so `publish` can run a before/after hook in a
|
||||
// later milestone (M11). Unused until then.
|
||||
#[allow(dead_code)]
|
||||
// §9.4: carried so shared `publish` runs the before/after hook too (M11).
|
||||
ictx: InterceptorCtx,
|
||||
}
|
||||
|
||||
@@ -125,16 +146,41 @@ fn shared_publish(
|
||||
) -> Result<(), Box<EvalAltResult>> {
|
||||
crate::sdk::emit_budget::charge_emission("pubsub::shared_topic")?;
|
||||
let json = message_to_json(&message)?;
|
||||
// §9.4 before-op interceptor also guards shared-topic publishes. The
|
||||
// collection is the namespace; the subtopic is the key.
|
||||
let write_val = super::interceptor::run_before(
|
||||
&h.ictx,
|
||||
&h.cx,
|
||||
"pubsub",
|
||||
"publish",
|
||||
&h.namespace,
|
||||
subtopic,
|
||||
Some(&json),
|
||||
)?
|
||||
.unwrap_or(json);
|
||||
let written = write_val.clone();
|
||||
let service = h.service.clone();
|
||||
let cx = h.cx.clone();
|
||||
let namespace = h.namespace.clone();
|
||||
let subtopic = subtopic.to_string();
|
||||
let subtopic_owned = subtopic.to_string();
|
||||
block_on("pubsub", async move {
|
||||
service.publish(&cx, &namespace, &subtopic, json).await
|
||||
service
|
||||
.publish(&cx, &namespace, &subtopic_owned, write_val)
|
||||
.await
|
||||
})
|
||||
// The fan-out count isn't surfaced to scripts (fire-and-forget, like the
|
||||
// per-app publish).
|
||||
.map(|_n: u32| ())
|
||||
.map(|_n: u32| ())?;
|
||||
super::interceptor::run_after(
|
||||
&h.ictx,
|
||||
&h.cx,
|
||||
"pubsub",
|
||||
"publish",
|
||||
&h.namespace,
|
||||
subtopic,
|
||||
Some(&written),
|
||||
Json::Null,
|
||||
)
|
||||
}
|
||||
|
||||
fn register_shared_publish(engine: &mut RhaiEngine) {
|
||||
|
||||
Reference in New Issue
Block a user