mirror of
https://github.com/profullstack/logicsrc.git
synced 2026-10-03 05:07:10 +00:00
feat(ans): implement @logicsrc/ans M1 — resolver + offline verifier
M1 of the ANS SDK (docs/ans-sdk.md): - name: parse/format ans://v<semver>.<agent>.<domain> - cbor: minimal RFC 8949 codec for the COSE_Sign1 subset - verify/merkle: RFC 6962 leaf/node hashing, tree build, inclusion-proof generation + verification - verify/es256 + cose: COSE_Sign1 build/parse, Sig_structure, ES256 (WebCrypto) - verify/rootkeys: kid -> verifier key (JWKS entries; sumdb-note is M2) - verify: verifyReceipt() + verifyResolution() (signature + inclusion proof + name binding), pure and offline - client: AnsClient resolve/rootKeys/register/status over injectable fetch, with a DnsApplier hook for verify-dns Tests (24) cover name parsing, CBOR round-trips/vectors, RFC 6962 proofs, a full ES256+Merkle receipt round-trip with positive/negative cases, and the client against mocked fetch. Wired into the root build script. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
0ae24719e3
commit
29975b2df7
20 changed files with 1009 additions and 28 deletions
49
packages/ans/src/client.test.ts
Normal file
49
packages/ans/src/client.test.ts
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { AnsClient } from './client.js';
|
||||
|
||||
function jsonResponse(body: unknown): Response {
|
||||
return { ok: true, status: 200, statusText: 'OK', text: async () => JSON.stringify(body) } as unknown as Response;
|
||||
}
|
||||
|
||||
describe('AnsClient', () => {
|
||||
it('resolves a name and decodes the base64 receipt', async () => {
|
||||
const receiptB64 = btoa(String.fromCharCode(0xd2, 0x84, 0x40));
|
||||
const fetchMock = vi.fn(async () => jsonResponse({
|
||||
name: 'ans://v1.0.0.my-agent.example.com',
|
||||
endpoint: 'https://my-agent.example.com',
|
||||
capabilities: ['chat'],
|
||||
receipt: receiptB64,
|
||||
}));
|
||||
|
||||
const client = new AnsClient({ registryUrl: 'https://registry.ans.dev/', fetch: fetchMock as unknown as typeof fetch });
|
||||
const id = await client.resolve('ans://v1.0.0.my-agent.example.com');
|
||||
|
||||
expect(fetchMock).toHaveBeenCalledWith(
|
||||
'https://registry.ans.dev/v1/resolve/ans%3A%2F%2Fv1.0.0.my-agent.example.com',
|
||||
expect.objectContaining({ method: 'GET' }),
|
||||
);
|
||||
expect(id.name.agent).toBe('my-agent');
|
||||
expect(id.capabilities).toEqual(['chat']);
|
||||
expect([...id.receipt.cbor]).toEqual([0xd2, 0x84, 0x40]);
|
||||
});
|
||||
|
||||
it('register applies the DNS challenge via the supplied applier', async () => {
|
||||
const fetchMock = vi.fn(async () => jsonResponse({ challengeToken: 'tok-xyz', recordName: '_ans-challenge.my-agent.example.com' }));
|
||||
const upsertTxt = vi.fn(async () => {});
|
||||
|
||||
const client = new AnsClient({ registryUrl: 'https://registry.ans.dev', token: 't', fetch: fetchMock as unknown as typeof fetch });
|
||||
const reg = await client.register({ agent: 'my-agent', domain: 'example.com', version: '1.0.0', verify: 'dns', dns: { upsertTxt } });
|
||||
|
||||
expect(reg.name.raw).toBe('ans://v1.0.0.my-agent.example.com');
|
||||
expect(reg.challenge).toEqual({ type: 'TXT', name: '_ans-challenge.my-agent.example.com', value: 'tok-xyz' });
|
||||
expect(upsertTxt).toHaveBeenCalledWith({ name: '_ans-challenge.my-agent.example.com', value: 'tok-xyz' });
|
||||
const [, init] = fetchMock.mock.calls[0];
|
||||
expect((init as RequestInit).headers).toMatchObject({ Authorization: 'Bearer t' });
|
||||
});
|
||||
|
||||
it('throws on a non-OK response', async () => {
|
||||
const fetchMock = vi.fn(async () => ({ ok: false, status: 404, statusText: 'Not Found', text: async () => 'no such name' } as unknown as Response));
|
||||
const client = new AnsClient({ registryUrl: 'https://registry.ans.dev', fetch: fetchMock as unknown as typeof fetch });
|
||||
await expect(client.resolve('ans://v1.0.0.ghost.example.com')).rejects.toThrow('ANS GET /v1/resolve/');
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue