feat(opencode): Tier A — ship the enforcement adapter, proven end to end - #37
Merged
Conversation
…7-17) Finished the 2026-07-12 investigation's open probes. All three Tier-A capabilities attested with an instrumented transcript (each firing logged, so nothing-happened vs it-happened is decidable): pre-tool-veto tool.execute.before → spec-gate exit 2 → edit BLOCKED post-tool-events tool.execute.after fires with the result payload subagent-dispatch the task tool spawns an independent reviewer session Plus the load-bearing bonus: hooks fire INSIDE the dispatched subagent (the reviewer's own read fired before/after in the child session), so a subagent is not an enforcement escape hatch — the veto and config-gate apply within it. Under ADR-11 opencode's capabilities support Tier A. NOT flipping the contract here: that requires Sage to SHIP the adapter that uses these hooks (the generator currently writes only AGENTS.md + commands), else the contract would advertise enforcement sage init does not deliver — the false claim ADR-11 forbids. The adapter-shipping build is the next step, tracked separately.
The production port. sage init (opencode) now writes .opencode/plugin/sage.js and
copies the gate scripts to .opencode/sage-hooks/. The adapter bridges opencode's
plugin API to Sage's platform-agnostic gates:
tool.execute.before → map tool (edit/write/apply_patch→Edit/Write, paths parsed
from patchText; bash→Bash) → run PreToolUse gates →
throw on exit 2 (opencode blocks the call)
tool.execute.after → run PostToolUse recorders (verify-tracker, degradation-
log, manifest-sync), non-blocking
Proven: capabilities attested with an instrumented transcript
(docs/attestations/opencode-tier-a-2026-07-17.md — veto blocks an edit, after
fires with the result, task dispatches an independent reviewer, and hooks fire
INSIDE the subagent so it is not an escape hatch). The shipped adapter is tested
deterministically against the real gates (setup/adapter-test.mjs, 5/5:
pre-spec veto, spec-approved allow, config self-disable veto, secrets veto,
read untouched) — independent of opencode's model backend, which was flaky
during the probe.
platform.yaml: pre-tool-veto/post-tool-events/subagent-dispatch → attested with
evidence + expiry; derived tier flips C→A (contract --check passes). STATUS.md,
README truth table, and generator output all updated. session-events stays
false — the platform's event hook fires but the adapter ships no session-init
that uses it.
The debugging that mattered: the first live edit 'landed' not because the veto
failed but because a prior run's manifest-sync had advanced the cycle past
pre-spec, where the gate correctly allows. On a clean pre-spec manifest the gate
exits 2 — confirmed directly and in the deterministic test. Instrument the
mechanism, not the outcome.
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.
The opencode port, completed. opencode goes Tier C → Tier A:
sage initnow ships an enforcement adapter that makes Sage's gates mechanical on opencode, and the contract derives the tier from attested capabilities.What ships
.opencode/plugin/sage.js— bridges opencode's plugin API to Sage's platform-agnostic gate scripts:tool.execute.before→ maps the tool (edit/write/apply_patch→Edit/Write, paths parsed frompatchText;bash→Bash) → runs the PreToolUse gates →throws on exit 2, and opencode blocks the call.tool.execute.after→ runs the PostToolUse recorders (verify-tracker, degradation-log, manifest-sync)..opencode/sage-hooks/and writes the adapter (verified through a real init).Proven — capability attestation + a deterministic adapter test
docs/attestations/opencode-tier-a-2026-07-17.md), each firing logged so nothing-happened ≠ it-happened:pre-tool-veto— spec-gate exit 2 → throw → edit blockedpost-tool-events—tool.execute.afterfires with the result payloadsubagent-dispatch— thetasktool spawns an independentreviewersessionsetup/adapter-test.mjs, 5/5) drives the realSagePluginhooks against the real gates — pre-spec veto, spec-approved allow, config self-disable veto, secrets veto, read untouched — with no opencode model backend (which was flaky during the probe). Imports only node builtins, so it's CI-runnable.Contract
pre-tool-veto/post-tool-events/subagent-dispatch→attestedwith evidence + expiry (release 1.5).contract.py --checkpasses; derived tier A.session-eventsstaysfalse— the platform's event hook fires, but the adapter ships no session-init that uses it (don't claim what isn't delivered). STATUS.md, README truth table, and generator output updated.The debugging that mattered
A live edit "landed" mid-probe — not because the veto failed, but because a prior run's
manifest-synchad advanced the cycle past pre-spec, where the gate correctly allows. On a clean pre-spec manifest the gate exits 2 (confirmed directly and in the deterministic test). Instrument the mechanism, not the outcome — the same lesson three times this session.Honest edges
No native skill discovery on opencode (system skills are inlined into AGENTS.md; Tier A is about enforcement, not discovery). The coarse Bash-evasion matcher in the shared config-gate has a documented residual. codex remains Tier C (its hooks never provably fired).
fastcheck14/14 ·contract.py --checkgreen · adapter-test 5/5.