Skip to content

RFC: Hook + Plugin System for Guardian Gateway

Status: Foundation implemented; project-scoped bootstrap extension implemented by GAT-221 Last updated: 2026-07-10 Applies to: llm-gateway/service (runtime hooks) and control-plane/service (plugin management + snapshot activation)

This RFC defines a production-grade lifecycle hook and marketplace plugin system for llm-gateway/service.

The system supports:

  • first-party plugins (native Go)
  • curated marketplace custom plugins (WASM, runtime-loaded without gateway rebuild)
  • deterministic execution with strict safety defaults
  • immutable, signed, control-plane-managed snapshots as the only runtime policy source

Canonical lifecycle hook names are:

  • request.start
  • project.bootstrap
  • proxy.pre
  • proxy.post
  • request.end

project.bootstrap is a typed host stage, not a general-purpose hook: for a bound project candidate it runs only the mandatory premade builtin.project_membership entry from that project’s pinned snapshot. Ordinary project plugins remain unavailable until Gateway validates the typed bootstrap result and establishes authoritative project context. The other hooks retain the generic event-envelope contract.

Plugins may also perform bounded, read-only, host-mediated CID resolution when cid.resolve is granted by snapshot policy. This capability reads from the gateway blob store, reports request-reference and host-verification status as metadata, and is not general network access.

Plugins may also perform host-managed outbound HTTP(S) requests to policy-allowed destinations and opt-in full request body reads when granted by snapshot policy. Direct/raw runtime network access remains disallowed.

The deny response envelope remains unchanged (code, message, correlation_id).

The gateway/control-plane foundation covered by GAT-66 is now implemented:

  • deterministic premade + WASM hook execution in the gateway
  • control-plane-managed artifact, definition, snapshot, and activation APIs
  • direct object-storage-backed artifact loading with proxied control-plane uploads
  • first-party builtin.authorization as the baseline end-to-end authorization plugin
  • durable async request.end dispatch with retry, drop accounting, and DLQ persistence
  • persisted last-known-good snapshot metadata plus local artifact cache for cold-start recovery
  • queue and hook runtime metrics plus end-to-end WASM and representative plugin-chain test coverage

Remaining feature work for the hook/plugin platform now belongs to follow-on plugin tickets rather than the core foundation issue.

  1. Enable deterministic, pluggable lifecycle policy around proxied LLM requests.
  2. Run first-party and custom plugins in one ordered pipeline.
  3. Allow custom plugin deployment without gateway recompilation.
  4. Keep deny envelope compatibility and preserve verification guarantees.
  5. Persist plugin decisions/events for audit and operational visibility.
  6. Keep plugin-induced overhead within <=20ms p95 under representative load.
  7. Establish a generic event contract that can expand to non-proxy surfaces later.
  8. Allow bounded host-mediated CID resolution, outbound HTTP(S) access, and opt-in request body access without granting raw plugin runtime network egress.
  1. Hooking non-proxy endpoints (/health, registration, blob/state/history) in V1 runtime.
  2. Plugin mutation of request/response bodies or headers.
  3. Per-chunk streaming hook callbacks and chunk-time stream termination.
  4. Plugin-managed durable local state across requests.
  5. Request-time policy overlays outside active snapshot policy.
  6. Direct/raw plugin runtime network egress or arbitrary transport access.
  7. Multiple project policy scopes or switching project snapshots after bootstrap. Pre-verification request.start still uses the environment runtime because no safe candidate exists yet; after binding, the governed sequence pins exactly one project snapshot, or the environment default for projectless traffic.
  8. Remote sidecar/plugin RPC execution in V1.
  9. Full response body access in V1.

Current proxy path in llm-gateway/service is:

  1. Correlation ID assignment
  2. Route/provider recognition from the inbound gateway path
  3. Request body read/restore
  4. request.start
  5. Signature/profile/attestation/registry/replay verification
  6. Bind any project presentation to a non-authoritative candidate
  7. Pin the exact project snapshot and run project.bootstrap, or pin the environment-default snapshot for an explicitly projectless request
  8. Establish authoritative project context only from a validated typed bootstrap success
  9. Provider target resolution and finalized upstream metadata
  10. proxy.pre
  11. Upstream proxy dispatch
  12. proxy.post
  13. Audit capture plus async request.end enqueue from the same pin

