Add exact runtime conformance lookup and agent-visible language limits

This commit is contained in:
Timothy J. Aveni
2026-09-17 11:29:56 -07:00
parent 59735c5e38
commit ea51ca89e7
10 changed files with 378 additions and 55 deletions
+58 -18
View File
@@ -7,6 +7,7 @@ import type {
} from "../capability-model/types.js";
import type { CompiledCapabilityResourceRepository } from "../capability-language/assembly.js";
import { genericImplementationType } from "./generics.js";
import { capabilityId } from "../capability-model/types.js";
/** Portable generator input. New backends consume this instead of the parser or TS runtime. */
export type BindingSchema = {
@@ -183,26 +184,28 @@ export const generateTypeScriptBindings = (
.filter((operation) => operation.mode === "call" && operation.scope !== "class")
.map((operation) => ({ ...operation, name: `${member.displayName}.${operation.displayName}` })),
);
return {
type: object([
["objectId", ref({ kind: "interface", interfaceRevisionId: requirement.interfaceRevisionId })],
[
"live",
object(
operations.map((operation) => [
operation.name,
`(${params(operation.inputType)}) => Promise<QxLiveValue>`,
]),
),
],
...operations.map((operation): [string, string] => [
operation.name,
`(${params(operation.inputType)}) => Promise<${type(operation.outputType)}>`,
]),
const methods: [string, string][] = [
["objectId", ref({ kind: "interface", interfaceRevisionId: requirement.interfaceRevisionId })],
[
"live",
object(
operations.map((operation) => [
operation.name,
`(${params(operation.inputType)}) => Promise<QxLiveValue>`,
]),
),
],
...operations.map((operation): [string, string] => [
operation.name,
`(${params(operation.inputType)}) => Promise<${type(operation.outputType)}>`,
]),
];
return {
type: object([...methods, ["contract", `QxInterfaceContract<${object(methods)}>`]]),
spec: {
kind: "interface",
id: entry.id,
interfaceRevisionId: requirement.interfaceRevisionId,
operations: Object.fromEntries(
operations.map(({ name, id, inputType, outputType }) => [name, { id, inputType, outputType }]),
),
@@ -245,6 +248,7 @@ export const generateTypeScriptBindings = (
? `QxObjectRef<${entry.receiverRequirement.interfaceRevisionIds.map((id) => q(`interface:${id}`)).join(" | ") || "never"}>`
: "QxObjectRef<string>";
const contextShape = object([
["conform", "QxConformer"],
...(entry.kind === "function" ? [] : [["objectId", receiver] as [string, string]]),
["input", type(entry.inputType)],
["ports", object(ports.map((port) => [port.name, port.type]))],
@@ -281,13 +285,42 @@ export const generateTypeScriptBindings = (
genericImplementationType(definition, [...schema.interfaces, ...(schema.interfaceTemplates ?? [])], type),
]);
}
// Unused imported interfaces must not introduce new message-codec obligations.
// A used port still fails above if its own required codec is missing.
const hasCodec = (value: ValueType): boolean => {
if (value.kind === "message") return !!options.messages?.[value.descriptorId];
if (value.kind === "record") return Object.values(value.fields).every(hasCodec);
if (value.kind === "list" || value.kind === "optional") return hasCodec(value.value);
return true;
};
const contracts = schema.interfaces
.filter((iface) =>
iface.members.every((member) =>
member.operations
.filter((operation) => operation.mode === "call" && operation.scope !== "class")
.every((operation) => hasCodec(operation.inputType) && hasCodec(operation.outputType)),
),
)
.map((iface) => {
const generated = port({
id: capabilityId.dependencyPort("contract"),
displayName: iface.displayName,
requirement: { kind: "interface", interfaceRevisionId: iface.revisionId },
});
const spec = generated.spec as { operations: unknown };
return {
iface,
type: generated.type,
code: `defineQxInterfaceContract<${generated.type}>(${q(iface.revisionId)}, ${JSON.stringify(spec.operations)})`,
};
});
const imports = [...messages].map(([id, alias]) => {
const binding = options.messages![id]!;
if (!/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(binding.export))
throw new Error(`Invalid message binding export ${binding.export}`);
return `import { ${binding.export} as ${alias} } from ${q(binding.module)};`;
});
const signatures = `${object(contexts)} ${object(handlers)} ${object(results)}`;
const signatures = `${object(contexts)} ${object(handlers)} ${object(results)} ${contracts.map((entry) => entry.type).join(" ")}`;
const typeImports = [
"BindingValue",
"QxObjectRef",
@@ -295,17 +328,24 @@ export const generateTypeScriptBindings = (
"QxHandler",
"QxDerived",
"QxContextLifecycle",
"QxConformer",
"QxInterfaceContract",
"QxLiveValue",
"RelationshipCollection",
"RelationshipEntry",
].filter((name) => new RegExp(`\\b${name}\\b`).test(signatures));
return (
`// Generated by quixos-codegen-ts. Do not edit. Binding ABI version 1.\n` +
`import { ${exports.length ? "bindQxHandler, " : ""}${[...typeImports, "QxHandlerSpec", "QxMessages"].map((name) => `type ${name}`).join(", ")} } from ${q(options.runtimeModule ?? "@quixos/camino-package-runtime")};\n` +
`import { ${contracts.length ? "defineQxInterfaceContract, " : ""}${exports.length ? "bindQxHandler, " : ""}${[...typeImports, "QxHandlerSpec", "QxMessages"].map((name) => `type ${name}`).join(", ")} } from ${q(options.runtimeModule ?? "@quixos/camino-package-runtime")};\n` +
imports.join("\n") +
`\nexport const packageRevisionId = ${q(pkg.revisionId)};\n` +
`declare const appliedType: unique symbol;\ntype QxApplied<Definition extends string, Arguments extends readonly unknown[]> = string & {readonly [appliedType]: (value: [Definition, Arguments]) => [Definition, Arguments]};\n` +
`export type Contexts = ${object(contexts)};\nexport type Results = ${object(results)};\nexport type Implementation = ${object(handlers)};\n` +
`export const contracts = {${contracts.map((entry) => `${q(entry.iface.revisionId)}: ${entry.code}`).join(",\n")}};\n` +
`export const interfaces = {${contracts
.filter((entry) => contracts.filter((other) => other.iface.displayName === entry.iface.displayName).length === 1)
.map((entry) => `${q(entry.iface.displayName)}: contracts[${q(entry.iface.revisionId)}]`)
.join(",\n")}};\n` +
`const messages = { ${[...messages].map(([id, alias]) => `${q(id)}: ${alias}`).join(", ")} } satisfies QxMessages;\n` +
`const specs = ${JSON.stringify(specs, null, 2)} satisfies Record<string, QxHandlerSpec>;\n` +
`export const createRuntime = (implementation: Implementation) => ({\n packageRevisionId,\n exports: {\n` +