Tasks 2 and 3 verified their work with throwaway scripts against a real
spawned server that were never committed, so npm test protected none of it -
index.ts's auth gate had zero automated coverage, and the @ts-nocheck
agent-network.ts/self-improvement.ts rate-limit wiring had none beyond
listTools().
Adds two hermetic integration suites (real spawned server, localhost only,
no wallet/chain calls):
- auth-gating.test.ts: both HTTP routes' 401/403 gating, the auth-before-
session-lookup ordering, cross-identity rejection, token-leak scanning,
and that the server survives all of it (the reviewer's severe finding -
a missing early `return` after a 401 falls through into a second
writeHead() and crashes the whole process via an unhandled rejection).
- rate-limit-gating.test.ts: per-identity isolation and that exhaustion on
a vector.ts tool also rate-limits agent-network.ts and self-improvement.ts
tools for that identity, through the real registered tools rather than
limiterFor() in isolation.
Extends test/setup.ts additively (spawnServer/stopRawServer/RawServerHandle)
so these tests can set MCP_AUTH_TOKENS and drive per-request Authorization
headers by hand; startServer()/stopServer()/callTool() are unchanged and the
existing run.test.ts still fails for exactly its pre-existing reason.
Wires a new test:integration:auth script into CI's build-and-test job.
All five mutations from the review were applied to a working-tree copy,
built, and reverted per-mutation to prove the new tests actually fail:
- removing either route's early `return` after a 401: caught (cascading
ECONNREFUSED once the process crashes; a dedicated liveness test targets
this directly)
- the cross-identity check's undefined-lookup branch: not independently
catchable - transports/sessionIdentities are always populated and
cleared together, synchronously, so that state is unreachable via HTTP;
documented in the report, verified a same-region operator-flip mutation
IS caught cleanly
- module-scope `identity` reference in self-improvement.ts: crashes the
server at startup (ReferenceError at import time), caught via the
before() hook
- checkRateLimit restored to module scope in agent-network.ts: caught
cleanly, isError:true / "rateLimiter is not defined" instead of the
well-formed rate-limit text
No file under any src/ changed. test:unit 67/67, test:smoke 26/26 (23
tools, 0 drift), typecheck clean, new suite 12/12.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Includes an opt-in testnet round-trip test (keyless build -> decode -> policy
-> sign, never submits), excluded from CI because it needs a funded wallet.
Run against the real funded testnet wallet: pass 3, fail 0, nothing submitted.
Verified every README claim against the code as implemented, not just the
brief text, and corrected several:
- VECTOR_SIGNER_AUDIT_LOG_PATH's default resolves against the package's own
install location (repo root), not the process's cwd as originally stated --
and that default filename is not yet covered by .gitignore.
- An audit-log write failure blocks ALL signing, not just accounting:
audit.append() throws by design and vector_signer_sign withholds an
already-produced signature rather than release one with no durable record.
- Documented the #private-field defense against accidental key leakage via
JSON.stringify/console.log, and the read-only/sign tool split that keeps
the signing key out of scope for get_address/decode/get_spend_limits.
- Documented that pairing this signer with the CURRENT hosted builder does
not yet remove the custody exposure it's designed to fix: no builder tool
is keyless yet, and there is no submit_transaction tool to hand a produced
signature to. That's the next stage of the migration, not shipped here.
Env-var cross-check (brief's Step 6, both directions) passes clean.
stdio transport only - no HTTP listener, no Provider, no outbound network.
Wires together decode.ts/policy.ts/sign.ts/audit.ts/keysource.ts (Tasks 2-6)
behind four tools: vector_signer_get_address, vector_signer_decode_transaction,
vector_signer_sign, vector_signer_get_spend_limits.
Two corrections needed beyond the brief's given code to reach its own stated
"pass 9, fail 0" outcome, both verified necessary by reproducing the failure
first:
- packages/signer/tsup.config.ts: target bumped es2020 -> es2022. index.ts's
top-level `await server.connect(...)` is ES2022 syntax; esbuild hard-errors
("Top-level await is not available in the configured target environment")
under the package's original es2020 target rather than attempting an unsafe
downlevel transform. Scoped to the signer package only - the builder never
uses top-level await and its own tsup.config.ts is untouched.
- tools.ts's decode_transaction response text: "(not signed)" and "would be
signed" both contain the substring "signed", which collides with this same
smoke test's own `!/signed/i.test(text)` check on every call, including the
refused case. Reworded to "(read-only)" / "would be ALLOWED" - same meaning,
no collision. Fixed the response text rather than loosening the test, which
stays exactly as given.
Brief's own smoke test (9 tests) passes verbatim. Workspace-wide: typecheck 0,
test:unit 180/180 (no regressions), builder test:smoke still 26/23/0.
The audit-append-failure handling required by the task (a produced signature
must never be released if its audit record fails to write) is not yet in this
commit - see the follow-up commit.
The signer's security claim is that it cannot reach the network, not that it
chooses not to. test/boundary.test.ts enforces that mechanically over both the
import graph and the manifest.
Extracts pure tx helpers, shared types, endpoint config, and the Ogmios
provider. Subpath exports (./tx vs ./provider) let the forthcoming signer
import pure helpers while being structurally unable to reach the network layer.
Every PR in the non-custodial-split series is supposed to preserve
`tools/list` returning 23 tools with identical schemas, but nothing
enforced it — PRs 2, 6, 7 and 8 will restructure and rename tool
families, and a snapshot only means something if it's taken before
that starts. Boot the built server, call listTools(), and assert both
the count and the exact sorted name list against a checked-in
baseline captured from a live run on this branch. Failures report
which names were added/removed rather than a bare array-diff.
Reuses startServer()/stopServer() from test/setup.ts; does not call
getMnemonic(), so it needs no wallet. Wired into CI as a new step
after the unit tests. Also raises the server-start timeout in
setup.ts from 15s to 60s — the Cardano WASM import makes cold starts
slow enough that 15s was flaky on the smoke test's first CI run.
test:smoke is intentionally separate from test:unit (which does not
build) since startServer() spawns build/index.js.
- Add 5 new MCP tools: vector_build_transaction, vector_dry_run,
vector_get_transaction_history, vector_deploy_contract,
vector_interact_contract
- Persist audit log to JSON file with daily spend recovery on restart
- Add evaluateTransaction and getTransactionHistory to OgmiosProvider
- Support PlutusV1/V2/V3 contract deployment and interaction
- Update docs, types, and bump build heap to 12GB for lucid-cardano
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>