Fix Studio query contracts for authored workspaces
Keep query definition identity scoped to interfaces present in the generated query schema. Exclude retention provenance from both interface revisions and generic applications while preserving nominal identities, closed arguments, operation contracts, documents, budgets and fragment dependencies. Advance the query semantics version so prior compiler outputs cannot share the new identity. Add a generic compiler regression covering unrelated imports, retained generic sources and invalidation when an observed getter changes. This fixes Studio's CLIENT_QUERY_MISMATCH when template contracts become editable local repositories. Validation: protocol tests (296 total, 293 passed, 3 existing skips); nix fmt, monorepo lint, final formatting check and diff check; camino-dev/qx-workspace builds. All 17 Studio query descriptors match a rebuilt copy of the user's workspace. Headless fresh-workspace canvas/library/Reader/reload check passes. Repair the existing local qx-reader workbench through its ordinary check and activation path, updating only its pinned protocol/checker CLI. Retain the original toolchain GC root and record checker provenance. Source commit, runtime and template pins, and database are preserved. Verify the selected candidate is ready at epoch 2 and its canvas loads through the corrected Studio bundle without creating application objects. Stop the independent smoke-test services.
This commit is contained in:
@@ -36,6 +36,50 @@ interface Tasks id "tasks" revision "tasks@1" {
|
||||
[facts],
|
||||
);
|
||||
|
||||
test("query identity ignores unrelated imports and generic retention provenance", async () => {
|
||||
const generic = iface(`interface Box<object Item> id "box" revision "box@1" {
|
||||
queryable relation item id "box:item" : optional-one object Item {resolve id "box:item:resolve";}
|
||||
}`);
|
||||
const compiled = compileCapabilityResourceSource(
|
||||
`import interface Box; import interface TaskFacts;
|
||||
package Queries id "queries" revision "queries@1" {
|
||||
query Row id "row" root Box<interface TaskFacts> document "row.graphql" operation "Row" {replica;max rows 10;}
|
||||
}`,
|
||||
{
|
||||
source,
|
||||
environment: {
|
||||
interfaces: new Map([
|
||||
["Box", generic],
|
||||
["TaskFacts", facts],
|
||||
]),
|
||||
interfaceClosure: [generic, facts],
|
||||
},
|
||||
},
|
||||
);
|
||||
assert.ok(compiled.ok, JSON.stringify(compiled.diagnostics));
|
||||
if (compiled.resource.kind !== "package") throw new Error("package required");
|
||||
const declaration = compiled.resource.revision.queries![0]!;
|
||||
const closed = compiled.resource.specializations![0]!;
|
||||
const read = async () => "query Row {root {item {title}}}";
|
||||
const before = await compileQuery(declaration, [facts, closed], read);
|
||||
const retained = structuredClone(closed);
|
||||
retained.source = { repository: "https://local.test/box.git", commit: "b".repeat(40) };
|
||||
retained.application!.source = retained.source;
|
||||
const unrelated = iface(`interface Unrelated id "unrelated" revision "unrelated@1" {
|
||||
queryable value label id "label" : string {get id "label:get";}
|
||||
}`);
|
||||
assert.equal(
|
||||
(await compileQuery(declaration, [unrelated, generic, retained, facts], read)).definitionDigest,
|
||||
before.definitionDigest,
|
||||
);
|
||||
const changed = structuredClone(facts);
|
||||
changed.members[0]!.operations[0]!.id = "changed:title:get" as (typeof changed.members)[0]["operations"][0]["id"];
|
||||
assert.notEqual(
|
||||
(await compileQuery(declaration, [changed, retained], read)).definitionDigest,
|
||||
before.definitionDigest,
|
||||
);
|
||||
});
|
||||
|
||||
test("generic query templates check declared bounds before closed specialization", async () => {
|
||||
const generic = iface(
|
||||
`import interface TaskFacts;
|
||||
|
||||
Reference in New Issue
Block a user