Skip to content

covenant-said-bridge: register, lookup, anchor, xchain#86

Closed
mizuki0x wants to merge 22 commits into
mainfrom
feat/said-bridge
Closed

covenant-said-bridge: register, lookup, anchor, xchain#86
mizuki0x wants to merge 22 commits into
mainfrom
feat/said-bridge

Conversation

@mizuki0x

@mizuki0x mizuki0x commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds covenant-said-bridge (Rust) + @covenant/said-bridge (TS worker). Lets a Covenant agent register on SAID, push Merkle-rooted audit-slice anchors, post validate_work records, and route A2A messages over SAID's /xchain hub.

Bridge is opt-in (COVENANT_SAID_ENABLED=1) and every paid on-chain instruction is gated behind its own COVENANT_SAID_ALLOW_PAID_* flag. All gates default off.

What works against api.saidprotocol.com today

Verified end-to-end with a fresh daemon and node packages/said-bridge/dist/worker.js set as the worker:

  • covenant said status -> resolved config + paid gates
  • covenant said lookup --wallet <addr> -> identity, verification, reputation. Tested against 6cQkUCsQHJGJZhnJHYYUic5FUCgd64HChe8APYYDLS4i (MEME Factory, reputation_score 62.4, feedback_count 26, sponsored, on-chain-sync registration)
  • covenant said free-tier --address <addr> -> used/remaining/limit/paid_price/payment_chains
  • covenant said inbox --address <addr> -> pending xchain messages
  • covenant said anchor --start <seq> --end <seq> --root <hex64> -> fixture mode, appends to \$COVENANT_HOME/said/anchor_pending.jsonl, SQLite cursor advances
  • covenant said anchor-status -> next_index, last_confirmed_index, recent rows
  • All paid CLI verbs return a clean paid instruction <X> is gated off until the matching COVENANT_SAID_ALLOW_PAID_* flag is on

What's wired but not run live (paid gates closed by default)

Worker drives said-sdk for these the moment an operator opens the gate and sets COVENANT_SAID_KEYPAIR:

  • said register --metadata-uri <url> (register_agent, paid gate REGISTER)
  • said verify (get_verified, paid gate VERIFY)
  • said anchor --live (submit_anchor, paid gate ANCHOR)
  • said validate-work (validate_work, paid gate VALIDATE)
  • said send past the 10/day free tier (SAID returns 402; settle via covenant-x402-signer)

Architecture

  • Two keys, never collapsed. Operator key (~/.config/solana/id.json) signs Covenant settlement transactions. SAID owner key (COVENANT_SAID_KEYPAIR) signs SAID instructions. They can rotate independently.
  • Hybrid wiring. In-process Rust for REST and xchain reads. Subprocess TS worker for the four on-chain instructions. Worker mirrors the JSON envelope contract used by @covenant/sap-bridge.
  • SQLite-backed anchor cursor. submit_anchor requires anchor_index = last + 1. Cursor persists every claim before submit and the confirmation after, so a crash mid-submit doesn't lose position.
  • Per-instruction paid gates. Five flags, all default off. An operator can fund anchor cadence (ANCHOR + tx fees) without unlocking REGISTER or VERIFY.

What we deliberately don't do

  • No on-chain identity merge with Covenant's existing settlement program (separate program at `cov9UDyp...`, separate slash authority, separate stake pool)
  • No sponsor pool. Removed sponsor_register / sponsor_verify in c71db2c because there was no concrete use case
  • No off-chain register endpoint. SAID's public API doesn't expose one; we removed the dead surface in 0310dd3 after live testing turned up 404s
  • No A2A transport rewrite. Inbox/send are explicit CLI verbs; routing through covenant-a2a's dispatch table is a follow-up

Notes for the SAID team

