Skip to content

Local mode → offline floor + local ledger + on-ramp (phase 1 additive now; phase 2 engine removal gated on flush data) #29

Description

@davidcrowe

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.
  • Release hygiene. Version bump + attestation re-baseline in the same release (#888). install.sh embedded copy synced (install.sh embeds v0.3.0 govern.mjs as heredoc — needs sync to current source #4).
  • 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.
  • Below the threshold → remove the local policy engine; floors-only offline; docs say so; closes Manifest version 0.11.0 — the bump #17 announced but never made #18 by removing the surface.
  • 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.

Gateway ingest side: davidcrowe/gatewaystack-connect#1115.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions