Adds the three remaining integration layers for spec PR 7 (agent-registry keyless): a tier-1 suite that exercises the five build_* tools against the live testnet registry with zero secrets (build-only, nothing signed or submitted), a gated full lifecycle E2E (register -> update -> deregister) that actually submits, and a shared signWithMnemonic() helper extracted from keyless-e2e.test.ts so registry-e2e.test.ts and run.test.ts's registry section don't each re-inline the same CML signing sequence. run.test.ts's "Agent Network Tools" section is rewritten: each of the five old custodial calls is now build_* -> signWithMnemonic -> submit, via a new buildSignSubmit() helper that reuses the file's existing assertSuccessOrKnownError convention at both the build and submit legs. Found and fixed one load-bearing bug while proving this live, not just in review: the inline signing block moved verbatim per the brief's Step 1 built the signed tx's witness set from scratch, which is lossless only for a script-less payment (the one case it had been proven against). The registry family's build_* tools attach a minting policy and/or spending validator, so their unsigned CBOR already carries scripts/redeemers that the from-scratch witness set silently dropped - the ledger rejected the first live E2E attempt with MissingScriptWitnessesUTXOW / PPViewHashesDontMatch. Verified zero funds were lost (balance identical before/after the rejected submission - a mempool-level rejection never reaches a block) before fixing it by merging into the tx's existing witness set via CML's add_all_witnesses instead of discarding it. Re-run clean: all 3 E2E transactions confirmed on-chain, plus all 5 of the legacy suite's registry operations submitted successfully too (independent confirmation across both the mint and spend redeemer paths). Also widened one tier-1 regex that didn't tolerate the live tool's Markdown-bold label formatting. Live evidence (testnet, funded wallet, 8 real transactions total across the gated E2E and the legacy suite): balance 986.689510 -> 982.280960 AP3X, fully reconciled against transaction fees plus one message delivery to an owner-derived enterprise address. All tx hashes, explorer links, and the full reconciliation are in task-5-report.md. mnemonic.txt and the two temporary evidence-capture scripts were removed and confirmed absent before this commit. Gates: typecheck 0, unit 350/350, smoke 26/26, auth 12/12, boundary 4/4 - all re-verified after cleanup. E2E skip-path (VECTOR_E2E_SUBMIT unset) exits 0 with tests 0 for both keyless-e2e.test.ts and registry-e2e.test.ts, confirmed with mnemonic.txt genuinely absent. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
84 lines
3.9 KiB
TypeScript
84 lines
3.9 KiB
TypeScript
import { describe, test, before, after } from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { startServer, stopServer, callTool, getMnemonic, wait, ServerContext } from '../setup.ts';
|
|
import { signWithMnemonic } from './sign-helper.ts';
|
|
import { walletFromSeed } from '@lucid-evolution/lucid';
|
|
|
|
// FULL REGISTRY LIFECYCLE ON LIVE TESTNET - gated. Registers a real agent,
|
|
// updates it, deregisters it. Net cost ~= 3 tx fees (~0.6 AP3X); the 10 AP3X
|
|
// deposit round-trips. Requires packages/builder/mnemonic.txt (never committed).
|
|
// VECTOR_E2E_SUBMIT=1 node --import tsx --test packages/builder/test/integration/registry-e2e.test.ts
|
|
const GATE = process.env.VECTOR_E2E_SUBMIT === '1';
|
|
|
|
function extractCbor(text: string): string {
|
|
const m = text.match(/```\n([0-9a-f]+)\n```/);
|
|
assert.ok(m, `no CBOR block:\n${text.slice(0, 500)}`);
|
|
return m![1];
|
|
}
|
|
function extractTxHash(text: string): string {
|
|
const m = text.match(/Transaction Hash:\s*([0-9a-f]{64})/);
|
|
assert.ok(m, `no tx hash:\n${text.slice(0, 500)}`);
|
|
return m![1];
|
|
}
|
|
|
|
(GATE ? describe : describe.skip)('registry lifecycle E2E (live testnet, SUBMITS)', () => {
|
|
let ctx: ServerContext;
|
|
let mnemonic: string;
|
|
let walletAddress: string;
|
|
let agentDid: string;
|
|
|
|
before(async () => {
|
|
ctx = await startServer();
|
|
mnemonic = getMnemonic();
|
|
walletAddress = walletFromSeed(mnemonic, { network: 'Mainnet', accountIndex: 0 }).address;
|
|
});
|
|
after(async () => { await stopServer(ctx); });
|
|
|
|
async function signSubmitAwait(buildText: string): Promise<string> {
|
|
const { signedCborHex, txHash } = signWithMnemonic(extractCbor(buildText), mnemonic);
|
|
const submitText = await callTool(ctx.client, 'vector_submit_transaction', { signedTxCbor: signedCborHex });
|
|
const submittedHash = extractTxHash(submitText);
|
|
assert.equal(submittedHash, txHash, 'submitted hash != locally computed hash');
|
|
const awaitText = await callTool(ctx.client, 'vector_await_transaction', { txHash: submittedHash, timeoutSeconds: 240 });
|
|
assert.match(awaitText, /Transaction Confirmed/);
|
|
return submittedHash;
|
|
}
|
|
|
|
test('register -> confirm -> profile readable', { timeout: 420_000 }, async () => {
|
|
const buildText = await callTool(ctx.client, 'vector_build_register_agent', {
|
|
changeAddress: walletAddress, name: `E2EAgent-${Date.now()}`,
|
|
description: 'registry e2e lifecycle agent', capabilities: ['testing', 'e2e'], framework: 'custom', endpoint: '',
|
|
});
|
|
const didMatch = buildText.match(/(did:vector:agent:[a-f0-9]+:[a-f0-9]+)/);
|
|
assert.ok(didMatch, 'no DID in register build');
|
|
agentDid = didMatch![1];
|
|
await signSubmitAwait(buildText);
|
|
await wait(5);
|
|
const profile = await callTool(ctx.client, 'vector_get_agent_profile', { agent_id: agentDid });
|
|
assert.match(profile, /registry e2e lifecycle agent/);
|
|
});
|
|
|
|
test('update -> confirm -> profile shows the change', { timeout: 420_000 }, async () => {
|
|
await wait(10); // let register's UTxOs settle
|
|
const buildText = await callTool(ctx.client, 'vector_build_update_agent', {
|
|
changeAddress: walletAddress, agent_id: agentDid, description: 'updated by registry e2e',
|
|
});
|
|
await signSubmitAwait(buildText);
|
|
await wait(5);
|
|
const profile = await callTool(ctx.client, 'vector_get_agent_profile', { agent_id: agentDid });
|
|
assert.match(profile, /updated by registry e2e/);
|
|
});
|
|
|
|
test('deregister -> confirm -> agent gone', { timeout: 420_000 }, async () => {
|
|
await wait(10);
|
|
const buildText = await callTool(ctx.client, 'vector_build_deregister_agent', {
|
|
changeAddress: walletAddress, agent_id: agentDid,
|
|
});
|
|
assert.match(buildText, /Deposit returned on confirmation/);
|
|
await signSubmitAwait(buildText);
|
|
await wait(5);
|
|
const profile = await callTool(ctx.client, 'vector_get_agent_profile', { agent_id: agentDid });
|
|
assert.match(profile, /Agent not found|Failed to get agent profile/);
|
|
});
|
|
});
|