/* RFC-0286: determinism + shape tests for the Agent Surface Manifest builder. Not a contract source of truth — the module under test owns the authoritative behavior; fixtures are minimal scaffolding only. RFC-0286: initial builder tests. RFC-0954: add search interface tests. RFC-1113: align agent discovery surface with RFC 9727 and MCP 2026-07-28 stateless era RFC-1114: add a2a interface + supportedProtocols tests (AC-5). RFC-1114: minimal real A2A endpoint + honest agent card */ import { test, expect } from "vitest"; import { AGENT_SURFACE_SCHEMA_ID, AGENT_SURFACE_VERSION, buildAgentSurfaceManifest, computeAgentManifestContentHash, computeSignedContentHash, canonicalJson, } from "../manifest.ts"; const baseInput = { site: "warpgogol-com", baseUrl: "https://warpgogol.com/", languages: { default: "de", supported: ["uk", "de"] }, generatedAt: "2026-01-01T00:00:00.000Z", }; test("buildAgentSurfaceManifest: strips trailing slash and sorts supported languages", () => { const m = buildAgentSurfaceManifest(baseInput); expect(m.baseUrl).toBe("https://warpgogol.com"); expect(m.languages.supported).toEqual(["de", "uk"]); }); test("buildAgentSurfaceManifest: safe default — empty knowledge/actions, null interfaces", () => { const m = buildAgentSurfaceManifest(baseInput); expect(m.knowledge).toEqual([]); expect(m.actions).toEqual([]); expect(m.interfaces.openapi).toBe(null); expect(m.interfaces.mcp).toBe(null); expect(m.interfaces.a2a).toBe(null); expect(m.interfaces.twins).toBe(null); expect(m.interfaces.search).toBe(null); // RFC-1164: schemamap is unconditional — same precedent as interfaces.llms. expect(m.interfaces.schemamap).toBe("/schemamap.xml"); expect(m.proof).toBe(null); expect(m.surfaceVersion).toBe(AGENT_SURFACE_VERSION); }); test("buildAgentSurfaceManifest: is deterministic across repeated calls (byte-identical)", () => { const a = buildAgentSurfaceManifest(baseInput); const b = buildAgentSurfaceManifest(baseInput); expect(JSON.stringify(a)).toBe(JSON.stringify(b)); expect(a.contentHash).toBe(b.contentHash); }); test("buildAgentSurfaceManifest: sorts knowledge refs by domain and actions by id", () => { const m = buildAgentSurfaceManifest({ ...baseInput, knowledge: [ { domain: "offer", url: "/api/agent/v1/offer.json", schema: "gogol.agent.knowledge/offer@1" }, { domain: "company", url: "/api/agent/v1/company.json", schema: "gogol.agent.knowledge/company@1", }, ], actions: [ { id: "lead.submit", url: "/api/agent/v1/actions/lead.submit", title: { de: "Anfrage senden" }, inputSchemaRef: "#/components/schemas/lead.submit-input", entitlement: "agent.actions", }, { id: "appointment.request", url: "/api/agent/v1/actions/appointment.request", title: { de: "Termin anfragen" }, inputSchemaRef: "#/components/schemas/appointment.request-input", entitlement: "agent.actions", }, ], }); expect(m.knowledge.map((k) => k.domain)).toEqual(["company", "offer"]); expect(m.actions.map((a) => a.id)).toEqual(["appointment.request", "lead.submit"]); }); test("buildAgentSurfaceManifest: populates search interface when search input is provided", () => { const m = buildAgentSurfaceManifest({ ...baseInput, search: { url: "/api/agent/search", model: "@cf/baai/bge-m3", dimensions: 1024 }, }); expect(m.interfaces.search).toEqual({ url: "/api/agent/search", model: "@cf/baai/bge-m3", dimensions: 1024, }); }); test("buildAgentSurfaceManifest: search interface is null by default", () => { const m = buildAgentSurfaceManifest(baseInput); expect(m.interfaces.search).toBe(null); }); test("buildAgentSurfaceManifest: search interface presence changes contentHash", () => { const without = buildAgentSurfaceManifest(baseInput); const withSearch = buildAgentSurfaceManifest({ ...baseInput, search: { url: "/api/agent/search", model: "@cf/baai/bge-m3", dimensions: 1024 }, }); expect(without.contentHash).not.toBe(withSearch.contentHash); }); test("computeAgentManifestContentHash: excludes contentHash and proof fields from the hash input", () => { const doc: Record = { a: 1, b: 2, contentHash: "whatever", proof: { x: 1 } }; const withoutHashFields = { a: 1, b: 2 }; expect(computeAgentManifestContentHash(doc)).toBe( computeAgentManifestContentHash({ ...withoutHashFields, contentHash: "other", proof: null }), ); }); test("computeAgentManifestContentHash: is a 64-char lowercase hex sha256 digest", () => { const hash = computeAgentManifestContentHash({ a: 1 }); expect(hash).toMatch(/^[0-9a-f]{64}$/); }); test("canonicalJson: sorts object keys deeply, preserves array order", () => { expect(canonicalJson({ b: 1, a: { d: 2, c: 3 } })).toBe('{"a":{"c":3,"d":2},"b":1}'); expect(canonicalJson([3, 1, 2])).toBe("[3,1,2]"); }); test("computeSignedContentHash: always excludes contentHash and proof", () => { const base = { a: 1, b: 2 }; const withHash = { ...base, contentHash: "abc", proof: { type: "test" } }; const withDifferentHash = { ...base, contentHash: "xyz", proof: { type: "other" } }; expect(computeSignedContentHash(withHash)).toBe(computeSignedContentHash(base)); expect(computeSignedContentHash(withDifferentHash)).toBe(computeSignedContentHash(base)); }); test("computeSignedContentHash: supports additional excludeFields", () => { const base = { a: 1, b: 2 }; const withTimestamp = { ...base, generatedAt: "2026-01-01T00:00:00Z" }; const withDifferentTimestamp = { ...base, generatedAt: "2026-12-31T23:59:59Z" }; expect(computeSignedContentHash(withTimestamp, ["generatedAt"])).toBe( computeSignedContentHash(withDifferentTimestamp, ["generatedAt"]), ); }); test("computeSignedContentHash: matches computeAgentManifestContentHash for same inputs", () => { const doc = { a: 1, b: 2, contentHash: "x", proof: null }; expect(computeSignedContentHash(doc)).toBe(computeAgentManifestContentHash(doc)); }); // --------------------------------------------------------------------------- // RFC-1113: discovery metadata fields // --------------------------------------------------------------------------- test("buildAgentSurfaceManifest: emits $schema, generatedAt, supportedProtocols", () => { const m = buildAgentSurfaceManifest(baseInput); expect(m.$schema).toBe(AGENT_SURFACE_SCHEMA_ID); expect(m.generatedAt).toBe("2026-01-01T00:00:00.000Z"); // Unconditional discovery artifacts only — no interfaces were provided. expect(m.supportedProtocols).toEqual([ "agent-skills", "ard", "dns-aid", "linkset", "llms", "schemamap", ]); }); test("buildAgentSurfaceManifest: supportedProtocols follows emitted interfaces", () => { const m = buildAgentSurfaceManifest({ ...baseInput, hasTwins: true, openapiUrl: "/.well-known/agent.openapi.json", mcp: { url: "/api/agent/mcp", protocolVersion: "2026-07-28" }, search: { url: "/api/agent/search", model: "@cf/baai/bge-m3", dimensions: 1024 }, nlweb: "/ask", }); expect(m.interfaces.nlweb).toBe("/ask"); expect(m.supportedProtocols).toEqual([ "agent-skills", "ard", "dns-aid", "linkset", "llms", "mcp", "nlweb", "openapi", "schemamap", "search", "twins", ]); }); test("buildAgentSurfaceManifest: interfaces.nlweb is null when unset (RFC-1165)", () => { const m = buildAgentSurfaceManifest(baseInput); expect(m.interfaces.nlweb).toBeNull(); expect(m.supportedProtocols).not.toContain("nlweb"); }); // --------------------------------------------------------------------------- // RFC-1114: a2a interface (AC-5) // --------------------------------------------------------------------------- test("buildAgentSurfaceManifest: populates interfaces.a2a and lists a2a in supportedProtocols", () => { const m = buildAgentSurfaceManifest({ ...baseInput, a2a: { url: "/api/agent/a2a", protocolVersion: "1.0" }, }); expect(m.interfaces.a2a).toEqual({ url: "/api/agent/a2a", protocolVersion: "1.0" }); expect(m.supportedProtocols).toContain("a2a"); }); test("buildAgentSurfaceManifest: a2a absent from supportedProtocols when no a2a interface", () => { const m = buildAgentSurfaceManifest(baseInput); expect(m.interfaces.a2a).toBe(null); expect(m.supportedProtocols).not.toContain("a2a"); }); test("buildAgentSurfaceManifest: generatedAt does not change contentHash (AC-3)", () => { const a = buildAgentSurfaceManifest(baseInput); const b = buildAgentSurfaceManifest({ ...baseInput, generatedAt: "2026-12-31T23:59:59.000Z" }); expect(a.contentHash).toBe(b.contentHash); expect(a.generatedAt).not.toBe(b.generatedAt); }); test("computeAgentManifestContentHash: excludes generatedAt", () => { const doc = { a: 1, generatedAt: "2026-01-01T00:00:00Z" }; const other = { a: 1, generatedAt: "2026-06-06T06:06:06Z" }; expect(computeAgentManifestContentHash(doc)).toBe(computeAgentManifestContentHash(other)); });