import assert from "node:assert/strict"; import { test } from "node:test"; import { compileCapabilityResourceSource } from "../src/capability-language/index.js"; import { compileQuery } from "../src/query/compile.js"; import { QueryCompileError } from "../src/query/types.js"; import { checkQueryTemplate } from "../src/query/templates.js"; import { querySelectionToWire } from "../src/query/proto.js"; import type { InterfaceRevision } from "../src/capability-model/types.js"; const source = { repository: "https://example.test/queries.git", commit: "a".repeat(40) }; const iface = (text: string, dependencies: InterfaceRevision[] = []) => { const compiled = compileCapabilityResourceSource(text, { source, environment: { interfaces: new Map(dependencies.map((entry) => [entry.displayName, entry])), interfaceClosure: dependencies, }, }); assert.ok(compiled.ok, JSON.stringify(compiled.diagnostics)); assert.equal(compiled.resource.kind, "interface"); if (compiled.resource.kind !== "interface") throw new Error("interface required"); return compiled.resource.revision; }; const facts = iface(`interface TaskFacts id "facts" revision "facts@1" { queryable value title id "title" : string { get id "title:get"; } queryable value done id "done" : bool { get id "done:get"; } queryable value due id "due" : optional { get id "due:get"; } queryable rpc value score id "score" : int32 { get id "score:get"; } value hidden id "hidden" : string { get id "hidden:get"; } }`); const collection = iface( `import interface TaskFacts; interface Tasks id "tasks" revision "tasks@1" { queryable relation items id "items" : many interface TaskFacts ordered { resolve id "items:resolve"; } }`, [facts], ); test("generic query templates check declared bounds before closed specialization", async () => { const generic = iface( `import interface TaskFacts; interface Collection id "generic-collection" revision "generic-collection@1" { queryable relation items id "items" : many object Item { resolve id "items:resolve"; } }`, [facts], ); const build = (bound: string, workspace = false) => compileCapabilityResourceSource( `import interface Collection; import interface TaskFacts; package GenericQueries id "generic-queries" revision "generic-queries@1" { query Rows id "rows-template" root ${workspace ? "workspace " : ""}Collection document "rows.graphql" operation "Rows" { view object Item as TaskFacts; max rows 30; } query TaskRows id "task-rows" specialize Rows; }`, { source, environment: { interfaces: new Map([ ["Collection", generic], ["TaskFacts", facts], ]), interfaceClosure: [generic, facts], }, }, ); const result = build("implements TaskFacts"); assert.ok(result.ok, JSON.stringify(result.diagnostics)); if (result.resource.kind !== "package") throw new Error("package expected"); const pkg = result.resource.revision; assert.equal(pkg.queries?.length, 1); assert.equal(pkg.queryTemplates?.length, 1); assert.equal(pkg.queries![0]!.application!.templateId, "rows-template"); const read = async () => `query Rows { root { items(first: 3) { entries { node { title } } } } }`; const universal = await checkQueryTemplate(pkg.queryTemplates![0]!, [generic, facts], read); assert.equal( universal.effects.some((effect) => effect.memberId === "title"), true, ); const specialized = await compileQuery( pkg.queries![0]!, [generic, facts, ...(result.resource.specializations ?? [])], read, ); assert.equal( specialized.effects.some((effect) => effect.memberId === "title"), true, ); const invalid = build(""); assert.ok(invalid.ok, JSON.stringify(invalid.diagnostics)); if (invalid.resource.kind !== "package") throw new Error("package expected"); await assert.rejects( checkQueryTemplate(invalid.resource.revision.queryTemplates![0]!, [generic, facts], read), /bound|guarantee|implement/i, ); const extent = build("implements TaskFacts", true); assert.ok(extent.ok, JSON.stringify(extent.diagnostics)); if (extent.resource.kind !== "package") throw new Error("package expected"); const extentPackage = extent.resource.revision; const readExtent = async () => `query Rows {root {objects(first: 2) {entries {node {items(first: 3) {entries {node {title}}}}}}}}`; await checkQueryTemplate(extentPackage.queryTemplates![0]!, [generic, facts], readExtent); const closed = await compileQuery( extentPackage.queries![0]!, [generic, facts, ...(extent.resource.specializations ?? [])], readExtent, ); assert.equal(closed.declaration.root.kind, "workspace-interface"); assert.equal(closed.declaration.root.interface, specialized.declaration.root.interface); const invalidExtent = build("", true); assert.ok(invalidExtent.ok, JSON.stringify(invalidExtent.diagnostics)); if (invalidExtent.resource.kind !== "package") throw new Error("package expected"); await assert.rejects( checkQueryTemplate(invalidExtent.resource.revision.queryTemplates![0]!, [generic, facts], readExtent), /bound|guarantee|implement/i, ); }); function fixture(clauses = "", root = "Tasks") { const result = compileCapabilityResourceSource( `import interface Tasks; import interface TaskFacts; package Queries id "queries" revision "queries@1" { query Upcoming id "upcoming" root ${root} document "queries/upcoming.graphql" operation "Upcoming" { fragments "queries/row.graphql"; max rows 30; ${clauses} } }`, { source, environment: { interfaces: new Map([ ["Tasks", collection], ["TaskFacts", facts], ]), interfaceClosure: [collection, facts], }, }, ); assert.ok(result.ok, JSON.stringify(result.diagnostics)); assert.equal(result.resource.kind, "package"); if (result.resource.kind !== "package") throw new Error("package required"); assert.equal(result.resource.revision.exports.length, 0); return result.resource.revision.queries![0]!; } test("workspace roots expose a bounded interface extent, not a root object or writable relationship", async () => { const declaration = { ...fixture("watch;", "workspace TaskFacts"), fragments: [] }; assert.deepEqual(declaration.root, { kind: "workspace-interface", interface: "facts@1" }); const checked = await compileQuery( declaration, [facts, collection], async () => `query Upcoming { root { objects(first: 3, where: {done: {eq: false}}, orderBy: {title: ASC}) { entries {key node {_camino {ref} title}} pageInfo {hasNextPage endCursor} } _camino {relations {objects {aggregate {count sum {due}}}}} } }`, ); assert.deepEqual(new Set(checked.effects.map((effect) => effect.memberId)), new Set(["done", "title", "due"])); assert.ok(!checked.effects.some((effect) => effect.memberId.includes("workspace-objects"))); assert.ok(JSON.stringify(checked.selection).includes("qx-internal-workspace-objects")); await assert.rejects( compileQuery(declaration, [facts], async () => `query Upcoming {root {_camino {ref}}}`), /QUERY_VALIDATION/, ); await assert.rejects( compileQuery(declaration, [facts], async () => `query Upcoming {root {objects {entries {key}}}}`), /QUERY_ROW_LIMIT/, ); }); const document = `query Upcoming($first: Int!, $before: Int64!) { root { items(first: $first, where: {done: {eq: false}, due: {lt: $before}}, orderBy: [{due: ASC}]) { entries { key node { _camino { ref } ...Row } } pageInfo { hasNextPage endCursor } } } }`; const compile = (query = document, row = "fragment Row on TaskFacts { title due }", clauses = "") => compileQuery(fixture(clauses), [collection, facts], async (name) => (name.endsWith("row.graphql") ? row : query)); test("ordinary selections preserve aliases, fragment conditions and wire identity", async () => { const checked = await compile( `query Upcoming($show: Boolean!) {root {items(first: 3) {entries {node {plain: title ...Row @include(if: $show)}}}}}`, `fragment Row on TaskFacts {label: title due}`, ); const node = checked.selection[0]!.selection[0]!.selection[0]!.selection[0]!; assert.deepEqual( node.selection.map((s) => s.key), ["plain", "label", "due", "_camino"], ); assert.equal(node.selection[1]!.conditions.length, 1); assert.ok(JSON.stringify(checked.selection.map(querySelectionToWire)).includes("label")); }); test("object references are unconditional, canonical and idempotent through fragments", async () => { const checked = await compile( `query Upcoming($show: Boolean!) {root {items(first: 3) {entries {node {...Row @include(if: $show)}}}}}`, `fragment Row on TaskFacts {label: title identity: _camino {alias: ref} _camino @include(if: $show) {ref}}`, ); const root = checked.selection[0]!; const node = root.selection.find((s) => s.name === "items")!.selection[0]!.selection[0]!; for (const object of [root, node]) { const metadata = object.selection.filter((s) => s.key === "_camino"); assert.equal(metadata.length, 1); assert.deepEqual(metadata[0]!.conditions, []); assert.deepEqual( metadata[0]!.selection.filter((s) => s.key === "ref").map((s) => s.conditions), [[]], ); } assert.equal(node.selection.find((s) => s.key === "identity")!.conditions.length, 1); const recompiled = await compileQuery( { ...checked.declaration, fragments: [] }, [collection, facts], async () => checked.document, ); assert.deepEqual(recompiled.selection, checked.selection); assert.deepEqual(recompiled.output, checked.output); }); test("retired live syntax fails with migration guidance", async () => { for (const selected of ["score @live", "_camino @live {ref}", "title @live title", "title @live(unchecked: true)"]) await assert.rejects( compile( `query Upcoming {root {items(first: 3) {entries {node {...Row}}}}}`, `fragment Row on TaskFacts {${selected}}`, 'allow TaskFacts.score select "bounded";', ), (error: unknown) => error instanceof QueryCompileError && error.code === "QUERY_RETIRED_DIRECTIVE", ); }); test("implicit references reject aliases which occupy the canonical metadata path", async () => { for (const invalid of ["_camino: title", "_camino {ref: __typename}"]) await assert.rejects( compile( `query Upcoming {root {items(first: 3) {entries {node {...Row}}}}}`, `fragment Row on TaskFacts {${invalid}}`, ), /QUERY_VALIDATION|Introspection/, ); }); test("implicit reference merges with conditional aggregate metadata", async () => { const checked = await compileQuery( { ...fixture(""), fragments: [] }, [collection, facts], async () => `query Upcoming($show: Boolean!) {root {_camino @include(if: $show) {relations {items {aggregate {count}}}}}}`, ); const metadata = checked.selection[0]!.selection.filter((field) => field.key === "_camino"); assert.equal(metadata.length, 1); assert.deepEqual(metadata[0]!.conditions, []); assert.equal(metadata[0]!.selection.find((field) => field.name === "relations")!.conditions.length, 1); assert.deepEqual(metadata[0]!.selection.find((field) => field.name === "ref")!.conditions, []); assert.equal( metadata[0]!.selection .find((field) => field.name === "relations")! .selection[0]!.selection[0]!.selection.some((field) => field.name === "_camino"), false, ); }); test("qualified interface predicates and guaranteed inline projections retain nominal reads", async () => { const owner = iface( `interface Owner id "owner" revision "owner@1" { queryable value name id "name" : string {get id "name:get";} }`, ); const owned = iface( `import interface Owner; interface Owned id "owned" revision "owned@1" { queryable relation owner id "owner" : optional-one interface Owner {resolve id "owner:resolve";} queryable value label id "owned:label" : string {get id "owned:label:get";} }`, [owner], ); const item = iface( `import interface Owned; interface Item id "item" revision "item@1" requires Owned { queryable value label id "item:label" : string {get id "item:label:get";} }`, [owned, owner], ); const declaration = { ...fixture(""), root: { kind: "workspace-interface" as const, interface: item.revisionId }, fragments: [], }; const document = `query Upcoming($owner: QxRef_Owner!) {root {objects(all: 10, where: {_camino: {as: {Owned: {owner: {eq: $owner}}}}}) {entries {node {label ... on Owned {owner {name} ownerLabel: label}}}}}}`; const checked = await compileQuery(declaration, [item, owned, owner], async () => document); assert.ok( checked.effects.some( (effect) => effect.interfaceRevisionId === owned.revisionId && effect.memberId === "owner" && effect.uses.includes("predicate"), ), ); const objects = checked.selection[0]!.selection[0]!; assert.equal(objects.predicate?.kind, "and"); assert.ok(JSON.stringify(objects.predicate).includes('"kind":"as"')); const node = objects.selection[0]!.selection[0]!; assert.equal(node.selection.find((s) => s.key === "ownerLabel")!.interfaceRevisionId, owned.revisionId); assert.equal(node.selection.filter((s) => s.key === "_camino").length, 1); await assert.rejects( compileQuery(declaration, [item, owned, owner], async () => document.replace("ownerLabel: label", "ownerLabel: label _camino {ref}"), ), /QUERY_VALIDATION/, ); const explicitProjectionRef = await compileQuery(declaration, [item, owned, owner], async () => document.replace("ownerLabel: label", "ownerLabel: label ownedIdentity: _camino {ref}"), ); const projectedNode = explicitProjectionRef.selection[0]!.selection[0]!.selection[0]!.selection[0]!; assert.equal( projectedNode.selection.find((field) => field.key === "_camino")!.selection.find((field) => field.key === "ref")! .targetInterfaceRevisionId, item.revisionId, ); await assert.rejects( compileQuery(declaration, [item, owned, owner], async () => document.replace("ownerLabel: label", "label")), /QUERY_PROJECTION_CONFLICT|QUERY_VALIDATION/, ); }); test("query dependency ports resolve exact exports without declaration ordering constraints", () => { const result = compileCapabilityResourceSource( `import interface Tasks; package Queries id "queries" revision "queries@1" { operation load id "read" : unit -> unit mode call receiver interfaces [Tasks] requires { query upcoming id "upcoming-port" : Queries.Upcoming; }; query Upcoming id "upcoming" root Tasks document "upcoming.graphql" operation "Upcoming" { max rows 30; } }`, { source, environment: { interfaces: new Map([["Tasks", collection]]), interfaceClosure: [collection, facts] } }, ); assert.ok(result.ok, JSON.stringify(result.diagnostics)); assert.equal(result.resource.kind, "package"); if (result.resource.kind !== "package") throw new Error("package expected"); assert.deepEqual(result.resource.revision.exports[0]!.dependencyPorts[0]!.requirement, { kind: "query", packageRevisionId: "queries@1", queryId: "upcoming", }); }); test("result types merge repeated selections and preserve conditional fragment fields", async () => { const checked = await compile( `query Upcoming($show: Boolean!) { root { items(first: 3) { entries { node { title } } } } root { items(first: 3) { entries { node { ...Row @include(if: $show) } } } } }`, "fragment Row on TaskFacts { due done }", ); const output = JSON.stringify(checked.output); assert.match(output, /\"title\":\{\"kind\":\"scalar\",\"name\":\"string\"\}/); assert.match(output, /\"done\":\{\"kind\":\"optional\",\"value\":\{\"kind\":\"scalar\",\"name\":\"bool\"\}\}/); assert.match(output, /\"due\"/); }); test("fixed GraphQL yields exact effects, typed references and distinct query artifacts", async () => { const checked = await compile(); assert.deepEqual(checked.variables, { kind: "record", fields: { first: { kind: "scalar", name: "int32" }, before: { kind: "scalar", name: "int64" }, }, }); assert.match(JSON.stringify(checked.output), /"kind":"object-ref"/); assert.ok( checked.effects.some( (effect) => effect.memberId === "due" && effect.uses.includes("order") && effect.uses.includes("predicate"), ), ); assert.equal(checked.definitionDigest, (await compile()).definitionDigest); assert.notEqual( checked.definitionDigest, (await compile(document, "fragment Row on TaskFacts { title }")).definitionDigest, ); }); test("query allowlist and two-sided RPC effects fail during compilation", async () => { const rejects = (promise: Promise, code: string) => assert.rejects(promise, (error: unknown) => error instanceof QueryCompileError && error.code === code); const substring = (field: string) => `query Upcoming {root {items(first: 3, where: {${field}: {containsInsensitive: "TASK"}}) {entries {node {...Row}}}}}`; await compile(substring("title")); await rejects(compile(substring("due")), "QUERY_VALIDATION"); await rejects(compile(document, "fragment Row on TaskFacts { hidden }"), "QUERY_VALIDATION"); await rejects(compile(document, "fragment Row on TaskFacts { score }"), "QUERY_RPC_CONSUMER_REASON"); await compile( document, "fragment Row on TaskFacts { score }", 'allow TaskFacts.score select "Only the visible page is enriched";', ); await rejects( compile(document, "fragment Row on TaskFacts { score }", 'watch; allow TaskFacts.score select "Visible rows";'), "QUERY_WATCH_UNSUPPORTED", ); await compile( document, "fragment Row on TaskFacts { score }", 'watch; poll 5000 "External score has no push API"; allow TaskFacts.score select "Visible rows";', ); await rejects(compile(document.replace("first: $first", "first: 31")), "QUERY_VALIDATION"); await rejects(compile(document.replace("...Row", "__typename ...Row")), "QUERY_UNSUPPORTED_FEATURE"); await rejects(compile(document, "fragment Row on TaskFacts { ...Row }"), "QUERY_VALIDATION"); }); test("unselected atom relationships do not require views on reference-only targets", async () => { const target = iface(`external atom Detail id "detail"; interface Referenced id "referenced" revision "referenced@1" { queryable relation details id "details" : many atom Detail {resolve id "details:resolve";} }`); const subject = iface( `external atom Detail id "detail"; import interface Referenced; interface Subject id "subject" revision "subject@1" { queryable relation selected id "selected" : optional-one interface Referenced {resolve id "selected:resolve";} }`, [target], ); const declaration = { ...fixture(""), root: { kind: "object" as const, interface: subject.revisionId }, fragments: [], }; const document = "query Upcoming {root {selected {_camino {ref}}}}"; const checked = await compileQuery(declaration, [subject, target], async () => document); assert.match(JSON.stringify(checked.output), /referenced@1/); await assert.rejects( compileQuery( declaration, [subject, target], async () => "query Upcoming {root {selected {details(all: 3) {entries {node {_camino {ref}}}}}}}", ), /QUERY_VIEW_REQUIRED/, ); }); test("React tooling generates checked owner queries from imported closed navigation fields", async () => { const { presentationQueries } = await import("../src/bindings/presentation-queries.js"); const mount = iface('interface PresentationMount id "mount" revision "mount@1" {}'); const subject = iface('interface Subject id "subject" revision "subject@1" {}'); const state = iface( `import interface PresentationMount; import interface Subject; interface PresentationState id "state" revision "state@1" { queryable value occurrence id "occurrence" : string {get id "occurrence:get" execution snapshot;} queryable relation presentationMount id "mount" : optional-one interface PresentationMount {resolve id "mount:resolve" execution snapshot;} queryable relation parent id "parent" : optional-one interface PresentationState {resolve id "parent:resolve" execution snapshot;} queryable relation source id "source" : exactly-one interface Subject {resolve id "source:resolve" execution snapshot;} }`, [mount, subject], ); const navigation = iface( `import interface PresentationState; interface Navigation id "navigation" revision "navigation@1" requires PresentationState { queryable value custom id "custom" : T {get id "custom:get" execution snapshot;} }`, [mount, subject, state], ); const react = iface( `import interface PresentationState; interface ReactComponent id "react" revision "react@1" { module render id "render" contract "org.quixos.react.component/2" subject object Subject; }`, [mount, subject, state], ); const dependencies = [mount, subject, state, navigation, react]; const parsed = compileCapabilityResourceSource( `import interface ReactComponent; import interface Navigation; import interface Subject; package Example id "example" revision "example@1" { module Screen id "screen" implements ReactComponent>.render; }`, { source, environment: { interfaces: new Map(dependencies.map((entry) => [entry.displayName, entry])), interfaceClosure: dependencies, }, }, ); assert.ok(parsed.ok, JSON.stringify(parsed.diagnostics)); if (parsed.resource.kind !== "package") throw Error("package required"); const contracts = [...dependencies, ...(parsed.resource.specializations ?? [])]; const documents = presentationQueries(parsed.resource.revision, contracts); assert.equal(documents.size, 2); const checked = await Promise.all( parsed.resource.revision.queries!.map((query) => compileQuery(query, contracts, async (name) => documents.get(name)!), ), ); assert.deepEqual( checked.map((query) => query.declaration.placement?.kind), ["replica", "local"], ); assert.match(JSON.stringify(checked[0]!.output), /"custom":\{"kind":"record","fields":\{"label"/); assert.match(JSON.stringify(checked[0]!.selection), /"kind":"as"/); assert( checked.every((query) => query.effects.some((effect) => effect.memberId === "mount" && effect.uses.includes("predicate")), ), ); });