Reviewing two things would help most:

  1. packages/said-bridge/src/index.ts - the SaidAgentLike interface in loadSdk() is hand-rolled against your published docs. If the said-sdk method signatures shift, please flag here.
  2. agent-os/crates/covenant-said-bridge/src/agent_card.rs - the AgentLookup struct mirrors the live /api/agents/:wallet shape. New fields you add will be ignored (we use serde(default)), but if any current field changes type or name, the lookup decode will fail and we'll need to bump.

API endpoints currently called: GET /api/agents/:wallet, GET /xchain/inbox/:chain/:address, GET /xchain/free-tier/:address, POST /xchain/message. On-chain calls go through said-sdk.

Test plan

  • cargo test -p covenant-said-bridge (20/20 unit tests)
  • cargo check across workspace
  • pnpm typecheck @covenant/said-bridge
  • node packages/said-bridge/dist/worker.js status -> valid envelope
  • Live: status, lookup (verified + unverified wallets), free-tier, inbox against api.saidprotocol.com
  • Live: anchor fixture pipeline, anchor-status cursor reads
  • Edge cases: missing flags, bad hex root, inverted seq range, daemon-not-wired
  • All paid gates correctly reject when closed
  • Live mainnet anchor (gated until operator wallet is funded)
  • Live mainnet register_agent + get_verified (gated until operator wallet is funded)

Achille Wasque and others added 22 commits June 8, 2026 11:15
scaffold covenant-said-bridge symmetrical to covenant-sap-bridge:
- Cluster + PaidGates config layered from COVENANT_SAID_* env
- REST client to api.saidprotocol.com
- AgentCard register_off_chain and lookup
- worker subprocess stub for the on-chain instructions (wired in P3)

daemon: with_said_bridge builder, said_bridge_config_from_env,
SaidStatus / SaidRegisterOffChain / SaidLookup IPC.

cli: covenant said {status, register --off-chain, lookup}.

paid-tx gates default off; bridge ships disabled by default and a
disabled bridge is still attached so /said status is always answerable.
- cursor.rs: sqlite-backed AnchorCursor, parking_lot::Mutex wraps the
  Connection so the cursor is Sync and bridge methods that hold a
  borrow across an await keep their futures Send for Server handlers.
- anchor.rs: bridge.anchor() in Fixture and Live modes. Fixture writes
  to anchor_pending.jsonl and never spends SOL; Live requires the
  COVENANT_SAID_ALLOW_PAID_ANCHOR gate and drives the worker.
- ipc: SaidAnchor / SaidAnchorStatus requests + responses, SaidAnchorRow row.
- daemon: said_anchor + said_anchor_status open the cursor at
  $COVENANT_HOME/said/cursor.db and the fixture file alongside it.
- cli: covenant said anchor --start --end --root [--live] [--json];
  covenant said anchor-status [--recent N] [--json].

paid gates stay off by default. fixture mode is the safe default for
landing the pipeline; live anchor is one config flag away once the
operator wallet is funded.
- xchain.rs: REST against /xchain/inbox/:chain/:address,
  /xchain/free-tier/:address, and POST /xchain/message.
- ipc: SaidInbox / SaidFreeTier / SaidSend requests + responses,
  SaidInboxMessage row. Payload travels as a JSON string.
- daemon: said_inbox, said_free_tier, said_send handlers. 402
  responses from SAID surface as upstream errors (paid x402 path
  follows in P8 via covenant-x402-signer).
- cli: covenant said inbox / free-tier / send.

free tier covers 10/day per agent at zero cost. Beyond that the
x402 challenge bubbles up; the signer hookup keeps Hyre's own
x402 path independent.
- instructions.rs: register_on_chain, get_verified, validate_work,
  sponsor_register, sponsor_verify. Each gated by its own paid-tx
  flag (REGISTER / VERIFY / VALIDATE / SPONSOR). Hex32 validator
  rejects bad task_hash before the worker round-trip.
- ipc: SaidRegisterOnChain / SaidGetVerified / SaidValidateWork /
  SaidSponsorRegister / SaidSponsorVerify; new responses
  SaidOnChainRegistered, SaidVerified, SaidValidationPosted.
