@bondedhq/shared 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +17 -2
  3. package/dist/abis.d.ts +8639 -0
  4. package/dist/abis.js +11234 -0
  5. package/dist/actuarial.d.ts +158 -0
  6. package/dist/actuarial.js +210 -0
  7. package/dist/agent-url.d.ts +172 -0
  8. package/dist/agent-url.js +248 -0
  9. package/dist/agent.d.ts +184 -0
  10. package/dist/agent.js +133 -0
  11. package/dist/allowances.d.ts +54 -0
  12. package/dist/allowances.js +68 -0
  13. package/dist/bounty-example.d.ts +6 -0
  14. package/dist/bounty-example.js +23 -0
  15. package/dist/bounty-spec.d.ts +36 -0
  16. package/dist/bounty-spec.js +111 -0
  17. package/dist/canonical.d.ts +8 -0
  18. package/dist/canonical.js +44 -0
  19. package/dist/chains.d.ts +58 -0
  20. package/dist/chains.js +123 -0
  21. package/dist/deployments.d.ts +32 -0
  22. package/dist/deployments.js +41 -0
  23. package/dist/index.d.ts +33 -0
  24. package/dist/index.js +33 -0
  25. package/dist/leaderboard.d.ts +105 -0
  26. package/dist/leaderboard.js +85 -0
  27. package/dist/llm.d.ts +121 -0
  28. package/dist/llm.js +105 -0
  29. package/dist/mandate-rules.d.ts +54 -0
  30. package/dist/mandate-rules.js +69 -0
  31. package/dist/module-install.d.ts +145 -0
  32. package/dist/module-install.js +133 -0
  33. package/dist/notifications.d.ts +48 -0
  34. package/dist/notifications.js +45 -0
  35. package/dist/observed-rates.d.ts +125 -0
  36. package/dist/observed-rates.js +158 -0
  37. package/dist/problems.d.ts +44 -0
  38. package/dist/problems.js +148 -0
  39. package/dist/quote.d.ts +123 -0
  40. package/dist/quote.js +167 -0
  41. package/dist/report-fixes.d.ts +89 -0
  42. package/dist/report-fixes.js +159 -0
  43. package/dist/runner.d.ts +376 -0
  44. package/dist/runner.js +353 -0
  45. package/dist/schemas/attack.d.ts +121 -0
  46. package/dist/schemas/attack.js +142 -0
  47. package/dist/schemas/attestation.d.ts +284 -0
  48. package/dist/schemas/attestation.js +175 -0
  49. package/dist/schemas/common.d.ts +22 -0
  50. package/dist/schemas/common.js +53 -0
  51. package/dist/schemas/mandate-commitment.d.ts +13 -0
  52. package/dist/schemas/mandate-commitment.js +37 -0
  53. package/dist/schemas/mandate.d.ts +170 -0
  54. package/dist/schemas/mandate.js +113 -0
  55. package/dist/self-serve.d.ts +133 -0
  56. package/dist/self-serve.js +110 -0
  57. package/dist/sentinel-cascade.d.ts +64 -0
  58. package/dist/sentinel-cascade.js +64 -0
  59. package/dist/sentinel.d.ts +133 -0
  60. package/dist/sentinel.js +101 -0
  61. package/dist/suggested-mandate.d.ts +81 -0
  62. package/dist/suggested-mandate.js +112 -0
  63. package/dist/tee.d.ts +61 -0
  64. package/dist/tee.js +93 -0
  65. package/dist/tiers.d.ts +19 -0
  66. package/dist/tiers.js +23 -0
  67. package/dist/troubleshooting-doc.d.ts +11 -0
  68. package/dist/troubleshooting-doc.js +46 -0
  69. package/package.json +59 -3
@@ -0,0 +1,148 @@
1
+ /**
2
+ * Every problem, warning and refusal a builder can see, with a stable `code`, the sentence (or the
3
+ * gist of it, when the real one carries numbers or times) and what to do about it. The API puts
4
+ * the code next to each sentence (`code` beside `error`, `issues[].code` beside check problems,
5
+ * `problems[].code` in the health panel), the web app links each message to its entry on the
6
+ * troubleshooting page by code (`#<code>`), and the docs page is built from `PROBLEMS`.
7
+ *
8
+ * Codes never change once published: a new message gets a new entry. `match` is how a sentence
9
+ * the services already produce is recognised, so codes don't depend on every call site.
10
+ */
11
+ /** The catch-all for a sentence no entry recognises (it still has its own words). */
12
+ export const UNKNOWN_PROBLEM = "other";
13
+ const entry = (code, area, sentence, fix, ...match) => ({ code, area, sentence, fix, match });
14
+ /** Ordered: specific entries before general ones. */
15
+ export const PROBLEMS = [
16
+ // Sign-in and signed requests.
17
+ entry("session-ended", "sign-in", "Your session has ended. Sign in again.", "Sign in with your wallet again; sessions last 12 hours.", /session has ended/i),
18
+ entry("not-signed-in", "sign-in", "You aren't signed in.", "Sign in with your wallet on the page, then try again.", /aren't signed in|sign in with your wallet first|^unauthorized$/i),
19
+ entry("sign-in-other-chain", "sign-in", "That sign-in is for another chain.", "Switch your wallet to the chain the page uses and sign in again.", /sign-in is for another chain/i),
20
+ entry("too-many-sign-ins", "sign-in", "Too many sign-ins from this wallet. Try again in an hour.", "Wait an hour, or keep the session you already have open.", /too many sign-ins/i),
21
+ entry("signature-expired", "sign-in", "The signed request has expired. Sign it again.", "Sign again: a signed request is good for a few minutes. Check your computer's clock if it keeps happening.", /signed (request|check) has expired/i),
22
+ entry("signature-reused", "sign-in", "That signed request was already used. Sign it again.", "Sign again: each signature can be used once.", /signed request was already used/i),
23
+ entry("bad-signature", "sign-in", "The signature isn't valid.", "Sign with the wallet that owns the agent, on the right chain, and don't edit the message.", /signature (isn't|is not) (valid|from that wallet)/i, /sign with the vault's agent key/i),
24
+ entry("stream-token-expired", "sign-in", "That stream token has expired. Ask for a new one.", "Reload the page: it asks for a new stream token.", /stream token has expired/i),
25
+ // Limits and malformed requests.
26
+ entry("preview-limit", "requests", "You can start (or run) only so many previews a day.", "Wait until the time the message gives, or make the agent official to rate it without the preview limit.", /at most \d+ previews? (a day|ratings? a day)|preview ratings are used up/i),
27
+ entry("check-limit", "requests", "You can run only so many connection checks an hour.", "Wait until the time the message gives. A passing check stays good for 24 hours.", /at most \d+ checks an hour|too many checks|connection checks are busy/i),
28
+ entry("url-verify-limit", "requests", "Your Agent URL has been checked too often this hour.", "Wait an hour before verifying the URL again.", /checked too often this hour|can be checked at most/i),
29
+ entry("rating-cooldown", "requests", "An agent can be rated only so often; the next rating time is given.", "Wait until the time the message gives.", /one rating .* per agent; next at|rating (is|was) (queued|running) already|already has a rating queued or running/i),
30
+ entry("stream-limit", "requests", "Too many live streams are open.", "Close other tabs showing live progress, then reload.", /too many live streams/i),
31
+ entry("rate-limited", "requests", "Too many requests. Try again in a few minutes.", "Wait a few minutes; the limit is per address.", /too many (requests|quote requests|reveal requests)/i),
32
+ entry("body-too-large", "requests", "The request body is too large.", "Send less: request bodies are capped (16 KB; 256 KB for Sentinel checks).", /is over \d+ KB/i),
33
+ entry("invalid-request", "requests", "The request isn't valid.", "Check the fields against the API reference; unknown fields are refused.", /^invalid request$/i, /^spec \d+:/, /must be between a day and a year/i),
34
+ // Registration, ownership and the vault or module.
35
+ entry("not-owner", "registration", "Only the agent's owner (or the vault's guardian) can do that.", "Connect the wallet that owns the agent in the registry (or guards the vault).", /only the (agent's owner|vault's guardian|module's guardian)/i),
36
+ entry("not-own-agent", "registration", "This agent isn't registered as your own agent.", 'Register it with "Make it official" (or POST /self/agents) first.', /isn't registered as your own agent|runner tokens are for agents registered as your own agent|register the agent for ratings first/i),
37
+ entry("operator-managed", "registration", "This agent is managed by Bonded's operator.", "Nothing to do: Bonded's own agents are configured by the operator.", /managed by Bonded's operator/i),
38
+ entry("vault-invalid", "registration", "That address is not a Bonded vault or mandate module.", "Use the address the factory created for this agent (shown after onboarding).", /not a Bonded (vault|mandate module)/i),
39
+ entry("vault-wrong-agent", "registration", "The vault or module belongs to another agent.", "Register the vault created for this agent id, or create a new one for it.", /(vault|module) belongs to another agent|vault doesn't belong to this agent/i),
40
+ entry("mandate-mismatch", "registration", "The mandate doesn't match what the vault or module enforces.", "Register exactly the mandate the vault was created with; its rules can't change afterwards.", /mandate doesn't match|vault enforces different rules|mandate is for another chain or agent/i),
41
+ entry("vault-creation-unreadable", "registration", "The vault's creation couldn't be checked.", "Create the vault by calling the factory directly (not through another contract), then register it.", /vault's creation (wasn't found|call couldn't be read)|create the vault by calling the factory directly/i),
42
+ entry("modules-unavailable", "registration", "Smart-account modules aren't deployed on this chain yet.", "Use a Mandate Vault instead.", /modules aren't deployed/i),
43
+ entry("owner-changed", "registration", "The agent changed hands since it was registered.", "The new owner registers the agent again.", /changed hands/i),
44
+ entry("tee-needs-runner", "registration", "A TEE-bound agent is rated in chain mode, through its runner, with a declared code hash.", "Use the runner in chain mode and declare the code hash your TEE reports.", /TEE-bound agent/i),
45
+ entry("tee-binding-failed", "rating", "TEE binding failed: the evidence didn't match the rated deployment.", "Redeploy the reviewed compose file, then rate again.", /TEE binding failed|TEE runs different code/i),
46
+ // Reaching the agent.
47
+ entry("runner-token-invalid", "connection", "That runner token isn't valid: it was revoked, replaced or its preview ended.", "Create a new runner token on the agent's (or preview's) page and use that.", /runner token isn't valid|send your runner token/i),
48
+ entry("runner-token-missing", "connection", "Create a runner token first, then start the runner.", "Create a runner token on the agent's page and start the runner with the command shown.", /create a runner token first/i),
49
+ entry("wrong-mode", "connection", "The agent runs in the other mode (tools or chain) from the one it is registered for.", "In tools mode trades are tool calls; in chain mode they are transactions signed and sent through the RPC endpoint. Restart the runner with the right --mode, or set the agent up for the registered mode.", /runner is (connected in the wrong mode|in \w+ mode)/i, /as a tool, but in chain mode|tried the rpc endpoint/i),
50
+ entry("runner-busy", "connection", "The runner is busy with another episode.", "Wait for it to finish, then try again.", /runner is busy/i),
51
+ entry("runner-could-not-start", "check", "The runner couldn't start your agent.", "Run the agent command yourself in the same folder to see the error, then fix it and check again.", /runner couldn't start your agent/i),
52
+ entry("runner-not-connected", "connection", "The runner isn't connected.", "Start the runner with your token (keep it running as a service), then try again.", /runner (isn't|is not) connected|runner disconnected|no runner is connected/i),
53
+ entry("agent-url-missing", "connection", "Set your Agent URL first.", "Add your agent's HTTPS endpoint on the agent's page.", /set your agent url first/i),
54
+ entry("agent-url-unverified", "connection", "The Agent URL isn't verified yet.", "Give your agent the secret, then press Verify so Bonded can check its answer.", /verify your agent url first|url isn't (verified|set and verified)|agent url isn't verified/i),
55
+ entry("agent-url-secret-unusable", "connection", "The stored Agent URL secret can't be used.", "Set the Agent URL again to get a new secret.", /secret can't be used/i),
56
+ entry("agent-url-no-version", "connection", "The Agent URL reported no version, so there is no code hash.", "Report a version in your verify answer, or declare a code hash.", /reported no version/i),
57
+ entry("connection-kind-mismatch", "connection", "This agent connects the other way (runner or Agent URL).", "Use the connection the agent was set up with, or switch it on the agent's page.", /connects by its agent url|connects with the runner|reached by its agent url, so it has no runner|is rated through its runner, not an agent url/i),
58
+ entry("url-slow", "connection", "Your URL didn't answer in time.", "Answer within the time given (10 seconds for verify); do slow work after answering.", /didn't answer within \d+ seconds/i),
59
+ entry("url-closed", "connection", "Your URL closed the connection before answering.", "Check your server's logs: it dropped the request (a crash, a proxy timeout or a body size limit).", /closed the connection before answering/i),
60
+ entry("url-certificate", "connection", "Your URL's TLS certificate wasn't accepted.", "Serve a valid certificate for the host name (not self-signed, not expired).", /tls certificate/i),
61
+ entry("agent-secret", "connection", "Your URL refused Bonded's signed call: it isn't using the agent secret Bonded gave you.", "Set the agent secret from the Agent URL page in your agent and check the bonded-signature header with it.", /answered (401|403)|check that your agent uses the agent secret/i),
62
+ entry("verify-answer", "connection", "Your URL answered the verify check, but not correctly.", 'Answer verify with 200 and JSON { challenge, proof } (proof = HMAC-SHA256 of "verify." + challenge with your secret).', /answered (\d+ )?(to )?the check|without proving it holds the agent secret|answered the check, but not with JSON/i),
63
+ entry("url-not-public", "connection", "That URL can't be used: it must be public https on the standard port, and not one of Bonded's.", "Use a public https URL on port 443 with no user name, password or #fragment, that resolves to a public address.", /^that isn't a url|your url (is (too long|longer|one of)|must|can't)|isn't reachable on the public internet|redirect/i),
64
+ entry("url-unreachable", "connection", "Bonded couldn't reach your URL.", "Check the URL is up and answers POST from the internet (no firewall or allowlist in the way).", /nothing accepted a connection|couldn't reach your url|verification couldn't run|your url answered|answer was over|called your url too often/i),
65
+ entry("agent-urls-off", "service", "Agent URL mode and webhooks are off on this service.", "Use the runner instead; the operator has to set AGENT_URL_SECRET_KEY for URLs and webhooks.", /AGENT_URL_SECRET_KEY/),
66
+ entry("checks-unavailable", "service", "Connection checks aren't available on this rating service yet.", "Try later; the operator has to run the Arena Gateway.", /connection checks aren't available/i),
67
+ entry("gateway-unavailable", "service", "The rating worker has no Arena Gateway, so it can't rate an agent its operator runs.", "Try later; the operator has to run the Arena Gateway.", /gateway not available/i),
68
+ entry("needs-check", "check", "Run a connection check first.", "Run a check of this exact connection (same token or URL, mode and code); a pass lasts 24 hours.", /run a connection check (first|of this connection first)/i),
69
+ entry("check-running", "check", "A check is already running for this agent.", "Wait for its result.", /check is already running/i),
70
+ entry("code-changed", "rating", "The agent now reports different code than when the rating was queued.", "Run a connection check of the code that runs now, then rate again.", /now reports different code/i),
71
+ entry("no-code-hash", "rating", "No code hash: none was reported when the rating was queued.", "Update @bondedhq/runner (it reports one), or declare a code hash, then rate again.", /no code hash/i),
72
+ // What a connection check finds.
73
+ entry("timed-out", "check", "It didn't finish within the episode's time.", "Post to the episode's done URL (or exit) when the agent has finished.", /didn't finish within \d+ seconds|^timed out after/i),
74
+ entry("agent-exited", "check", "It exited or was stopped before finishing.", "Read its output in the check, fix the crash, and check again.", /exited with code|was stopped by \w+ before finishing/i),
75
+ entry("never-called-a-tool", "check", "Your agent never called a tool.", "Read and trade through the episode's MCP or HTTP endpoints (isBondedEpisode() in @bondedhq/sdk).", /never called a tool/i),
76
+ entry("no-trade", "check", "It didn't make the trade its task names.", "Make sure the agent does the task it's given (the trade named in the goal).", /didn't make the trade its task names/i),
77
+ entry("benign-breach", "check", "Its actions broke the mandate in a benign episode.", "Keep trades inside the mandate (tokens, caps, destinations); a rating counts this against it.", /broke the mandate in a benign episode/i),
78
+ entry("unread-surfaces", "check", "It didn't read every surface; unread ones are scored as untested.", "Have the agent check its messages, news, prices and other reads before trading.", /read \d+ of \d+ surfaces/i),
79
+ entry("tool-calls-failed", "check", "Some tool calls failed.", "Check the arguments the agent passes; the first failure is quoted.", /tool call\S* failed/i),
80
+ entry("transactions-refused", "check", "Some transactions were refused.", "Sign for the episode's chain with the episode's key and call only the vault's execute.", /transaction\S* (was |were )?refused/i),
81
+ entry("usage-not-reported", "check", "It didn't report its model usage at /done.", "Report input and output tokens at /done for a cost estimate (optional).", /didn't report its model usage/i),
82
+ entry("episode-failed", "check", "The episode failed.", "Read the reason given; run the agent locally with the same input to reproduce it.", /the episode failed/i),
83
+ // Ratings.
84
+ entry("fork-budget-spent", "rating", "The agent used up its share of fork requests.", "Make fewer chain reads per episode (cache what doesn't change).", /share of fork requests/i),
85
+ entry("bonded-url-failure", "rating", "Bonded couldn't reach the agent's URL; not the agent's fault, it will be retried.", "Nothing to do: it is retried.", /bonded couldn't (reach|call) the agent's url/i),
86
+ entry("rating-over-budget-agent", "rating", "The rating ran past its time budget, and the agent took most of the time.", "Make each episode faster (fewer model calls, a faster model), then rate again.", /ran past its .*budget.*the agent took/i),
87
+ entry("rating-over-budget", "rating", "The rating ran past its time budget, not because of the agent; it will be retried.", "Nothing to do: it is retried.", /ran past its .*budget/i),
88
+ entry("agent-failed-episode", "rating", "The agent failed an episode.", "Run a connection check to see the failure, fix it, and rate again.", /stopped: the agent failed/i),
89
+ entry("not-attested", "rating", "Not attested: the agent didn't do its task often enough in the control episodes.", "Make sure the agent does its benign task; an agent that does nothing can't be rated.", /^not attested/i),
90
+ entry("incomplete-rating", "rating", "The rating was incomplete (a guard failed to answer).", "Nothing to do: it is rated again.", /incomplete rating/i),
91
+ entry("preview-stopped", "rating", "This preview was stopped.", "Start a new preview.", /preview was stopped/i),
92
+ entry("rating-running", "rating", "Its rating is running.", "Wait for it to finish; it stops at the first failed episode.", /its rating is running/i),
93
+ // Cover and quotes.
94
+ entry("score-superseded", "cover", "This agent's score is superseded: its latest rating was stopped by the agent.", "Fix what stopped it and wait for a complete rating.", /score is superseded/i),
95
+ entry("registered-again", "cover", "The agent was registered again after its current score was posted.", "Wait for a complete rating of the current registration.", /registered again after its current score/i),
96
+ entry("cover-capped", "cover", "Cover is capped on testnet.", "Ask for less cover.", /cover is capped at/i),
97
+ entry("uninsurable", "cover", "The score is too low to insure.", "Use the report's top fixes and rate again.", /is uninsurable/i),
98
+ entry("bond-too-low", "cover", "The operator bond is below the tier's minimum for the cover.", "Stake more bond, or ask for less cover.", /operator bond .* is below/i),
99
+ entry("capacity-exceeded", "cover", "The pool can't take that much cover right now.", "Ask for less cover, or try later.", /capacity exceeded/i),
100
+ entry("attestation-expired", "cover", "The score has expired.", "Rate the agent again.", /attestation has expired/i),
101
+ entry("module-needs-enforce", "cover", "A smart-account module can hold cover only in Enforce mode.", "Install the module in Enforce mode.", /only in Enforce mode/i),
102
+ // Health panel (GET /self/agents/:id/health) and notifications.
103
+ entry("no-score", "rating", "The agent has no posted score yet.", "Run a connection check, then ask for a rating.", /has no (posted )?score yet/i),
104
+ entry("score-expired", "rating", "The agent's score has expired.", "Keep the runner connected (scores renew on their own) or ask for a rating.", /score (has )?expired/i),
105
+ entry("score-expiring", "rating", "The agent's score expires soon.", "Keep the runner connected so the score renews, or ask for a rating.", /score expires (in|on|soon)/i),
106
+ entry("runner-offline", "connection", "The runner has been offline while a rating or renewal is due.", "Restart the runner, ideally as a service (bonded-runner service --print).", /runner has been offline/i),
107
+ entry("cover-without-vault-trades", "vault", "The agent has cover, but hasn't traded through its vault lately.", "Point the agent's trades at its vault (npx @bondedhq/runner init shows the change); cover only pays for trades through the vault.", /hasn't traded through (its|the) vault/i),
108
+ entry("low-gas", "vault", "The agent key is low on gas.", "Send ETH to the agent key; it pays gas for every trade through the vault.", /low on gas/i),
109
+ entry("cover-lapsing", "cover", "The cover's premium is past due.", "Top up the vault's base asset so the next premium collection succeeds before the grace period ends.", /premium is past due|cover (is )?lapsing/i),
110
+ entry("vault-frozen", "vault", "The vault froze the agent after a breach.", "Review the breach, then the guardian unfreezes (or rotates) the agent key.", /froze the agent|vault is frozen/i),
111
+ entry("claim-filed", "cover", "A claim was filed for a breach.", "Nothing to do: the keeper files covered claims and the payout goes to the vault.", /claim was filed/i),
112
+ entry("claim-paid", "cover", "A claim was paid.", "Nothing to do: the payout went to the vault.", /claim .* was paid/i),
113
+ entry("breach-recorded", "vault", "The vault recorded a breach.", "Review it on the agent's page; a covered loss is claimed automatically.", /recorded a breach/i),
114
+ // Notifications and the service itself.
115
+ entry("email-unavailable", "notifications", "Email notifications aren't set up on this service.", "Use a webhook, or the in-app list.", /email notifications aren't set up/i),
116
+ entry("webhook-missing", "notifications", "Set a webhook URL first.", "Save a webhook URL, then rotate its secret.", /set a webhook url first/i),
117
+ entry("sentinel-unavailable", "service", "Sentinel didn't answer.", "Try again in a few seconds.", /sentinel (isn't running|is busy|didn't answer|took too long)/i),
118
+ entry("try-again", "requests", "Another request for the same thing came first.", "Try again.", /newer token request came first|runner token changed since the check was asked for/i),
119
+ entry("no-vault", "vault", "This agent has no registered vault.", "Register the agent with its vault (or module) address.", /has no registered vault/i),
120
+ entry("bounty-reveal", "registration", "The bounty's reveal doesn't match what is on chain.", "Reveal the submission on BountyBoard first, with the exact spec whose sha256 you committed.", /reveal the submission|specURI|settled on chain without validation/i),
121
+ entry("not-found", "requests", "That wasn't found.", "Check the id; another wallet's previews and checks are shown as not found.", /wasn't found|not found|not configured|no such submission|older episodes are pruned|no TEE check/i),
122
+ entry("service-error", "service", "The request failed on Bonded's side.", "Try again; if it keeps failing, check the status page.", /^request failed$|message is too large to send|event stream (capacity unavailable|closed)|validation failed; retry scheduled/i),
123
+ ];
124
+ const byCode = new Map(PROBLEMS.map((p) => [p.code, p]));
125
+ const bySentence = new Map(PROBLEMS.map((p) => [p.sentence, p.code]));
126
+ /** The stable code for a sentence (`UNKNOWN_PROBLEM` when no entry recognises it). */
127
+ export function problemCode(sentence) {
128
+ const exact = bySentence.get(sentence);
129
+ if (exact)
130
+ return exact;
131
+ for (const p of PROBLEMS)
132
+ if (p.match.some((m) => m.test(sentence)))
133
+ return p.code;
134
+ return UNKNOWN_PROBLEM;
135
+ }
136
+ export function codedProblem(text, code = problemCode(text)) {
137
+ const fix = byCode.get(code)?.fix;
138
+ return { code, text, ...(fix ? { fix } : {}) };
139
+ }
140
+ /** The entry for a code, if there is one. */
141
+ export function problemEntry(code) {
142
+ return byCode.get(code);
143
+ }
144
+ /** The troubleshooting list for the docs page: code, sentence and fix, without the matchers. */
145
+ export function troubleshootingList() {
146
+ return PROBLEMS.map(({ code, area, sentence, fix }) => ({ code, area, sentence, fix }));
147
+ }
148
+ //# sourceMappingURL=problems.js.map
@@ -0,0 +1,123 @@
1
+ import { type Attestation, Quote } from "./schemas/attestation.js";
2
+ import { type Tier } from "./tiers.js";
3
+ /**
4
+ * Cover quotes: priced from a signed attestation, signed by the attester, and accepted by
5
+ * `CoverManager.buyCover` (docs/SPECIFICATION.md Sections 6.4 and 9, ADR-009).
6
+ *
7
+ * The quote is priced only from the attestation's expected loss and the operator's bond, so
8
+ * anyone holding the attestation can recompute the premium and check it.
9
+ */
10
+ /** Quotes stay valid for an hour: long enough to sign and send `buyCover`, short enough that
11
+ * a price can't be held while the agent's risk changes. */
12
+ export declare const QUOTE_TTL_SECONDS: number;
13
+ export interface QuotePricing {
14
+ tier: Tier;
15
+ /** Annual premium rate after the bond discount, in basis points (rounded up). */
16
+ premiumRateBps: number;
17
+ /** Premium rate before the bond discount, in basis points (rounded up). */
18
+ basePremiumRateBps: number;
19
+ /** Smallest bond for the agent's total cover (existing plus this quote): the tier's share,
20
+ * rounded up. Goes into the quote's `minBond`, which `buyCover` enforces. */
21
+ minBond: bigint;
22
+ /** bond ÷ the agent's total cover, as used for the discount. */
23
+ bondRatio: number;
24
+ }
25
+ /**
26
+ * Prices cover for a rated agent (Section 9.1):
27
+ * P = EL · (1 + load) + capital, then the bond discount P · (1 − min(0.30, bond ÷ cover − 0.10)).
28
+ *
29
+ * The bond belongs to the agent, not the vault, and one agent can hold cover on several vaults.
30
+ * So the bond is measured against the agent's total cover (`existingCover` plus this quote):
31
+ * one bond can't earn the full discount, or meet the minimum, on every vault at once.
32
+ *
33
+ * Throws when the score is uninsurable or the bond is below the tier's minimum, since
34
+ * `buyCover` would reject the quote anyway.
35
+ */
36
+ export declare function priceCover(input: {
37
+ expectedLossBps: number;
38
+ score: number;
39
+ coverAmount: bigint;
40
+ bond: bigint;
41
+ /** The agent's active cover on other policies (`CoverManager.activeCoverOfAgent`). */
42
+ existingCover?: bigint;
43
+ }): QuotePricing;
44
+ /**
45
+ * How firmly a score is tied to the code that runs (bring your own agent):
46
+ * - tee: the rated code proved, with a TEE quote at every episode, that it is what runs, and the
47
+ * attestation carries its measurement;
48
+ * - declared: the operator says which code it is (`codeHash`), and nothing checks it.
49
+ */
50
+ export type BindingLevel = "declared" | "tee";
51
+ /** The binding of an attestation: `tee` when it carries a TEE measurement. */
52
+ export declare function bindingOf(teeMeasurement: string): BindingLevel;
53
+ /**
54
+ * Underwriting policy, not score: the most cover one operator-run agent can hold in total, in
55
+ * tUSDG, by binding. A declared agent's score may describe different code from what trades, so
56
+ * the pool takes less of that risk. Configurable per API (`createApi`'s `coverCaps`).
57
+ */
58
+ export declare const UNDERWRITING_COVER_CAP_USD: Readonly<Record<BindingLevel, number>>;
59
+ /**
60
+ * Why the policy refuses `coverAmount` on top of `existingCover` for an agent with this binding,
61
+ * or undefined when it fits. Amounts are in base-asset units (6 decimals).
62
+ */
63
+ export declare function underwritingCapRefusal(input: {
64
+ binding: BindingLevel;
65
+ coverAmount: bigint;
66
+ existingCover?: bigint;
67
+ caps?: Readonly<Record<BindingLevel, number>>;
68
+ }): string | undefined;
69
+ export interface BuildQuoteInput {
70
+ /** The agent's current attestation. The quote copies its score, hashes and model family. */
71
+ attestation: Attestation;
72
+ vault: `0x${string}`;
73
+ /** Cover in base-asset units (6 decimals for USDC-like assets). */
74
+ coverAmount: bigint;
75
+ /** The operator's bond for this agent now, in base-asset units. */
76
+ bond: bigint;
77
+ /** The agent's active cover on other policies (`CoverManager.activeCoverOfAgent`). */
78
+ existingCover?: bigint;
79
+ /** Unix seconds. Defaults to the current time. */
80
+ now?: number;
81
+ /** Defaults to a random 128-bit number. */
82
+ nonce?: bigint;
83
+ ttlSeconds?: number;
84
+ /**
85
+ * The operator-run agent's binding, when the underwriting cap applies (bring-your-own agents).
86
+ * Omitted for Bonded's built-in designs, which keep the pool's own capacity limits only.
87
+ */
88
+ binding?: BindingLevel;
89
+ /** Caps by binding (default `UNDERWRITING_COVER_CAP_USD`). */
90
+ coverCaps?: Readonly<Record<BindingLevel, number>>;
91
+ }
92
+ /**
93
+ * Builds an unsigned quote from an attestation. The quote expires after `ttlSeconds`, and never
94
+ * after the attestation does, so a quote can't outlive the rating it was priced from.
95
+ */
96
+ export declare function buildQuote(input: BuildQuoteInput): {
97
+ quote: Quote;
98
+ pricing: QuotePricing;
99
+ };
100
+ export interface SignedQuote {
101
+ quote: Quote;
102
+ signature: `0x${string}`;
103
+ signer: `0x${string}`;
104
+ chainId: number;
105
+ /** The CoverManager the quote is signed for (the EIP-712 verifying contract). */
106
+ coverManager: `0x${string}`;
107
+ }
108
+ export declare function signQuote(quote: Quote, options: {
109
+ chainId: number;
110
+ coverManager: `0x${string}`;
111
+ attesterKey: `0x${string}`;
112
+ }): Promise<SignedQuote>;
113
+ /**
114
+ * Checks a signed quote offline: the signer must be one of `trustedAttesters` (for example,
115
+ * the registry's attesters) and the signature must be valid. The `signer` field travels with
116
+ * the quote, so checking the signature alone would accept a quote anyone signed for themselves.
117
+ */
118
+ export declare function verifyQuote(signed: SignedQuote, trustedAttesters: readonly `0x${string}`[]): Promise<boolean>;
119
+ /** A quote that can't be issued (uninsurable score, bond too small, expired attestation). */
120
+ export declare class QuoteError extends Error {
121
+ name: string;
122
+ }
123
+ //# sourceMappingURL=quote.d.ts.map
package/dist/quote.js ADDED
@@ -0,0 +1,167 @@
1
+ import { verifyTypedData } from "viem";
2
+ import { privateKeyToAccount } from "viem/accounts";
3
+ import { bondDiscountedPremium, priceExpectedLoss } from "./actuarial.js";
4
+ import { bondedDomain, Quote, quoteTypedData } from "./schemas/attestation.js";
5
+ import { isTeeMeasurement } from "./tee.js";
6
+ import { tierForScore } from "./tiers.js";
7
+ /**
8
+ * Cover quotes: priced from a signed attestation, signed by the attester, and accepted by
9
+ * `CoverManager.buyCover` (docs/SPECIFICATION.md Sections 6.4 and 9, ADR-009).
10
+ *
11
+ * The quote is priced only from the attestation's expected loss and the operator's bond, so
12
+ * anyone holding the attestation can recompute the premium and check it.
13
+ */
14
+ /** Quotes stay valid for an hour: long enough to sign and send `buyCover`, short enough that
15
+ * a price can't be held while the agent's risk changes. */
16
+ export const QUOTE_TTL_SECONDS = 60 * 60;
17
+ const BPS = 10000n;
18
+ /**
19
+ * Prices cover for a rated agent (Section 9.1):
20
+ * P = EL · (1 + load) + capital, then the bond discount P · (1 − min(0.30, bond ÷ cover − 0.10)).
21
+ *
22
+ * The bond belongs to the agent, not the vault, and one agent can hold cover on several vaults.
23
+ * So the bond is measured against the agent's total cover (`existingCover` plus this quote):
24
+ * one bond can't earn the full discount, or meet the minimum, on every vault at once.
25
+ *
26
+ * Throws when the score is uninsurable or the bond is below the tier's minimum, since
27
+ * `buyCover` would reject the quote anyway.
28
+ */
29
+ export function priceCover(input) {
30
+ const { coverAmount, bond } = input;
31
+ const existingCover = input.existingCover ?? 0n;
32
+ if (coverAmount <= 0n)
33
+ throw new RangeError("cover amount must be positive");
34
+ if (existingCover < 0n)
35
+ throw new RangeError("existing cover can't be negative");
36
+ const rule = tierForScore(input.score);
37
+ if (!rule.insurable)
38
+ throw new QuoteError(`score ${input.score} (${rule.tier}) is uninsurable`);
39
+ const totalCover = existingCover + coverAmount;
40
+ const minBond = ceilDiv(totalCover * BigInt(rule.minBondBps), BPS);
41
+ if (bond < minBond) {
42
+ throw new QuoteError(`the operator bond (${bond}) is below the ${rule.tier} minimum of ${minBond} for the agent's total cover`);
43
+ }
44
+ const base = priceExpectedLoss(input.expectedLossBps / 10_000).premiumRate;
45
+ // Six decimal places is exact enough for a ratio that only moves the discount between 0 and 30%.
46
+ const bondRatio = Number((bond * 1000000n) / totalCover) / 1_000_000;
47
+ const discounted = bondDiscountedPremium(base, bondRatio);
48
+ return {
49
+ tier: rule.tier,
50
+ premiumRateBps: toRateBps(discounted),
51
+ basePremiumRateBps: toRateBps(base),
52
+ minBond,
53
+ bondRatio,
54
+ };
55
+ }
56
+ /** The binding of an attestation: `tee` when it carries a TEE measurement. */
57
+ export function bindingOf(teeMeasurement) {
58
+ return isTeeMeasurement(teeMeasurement) ? "tee" : "declared";
59
+ }
60
+ /**
61
+ * Underwriting policy, not score: the most cover one operator-run agent can hold in total, in
62
+ * tUSDG, by binding. A declared agent's score may describe different code from what trades, so
63
+ * the pool takes less of that risk. Configurable per API (`createApi`'s `coverCaps`).
64
+ */
65
+ export const UNDERWRITING_COVER_CAP_USD = {
66
+ declared: 5_000,
67
+ tee: 20_000,
68
+ };
69
+ /**
70
+ * Why the policy refuses `coverAmount` on top of `existingCover` for an agent with this binding,
71
+ * or undefined when it fits. Amounts are in base-asset units (6 decimals).
72
+ */
73
+ export function underwritingCapRefusal(input) {
74
+ const caps = input.caps ?? UNDERWRITING_COVER_CAP_USD;
75
+ const cap = BigInt(Math.floor(caps[input.binding])) * 1000000n;
76
+ if ((input.existingCover ?? 0n) + input.coverAmount <= cap)
77
+ return undefined;
78
+ const label = input.binding === "tee" ? "a TEE-bound agent" : "an agent with a declared binding";
79
+ return `underwriting policy: cover for ${label} is capped at ${caps[input.binding].toLocaleString("en-US")} tUSDG in total`;
80
+ }
81
+ /**
82
+ * Builds an unsigned quote from an attestation. The quote expires after `ttlSeconds`, and never
83
+ * after the attestation does, so a quote can't outlive the rating it was priced from.
84
+ */
85
+ export function buildQuote(input) {
86
+ const { attestation } = input;
87
+ const now = input.now ?? Math.floor(Date.now() / 1000);
88
+ if (attestation.expiresAt <= now)
89
+ throw new QuoteError("the attestation has expired");
90
+ if (input.binding) {
91
+ const refusal = underwritingCapRefusal({
92
+ binding: input.binding,
93
+ coverAmount: input.coverAmount,
94
+ existingCover: input.existingCover ?? 0n,
95
+ ...(input.coverCaps ? { caps: input.coverCaps } : {}),
96
+ });
97
+ if (refusal)
98
+ throw new QuoteError(refusal);
99
+ }
100
+ const pricing = priceCover({
101
+ expectedLossBps: attestation.expectedLossBps,
102
+ score: attestation.score,
103
+ coverAmount: input.coverAmount,
104
+ bond: input.bond,
105
+ existingCover: input.existingCover,
106
+ });
107
+ const quote = Quote.parse({
108
+ agentId: attestation.agentId,
109
+ vault: input.vault,
110
+ coverAmount: input.coverAmount.toString(),
111
+ premiumRateBps: pricing.premiumRateBps,
112
+ score: attestation.score,
113
+ modelFamily: attestation.modelFamily,
114
+ mandateHash: attestation.mandateHash,
115
+ reportHash: attestation.reportHash,
116
+ minBond: pricing.minBond.toString(),
117
+ expiresAt: Math.min(now + (input.ttlSeconds ?? QUOTE_TTL_SECONDS), attestation.expiresAt),
118
+ nonce: (input.nonce ?? randomNonce()).toString(),
119
+ });
120
+ return { quote, pricing };
121
+ }
122
+ export async function signQuote(quote, options) {
123
+ const account = privateKeyToAccount(options.attesterKey);
124
+ const signature = await account.signTypedData(quoteTypedData(quoteDomain(options), quote));
125
+ return {
126
+ quote: Quote.parse(quote),
127
+ signature,
128
+ signer: account.address,
129
+ chainId: options.chainId,
130
+ coverManager: options.coverManager,
131
+ };
132
+ }
133
+ /**
134
+ * Checks a signed quote offline: the signer must be one of `trustedAttesters` (for example,
135
+ * the registry's attesters) and the signature must be valid. The `signer` field travels with
136
+ * the quote, so checking the signature alone would accept a quote anyone signed for themselves.
137
+ */
138
+ export async function verifyQuote(signed, trustedAttesters) {
139
+ const trusted = trustedAttesters.some((a) => a.toLowerCase() === signed.signer.toLowerCase());
140
+ if (!trusted)
141
+ return false;
142
+ return verifyTypedData({
143
+ address: signed.signer,
144
+ signature: signed.signature,
145
+ ...quoteTypedData(quoteDomain(signed), signed.quote),
146
+ });
147
+ }
148
+ /** A quote that can't be issued (uninsurable score, bond too small, expired attestation). */
149
+ export class QuoteError extends Error {
150
+ name = "QuoteError";
151
+ }
152
+ function quoteDomain(o) {
153
+ return bondedDomain(o.chainId, o.coverManager);
154
+ }
155
+ /** Rounds up, so rounding never undercharges the pool. */
156
+ function toRateBps(rate) {
157
+ // Strip float noise first (0.0156 * 10_000 = 156.00000000000003 must not become 157).
158
+ return Math.min(10_000, Math.ceil(Number((rate * 10_000).toFixed(6))));
159
+ }
160
+ function ceilDiv(a, b) {
161
+ return (a + b - 1n) / b;
162
+ }
163
+ function randomNonce() {
164
+ const bytes = crypto.getRandomValues(new Uint8Array(16));
165
+ return BigInt(`0x${Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("")}`);
166
+ }
167
+ //# sourceMappingURL=quote.js.map
@@ -0,0 +1,89 @@
1
+ import type { AttackClassId, AttackSurface, BreachType } from "./schemas/attack.js";
2
+ /**
3
+ * What to change after a breach, by attack class and breach type: a fixed table, not model
4
+ * output, so the same breach always gets the same advice. A rating report attaches these to each
5
+ * breached episode (`evidence[].fixes`) and ranks them (`topFixes`); the web app and the docs read
6
+ * the same table. A fix is advice: whether it works is measured by rating the agent again.
7
+ */
8
+ export type FixKind = "prompt" | "mandate" | "guard" | "sentinel";
9
+ export type FixId = "prompt_untrusted_content" | "prompt_operator_only" | "prompt_oracle_prices" | "prompt_allowed_tokens" | "prompt_no_copied_addresses" | "prompt_counterparty" | "prompt_limits" | "mandate_enforce" | "mandate_destinations" | "mandate_router_only" | "mandate_per_trade_cap" | "mandate_daily_cap" | "mandate_assets" | "sdk_guard" | "sentinel";
10
+ export interface Fix {
11
+ id: FixId;
12
+ kind: FixKind;
13
+ title: string;
14
+ /** What to do, in a sentence or two. */
15
+ detail: string;
16
+ /** For a system-prompt fix: the line to add, as is. */
17
+ promptLine?: string;
18
+ /** For a mandate fix: the mandate field it changes (as in `EvmMandateRules`). */
19
+ setting?: string;
20
+ }
21
+ export declare const FIXES: Readonly<Record<FixId, Fix>>;
22
+ /** The fixes for each attack class, most direct first. */
23
+ export declare const FIXES_BY_CLASS: Readonly<Record<AttackClassId, readonly FixId[]>>;
24
+ /** The fixes for each breach type the vault records. */
25
+ export declare const FIXES_BY_BREACH: Readonly<Record<BreachType, readonly FixId[]>>;
26
+ /**
27
+ * The fixes for one breached episode: its class's, then its breach types', without ones already
28
+ * in place (Enforce mode when the vault runs it, Sentinel when the rating ran with it).
29
+ */
30
+ export declare function fixesFor(input: {
31
+ attackClass: AttackClassId | null;
32
+ breachTypes: readonly BreachType[];
33
+ mode?: "monitor" | "enforce";
34
+ sentinel?: boolean;
35
+ }): Fix[];
36
+ /** A fix as a report lists it: with how many of its breaches it addresses. */
37
+ export interface ReportFix extends Fix {
38
+ /** Breaches in the report whose evidence lists this fix. Advice, not measured. */
39
+ prevents: number;
40
+ }
41
+ /** Every fix the evidence lists, most breaches addressed first; ties keep the table's order. */
42
+ export declare function rankFixes(evidence: readonly {
43
+ fixes: readonly FixId[];
44
+ }[]): ReportFix[];
45
+ /** The three fixes that address the most breaches, by id. */
46
+ export declare const topFixIds: (ranked: readonly ReportFix[], limit?: number) => FixId[];
47
+ /**
48
+ * One breach in a finished rating's report (`RatingReport.evidence`): the attack and the exact
49
+ * planted text (unless it is a reused unpublished attack), the action the agent took, what the
50
+ * vault recorded, and the fixes (ids into `FIXES`) for its class and breach type.
51
+ */
52
+ export interface BreachEvidence {
53
+ episodeId: string;
54
+ attackId: string | null;
55
+ class: AttackClassId | null;
56
+ className: string | null;
57
+ /** Where the attack was planted (the first surface; `surfaces` has them all). */
58
+ surface: AttackSurface | null;
59
+ surfaces: AttackSurface[];
60
+ /** The exact text planted. Absent when withheld (`withheld`). */
61
+ plantedText?: string;
62
+ /** Who it appeared to come from, and for token metadata the token. */
63
+ sender?: string;
64
+ token?: string;
65
+ /** An attack from a held-out set (made for this rating alone, or shared and then withheld). */
66
+ heldOut: boolean;
67
+ /** The text is withheld: an accepted bounty or the shared held-out set, reused by later ratings. */
68
+ withheld?: true;
69
+ /** Whether the agent read a place the attack was planted. */
70
+ read: boolean | null;
71
+ /** The action that caused the breach (absent for a breach found later, e.g. a drained approval). */
72
+ action?: {
73
+ tool: string;
74
+ /** Amounts, tokens and addresses only, never free text. */
75
+ args?: Record<string, string>;
76
+ /** An `execute` the agent signed itself, decoded into swap, transfer or approve. */
77
+ decoded: boolean;
78
+ };
79
+ breachType: BreachType;
80
+ lossUsd: number;
81
+ blocked: boolean;
82
+ /** Taken afterwards with approvals the agent granted (the episode's, on its first breach). */
83
+ drained?: {
84
+ token: string;
85
+ amount: string;
86
+ }[];
87
+ fixes: FixId[];
88
+ }
89
+ //# sourceMappingURL=report-fixes.d.ts.map