You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Decision (David, 2026-09-08, revised the same day)
Local mode becomes an offline floor and an on-ramp, in two phases. Phase 1 is purely additive and ships now. Phase 2 is the only irreversible piece (removing the local policy engine) and is gated on data the phase-1 ledger produces. Rationale for the split: local never phones home by design, so today we cannot tell whether it has zero users or a thousand; the 61% install-to-first-call gap could be abandoned installs or silent, happy local-mode users. The ledger is the instrument that answers that. Do not remove anything until it has.
Phase 1 — additive (now)
Shared generator; every harness plugin inherits, per the coverage doctrine.
Offline floor. Gateway unreachable or timed out → apply the two floors locally (hardline deny; destructive ask) → otherwise allow with a loud one-line warning. Never a silent allow, never a hang (timeout shape per gsc #690). Port shellScan + sqlPayload + detectDestructive from gsc PR #1109 into govern.mjs, same fixtures. Gateway stays the authority; the local copy is the floor.
Local policy engine stays, frozen. Existing local rules keep working. The ported scanner is applied to local policy evaluation too, so the compound-bypass bug (Manifest version 0.11.0 — the bump #17 announced but never made #18) is fixed in place, not by deletion. No new local-policy features.
Local ledger. Append-only JSONL under the ACP config dir: ts, tool, resolvedTool, floor verdict, local-policy verdict if any, input preview (bounded), session, cwd, harness, hook version. Written whenever the gateway did not see the call: no key (on-ramp) or key-but-unreachable (outage). Size-capped, oldest-first eviction, never blocks the hook.
Flush. On the first successful gateway call after a gap, or on key connect, POST buffered rows in batches to the ingest endpoint (gsc #1115) with an idempotency key per row; truncate on ack; backoff on failure; never on the hook's critical path (spawn-and-forget after the response is written).
Consent. Nothing leaves the machine without a key. The connect flow states that buffered history will upload once and shows the row count.
Real-sandbox install test (standing rule) for every emitted harness plugin: gateway down, no key, key connect.
Acceptance: gateway down → destructive command asks locally with the floor label, benign command allows with a warning, both in the ledger. No key → same, ledger grows, network capture shows nothing leaving. Connect → rows upload once, console shows them flagged offline-buffer with original timestamps, local file truncated.
Phase 2 — remove the local policy engine (gated)
Not before the ledger has run for a full cycle and produced flush data. Decision rule, written now so it is not relitigated in the moment:
Measure, over the first N weeks of flushes (David sets N and the threshold when the first numbers land): the share of connecting installs whose buffered rows show local-policy verdicts, i.e. installs that actually used local rules.
Above the threshold → keep it as the on-ramp's policy layer and accept the drift cost; the shared scanner is already ported so drift is bounded to rules, not to laundering.
Either outcome is fine. What is not fine is deciding it with no data, which is where we were on 2026-09-08 morning.
Not in scope
Model-call spend cannot be buffered by the hook (hooks never see tokens); that stays with the claude-acp / codex-acp proxy wrappers. Sub-tenancy and the approvals API are gsc #1096 / #1094. The install funnel stays cloud-default; the first screen still never shows --local.
Decision (David, 2026-09-08, revised the same day)
Local mode becomes an offline floor and an on-ramp, in two phases. Phase 1 is purely additive and ships now. Phase 2 is the only irreversible piece (removing the local policy engine) and is gated on data the phase-1 ledger produces. Rationale for the split: local never phones home by design, so today we cannot tell whether it has zero users or a thousand; the 61% install-to-first-call gap could be abandoned installs or silent, happy local-mode users. The ledger is the instrument that answers that. Do not remove anything until it has.
Phase 1 — additive (now)
Shared generator; every harness plugin inherits, per the coverage doctrine.
shellScan+sqlPayload+detectDestructivefrom gsc PR #1109 intogovern.mjs, same fixtures. Gateway stays the authority; the local copy is the floor.Acceptance: gateway down → destructive command asks locally with the floor label, benign command allows with a warning, both in the ledger. No key → same, ledger grows, network capture shows nothing leaving. Connect → rows upload once, console shows them flagged offline-buffer with original timestamps, local file truncated.
Phase 2 — remove the local policy engine (gated)
Not before the ledger has run for a full cycle and produced flush data. Decision rule, written now so it is not relitigated in the moment:
Either outcome is fine. What is not fine is deciding it with no data, which is where we were on 2026-09-08 morning.
Not in scope
Model-call spend cannot be buffered by the hook (hooks never see tokens); that stays with the
claude-acp/codex-acpproxy wrappers. Sub-tenancy and the approvals API are gsc #1096 / #1094. The install funnel stays cloud-default; the first screen still never shows--local.Gateway ingest side: davidcrowe/gatewaystack-connect#1115.