fix(builder): close boundary-test gaps, drop dangling spend-limits copy, guard tool descriptions end-to-end

Addresses opus reviewer findings on the Task 5 commit (33c4b52).

Important:
- agent-network.ts (3 sites) and self-improvement.ts (2 sites): response
  copy no longer tells a caller to run vector_get_spend_limits (deleted in
  Task 5) after a safety-limit error. Replaced with
  "Server spend limits for this tool are set via VECTOR_SPEND_LIMIT_PER_TX /
  VECTOR_SPEND_LIMIT_DAILY" (matching each site's existing phrasing).
  Nothing else in either file touched - both stay custodial until PRs 7/8.
  Also reworded the two comments (of my own, from Task 5) that named the
  deleted tool - tool-inventory.test.ts's header docblock and
  rate-limit-gating.test.ts's probe-choice comment - so a final
  `grep -rn vector_get_spend_limits packages/` is clean except
  run.test.ts (Task 6's, left untouched as instructed).
- boundary.test.ts: added a third test pinning
  CUSTODIAL_UNTIL_LATER_PR.size === 2, so PR 7/8 shrinking it is a visible,
  reviewable number change rather than a silent list edit.
- boundary.test.ts: FORBIDDEN is no longer word-bounded
  (`/(mnemonic|seed|bip39|fromSeed|walletFromSeed|selectWallet\.fromSeed)/i`,
  \b dropped) so it catches `seedPhrase`, `WALLET_SEED`, `bip39x`-shaped
  vocabulary, not just whole-word matches. Verified empirically (not just by
  inspection) that this introduces zero new hits outside the two allowlisted
  files - grepped `seed`/`bip39`/`mnemonic` case-insensitively across all of
  packages/builder/src first, confirmed every hit was already confined to
  agent-network.ts/self-improvement.ts, then reran the full boundary suite
  to prove it: no benign collisions, nothing to report back before
  committing. Added a fourth test pinning every FORBIDDEN alternative
  against a fixture corpus (mnemonic/MNEMONIC/seedPhrase/WALLET_SEED/bip39/
  fromSeed/walletFromSeed/selectWallet.fromSeed/seed all match;
  "address-only wallet"/"unsigned transaction"/"changeAddress" don't) so
  deleting an alternative from the regex fails a named test instead of
  silently narrowing the guard.

Cheap folds:
- vector.ts: the five build_* tool DESCRIPTIONS (not response copy - that
  already taught the confirm step as of Task 5) now end with "...then
  broadcast with vector_submit_transaction and confirm with
  vector_await_transaction." Regenerated the snapshot; verified structurally
  that the delta is description-field-only on exactly those five entries
  (inputSchema untouched, all 23 keys unchanged) - this is also what makes
  the Task 5 addition-A docblock's "descriptions teach the full flow" claim
  actually true end-to-end.
- tool-inventory.test.ts: each per-tool test now also asserts
  'description' in schemaSnapshot[toolName] before the deepStrictEqual, so
  a regen that silently drops a description (e.g. JSON.stringify eating an
  undefined value) fails by naming the tool, not as an opaque diff.
- regen-tool-schemas.mjs: server entry point and snapshot output path are
  now resolved from import.meta.dirname instead of hardcoded
  repo-root-relative strings, so the script runs from any cwd. Verified by
  actually invoking it from packages/builder (not the repo root) and
  confirming it wrote to the one correct
  packages/builder/test/smoke/tool-schemas.snapshot.json.

Full gate re-run after all of the above: typecheck 0 errors, test:unit
144/144 (140 baseline + 4 boundary tests, up from 2), test:smoke 26/26,
test:integration:auth 12/12.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
FilipBl4gojevic
2026-07-29 13:13:16 +02:00
parent 6bbeb6ca7c
commit a9d356fd26
8 changed files with 78 additions and 24 deletions

View File

@@ -7,9 +7,10 @@ import { startServer, stopServer, ServerContext } from '../setup.ts';
/**
* Snapshot of the current tool surface (23 tools) for the non-custodial split
* series (see docs/architecture/non-custodial-split.md). PR 6 (Task 5) is the
* PR that took this list from 25 to 23: it deleted `vector_get_address` and
* `vector_get_spend_limits` outright (the safety-layer/wallet-info tools have
* no keyless equivalent — spend limits are gone because the builder no longer
* PR that took this list from 25 to 23: it deleted `vector_get_address`
* outright, along with the tool that reported per-transaction/daily
* SafetyLayer spend status (the safety-layer/wallet-info tools have no
* keyless equivalent — spend status is gone because the builder no longer
* holds keys to limit) as part of making the remaining query tools
* (`vector_get_utxos`, `vector_get_transaction_history`, `vector_dry_run`)
* keyless. PRs 2, 6, 7 and 8 each deliberately restructure and rename tool
@@ -141,6 +142,14 @@ describe('tool schema snapshot', () => {
live,
`Tool "${toolName}" is in the snapshot but was not returned by the live server's listTools().`
);
// Named separately from the deepStrictEqual below: a regen that
// silently drops descriptions (e.g. JSON.stringify dropping an
// `undefined` description key) should fail by naming exactly that,
// not just show up as an opaque diff.
assert.ok(
'description' in (schemaSnapshot[toolName] as any),
`Snapshot entry for "${toolName}" has no "description" key in test/smoke/tool-schemas.snapshot.json.`
);
assert.deepStrictEqual(
{ description: live.description, inputSchema: live.inputSchema },
schemaSnapshot[toolName],

View File

@@ -22,7 +22,7 @@
}
},
"vector_build_deploy_contract": {
"description": "Build an UNSIGNED transaction deploying a Plutus/Aiken smart contract (locks funds at its script address). Takes no key material - sign with your local signer, then broadcast with vector_submit_transaction.",
"description": "Build an UNSIGNED transaction deploying a Plutus/Aiken smart contract (locks funds at its script address). Takes no key material - sign with your local signer, then broadcast with vector_submit_transaction and confirm with vector_await_transaction.",
"inputSchema": {
"type": "object",
"properties": {
@@ -61,7 +61,7 @@
}
},
"vector_build_interact_contract": {
"description": "Build an UNSIGNED transaction interacting with a deployed Plutus/Aiken contract - lock AP3X at it or spend from it. Takes no key material - sign with your local signer, then broadcast with vector_submit_transaction.",
"description": "Build an UNSIGNED transaction interacting with a deployed Plutus/Aiken contract - lock AP3X at it or spend from it. Takes no key material - sign with your local signer, then broadcast with vector_submit_transaction and confirm with vector_await_transaction.",
"inputSchema": {
"type": "object",
"properties": {
@@ -139,7 +139,7 @@
}
},
"vector_build_send_apex": {
"description": "Build an UNSIGNED transaction sending AP3X to a recipient. Takes no key material - sign the returned CBOR with your local signer (vector_signer_sign), then broadcast with vector_submit_transaction.",
"description": "Build an UNSIGNED transaction sending AP3X to a recipient. Takes no key material - sign the returned CBOR with your local signer (vector_signer_sign), then broadcast with vector_submit_transaction and confirm with vector_await_transaction.",
"inputSchema": {
"type": "object",
"properties": {
@@ -170,7 +170,7 @@
}
},
"vector_build_send_tokens": {
"description": "Build an UNSIGNED transaction sending Vector native tokens. Takes no key material - sign the returned CBOR with your local signer (vector_signer_sign), then broadcast with vector_submit_transaction.",
"description": "Build an UNSIGNED transaction sending Vector native tokens. Takes no key material - sign the returned CBOR with your local signer (vector_signer_sign), then broadcast with vector_submit_transaction and confirm with vector_await_transaction.",
"inputSchema": {
"type": "object",
"properties": {
@@ -210,7 +210,7 @@
}
},
"vector_build_transaction": {
"description": "Build an UNSIGNED multi-output transaction with optional metadata. Takes no key material and never submits - sign the returned CBOR with your local signer (vector_signer_sign), then broadcast with vector_submit_transaction.",
"description": "Build an UNSIGNED multi-output transaction with optional metadata. Takes no key material and never submits - sign the returned CBOR with your local signer (vector_signer_sign), then broadcast with vector_submit_transaction and confirm with vector_await_transaction.",
"inputSchema": {
"type": "object",
"properties": {