Primary extensibility is now the snapshot-driven hook/plugin runtime. The legacy Authorizer interface remains only as compatibility/fallback plumbing; in the shipped gateway binary it is wired as a noop allow authorizer.

Compared to common gateway hook implementations, Guardian chooses:

  1. Runtime-doc contract parity as a hard requirement.
  2. Deterministic sequential execution with first-deny short-circuit.
  3. Snapshot-driven runtime plugin loading.
  4. Strict snapshot validation (supported_hooks, config schema, timeout limits, artifact integrity).
  5. No body/header mutation in V1.
  6. Signed control-plane snapshot as sole runtime policy source.
  7. Curated marketplace with signature-verified artifacts.
  8. Host-mediated privileged capabilities (cid.resolve, request.body.read, http.request) instead of raw runtime privileges.

In scope:

  • proxy lifecycle hooks in llm-gateway/service
  • first-party plugins in native Go
  • custom plugins as in-process WASM modules (wazero)
  • control-plane APIs/persistence for artifacts, definitions, snapshots, activation
  • gateway runtime sync and atomic active snapshot swap
  • shared host-managed capabilities for CID resolution, outbound HTTP(S) calls, and opt-in full request body reads
  • plugin-aware audit/metrics/logs

V1 lifecycle coverage:

  • request.start (pre-verify)
  • project.bootstrap (post-verify, pre-project-context, premade typed membership decision only)
  • proxy.pre (post-verify, pre-proxy authorization)
  • proxy.post once per response (non-stream and stream headers stage only)
  • request.end async, durable dispatch

Deferred to V2+:

  • per-chunk streaming callbacks
  • chunk-stage stream termination policy
  • side-effect sink dispatch beyond internal event persistence
  • remote sidecar / RPC plugin execution model
  • PluginManager: environment snapshot lifecycle plus bounded concurrency-safe compilation and immutable pinning for exact project snapshots, executor lifecycle, sync loop, artifact verification
  • HookEngine: stage orchestration and deterministic plugin execution
  • CapabilityBroker: host-mediated capability enforcement, outbound HTTP dispatch, secret injection, size/time budgets, and capability telemetry
  • PremadeExecutor: native Go plugin execution
  • WASMExecutor: wazero runtime execution for custom plugins
  • EndHookWorker: durable async queue, retry, DLQ, and persistence for request.end
  • artifact registration and publishing metadata
  • plugin definition CRUD
  • immutable snapshot creation
  • admin-only snapshot activation and current-snapshot reads
  • One environment-default active snapshot plus independently activated snapshots keyed by environment + project_urn.
  • Snapshot activation is manual and atomic for new resolutions; an in-flight request retains its pinned identity across every stage.

V1 foundation is anchored around builtin.authorization, which serves as the primary proxy authorization plugin and exercises the same snapshot, capability, audit, and activation contract as other plugins.

GAT-221 adds premade builtin.project_membership as the mandatory typed bootstrap policy for every project-scoped snapshot. It reuses the shared ProjectMembershipV1 validator and cannot be replaced by WASM or generic context writes.

Planned follow-on first-party plugins on the same shared model include:

  • OPA/Rego evaluation plugin
  • intent / VirusTotal plugin

Customer customization for these targets is configuration/policy driven, not customer-authored plugin code. The dedicated OPA/Rego builtin design is specified separately in rfc-hook-plugin-system-opa-rego-builtin.md.

Hooks are scoped per proxied request.

Execution order:

  1. route/provider recognition from the inbound gateway path
  2. request.start (pre-verify)
  3. core verify
  4. core binds an optional project candidate and pins one immutable snapshot
  5. project.bootstrap for a project candidate; projectless requests skip it
  6. Gateway validates the typed membership result and, on success, establishes protected project context
  7. provider target resolution and finalized upstream metadata
  8. proxy.pre (including builtin.authorization when configured in the pinned snapshot)
  9. proxy upstream
  10. proxy.post
  11. persist request + sync hook events
  12. request.end (async durable, from the same pin)

