feat(proxy): sideband governance API for hook-based adapters (#12)#62
Merged
Conversation
…date (#12) Primitives the sideband governance path builds on: - RateLimiter.record() / SpendLimiter.record() unconditionally commit a call against the window (even over the limit) for the deferred-consumption model: /evaluate peeks, /audit records once the call has run. Warnings fire only while within the limit, matching check(). Spend record() throws on a negative/non-finite amount (those are rejected at /evaluate; reaching record() is a logic bug). - ToolAnnotationCache.updateSingle() merges one tool definition incrementally without rebuilding the cache-wide present/current maps, so per-origin adapter caches can baseline tools one /evaluate at a time without degrading log-mode drift evaluation. - Extract canonicalize()/sortKeysDeep() into util/canonical-json for reuse by the audit idempotency hash.
Move the decision half of GovernedForwarder.handleToolsCall (rule evaluation, drift gate, flag_destructive escalation, evidence/dependency grounding, stricter-of-both log mode, dry-run determination) into policy/decision-pipeline as a pure decide() that never touches limiter state. The forwarder now calls decide() and applies the limiter step itself, exactly as before. Behavior-preserving: MCP enforcement is bit-identical and the existing proxy test suite passes unmodified. This lets the sideband governance path (#12) reuse one decision engine instead of forking security-critical orchestration.
Shared non-MCP-origin record shape for #12/#13/#16: - record_kind discriminator (tool_call | drift_event | install_scan | evaluation_expired), origin (mcp or an adapter origin string), and a metadata JSON column (reserved keys channel_id/sender_id/sender_name/conversation_id). - Columns join REQUIRED_AUDIT_COLUMNS (clean-break migration, consistent with prior schema additions) plus record_kind/origin indexes and query filters. - GovernedForwarder tags its writes origin:'mcp' with the appropriate record_kind (tool_call / drift_event).
Adapter-owned approvals where the framework runs its own approval UI: - ApprovalRouter.createNativeTicket()/resolveNativeTicket() create and resolve a queue ticket (channel_name native:<origin>) and fire the onSubmit/onResolve SSE callbacks, but hold no promise, start no timeout/escalation timers, and notify no channel (the adapter notifies). resolveNativeTicket refuses router-managed tickets so a held MCP request can never be left dangling. - Dashboard approve/deny/break-glass reject native tickets with 409 native_ticket (an operator decision cannot propagate to the adapter's UI). - New first-class 'cancelled' ApprovalStatus (proxy enum, queue.resolve, APPROVAL_STATUSES, dashboard badge) instead of overloading client_disconnected.
Four experimental endpoints on the SDK sideband so hook-based frameworks (OpenClaw first) can drive the policy engine without an MCP transport: - POST /evaluate — decide a tool call, side-effect-free on counters; carries an optional tool definition for per-origin drift baselining - POST /audit — record the outcome, consuming counters; idempotent on evaluation_id via canonical-payload tombstones - POST /install-scan — observational until install-time rules (#13) - POST /approval/:id/resolve — record a natively-handled approval GovernanceService owns the pending-evaluation registry (TTL + memory budgets), per-origin drift caches, the D5 finalize/response matrix, and on-access deadline enforcement on both /audit and resolve. Fail-closed wiring: construction and hot-reload throw GovernanceConfigError if an approval-capable policy has no ApprovalRouter. Mounted on the SDK sideband with a scoped HELIO_ADAPTER_TOKEN (distinct from HELIO_SDK_TOKEN) and a 1 MiB body limit; the CLI reuses the MCP limiters/queue/router/audit-writer so the budget is shared across both paths. Adds sdk.evaluation_ttl config and an optional id arg to AuditWriter push.
- New docs/adapter-api.md: the four endpoints, the enforcement-grade ladder (structural / network / host-enforced), the normative fail-closed adapter requirement, scoped tokens, idempotency, the crash-TTL/TOCTOU caveats, and the experimental label. - README gains the enforcement-grade ladder so the structural promise is scoped to where it physically holds; configuration.md documents sdk.evaluation_ttl and the dual SDK/adapter tokens; audit.md documents record_kind/origin/ metadata; sideband-api.md disambiguates the dashboard sideband from the SDK sideband.
04d4953 to
592f2dd
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Implements #12 — the first step of the OpenClaw adapter runway. Adds four experimental endpoints on the SDK sideband (
127.0.0.1:3200) so hook-based agent frameworks (OpenClaw first, future adapters next) can drive Helio's policy enginewithout an MCP transport to interpose on. Helio acts as the policy decision point; the framework's hook is the enforcement point.
POST /evaluate— decide a tool call; side-effect-free on rate/spend counters (peek, not consume). Optionally carries the tool definition so adapter-origin tools get the same Tool definition drift undetected - annotation/schema cache replaces wholesale, no baseline-and-diff (MCP rug-pull) #25 drift guard as MCP tools, via per-origin baselines.POST /audit— record the outcome; consumes counters. Idempotent onevaluation_idvia canonical-payload tombstones (safe network retries).POST /install-scan— observational until install-time rules ship (Policy engine: context-aware primitives #13); the request/response contract is final so the adapter and dashboard can build now.POST /approval/:id/resolve— record a natively-handled approval.Supporting changes:
GovernedForwarderinto a shared, side-effect-freedecide(); the MCP path is bit-identical (existing suite passes unmodified).record_kind,origin,metadatacolumns (clean-break migration) + indexes.cancelledstatus; dashboard approve/deny refuse native tickets.HELIO_ADAPTER_TOKEN(distinct fromHELIO_SDK_TOKEN), 1 MiB sideband body limit, per-origin/pending memory budgets, on-access deadline enforcement, and fail-closed wiring (GovernanceConfigErrorwhen an approval-capable policy has noApprovalRouter).Enforcement-grade note: the hook path is documented as
host-enforced— a cooperative tier, deliberately not marketed as proxy-grade. The structural guarantee is unchanged where it physically holds (MCP path).Follow-ups (not in this PR): policy primitives #13, dashboard rendering #16, the adapter itself #11.
Closes #12
Type of Change
Packages Affected
packages/proxypackages/dashboardpackages/python-sdkdocs/examples/Checklist
anytypes or@ts-ignorewithout justificationpnpm secrets:scan,pnpm docs:check:ci,pnpm audit --audit-level=high,pnpm build,pnpm lint,pnpm format:check,pnpm typecheck,pnpm test)feat:,fix:,docs:)How to Test
sdk.enabled: trueinhelio.yaml), runnpx @gethelio/proxy start, and note the printedHELIO_ADAPTER_TOKEN.POST /evaluateto127.0.0.1:3200with{ "origin": "openclaw", "tool": { "name": "send" }, "arguments": {} }andAuthorization: Bearer <adapter token>; confirm adecision+evaluation_idcome back.evaluation_id,POST /audit { "evaluation_id": "...", "status": "success" }; confirm201, then replay the identical payload and confirm200 { already_finalized: true }(idempotent, no double-count)./evaluaterejects theHELIO_SDK_TOKENwith401, and/evidencerejects theHELIO_ADAPTER_TOKENwith401.rate_limitrule, consume a slot via the sideband (/evaluate+/audit) and confirm a subsequent MCPtools/callsees the reduced budget.pnpm --filter @gethelio/proxy test— full suite (1513 tests) passes, includingdecision-pipeline,governance-service,governance-api, andsideband-shared-limiter.Additional Context
Endpoints ship labeled experimental until a second adapter validates the contract's neutrality. Full reference in
docs/adapter-api.md.