Add exact runtime conformance lookup and agent-visible language limits
Add authorized exact-contract lookup, revision-fenced views, generated typed package/browser descriptors, and lifecycle-safe React discovery. Preserve closed generic contracts without competing-conformance policies or new authority grants. Document supported workarounds and privacy boundaries for agent feedback. Publish matching protocol/SDK exports and update scaffold/starter pins; leave the deployed template unchanged.
This commit is contained in:
@@ -12,6 +12,100 @@ import { genericImplementationType } from "../src/bindings/generics.js";
|
||||
import { compileWorkspaceRevision, runtimeContracts } from "../src/capability-model/index.js";
|
||||
import { generateAppliedClientContracts } from "../src/bindings/client.js";
|
||||
const source = { repository: "https://example.test/generic.git", commit: "a".repeat(40) };
|
||||
test("runtime conformance descriptors preserve closed and universal collection item types", async (t) => {
|
||||
const sdk = path.resolve("../quixos-instance/packages/camino-package-runtime/dist/index.js");
|
||||
// The protocol is also published and tested as a standalone subtree. This
|
||||
// integration uses the real SDK only when both projects are checked out.
|
||||
if (
|
||||
!(await fs.access(sdk).then(
|
||||
() => true,
|
||||
() => false,
|
||||
))
|
||||
) {
|
||||
t.skip("Real SDK integration requires the monorepo checkout");
|
||||
return;
|
||||
}
|
||||
const collection = compileCapabilityResourceSource(
|
||||
`interface Collection<object Item> id "collection" revision "collection@1" {
|
||||
relation items id "items" : many-unique object Item {resolve id "get"; connect id "add";}
|
||||
}`,
|
||||
{ source },
|
||||
);
|
||||
assert.ok(collection.ok && collection.resource.kind === "interface");
|
||||
if (!collection.ok || collection.resource.kind !== "interface") throw Error("interface failed");
|
||||
const pkg = compileCapabilityResourceSource(
|
||||
`import interface Collection;
|
||||
external atom Book id "book"; external atom Tool id "tool";
|
||||
package P id "p" revision "p@1" {
|
||||
operation both id "both" : record {book:atom-ref<Book>; tool:atom-ref<Tool>;} -> unit mode call receiver any requires {
|
||||
interface books id "books" : Collection<atom Book>;
|
||||
interface tools id "tools" : Collection<atom Tool>;
|
||||
};
|
||||
operation inspect<object Item> id "inspect" : ref<Item> -> unit mode call receiver any requires {
|
||||
interface items id "items" : Collection<Item>;
|
||||
};
|
||||
}`,
|
||||
{ source, environment: { interfaces: new Map([["Collection", collection.resource.revision]]) } },
|
||||
);
|
||||
assert.ok(pkg.ok && pkg.resource.kind === "package", JSON.stringify(pkg.diagnostics));
|
||||
if (!pkg.ok || pkg.resource.kind !== "package") throw Error("package failed");
|
||||
const directory = await fs.mkdtemp(path.join(os.tmpdir(), "qx-conformance-types-"));
|
||||
try {
|
||||
const generated = generateTypeScriptBindings(
|
||||
{
|
||||
format: "quixos-bindings",
|
||||
version: 1,
|
||||
interfaces: pkg.resource.specializations ?? [],
|
||||
interfaceTemplates: [collection.resource.revision],
|
||||
packages: [pkg.resource.revision],
|
||||
},
|
||||
"p@1",
|
||||
{ runtimeModule: sdk },
|
||||
);
|
||||
await fs.writeFile(path.join(directory, "bindings.mts"), generated);
|
||||
await fs.writeFile(
|
||||
path.join(directory, "consumer.mts"),
|
||||
`import type {Implementation} from "./bindings.mjs";
|
||||
const implementation: Implementation = {
|
||||
both: async (context) => {
|
||||
const view = await context.conform.tryConform(context.objectId, context.ports.books.contract);
|
||||
if (view) {
|
||||
await view["items.connect"](context.input.book);
|
||||
// @ts-expect-error Tool is not Book
|
||||
await view["items.connect"](context.input.tool);
|
||||
}
|
||||
return null;
|
||||
},
|
||||
inspect: async (context) => {
|
||||
const view = await context.conform.tryConform(context.objectId, context.ports.items.contract);
|
||||
if (view) await view["items.connect"](context.input);
|
||||
return null;
|
||||
},
|
||||
};`,
|
||||
);
|
||||
const compiler = path.join(
|
||||
path.dirname(createRequire(import.meta.url).resolve("typescript/package.json")),
|
||||
"bin/tsc",
|
||||
);
|
||||
const result = await promisify(execFile)(process.execPath, [
|
||||
compiler,
|
||||
"--ignoreConfig",
|
||||
"--noEmit",
|
||||
"--strict",
|
||||
"--skipLibCheck",
|
||||
"--target",
|
||||
"es2023",
|
||||
"--module",
|
||||
"nodenext",
|
||||
path.join(directory, "consumer.mts"),
|
||||
]).catch((error) => {
|
||||
throw Error(error.stdout + error.stderr);
|
||||
});
|
||||
assert.equal(result.stderr, "");
|
||||
} finally {
|
||||
await fs.rm(directory, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
test("generic port aliases retain their defining scope, including shadowed alias names", () => {
|
||||
const iface = compileCapabilityResourceSource(
|
||||
'type Box<value V> = list<V>; interface Data<value V> id "data" revision "data@1" {value payload id "payload" : Box<V> {get id "payload:get";}}',
|
||||
@@ -108,7 +202,7 @@ test("generated universal implementations compile and cannot assume a concrete v
|
||||
const require = createRequire(import.meta.url);
|
||||
const compiler = path.join(path.dirname(require.resolve("typescript/package.json")), "bin/tsc");
|
||||
const run = promisify(execFile);
|
||||
const preamble = `type QxObjectRef<T extends string>={readonly identity:T}; type QxContextLifecycle<C>={signal?:AbortSignal}; type Handler=${signature};\n`;
|
||||
const preamble = `type QxObjectRef<T extends string>={readonly identity:T}; type QxConformer={}; type QxContextLifecycle<C>={signal?:AbortSignal}; type Handler=${signature};\n`;
|
||||
try {
|
||||
const file = path.join(directory, "generic.ts");
|
||||
await fs.writeFile(file, preamble + `const handler:Handler=({input})=>input;`);
|
||||
|
||||
@@ -33,6 +33,18 @@ test("React bindings preserve read-only, writable and nested reference contracts
|
||||
interfaces: [iface.resource.revision],
|
||||
packages: [pkg.resource.revision],
|
||||
} as const;
|
||||
const opaque = structuredClone(iface.resource.revision);
|
||||
opaque.revisionId = "opaque@1" as typeof opaque.revisionId;
|
||||
opaque.displayName = "Opaque";
|
||||
for (const member of opaque.members)
|
||||
for (const operation of member.operations)
|
||||
operation.outputType = { kind: "message", descriptorId: "UnsupportedBrowserMessage" };
|
||||
const withUnusedOpaque = generateReactBindings(
|
||||
{ ...schema, interfaces: [...schema.interfaces, opaque], packages: [...schema.packages] },
|
||||
"p@1",
|
||||
["props"],
|
||||
);
|
||||
assert.doesNotMatch(withUnusedOpaque, /"opaque@1": defineReactInterfaceContract/);
|
||||
const generated = generateReactBindings(
|
||||
{ ...schema, interfaces: [...schema.interfaces], packages: [...schema.packages] },
|
||||
"p@1",
|
||||
@@ -54,8 +66,18 @@ test("React bindings preserve read-only, writable and nested reference contracts
|
||||
);
|
||||
await fs.writeFile(
|
||||
path.join(root, "consumer.ts"),
|
||||
`import {useLiveField, type ReadableField, type WritableField} from "@quixos/web-studio-react-runtime";
|
||||
`import {useLiveField, tryConform, type ReadableField, type WritableField} from "@quixos/web-studio-react-runtime";
|
||||
import {reactInterfaces} from "./react-props.gen.js";
|
||||
import type {ReactResults} from "./react-props.gen.js";
|
||||
async function lookup() {
|
||||
const view = await tryConform("object", reactInterfaces.Fields);
|
||||
if (!view) return;
|
||||
await view.call["title.set"]("works");
|
||||
// @ts-expect-error exact checked setter input
|
||||
await view.call["title.set"](123);
|
||||
// @ts-expect-error no invented operation
|
||||
await view.call["summary.set"]("no setter");
|
||||
}
|
||||
declare const props: ReactResults["props"];
|
||||
const [title, setTitle] = useLiveField(props.fields.fields.title);
|
||||
setTitle("new");
|
||||
|
||||
Reference in New Issue
Block a user