Add the workspace coding agent, launch gate, and composable locks
- run a singleton Codex App Server adapter in each dev workspace - gate Web Studio with a reloadable fragment-token to HttpOnly-session exchange - add ChatGPT device login and streamed chat to Web Studio - isolate authoring access in a declarative NixOS service account - split Web Studio canvas, object, panel, and model concerns - check generated Quixos protocol clients for drift - resolve same-repository quixos.lock fragments deterministically - preserve the collaborative canvas gesture fixes
This commit is contained in:
@@ -2,18 +2,96 @@ import assert from "node:assert/strict";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { resolve } from "node:path";
|
||||
import { test } from "node:test";
|
||||
import { compileCapabilitySource } from "../src/capability-language/index.js";
|
||||
import {
|
||||
capabilityFixturePath,
|
||||
compileCapabilityResourceSource,
|
||||
compileCapabilitySource,
|
||||
} from "../src/capability-language/index.js";
|
||||
import {
|
||||
capabilityFixtureSource,
|
||||
capabilityResourceSources,
|
||||
compileCapabilityFixture,
|
||||
fixtureId,
|
||||
} from "./fixtures/capability-model.js";
|
||||
|
||||
test("ANTLR parses and validates a complete capability workspace", () => {
|
||||
const result = compileCapabilitySource(
|
||||
capabilityFixtureSource,
|
||||
capabilityFixturePath,
|
||||
const compileWebStudioFixture = (packageTransform = (source: string) => source) => {
|
||||
const fixture = (name: string) => readFileSync(
|
||||
resolve(process.cwd(), "test/fixtures", name),
|
||||
"utf8",
|
||||
);
|
||||
const react = compileCapabilityResourceSource(
|
||||
fixture("react-component.interface.qx"),
|
||||
{
|
||||
source: {
|
||||
repository: "https://repos.quixos.org/org-quixos-web-studio/interface-react-component.git",
|
||||
commit: "2".repeat(40),
|
||||
},
|
||||
},
|
||||
);
|
||||
if (!react.ok || react.resource.kind !== "interface") {
|
||||
throw new Error("ReactComponent fixture did not compile");
|
||||
}
|
||||
const named = compileCapabilityResourceSource(
|
||||
fixture("named.interface.qx"),
|
||||
{
|
||||
source: {
|
||||
repository: "https://repos.quixos.org/quixos-test/interface-named.git",
|
||||
commit: "5".repeat(40),
|
||||
},
|
||||
},
|
||||
);
|
||||
if (!named.ok || named.resource.kind !== "interface") {
|
||||
throw new Error("Named fixture did not compile");
|
||||
}
|
||||
const has = compileCapabilityResourceSource(
|
||||
fixture("has-react-component.interface.qx"),
|
||||
{
|
||||
source: {
|
||||
repository: "https://repos.quixos.org/org-quixos-web-studio/interface-has-react-component.git",
|
||||
commit: "3".repeat(40),
|
||||
},
|
||||
environment: {
|
||||
interfaces: new Map([["ReactComponent", react.resource.revision]]),
|
||||
interfaceClosure: [react.resource.revision],
|
||||
},
|
||||
},
|
||||
);
|
||||
if (!has.ok || has.resource.kind !== "interface") {
|
||||
throw new Error("HasReactComponent fixture did not compile");
|
||||
}
|
||||
const component = compileCapabilityResourceSource(
|
||||
packageTransform(fixture("component-runtime.package.qx")),
|
||||
{
|
||||
source: {
|
||||
repository: "https://repos.quixos.org/quixos-test/package-component-runtime.git",
|
||||
commit: "4".repeat(40),
|
||||
},
|
||||
environment: {
|
||||
interfaces: new Map([["Named", named.resource.revision]]),
|
||||
interfaceClosure: [named.resource.revision],
|
||||
},
|
||||
},
|
||||
);
|
||||
if (!component.ok || component.resource.kind !== "package") {
|
||||
throw new Error("ComponentRuntime fixture did not compile");
|
||||
}
|
||||
return compileCapabilitySource(
|
||||
fixture("web-studio.capabilities.qx"),
|
||||
"web-studio.capabilities.qx",
|
||||
{
|
||||
interfaces: new Map([
|
||||
["Named", named.resource.revision],
|
||||
["ReactComponent", react.resource.revision],
|
||||
["HasReactComponent", has.resource.revision],
|
||||
]),
|
||||
packages: new Map([["ComponentRuntime", component.resource.revision]]),
|
||||
interfaceClosure: [named.resource.revision, react.resource.revision, has.resource.revision],
|
||||
packageClosure: [component.resource.revision],
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
test("ANTLR parses and validates a complete capability workspace", () => {
|
||||
const result = compileCapabilityFixture();
|
||||
assert.equal(result.ok, true);
|
||||
if (!result.ok) return;
|
||||
assert.equal(result.workspace.workspaceId, fixtureId.workspace);
|
||||
@@ -24,26 +102,24 @@ test("ANTLR parses and validates a complete capability workspace", () => {
|
||||
});
|
||||
|
||||
test("conformances do not accept authored IDs", () => {
|
||||
const result = compileCapabilitySource(
|
||||
capabilityFixtureSource.replace(
|
||||
const result = compileCapabilityFixture({
|
||||
workspace: capabilityFixtureSource.replace(
|
||||
"conform Project as Named {",
|
||||
'conform Project as Named id "conformance:project:named" {',
|
||||
),
|
||||
"conformance-id.qx",
|
||||
);
|
||||
});
|
||||
assert.equal(result.ok, false);
|
||||
if (result.ok) return;
|
||||
assert.ok(result.diagnostics.some((entry) => entry.phase === "syntax"));
|
||||
});
|
||||
|
||||
test("the v1 language has no implicit relationship materialization rule", () => {
|
||||
const result = compileCapabilitySource(
|
||||
capabilityFixtureSource.replace(
|
||||
const result = compileCapabilityFixture({
|
||||
workspace: capabilityFixtureSource.replace(
|
||||
/\n}\s*$/,
|
||||
'\n materialize ProjectOwner.owner if absent with Person;\n}\n',
|
||||
),
|
||||
"materialize.qx",
|
||||
);
|
||||
});
|
||||
assert.equal(result.ok, false);
|
||||
if (result.ok) return;
|
||||
assert.ok(result.diagnostics.some((entry) => entry.phase === "syntax"));
|
||||
@@ -57,21 +133,23 @@ test("forward declarations make source order irrelevant", () => {
|
||||
/\n}\s*$/,
|
||||
'\n atom Project id "atom:project";\n atom Person id "atom:person";\n}\n',
|
||||
);
|
||||
assert.equal(compileCapabilitySource(source, "reordered.qx").ok, true);
|
||||
assert.equal(compileCapabilityFixture({ workspace: source }).ok, true);
|
||||
});
|
||||
|
||||
test("interfaces can declare ordinary call operations", () => {
|
||||
const source = capabilityFixtureSource.replace(
|
||||
" value summary id \"member:summary:summary\" : string {\n get id \"operation:summary:summary:get\";\n }",
|
||||
" operation summarize id \"member:summary:summarize\" : string -> string {\n call id \"operation:summary:summarize\";\n }",
|
||||
).replace(
|
||||
const workspace = capabilityFixtureSource.replace(
|
||||
"bind summary.get to package TodoRuntime.summaryGet",
|
||||
"bind summarize.call to package TodoRuntime.summaryGet",
|
||||
).replace(
|
||||
);
|
||||
const summary = capabilityResourceSources.summary.replace(
|
||||
" value summary id \"member:summary:summary\" : string {\n get id \"operation:summary:summary:get\";\n }",
|
||||
" operation summarize id \"member:summary:summarize\" : string -> string {\n call id \"operation:summary:summarize\";\n }",
|
||||
);
|
||||
const todo = capabilityResourceSources.todo.replace(
|
||||
"operation summaryGet id \"export:todo-runtime:summary-get\" : unit -> string",
|
||||
"operation summaryGet id \"export:todo-runtime:summary-get\" : string -> string",
|
||||
);
|
||||
const result = compileCapabilitySource(source, "operation-member.qx");
|
||||
const result = compileCapabilityFixture({ workspace, summary, todo });
|
||||
assert.equal(result.ok, true);
|
||||
if (!result.ok) return;
|
||||
const member = result.workspace.interfaceImports
|
||||
@@ -86,7 +164,7 @@ test("state defaults accept recursive JSON values", () => {
|
||||
shared state ProjectMetadata id "slot:project:metadata" on Project : message "example.Metadata"
|
||||
policy optimistic-register default {"labels":["compiler","runtime"],"score":1.5,"enabled":true,"extra":null};`,
|
||||
);
|
||||
const result = compileCapabilitySource(source, "json-default.qx");
|
||||
const result = compileCapabilityFixture({ workspace: source });
|
||||
assert.equal(result.ok, true);
|
||||
if (!result.ok) return;
|
||||
const metadata = result.workspace.sharedAttachments.find(
|
||||
@@ -103,25 +181,23 @@ test("state defaults accept recursive JSON values", () => {
|
||||
});
|
||||
|
||||
test("syntax errors retain source locations", () => {
|
||||
const result = compileCapabilitySource(
|
||||
capabilityFixtureSource.replace("atom Project", "atom Project ???"),
|
||||
"broken.qx",
|
||||
);
|
||||
const result = compileCapabilityFixture({
|
||||
workspace: capabilityFixtureSource.replace("atom Project", "atom Project ???"),
|
||||
});
|
||||
assert.equal(result.ok, false);
|
||||
if (result.ok) return;
|
||||
assert.ok(result.diagnostics.some((entry) =>
|
||||
entry.phase === "syntax" && entry.fileName === "broken.qx" && entry.line > 0
|
||||
entry.phase === "syntax" && entry.line > 0
|
||||
));
|
||||
});
|
||||
|
||||
test("unknown authoring names are lowering errors", () => {
|
||||
const result = compileCapabilitySource(
|
||||
capabilityFixtureSource.replace(
|
||||
const result = compileCapabilityFixture({
|
||||
workspace: capabilityFixtureSource.replace(
|
||||
"to state ProjectTitle.read",
|
||||
"to state NotAState.read",
|
||||
),
|
||||
"unknown-name.qx",
|
||||
);
|
||||
});
|
||||
assert.equal(result.ok, false);
|
||||
if (result.ok) return;
|
||||
assert.ok(result.diagnostics.some((entry) =>
|
||||
@@ -132,13 +208,12 @@ test("unknown authoring names are lowering errors", () => {
|
||||
});
|
||||
|
||||
test("well-formed but invalid programs report semantic paths", () => {
|
||||
const result = compileCapabilitySource(
|
||||
capabilityFixtureSource.replace(
|
||||
const result = compileCapabilityFixture({
|
||||
workspace: capabilityFixtureSource.replace(
|
||||
"bind name.get to state ProjectTitle.read",
|
||||
"bind name.get to state ProjectTitle.write",
|
||||
),
|
||||
"invalid-binding.qx",
|
||||
);
|
||||
});
|
||||
assert.equal(result.ok, false);
|
||||
if (result.ok) return;
|
||||
const diagnostic = result.diagnostics.find(
|
||||
@@ -150,11 +225,7 @@ test("well-formed but invalid programs report semantic paths", () => {
|
||||
});
|
||||
|
||||
test("Web Studio sidecars declare lazy materialization and checked cross-object ports", () => {
|
||||
const source = readFileSync(
|
||||
resolve(process.cwd(), "test/fixtures/web-studio.capabilities.qx"),
|
||||
"utf8",
|
||||
);
|
||||
const result = compileCapabilitySource(source, "web-studio.capabilities.qx");
|
||||
const result = compileWebStudioFixture();
|
||||
assert.equal(result.ok, true);
|
||||
if (!result.ok) return;
|
||||
|
||||
@@ -175,9 +246,9 @@ test("Web Studio sidecars declare lazy materialization and checked cross-object
|
||||
const binding = component?.operationBindings[0]?.binding;
|
||||
assert.equal(binding?.kind, "package");
|
||||
if (binding?.kind !== "package") return;
|
||||
assert.deepEqual(binding.dependencies[1]?.binding, {
|
||||
kind: "state",
|
||||
slotId: "slot:project:name",
|
||||
assert.deepEqual(binding.dependencies[0]?.binding, {
|
||||
kind: "interface",
|
||||
interfaceRevisionId: "interface:named@1",
|
||||
via: {
|
||||
edgeTypeId: "edge:project:component",
|
||||
projectionId: "projection:component:subject",
|
||||
@@ -186,14 +257,10 @@ test("Web Studio sidecars declare lazy materialization and checked cross-object
|
||||
});
|
||||
|
||||
test("relationship materializers require a constructor from the host atom", () => {
|
||||
const source = readFileSync(
|
||||
resolve(process.cwd(), "test/fixtures/web-studio.capabilities.qx"),
|
||||
"utf8",
|
||||
).replace(
|
||||
const result = compileWebStudioFixture((source) => source.replace(
|
||||
"constructs ProjectComponent : atom-ref<Project>;",
|
||||
"constructs ProjectComponent : unit;",
|
||||
);
|
||||
const result = compileCapabilitySource(source, "invalid-materializer.qx");
|
||||
));
|
||||
assert.equal(result.ok, false);
|
||||
if (result.ok) return;
|
||||
assert.ok(result.diagnostics.some((entry) =>
|
||||
@@ -201,3 +268,24 @@ test("relationship materializers require a constructor from the host atom", () =
|
||||
entry.message.includes("must accept")
|
||||
));
|
||||
});
|
||||
|
||||
test("callable interface ports require a full source dependency, not a nominal reference", () => {
|
||||
const result = compileCapabilityResourceSource(`
|
||||
external interface Named revision "interface:named@1";
|
||||
package Runtime id "package:runtime" revision "package:runtime@1" {
|
||||
function readName id "export:runtime:read-name" : unit -> string requires {
|
||||
interface named id "port:runtime:named" : Named;
|
||||
};
|
||||
}
|
||||
`, {
|
||||
source: {
|
||||
repository: "https://repos.quixos.org/quixos-test/package-runtime.git",
|
||||
commit: "6".repeat(40),
|
||||
},
|
||||
});
|
||||
assert.equal(result.ok, false);
|
||||
if (result.ok) return;
|
||||
assert.ok(result.diagnostics.some((entry) =>
|
||||
entry.code === "nominal-interface-port" && entry.message.includes("import interface Named")
|
||||
));
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user