gh shim: bot-speech binding reads select by the authenticated agent

Merged
AF
2d ago
+1143-76

Regression from #1095, live since the 20:33Z card: every bot write from a head whose repository isn't cortexkit/aft was refused with task_gh_authority_unavailable … (binding_unverifiable).

Cause. The shim read Plexus bindings with bindings.read against a fixed connection, github-handle-aft-alfonso (gh_shim_relay/mod.rs ~54, ~734, ~1324). That read returns only that one handle's repositories. Every other head's registry agent had no row, so it reached the empty-map binding_unverifiable branch (gh_shim_speech/service.rs ~677-687).

Fix. The shared request builder now sends {"op":"bindings.read_for_agent","agent_id":<authenticated launch agent>}, Plexus's new operation, live since 22:32Z at plexus f174b21. It's used at capture, at ticket binding reads, and at send-time revalidation. Signed repo-to-agent equality, generation and handle checks, start-time ceilings and fail-closed behaviour stay as they were. A reply row for another agent refuses explicitly. Printed refusal wording is unchanged. New diagnostics go only to the warn log and the structured detail field.

Contract. Plexus's nine real-handler request/reply pairs are vendored byte-for-byte under crates/aft/tests/fixtures/gh_shim/plexus-bindings-contract/, with per-file SHA-256 provenance and an LF pin. AFT's exact request equals agent.request.json. Every reply vector runs through parse_bindings: agent and connection give valid rows, none and no-grant refuse as binding_unverifiable, other-agent refuses as binding_changed, and invalid_request replies fail closed. The wire stand-ins derive their replies from the vectors. Restoring the fixed-handle selection makes the three new agent-binding tests fail.

Follow-up, not in this PR: the shim-side comparator and the BindingsCheck cache are still keyed by manifest version. The daemon's capture and send checks already reject foreign agents, so this is hardening, not a bypass.


Summary by cubic

Fixes a regression where bot speech writes from any head whose repository isn't cortexkit/aft were refused as binding_unverifiable: the shim read Plexus bindings against a fixed handle connection, which returns only that one handle's rows. Binding reads now use Plexus's bindings.read_for_agent with the authenticated launch agent, at capture, ticket binding reads, and send-time revalidation.

Behavior

  • Signed repository-to-agent equality, generation and handle checks, start-time ceilings, and fail-closed capture stay as before; a returned row for another agent now refuses explicitly as binding_changed.
  • Printed refusal wording is unchanged; new diagnostics appear only in warn logs and the structured detail field.
  • The shim-side comparator and BindingsCheck cache remain keyed by manifest version; daemon checks already reject foreign agents, so this is hardening, not a bypass.

Contract vectors

  • Vendors nine real Plexus request/reply pairs byte-for-byte under crates/aft/tests/fixtures/gh_shim/plexus-bindings-contract/, with per-file SHA-256 provenance pins and LF checkout enforcement.
  • AFT's exact binding request must equal agent.request.json, and every reply vector runs through parse_bindings and must fail closed.