Stage behavior:

  • request.start:
    • input is untrusted request metadata and bounded request preview
    • for recognized LLM proxy routes, input also includes resolved provider, operation, gateway_path, upstream_path, and upstream_url when provider target resolution is already available
    • CID resolution, outbound HTTP(S), and full request body read are available only when granted by snapshot policy
    • data derived from request-supplied headers/body or external lookups remains untrusted unless later verified by host logic
    • may deny (enforcement mode)
  • project.bootstrap:
    • receives only the typed, core-bound candidate and locally available membership evidence
    • executes exactly one premade builtin.project_membership entry in enforcement host mode; its own config chooses policy observe or enforce
    • grants no host capabilities and performs no external network calls
    • only a Gateway-validated typed success may establish authoritative project context; generic context_writes are never membership authority
  • proxy.pre:
    • input includes verified identity (AgentID, SignerKeyID) plus resolved route/provider metadata
    • host capabilities remain available when granted; verified identity/attestation metadata may be combined with external lookups for policy
    • may deny
  • proxy.post:
    • may deny only before response commit
    • V1 execution points: non-stream once, stream headers stage once
    • input includes stream_requested and stream_observed; response preview is exposed only when streaming was not actually observed
    • no body/header mutation and no full response body capability in V1
    • host-managed HTTP(S) calls remain allowed when granted but must complete before response commit
  • request.end:
    • runs after response path completion
    • is observability-only; deny decisions are recorded for audit and rollout analysis but never affect the already-sent response
    • never blocks response completion
    • host-managed HTTP(S) calls remain allowed under durable async execution policy

Host capabilities are available in all lifecycle hooks only when granted by snapshot policy. ResolveCID is host-mediated, read-only, backed by the gateway blob store, and bounded by the cid.resolve grant. Request-reference and host-verification status are returned as metadata on CIDResolution; they are not the resolution authorization boundary. Outbound HTTP(S) and request body reads are separate capabilities. Plugins do not receive raw runtime network access.

The primary proxy authorization seam is now the snapshot-configured builtin.authorization first-party plugin running in proxy.pre.

The legacy Authorizer interface remains in the runtime only for compatibility/fallback/shadow plumbing. In the shipped gateway binary it is wired as a noop allow authorizer, so policy enforcement is expected to come from hooks when plugin runtime is enabled and an active snapshot configures builtin.authorization.

10. Plugin Execution Model and Failure Policy

Section titled “10. Plugin Execution Model and Failure Policy”

Execution model:

  1. Deterministic sequential execution by order_index ASC, then plugin_definition_id ASC.
  2. First deny from an enforcement plugin short-circuits remaining plugins in that hook.
  3. No parallel execution within a hook in V1.

Plugin modes:

  • enforcement: deny is actionable
  • observability: advisory/shadow mode for rollout safety and signal collection; deny is ignored by host, outcome recorded. It is also the only valid mode for request.end.

The project.bootstrap entry is always stored as host enforcement; membership rollout mode belongs inside the strictly validated builtin.project_membership config. This prevents a generic observability wrapper from converting malformed or unavailable bootstrap execution into authorization.

Timeouts and limits:

  1. Default per-plugin timeout (no outbound HTTP(S)): 10ms.
  2. Plugins granted http.request must declare explicit timeout in snapshot policy.
  3. Hard per-plugin timeout upper bound: 5s.
  4. Per-hook total execution budget is enforced in addition to per-plugin timeout.
  5. Max enabled entries per hook: default 16, configurable with hard clamp 64.
  6. Capability-specific request/response size limits are enforced by host capability policy.
  7. Snapshot activation fails if limits/validation rules are violated.

Failure policy:

  1. Enforcement explicit deny: deny with plugin-provided fields or defaults.
  2. Enforcement timeout/runtime/engine/capability error: fail-closed deny with code=plugin_execution_error and default status 503 unless snapshot policy overrides, reflecting temporary policy-evaluation unavailability rather than an explicit plugin deny.
  3. Observability timeout/runtime/engine/capability error: fail-open, request continues, event marked failed.
  4. Capability denials/policy violations are returned as host errors and follow the same mode-based failure semantics.
  5. request.end errors never block response completion.

Deny contract remains unchanged:

