483bc68a94
Enable evolution by default for source-backed workspaces. Add stable conformance ownership, semantic-major review, candidate typechecking, and durable fenced cutover with explicit migrations and forward recovery. Independently supervise package runtimes so unchanged resource owners keep their processes and connections across cutover. Add scoped invocation authority, resource sessions, and typed callback rebinding. Wire opaque object references through generated bindings and RPCs. Add canonical relationship sets, keyed maps, and ordered lists with scoped transactional mutations, revision checks, and inverse consistency. Support planned cascade deletion, protection, tombstones, and lifecycle foundations. Add journaled structural edits, package/function/migration scaffolding, managed repository creation, and resumable bottom-up dependency pin publication. Document lifetime boundaries, revision pinning, prototype compatibility policy, commands, and deferred work. Validate with 210 tests, user-systemd process/connection continuity, generated-package TypeScript checks, and Nix host/protocol checks. TTL handoff, physical reclamation, general multi-step migrations, and root-systemd migration isolation acceptance remain deferred.
292 lines
10 KiB
TypeScript
292 lines
10 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import { readFileSync } from "node:fs";
|
|
import { resolve } from "node:path";
|
|
import { test } from "node:test";
|
|
import {
|
|
compileCapabilityResourceSource,
|
|
compileCapabilitySource,
|
|
} from "../src/capability-language/index.js";
|
|
import {
|
|
capabilityFixtureSource,
|
|
capabilityResourceSources,
|
|
compileCapabilityFixture,
|
|
fixtureId,
|
|
} from "./fixtures/capability-model.js";
|
|
|
|
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);
|
|
assert.equal(result.workspace.id, fixtureId.workspaceRevision);
|
|
assert.equal(result.workspace.interfaceImports.length, 3);
|
|
assert.equal(result.workspace.conformances.length, 4);
|
|
assert.equal("id" in result.workspace.conformances[0]!, false);
|
|
});
|
|
|
|
test("conformances preserve authored migration lineage IDs", () => {
|
|
const result = compileCapabilityFixture({
|
|
workspace: capabilityFixtureSource.replace(
|
|
"conform Project as Named {",
|
|
'conform Project as Named id "conformance:project:named" {',
|
|
),
|
|
});
|
|
assert.equal(result.ok, true);
|
|
if (!result.ok) return;
|
|
assert.equal(result.workspace.conformances[0]!.id, "conformance:project:named");
|
|
});
|
|
|
|
test("the v1 language has no implicit relationship materialization rule", () => {
|
|
const result = compileCapabilityFixture({
|
|
workspace: capabilityFixtureSource.replace(
|
|
/\n}\s*$/,
|
|
'\n materialize ProjectOwner.owner if absent with Person;\n}\n',
|
|
),
|
|
});
|
|
assert.equal(result.ok, false);
|
|
if (result.ok) return;
|
|
assert.ok(result.diagnostics.some((entry) => entry.phase === "syntax"));
|
|
});
|
|
|
|
test("forward declarations make source order irrelevant", () => {
|
|
const source = capabilityFixtureSource
|
|
.replace(/ atom Project[^\n]*\n/, "")
|
|
.replace(/ atom Person[^\n]*\n/, "")
|
|
.replace(
|
|
/\n}\s*$/,
|
|
'\n atom Project id "atom:project";\n atom Person id "atom:person";\n}\n',
|
|
);
|
|
assert.equal(compileCapabilityFixture({ workspace: source }).ok, true);
|
|
});
|
|
|
|
test("interfaces can declare ordinary call operations", () => {
|
|
const workspace = capabilityFixtureSource.replace(
|
|
"bind summary.get to package TodoRuntime.summaryGet",
|
|
"bind summarize.call to package TodoRuntime.summaryGet",
|
|
);
|
|
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 = compileCapabilityFixture({ workspace, summary, todo });
|
|
assert.equal(result.ok, true);
|
|
if (!result.ok) return;
|
|
const member = result.workspace.interfaceImports
|
|
.find((entry) => entry.displayName === "Summary")?.members[0];
|
|
assert.equal(member?.kind, "operation");
|
|
});
|
|
|
|
test("state defaults accept recursive JSON values", () => {
|
|
const source = capabilityFixtureSource.replace(
|
|
' policy optimistic-register default "Untitled project";',
|
|
` policy optimistic-register default "Untitled project";
|
|
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 = compileCapabilityFixture({ workspace: source });
|
|
assert.equal(result.ok, true);
|
|
if (!result.ok) return;
|
|
const metadata = result.workspace.sharedAttachments.find(
|
|
(entry) => entry.id === "slot:project:metadata",
|
|
);
|
|
assert.equal(metadata?.kind, "state");
|
|
if (metadata?.kind !== "state") return;
|
|
assert.deepEqual(metadata.defaultValue, {
|
|
labels: ["compiler", "runtime"],
|
|
score: 1.5,
|
|
enabled: true,
|
|
extra: null,
|
|
});
|
|
});
|
|
|
|
test("syntax errors retain source locations", () => {
|
|
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.line > 0
|
|
));
|
|
});
|
|
|
|
test("unknown authoring names are lowering errors", () => {
|
|
const result = compileCapabilityFixture({
|
|
workspace: capabilityFixtureSource.replace(
|
|
"to state ProjectTitle.read",
|
|
"to state NotAState.read",
|
|
),
|
|
});
|
|
assert.equal(result.ok, false);
|
|
if (result.ok) return;
|
|
assert.ok(result.diagnostics.some((entry) =>
|
|
entry.phase === "lowering" &&
|
|
entry.code === "unknown-symbol" &&
|
|
entry.message.includes("NotAState")
|
|
));
|
|
});
|
|
|
|
test("well-formed but invalid programs report semantic paths", () => {
|
|
const result = compileCapabilityFixture({
|
|
workspace: capabilityFixtureSource.replace(
|
|
"bind name.get to state ProjectTitle.read",
|
|
"bind name.get to state ProjectTitle.write",
|
|
),
|
|
});
|
|
assert.equal(result.ok, false);
|
|
if (result.ok) return;
|
|
const diagnostic = result.diagnostics.find(
|
|
(entry) => entry.code === "invalid-state-binding",
|
|
);
|
|
assert.ok(diagnostic);
|
|
assert.equal(diagnostic.phase, "validation");
|
|
assert.ok(diagnostic.path?.includes("operationBindings"));
|
|
});
|
|
|
|
test("Web Studio sidecars declare lazy materialization and checked cross-object ports", () => {
|
|
const result = compileWebStudioFixture();
|
|
assert.equal(result.ok, true);
|
|
if (!result.ok) return;
|
|
|
|
const host = result.workspace.conformances.find((entry) =>
|
|
entry.atomId === "atom:project" &&
|
|
entry.interfaceRevisionId === "interface:org.quixos.web-studio.has-react-component@1"
|
|
);
|
|
assert.deepEqual(host?.relationshipMaterializations, [{
|
|
memberId: "member:org.quixos.web-studio.has-react-component:component",
|
|
constructorAtomId: "atom:project-component",
|
|
edgeTypeId: "edge:project:component",
|
|
constructedProjectionId: "projection:component:subject",
|
|
}]);
|
|
|
|
const component = result.workspace.conformances.find((entry) =>
|
|
entry.atomId === "atom:project-component"
|
|
);
|
|
const binding = component?.operationBindings[0]?.binding;
|
|
assert.equal(binding?.kind, "package");
|
|
if (binding?.kind !== "package") return;
|
|
assert.deepEqual(binding.dependencies[0]?.binding, {
|
|
kind: "interface",
|
|
interfaceRevisionId: "interface:named@1",
|
|
via: {
|
|
edgeTypeId: "edge:project:component",
|
|
projectionId: "projection:component:subject",
|
|
},
|
|
});
|
|
});
|
|
|
|
test("relationship materializers require a constructor from the host atom", () => {
|
|
const result = compileWebStudioFixture((source) => source.replace(
|
|
"constructs ProjectComponent : atom-ref<Project>;",
|
|
"constructs ProjectComponent : unit;",
|
|
));
|
|
assert.equal(result.ok, false);
|
|
if (result.ok) return;
|
|
assert.ok(result.diagnostics.some((entry) =>
|
|
entry.code === "invalid-relationship-materialization" &&
|
|
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")
|
|
));
|
|
});
|