Repository navigation
v0.15.0: harness hooks, approved HTTP requests, scheduled anchors, Slack relay, trip, definition files, budget endpoint, shadow policy, call caps - #19
Merged
Conversation
- The tool gate binds a call's identity once and reads it from the request. - One strict JSON body reader serves every gate endpoint. - One way to spend a permit: unused state store entry points are gone. - Config.Pipeline(name) answers which pipeline a name refers to. - One config reader serves the subcommands. - One run-history query in the state store. - gate test reads and runs its cases in its own functions beside the compare. - The startup checks return their verdict with validate.Summarize. CLI help, config keys, stored formats and receipts are unchanged.
draftcat gate hook runs as the pre-tool hook of Claude Code, Codex, GitHub Copilot CLI or Gemini CLI. It reads the harness payload, asks POST /gate/tool-call with a stable action_id, waits and polls for a human decision, consumes the permit and answers allow or deny in the harness's own format. Anything short of permit: execute blocks the call. draftcat gate install writes that hook into the harness settings file (project or --user), keeping other keys and hooks, idempotent, with --remove, --dry-run and a list of the tool names the matcher sends.
…t to the operator channel when the history changed
…r's tool caps, behind the gate's tokens
contrib/slack-relay is a stdlib-only hitl/v0 relay in its own module. It verifies draftcat's signed dispatch (signature window, protocol, payload hash against the draft, callback, expiry), posts the draft to a Slack channel as Block Kit with a button per offered decision, verifies Slack's v0 request signature on every click, maps the Slack user to an operator identity, and posts one signed decision back. The message is updated with the outcome and loses its buttons; a later click changes nothing. A test runs `draftcat hitl verify` against it end to end. `channel: slack` (and `channel: teams`) stay validation errors, now pointing at `channel: relay` and the relay in contrib/.
The conformance suite listened in the background and dispatched at once, so a relay that answered within milliseconds could find no listener, and a callback port already in use left the suite waiting out its whole timeout. It now binds first and fails at once if it cannot.
A tool rule can pin its definition to a file holding the approved definition instead of to its hash. The pin is the file's canonical hash, so a rule moved from definition_sha256 to a file with the same content decides the same and keeps its policy hash. When a call's definition differs, the approval prompt and the denial reason name the changed fields and inputSchema paths, bounded and without echoing long text. The file is read at start, by validate and the offline checks, and again on every reload; a missing or unparsable file is refused. New command draftcat gate diff-definition compares two definitions the same way.
…tial draftcat holds The approval step before it shows the request itself and binds its approval to a digest of exactly that request; the action sends only a matching request, once, with an Idempotency-Key, no retry and no redirects. The credential comes from auth_env and joins the redaction set. Validation requires an approval step before each request.
…inutes Calls are counted at session_start and their minutes recorded at session_end, once per session id, in the budget ledger the other daily caps use. At a cap, pre_call_context and session_start answer 429 and dograh_staging_smoke is refused, with one operator notice per cap and UTC day. With caps set, an unreadable ledger refuses the call; without caps nothing is refused. /status and budget status show the recorded usage against the caps.
tool_gate.trip counts, per agent, the denials the gate makes (allowlist, caller tools, deny rules, daily caps, the repeat guard, operator refusals) and freezes the agent, or with scope: gate the whole gate, once it reaches the threshold inside the window. Frozen-gate and frozen-agent denials, timeouts and unavailable stores are not counted. The agent freeze is stored in the new gate_agent_freeze table, survives a restart, refuses the agent's consumes and grants, is listed by /status, gate status and the digest, and is lifted with /unfreeze <agent> or gate unfreeze --agent. One operator notice per trip.
tool_gate.shadow names a candidate config. After each live decision the candidate's tool_gate decides the same call in the background, as gate check does, with today's totals read from the live store; it never changes, delays or fails the live decision, and failures and panics are logged and counted. A differing verdict stores one row in the new gate_shadow_diffs table (time, agent, tool, args hash, value-free live and shadow verdicts, candidate hash), kept 30 days. draftcat gate shadow lists the differences grouped by tool and decision pair. The candidate is re-read on reload and a broken one switches the shadow off without holding up the reload. /metrics counts differences and errors.
A candidate rule pinned by definition_file now decides on that file, as a live rule does, and a candidate whose file cannot be read is refused.
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.
What's new
draftcat gate hook --format claude|codex|copilot|geminiruns as the pre-tool hook of Claude Code, Codex, GitHub Copilot CLI or Gemini CLI: it asks the gate about each call, waits for the decision, consumes the permit and answers allow or deny in the harness's format. A gate that is down, refuses the token, does not decide in time, denies or refuses the consume blocks the call.draftcat gate install <harness>writes the hook into the harness's settings file, keeps every other key and hook, and--removetakes out only draftcat's entry.http_requestaction. Sends one HTTP request after an approval step, with a credential read fromauth_envthat the agent never holds. The approval step shows the request itself and is bound to exactly that request; it is sent once with anIdempotency-Key, no retry, no redirects.receipts.anchor: {every | at, timezone, file, notify}appends the anchor to a file and sends it to the operator channel when the history changed.receipts verify-anchor --log anchors.jsonlnames the first changed receipt.contrib/slack-relay, a stdlib-onlyhitl/v0relay with Approve, Skip and Adjust buttons that verifies Slack's request signature on every click and maps Slack users to operator identities.tool_gate.trip: {denials, within, scope}freezes an agent the gate keeps refusing (or the whole gate), tells the operator once, and is lifted with/unfreeze <agent>orgate unfreeze --agent.definition_fileon a tool rule pins the approved definition itself; a changed definition is named field by field in the prompt and the denial reason, andgate diff-definitionprints the same summary.GET /gate/budget. Today's usage against the daily and per-pipeline caps, the frozen flag, and the caller's own capped tools, behind the gate's tokens, read-only.tool_gate.shadow: {config}decides every live call a second time under a candidate policy without touching the live decision, stores each difference by argument hash, andgate shadowgroups them.budgets.per_day_callsandper_day_call_minutesnow refuse new calls (HTTP 429) and are recorded durably.draftcat hitl verifybinds its callback listener before dispatching.Verification
gofmt,go build ./...,go vet ./...andgo vet -tags voice ./...pass.go test -short ./...,go test -short -tags voice ./...and the full non-short suite pass; each feature carries its own tests (about 5,900 added test lines).go run . validatereports 0 errors.golangci-lint run --new-from-revon master reports 0 issues, also with--build-tags voice.contrib/teams-relayandcontrib/slack-relaypass gofmt, vet and their tests. The Slack relay passesdraftcat hitl verify12/12 in an end-to-end test against a fake Slack API.npm ci --ignore-scripts && npm test && npm pack --dry-runpass (20 installer tests).Review notes
c757b57(master, v0.14.0). Version 0.14.0 → 0.15.0 inversion.go,package.jsonand bothpackage-lock.jsonentries.definition_file), then the release commit.receipt_anchor_state,gate_agent_freeze,gate_shadow_diffs,budget_voice_calls, plus an index ontool_actions(tool, created_at).http_requestthe approval message is the request itself; voice builds record calls whether or not caps are set;/unfreeze <agent>lifts one agent's freeze.Evidence
voice, short and full tests, validate, lint on new code, npm) passes on the final tree, and passed on each feature commit on its own before they were combined.draftcat hitl verify, 12 of 12) is an automated test incontrib/slack-relay.definition_sha256rule with the same content (TestToolGateHashesAreStable).Door
Two-way. Every feature is opt-in behind a new setting, command or action, and a configuration without them behaves as v0.14.0; the new tables are additive and a downgrade ignores them. The release is undone by not tagging it, or by a follow-up revert.
Blast radius
tool_gate.go,tool_policy.go, freeze, shadow), config and validation, the state store (additive tables), the voice receivers (call caps), the deterministic action switch (http_request), the receipts commands,hitl verify, docs andcontrib/.definition_filecontent and the shadow candidate.