{ "code": "...", "message": "...", "correlation_id": "..." }

Default deny values when omitted:

  • status: 403
  • code: plugin_denied
  • message: request denied by plugin policy

Policy source:

  1. Active signed snapshot from control-plane is the only runtime policy source.
  2. No request-time policy overlay in V1.
  3. No local static fallback beyond last-known-good snapshot data.

Snapshot activation and bootstrap:

  1. Manual activation API is required.
  2. Activation is atomic for new requests.
  3. Gateway can start without an active snapshot, but operators should still activate a valid baseline signed snapshot before relying on hooks for enforcement.
  4. On update validation/load failure, gateway keeps last-known-good snapshot active.

Snapshot entry validation rules:

  1. hook must be in definition supported_hooks.
  2. config_json must validate against definition schema.
  3. Unknown config fields are rejected.
  4. timeout_ms must be in allowed bounds.
  5. Requested capabilities must be supported by plugin runtime and allowed by snapshot policy.
  6. cid.resolve capability permits bounded gateway blob-store reads when granted; request-reference status is returned as metadata.
  7. request.body.read capability must declare max readable bytes.
  8. http.request capability must declare allowed destination policy, method policy, secret refs, timeout, and request/response size limits.
  9. Entry filter operators must be supported (exact, prefix).
  10. Disabled entries are skipped at compile/activation time.
  11. request.end entries must use observability mode; enforcement is invalid for that async stage.
  12. A project-scoped snapshot must contain exactly one enabled premade builtin.project_membership entry at project.bootstrap, in host enforcement mode and with no host capabilities. Missing, disabled, duplicate, custom, or unmaterializable bootstrap entries invalidate creation, activation, and rollback.

Versioning and pinning:

  1. Snapshot entries pin exact plugin version.
  2. Snapshot entries pin artifact digest.
  3. Floating channels/ranges are not allowed in V1.
  4. One snapshot ID/version is pinned from project bootstrap through proxy.pre, proxy.post, audit attribution, and request.end. A bound project candidate resolves only its exact project activation with no environment fallback; projectless traffic pins the environment default and skips bootstrap.
type HookName string
const (
HookRequestStart HookName = "request.start"
HookProxyPre HookName = "proxy.pre"
HookProxyPost HookName = "proxy.post"
HookRequestEnd HookName = "request.end"
)
type HookEventEnvelope struct {
EventName HookName
CorrelationID string
TimestampUnixMs int64
// Core HTTP metadata (always present where available)
Method string
GatewayPath string
UpstreamPath string
Query string
GatewayURI string
UpstreamURL string
// Route metadata (proxy lifecycle)
Provider string
Operation string
// Identity metadata (when verified)
AgentID string
SignerKeyID string // verified RFC 9421 keyid; typically a did:key in V1
// Bounded previews and headers
RequestHeaders map[string][]string
ResponseHeaders map[string][]string
RequestPreview []byte // default max 16KB
ResponsePreview []byte // default max 16KB
ResponseStatus int
StreamRequested bool
StreamObserved bool
// Host-populated immutable context visible to later hooks
Context map[string]string
}
type PluginMode string
const (
ModeEnforcement PluginMode = "enforcement"
ModeObservability PluginMode = "observability"
)
type EmittedEvent struct {
Name string
Tags map[string]string
Body map[string]any
}
type HookDecision struct {
Allow bool
StatusCode int
Code string
Message string
Tags map[string]string
ContextWrites map[string]string
// V1 side effects are internal events only.
EmittedEvents []EmittedEvent
// Reserved for V2 streaming chunk semantics.
TerminateStream bool
}
  1. Host/plugin can append namespaced context keys.
  2. Context is immutable after write for the remainder of request lifecycle.
  3. Later hooks can read context; they cannot mutate existing keys.

Plugins may call bounded host capabilities in addition to consuming HookEventEnvelope.

V1 host capabilities:

