covenant-said-bridge: register, lookup, anchor, xchain#86
Closed
mizuki0x wants to merge 22 commits into
Closed
Conversation
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.
9 tasks
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. |
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.
Summary
Adds
covenant-said-bridge(Rust) +@covenant/said-bridge(TS worker). Lets a Covenant agent register on SAID, push Merkle-rooted audit-slice anchors, postvalidate_workrecords, and route A2A messages over SAID's/xchainhub.Bridge is opt-in (
COVENANT_SAID_ENABLED=1) and every paid on-chain instruction is gated behind its ownCOVENANT_SAID_ALLOW_PAID_*flag. All gates default off.What works against
api.saidprotocol.comtodayVerified end-to-end with a fresh daemon and
node packages/said-bridge/dist/worker.jsset as the worker:covenant said status-> resolved config + paid gatescovenant said lookup --wallet <addr>-> identity, verification, reputation. Tested against6cQkUCsQHJGJZhnJHYYUic5FUCgd64HChe8APYYDLS4i(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_chainscovenant said inbox --address <addr>-> pending xchain messagescovenant said anchor --start <seq> --end <seq> --root <hex64>-> fixture mode, appends to\$COVENANT_HOME/said/anchor_pending.jsonl, SQLite cursor advancescovenant said anchor-status-> next_index, last_confirmed_index, recent rowspaid instruction <X> is gated offuntil the matchingCOVENANT_SAID_ALLOW_PAID_*flag is onWhat's wired but not run live (paid gates closed by default)
Worker drives
said-sdkfor these the moment an operator opens the gate and setsCOVENANT_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 sendpast the 10/day free tier (SAID returns 402; settle via covenant-x402-signer)Architecture
~/.config/solana/id.json) signs Covenant settlement transactions. SAID owner key (COVENANT_SAID_KEYPAIR) signs SAID instructions. They can rotate independently.@covenant/sap-bridge.submit_anchorrequiresanchor_index = last + 1. Cursor persists every claim before submit and the confirmation after, so a crash mid-submit doesn't lose position.What we deliberately don't do
Notes for the SAID team
Reviewing two things would help most:
packages/said-bridge/src/index.ts- the SaidAgentLike interface inloadSdk()is hand-rolled against your published docs. If the said-sdk method signatures shift, please flag here.agent-os/crates/covenant-said-bridge/src/agent_card.rs- the AgentLookup struct mirrors the live/api/agents/:walletshape. New fields you add will be ignored (we useserde(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