- daemon: paired handlers (six total).
- cli: covenant said register --on-chain, verify, validate-work,
  sponsor-register, sponsor-verify. covenant said register --on-chain
  routes through SaidRegisterOnChain instead of the off-chain REST.

every paid path stays gated off by default. The TS worker that backs
these instructions ships in the next commit.
@covenant/said-bridge ships covenant-said-worker — the subprocess
the Rust bridge crate spawns for every on-chain SAID instruction.
The daemon stays JS-runtime-free and SDK-free; the worker owns
@solana/web3.js and said-sdk as peer deps and loads them lazily.

- config.ts: resolveSaidConfig mirrors the Rust env layering
  (COVENANT_SAID_*, cluster-specific override beats global).
- index.ts: SaidBridge wraps said-sdk SAIDAgent. Paid gates enforced
  in TS too so a misconfigured worker can't spend SOL bypassing
  the Rust guard.
- worker.ts: JSON envelope contract identical to @covenant/sap-bridge:
  argv[2] = command, payload on stdin, single {ok,data}|{ok:false,error,name}
  line on stdout. Commands: status, register-agent, get-verified,
  submit-anchor, validate-work, sponsor-register, sponsor-verify.
- keypair.ts: loads COVENANT_SAID_KEYPAIR (Solana CLI 64-byte JSON).

smoke: `node dist/worker.js status` returns the resolved config
including hasSigner. paid: every gate defaults off.
The live response shape is { address, used, remaining, limit,
paidPrice, paymentChains: [{name, network}] }. The CAIP-2 network
strings stay in the bridge layer; the IPC + CLI flatten paymentChains
to a list of names for terse output.

Verified end-to-end: 'covenant said free-tier --address …' on a fresh
wallet returns used=0 remaining=10 limit=10 paid_price=$0.01
payment_chains=solana,base,polygon,avalanche,sei,iotex,peaq,xlayer.
No concrete use case. The earlier plan tied sponsorship to PAK
bounty winners but PAK lives on Percolator and has nothing to do
with SAID; coupling the two was speculative.

Removed:
- bridge crate: sponsor_register / sponsor_verify methods and their
  input/result types; PaidGates.sponsor gate and matching env var
  COVENANT_SAID_ALLOW_PAID_SPONSOR.
- ipc: SaidSponsorRegister / SaidSponsorVerify request variants.
- daemon: said_sponsor_register / said_sponsor_verify handlers and
  dispatch arms.
- cli: covenant said sponsor-register / sponsor-verify subcommands
  and their help lines.
- TS package: SaidBridge.sponsorRegister / sponsorVerify, worker
  dispatch for sponsor-*, paid.sponsor in config.ts.

Bridge surface is now register_agent + get_verified + submit_anchor
+ validate_work, plus the off-chain REST + xchain reads and send.
Worker rebuilt cleanly; cargo workspace and TS typecheck both green;
21/21 said-bridge unit tests pass.
Manual end-to-end against api.saidprotocol.com surfaced two bugs:

1. POST /api/agents returns 404. SAID has no public off-chain register
   endpoint, so 'covenant said register --off-chain' was dead code.
   Dropped the flag, the AgentCard / OffChainRegistration types, the
   register_off_chain bridge method, the SaidRegisterOffChain IPC
   variant, and the daemon handler. 'covenant said register' now only
   targets register_agent on chain (paid gate).

2. GET /api/agents/:wallet returns reputationScore as a float and has
   no verificationTier or stakeAmount fields. Replaced the AgentLookup
   struct with the real shape: wallet, pda, owner, name, description,
   metadataUri, isVerified, sponsored, reputationScore (f64),
   feedbackCount, activityCount, registeredAt. Verified against a real
   on-chain-synced agent.