type ResolveCIDOptions struct {
MaxBytes int
}
type CIDResolution struct {
CID string
Found bool
Bytes []byte
SizeBytes int64 // underlying blob size when known
Truncated bool
Source string // blob_store
RequestReferenced bool
VerifiedByHost bool
}
type ReadRequestBodyOptions struct {
MaxBytes int
}
type RequestBodyRead struct {
Bytes []byte
Truncated bool
}
type OutboundHTTPRequest struct {
URL string
Method string
Headers map[string][]string
Body []byte
}
type OutboundHTTPResponse struct {
StatusCode int
Headers map[string][]string
Body []byte
Truncated bool
}
type HostCapabilities interface {
ResolveCID(ctx context.Context, cid string, opts ResolveCIDOptions) (CIDResolution, error)
ReadRequestBody(ctx context.Context, opts ReadRequestBodyOptions) (RequestBodyRead, error)
DoHTTPRequest(ctx context.Context, req OutboundHTTPRequest) (OutboundHTTPResponse, error)
}

Capability rules:

  1. All capabilities are host-mediated and explicitly granted per snapshot entry.
  2. ResolveCID is read-only, host-mediated, and backed by the gateway blob store.
  3. request_referenced means the CID was present in request headers or otherwise added by host verification/attestation handling for the current request context. It is metadata, not an allowlist.
  4. Resolved blob contents may identify additional CIDs; plugins may resolve those CIDs when they have the cid.resolve grant and the blobs are available in the gateway blob store.
  5. request.start CID resolutions are still untrusted request-derived data unless later marked VerifiedByHost.
  6. ReadRequestBody is opt-in full request-body access bounded by request.body.read policy; full response body access is not available in V1.
  7. A plugin may send request body data outbound only if both request.body.read and http.request are granted.
  8. DoHTTPRequest supports HTTP(S) only and is gated by http.request capability policy.
  9. Host validates destination and method policy, injects referenced auth/secrets at dispatch time, and enforces timeout, request-size, response-size, and concurrency limits.
  10. DoHTTPRequest is not raw runtime network access; native and WASM plugins use the same capability policy model.
  11. Capability denials or policy violations are returned as host errors and use normal mode-based failure semantics.

Each snapshot entry can filter by:

  • provider
  • operation
  • method
  • gateway_path
  • upstream_path
  • agent_id

The contract intentionally does not overload a single generic path; inbound gateway routing and provider-relative upstream routing are exposed separately.

Matching rules:

  1. All provided filters are ANDed.
  2. Omitted filter is wildcard.
  3. Supported operators in V1: exact, prefix.
  4. Regex operators are not supported in V1.
  5. Non-match silently skips entry.

Runtime model:

  • engine: wazero
  • target: WASI-compatible wasm32
  • SDK baseline: Rust-first
  • ABI strategy in V1: JSON ABI

Guest module must export:

  • guardian_plugin_handle_v1

Contract:

  • input: UTF-8 JSON bytes of HookEventEnvelope
  • output: UTF-8 JSON bytes of HookDecision

Host capability imports use module guardian_host_v1 with V1 functions:

  • guardian_host_resolve_cid_v1
  • guardian_host_read_request_body_v1
  • guardian_host_http_request_v1

Each import accepts (request_ptr uint32, request_len uint32) and returns a packed (response_ptr,response_len) uint64. Requests and responses are UTF-8 JSON bytes. Responses use { "result": ..., "error": "..." }.

Sandbox policy by default:

  • no filesystem access
  • no direct outbound network access
  • no process spawning
  • no host env access
  • privileged capabilities exposed only through host imports/ABI calls
  • host-mediated capabilities only when explicitly granted by runtime policy (for example cid.resolve, request.body.read, http.request)

Marketplace model in V1:

  • curated signed marketplace
  • first-party and approved curated plugin entries only
  • custom plugins may use the same host-managed capability model as first-party plugins when granted by snapshot policy

Artifact trust policy:

  1. Plugin artifacts must be signed.
  2. Trust anchor is platform release signing keys only.
  3. Gateway verifies digest + signature before loading.
  4. Artifact objects are immutable and content-addressed by SHA-256-backed object keys.

Artifact storage model:

  • dedicated artifact storage plus metadata table
  • object storage bytes are separate from Postgres metadata
  • control-plane proxies uploads and writes immutable object references into metadata
  • gateway reads objects directly from storage with service credentials and re-verifies digest/signature before activation/load
  • not reused from gateway_blobs request-state storage path

Secrets model:

  1. Snapshot config uses secret references only.
  2. Raw secret values are not stored in plugin config JSON.
  3. Outbound HTTP(S) auth is injected by host from secret references at dispatch time.
  4. Plugins do not self-fetch secrets or CID data over network in V1.

15. Control-plane API Surface and RBAC (V1)

Section titled “15. Control-plane API Surface and RBAC (V1)”

Artifact APIs:

  1. POST /v1/plugins/artifacts/upload-url
  2. POST /v1/plugins/artifacts
  3. GET /v1/plugins/artifacts/{id}

POST /v1/plugins/artifacts/upload-url is a compatibility helper that returns control-plane upload metadata. In V1 implementation, the actual artifact bytes are streamed through control-plane ingress rather than uploaded via provider-specific signed URLs from the client.

Definition APIs:

  1. POST /v1/plugins/definitions
  2. PATCH /v1/plugins/definitions/{id}
  3. GET /v1/plugins/definitions
  4. GET /v1/plugins/definitions/{id}

Snapshot APIs:

  1. POST /v1/plugin-snapshots
  2. GET /v1/plugin-snapshots
  3. GET /v1/plugin-snapshots/{id}
  4. POST /v1/plugin-snapshots/{id}/activate
  5. GET /v1/plugin-snapshots/current

RBAC policy:

  • create/update/activate snapshot and plugin definitions: platform admins only
  • read status/events: broader read-only roles allowed

Core entities:

  • plugin_definitions
  • plugin_artifacts
  • plugin_snapshots
  • plugin_snapshot_entries
  • gateway_plugin_hook_events
  • gateway_plugin_async_events
  • gateway_plugin_end_dlq

Key fields (selected):

plugin_definitions:

  • plugin_definition_id
  • plugin_id
  • runtime (premade or wasm)
  • supported_hooks_json
  • default_mode
  • config_schema_json
  • required_capabilities_json
  • artifact_id (nullable for premade)
  • status
  • created_at

plugin_artifacts:

  • artifact_id
  • version
  • sha256
  • signature
  • signer_key_id
  • abi_version
  • download_url_template
  • size_bytes
  • status
  • created_at

plugin_snapshots:

  • snapshot_id
  • environment
  • version
  • manifest_json
  • manifest_signature
  • status
  • created_by
  • created_at

plugin_snapshot_entries:

  • snapshot_id
  • hook
  • order_index
  • plugin_definition_id
  • plugin_version
  • artifact_sha256
  • plugin_mode
  • timeout_ms
  • granted_capabilities_json
  • match_json
  • config_json
  • enabled

granted_capabilities_json may encode per-entry capability policy, for example:

  • cid.resolve: max returned bytes for gateway blob-store CID reads
  • request.body.read: max readable bytes
  • http.request: allowed URL prefixes or destination aliases, allowed methods, timeout_ms, max_request_bytes, max_response_bytes, max_inflight

gateway_plugin_hook_events:

  • event_id
  • correlation_id
  • snapshot_version
  • hook
  • plugin_id
  • mode
  • outcome
  • latency_ms
  • error_code
  • decision_code
  • created_at

gateway_plugin_async_events:

  • event_id
  • correlation_id
  • snapshot_version
  • plugin_id
  • hook (request.end)
  • outcome
  • attempt
  • latency_ms
  • created_at

gateway_plugin_end_dlq:

  • dlq_id
  • correlation_id
  • snapshot_version
  • plugin_id
  • hook
  • failure_reason
  • attempts
  • first_failed_at
  • last_failed_at

Audit linkage (gateway_audit_exchanges additions):

  • plugin_snapshot_version
  • plugin_denied_by
  • plugin_decision_code
  • plugin_summary_json

Retention policy:

  • detailed plugin event tables follow audit retention window by default
  1. Default poll interval: 15s.
  2. Poll current snapshot metadata with version/ETag checks.
  3. Resolve artifact refs from snapshot.
  4. Open artifact bytes directly from configured object storage using immutable provider/bucket/key refs.
  5. Verify digest and signature against platform release keys.
  6. Compile/load executors.
  7. Persist verified last-known-good snapshot metadata to disk and retain local artifact cache.
  8. Atomically swap active snapshot pointer for new requests.
  9. Keep previous snapshot active on failure.
  10. Continue on last-known-good snapshot during control-plane/data outages, including cold start when control-plane is unavailable.
  1. Strict custom-plugin sandbox.
  2. Artifact digest and signature verification is mandatory.
  3. Platform release signing keys are the only trusted signing authority in V1.
  4. Immutable snapshots prevent in-place policy drift.
  5. Strict schema validation with unknown-field rejection prevents silent config drift.
  6. Secret references only in plugin config (no plaintext secrets in snapshot config).
  7. Host-mediated CID resolution is read-only, grant-gated, bounded by max bytes, and backed by the gateway blob store.
  8. Host-managed outbound HTTP(S) is policy-gated to snapshot-approved destinations/methods and uses secret references for auth injection.
  9. Full request body access is opt-in and bounded by capability policy; full response body access is not available in V1.
  10. Bounded previews (16KB), CID byte limits, outbound size limits, and strict timeouts reduce abuse surface.
  11. Snapshot signature verification is required before activation.
  12. No direct/raw plugin runtime network egress in V1.
  13. Native first-party plugins are expected to use host capability APIs rather than ad hoc direct HTTP clients.

19. Observability, Audit, and Side Effects

Section titled “19. Observability, Audit, and Side Effects”

Metrics:

  • invocation count by hook/plugin/outcome
  • timeout/error/deny counters by mode
  • hook and plugin latency histograms
  • CID resolve count, latency, failure, truncation, not-found, request-referenced, and verified-by-host counters
  • outbound HTTP(S) call count, latency, status, destination alias, timeout, and capability-denied counters
  • request body read count and truncation counters
  • active snapshot version gauge
  • snapshot load/activation failure counters
  • request.end queue depth, drops, retries, DLQ count

Logs:

  • structured logs with correlation ID, snapshot version, hook, plugin, outcome, latency
  • structured CID resolution logs with correlation ID, hook, plugin, CID, result, and verification/truncation metadata
  • structured outbound HTTP(S) logs with correlation ID, hook, plugin, destination, method, status, latency, and policy outcome
  • warning logs for fail-open observability failures
  • error logs for fail-closed enforcement failures and snapshot load failures

Audit:

  • sync hook events persisted and linked to request correlation ID
  • async request.end events persisted and linked to correlation ID
  • request audit row includes plugin summary attribution and capability usage summary

Side effects:

  • V1 plugins may emit structured events
  • emitted events are persisted/observable internally only
  • host-managed outbound HTTP(S) for plugin evaluation is allowed separately from emitted-event sink dispatch
  • no outbound webhook/queue/SIEM dispatch for emitted events in V1
  1. Enforcement plugin timeout/runtime/engine errors fail closed.
  2. Observability timeout/runtime errors fail open.
  3. Invalid snapshot/artifact cannot be activated.
  4. Atomic swap ensures per-request snapshot consistency.
  5. request.end uses durable queueing with bounded retries and DLQ.
  6. Queue overflow uses drop-and-count and never blocks response completion.
  7. request.end failures never block response completion.
  1. Core in-process plugin overhead target, excluding external service latency: <=20ms p95.
  2. Plugins using http.request must fit within declared timeout budget.
  3. Default per-plugin timeout without outbound HTTP(S): 10ms.
  4. Per-hook total execution budget enforced.
  5. Default preview cap: 16KB.
  6. Deterministic sequential execution retained for debuggability and correctness.

Core correctness:

  1. deterministic ordering and filter matching (exact, prefix)
  2. first-deny short-circuit
  3. enforcement fail-closed
  4. observability fail-open
  5. deny envelope compatibility

Contract and validation:

  1. canonical hook name validation
  2. strict config schema enforcement with unknown-field rejection
  3. timeout bounds and hook-budget validation
  4. capability policy validation (cid.resolve, request.body.read, http.request)
  5. ABI compatibility checks
  6. digest/signature verification pass/fail
  7. blocked raw runtime access checks (fs/network/env/process/direct network)
  8. CID resolution grant enforcement, blob-store lookup behavior, and metadata reporting
  9. CID resolution bounds (max bytes, plugin timeout budget, truncation)
  10. request body grant/truncation enforcement
  11. outbound HTTP(S) destination/method/auth-ref/timeout/size enforcement

Lifecycle coverage (V1):

  1. request.start pre-verify deny
  2. project.bootstrap typed success, policy deny, invalid result, missing/unmaterializable plugin, and no-network guarantees
  3. project snapshot selection without environment downgrade plus projectless environment-default behavior
  4. one project pin across project.bootstrap, proxy.pre, proxy.post, audit, and request.end, including concurrent activation
  5. proxy.pre post-verify deny
  6. non-stream proxy.post deny pre-commit
  7. stream headers-stage proxy.post deny pre-commit
  8. async request.end dispatch, retry, DLQ, and non-blocking guarantees
  9. CID resolution succeeds for granted plugins across supported hooks when blobs exist in the gateway blob store
  10. request.start CID resolution remains untrusted until marked VerifiedByHost
  11. outbound HTTP(S) succeeds to allowed destinations in sync and async hooks
  12. full request body access is available only to explicitly granted plugins

Authorizer migration:

  1. shadow comparison parity checks
  2. mismatch telemetry correctness
  3. controlled cutover validation

Sync and resilience:

  1. baseline snapshot startup gating
  2. snapshot immutability and activation semantics
  3. atomic snapshot swap under load
  4. invalid update rejection with last-known-good fallback
  5. cold start from persisted cache

Performance:

  1. p95 overhead validation under representative plugin chains
  2. long-running stream behavior with headers-stage proxy.post
  3. async queue backpressure/drop/retry accounting
  4. machine-readable performance report output suitable for dedicated CI/perf jobs
  1. Applicable hooks execute for every proxied request in defined order with canonical names; project.bootstrap runs only for a bound project candidate.
  2. Premade and custom plugins run in one deterministic sequential pipeline.
  3. Current Authorizer logic runs as first-party plugin after migration cutover.
  4. OPA/Rego, intent/VirusTotal, and authorized registry plugins can all be implemented as first-party plugins on the shared hook/capability model.
  5. Custom plugins deploy without gateway rebuild.
  6. Existing deny envelope remains unchanged.
  7. V1 streaming behavior supports headers-stage proxy.post policy decisions only.
  8. Snapshot updates are signature-verified and atomically activated.
  9. Baseline snapshot requirement and last-known-good fallback both work.
  10. Host-managed outbound HTTP(S) and full request body access are policy-gated and observable.
  11. Plugin execution details are visible in metrics/logs/audit data.
  12. Async request.end durability, retry, DLQ, and drop accounting are observable.
  13. P95 core in-process plugin overhead meets target under acceptance load.
  1. Per-chunk streaming callbacks and chunk-stage termination semantics.
  2. Optional plugin SDK expansion beyond Rust baseline.
  3. Extended hook coverage outside proxy lifecycle.
  4. First planned expansion surface: gateway operational endpoint lifecycle hooks (/v1/agents/registrations, blobs, state/history endpoints).
  5. Optional outbound side-effect sink integrations (webhook/queue/SIEM).
  6. WIT/component-model ABI exploration.
  7. Remote sidecar / RPC plugin execution model exploration.
  1. Proxy lifecycle only in V1 runtime scope.
  2. One environment-default activation plus at most one active snapshot per environment + project_urn; after project binding, each request pins exactly one resolved snapshot for the remaining governed lifecycle.
  3. Curated signed marketplace.
  4. Platform release signing keys as artifact trust anchors.
  5. wazero runtime and JSON ABI.
  6. Stateless per-request plugin execution.
  7. Proxied control-plane artifact uploads plus direct gateway object-store reads.
  8. No request/response body or header mutation in V1.
  9. No direct/raw plugin runtime network egress in V1.
  10. Outbound plugin network access is host-managed and HTTP(S)-only in V1.
  11. Full request body access is opt-in per plugin entry.
  12. Full response body access is deferred.
  13. Secret references only for sensitive config.
  14. Strict schema validation with unknown-field rejection.
  15. Side effects are events-only in V1.
  16. Detailed plugin event retention follows audit retention by default.
  17. CID resolution is host-mediated, read-only, grant-gated, and backed by the gateway blob store in V1.