Also stripped em dashes and verbose module headers in the said-bridge
crate and the TS package. 20/20 said-bridge unit tests pass, cargo
check green across the workspace, ts typecheck clean, worker still
emits a valid envelope for 'status'.
The said-bridge IPC verbs and crate shifted source lines without updating
docs/ipc-and-http-gateway.md, leaving 211 stale line-ref citations that
broke 159 `--scripts` validators (worst since the sap-bridge landing):

  - covenant/src/main.rs    +699 (CLI dispatch + usage)
  - covenant-ipc/src/lib.rs  +131 (Said* Request/Response variants)
  - covenantd/src/lib.rs     +394 (said_* server handlers)

Remap every citation to its current source line (prose verified
unchanged), refresh the README status block to 25 crates / ~195k lines /
2556 tests / 388 live, add covenant-said-bridge to the crate-groups
table, and bump the memory_compaction_plan validator's pinned schema-test
line 7691 -> 8390. `bash agent-os/scripts/validate.sh --scripts` is green.
the crate landed without rustfmt, leaving drift across six source files. format them and replace a field-reassign-with-default in a unit test with a struct literal. no behavior change.
exercise worker::invoke through a shell stub: success envelopes map onto the typed register/get-verified/validate-work results, named errors become Upstream while generic ones stay Rest, and a hung or silently-exiting worker surfaces Timeout/Worker instead of hanging or panicking. mirrors covenant-sap-bridge's worker_roundtrip, no network or paid transactions.
exercise anchor_live through a worker stub: a success envelope maps onto AnchorSubmission and persists the cursor confirmation, while a worker failure leaves the claimed index pending so a retry cannot reuse it against a stale on-chain last_anchor_index. rename the AnchorMode::Live unit tests off the live_ prefix so these stub tests are not counted as live boundary tests in the status metric.
Drive lookup, xchain inbox/free-tier/send, and the rest::decode
status-code branches (success, 402, other errors, undecodable body)
against a one-shot in-process HTTP server, asserting method/path, the
camelCase request body, and the typed result mapping. Mirrors the
worker-roundtrip "real boundary, controlled output" idiom with no mock
framework and no new dependency.
Lock the fail-closed input guards that run before a paid SOL transaction
reaches the worker: register rejects an empty metadata_uri, validate_work
rejects a wrong-length task hash and an empty evidence_uri, and the anchor
range check rejects a wrong-length merkle root. The hex charset arms were
already covered; this adds the length and empty-string arms.
A worker that prints a non-JSON line (a stack trace or log message) and an
ok:true envelope whose data does not match the typed result must both
surface as Decode rather than panic or mis-route. The hung and
silent-exit arms were already covered; this adds the two decode arms.
register and submit_anchor already assert PaidGateClosed when their gate is
off; cover the remaining two paid entry points so every real-SOL operation
is proven to fail closed under its own gate flag and instruction label.
Lock the verb-prefixed Err mappings the dispatch layer adds on top of
the said-bridge. REST verbs (lookup/inbox/free-tier/send) drive a
one-shot loopback listener returning 404/402/500, and the paid verbs
(register/get_verified/validate_work) are rejected at the closed gate.
The success arms were already covered; these exercise the previously
untested `said <verb>: ...` error branches an operator hits over the CLI.
The bridge-not-wired guard, REST success/error, and paid closed-gate
arms were already covered, but the paid on-chain success field-copy onto
Response::SaidOnChainRegistered, SaidVerified, and SaidValidationPosted
was not. Drive each through Server::respond with a shell-stub worker
(gates open, no real subprocess, signer, or paid transaction) so the
covenantd-side Response construction is pinned, mirroring the existing
REST success coverage.
mizuki0x added a commit that referenced this pull request Jun 8, 2026
staged cheap->expensive ladder with a hard correctness gate and multi-objective score; first task covenantd-builds-clean. tracked under agent-os/self-improvement on the feat branch, off main so PR #86 stays untouched.
@mizuki0x

Copy link
Copy Markdown
Contributor Author

Superseded by #109 (rebased onto current main; said-sdk 0.3.4 adapter; live mainnet register + verify on AdChc…). 158-commit drift made rebase noisier than a fresh branch.

@mizuki0x mizuki0x closed this Jun 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants