Build Camino packages from explicit contract inputs

Project source-free capability contracts and keep exact source provenance in package assembly. Reuse compiled groups across unrelated graph and dependency implementation edits. Keep React public inputs separate from workspace query lowering and retain explicit type-companion dependencies.

Allow independent execution-package checks, scope resource compilation to declared dependencies, and memoize source closure traversal. Document generic-specialization limits and measured Reader rebuild behavior.

Verified protocol, artifact, package, React, and managed activation checks; retained-Git independent/workspace artifact equality and incompatible type dependency rejection; nix fmt -- --check and lint. One-off Reader measurements recompiled 1 of 13 groups for a child edit and 0 for a workspace QX default edit.
This commit is contained in:
Timothy J. Aveni
2026-09-30 22:55:07 -07:00
parent b95cd149c6
commit e8d19d2bb7
23 changed files with 726 additions and 227 deletions
+224
View File
@@ -0,0 +1,224 @@
import type { BindingSchema, BindingContractSchema } from "../bindings/index.js";
import type { InterfaceRevision, PackageRevision } from "../capability-model/types.js";
import { canonicalJson } from "../capability-model/evolution.js";
import { moduleContract } from "../capability-model/modules.js";
import { generatePackageComponentBindings, publicQueryContract, type PublicQueryContract } from "./client-contracts.js";
import { moduleTypeContext } from "./module-contracts.js";
import { executionVersions, hexDigest, ExecutionPlanError, type PlanPackage } from "./plan.js";
import { preparationArtifact, type ReactPreparationArtifact } from "../react-build/declarations.js";
/** Inputs to compilation, independent of source pins and installed providers.
* Declared ports select interface contracts; the React consumer projects its
* imported interfaces, public queries and module inputs. Ordinary code
* and richer language type companions remain separate, explicit Nix inputs. */
export interface PackageBuildContract {
definition: {
codec: "camino-package-build-v1";
package: Omit<PlanPackage, "source" | "sourceDigest" | "bindingDigest">;
versions: typeof executionVersions;
bindings: BindingContractSchema;
queries: PublicQueryContract[];
component?: string;
moduleInterfaces: { revisionId: string; definitionId?: string; members: string[] }[];
diagnostics: string[];
};
digest: string;
}
const normalize = <T>(value: T): T => JSON.parse(canonicalJson(value)) as T;
const indexSchema = (schema: BindingSchema) => ({
interfaces: new Map<string, InterfaceRevision>(
[...schema.interfaces, ...(schema.interfaceTemplates ?? [])].map((entry) => [entry.revisionId, entry]),
),
packages: new Map<string, PackageRevision>(schema.packages.map((entry) => [entry.revisionId, entry])),
});
export function packageBuildContexts(
schema: BindingSchema,
): Record<string, { contract: PackageBuildContract; sourceDigest: string }> {
const index = indexSchema(schema);
return Object.fromEntries(
schema.packages.map((pkg) => [
pkg.revisionId,
{
contract: projectPackageContract(schema, pkg.revisionId, index),
sourceDigest: hexDigest({ revision: pkg.revisionId, source: pkg.source }),
},
]),
);
}
export function packageBuildContract(schema: BindingSchema, revision: string): PackageBuildContract {
return projectPackageContract(schema, revision, indexSchema(schema));
}
function projectPackageContract(
schema: BindingSchema,
revision: string,
index: ReturnType<typeof indexSchema>,
): PackageBuildContract {
const declared = index.packages.get(revision);
if (!declared) throw new ExecutionPlanError("UNKNOWN_BUILD_PACKAGE", revision, "Missing package declaration");
const pkg = normalize(declared);
const interfaces = (ids: Iterable<string>) =>
[...ids].map((id) => {
const iface = index.interfaces.get(id);
if (!iface) throw new ExecutionPlanError("UNKNOWN_BUILD_INTERFACE", id, "Missing declared interface");
return normalize(iface);
});
const modules = [...(pkg.modules ?? [])]
.sort((a, b) => a.id.localeCompare(b.id))
.map((module) => {
const contract = moduleContract(interfaces([module.interfaceRevisionId]), module);
if (!contract) throw new ExecutionPlanError("UNKNOWN_MODULE_CONTRACT", module.id, "Missing module contract");
return {
...module,
contract: contract.contract,
...(contract.subject ? { subject: contract.subject } : {}),
...moduleTypeContext(index.interfaces.get(module.interfaceRevisionId)!),
};
});
const components = modules.flatMap((module) =>
["org.quixos.react.component/1", "org.quixos.react.component/2", "org.quixos.react.subject-only/1"].includes(
module.contract,
) && module.subject?.kind === "interface"
? [
{
id: module.id,
displayName: module.displayName,
subject: module.subject.interfaceRevisionId,
runtime: "react" as const,
},
]
: [],
);
const shape: PackageBuildContract["definition"]["package"] = {
revision,
exports: [...pkg.exports]
.sort((a, b) => a.id.localeCompare(b.id))
.map((entry) => ({ id: entry.id, execution: entry.execution ?? { kind: "remote" } })),
...(modules.length ? { modules } : {}),
...(components.length ? { components } : {}),
...(pkg.controllers?.length ? { controllers: [...pkg.controllers].sort((a, b) => a.id.localeCompare(b.id)) } : {}),
};
const portInterfaces = new Set(
pkg.exports.flatMap((entry) =>
entry.dependencyPorts.flatMap((port) =>
port.requirement.kind === "interface" ? [port.requirement.interfaceRevisionId] : [],
),
),
);
const bindings: BindingContractSchema = {
format: "quixos-bindings",
version: 1,
packages: [
{
revisionId: pkg.revisionId,
exports: [...pkg.exports].sort((a, b) => a.id.localeCompare(b.id)),
...(pkg.genericExports ? { genericExports: pkg.genericExports } : {}),
},
],
interfaces: interfaces(portInterfaces)
.map(({ source, ...contract }) => contract)
.sort((a, b) => a.revisionId.localeCompare(b.revisionId)),
};
const preparation = pkg.buildArtifacts?.[preparationArtifact] as ReactPreparationArtifact | undefined;
const diagnostics = components.flatMap((component) => {
const input = preparation?.modules.find((entry) => entry.module === component.id);
return input && pkg.queryCompositions?.some((entry) => entry.id === input.composition)
? []
: [`${component.id}: declare checked inputs in quixos.react.json`];
});
// Only interfaces whose modules are exposed by these bindings need language
// type companions. Reference types themselves do not recursively import the
// referenced object's implementation or its conformance inventory.
const used = new Set<string>(Object.values(pkg.clientInterfaces ?? {}));
for (const module of modules) used.add(module.interfaceRevisionId);
for (const component of components) used.add(component.subject);
for (const entry of preparation?.modules ?? [])
for (const child of entry.children) if (child.role) used.add(child.role.interface);
const moduleInterfaces = interfaces(used)
.flatMap((iface: InterfaceRevision) => {
const members = iface.members
.filter((member) => member.kind === "module")
.map((member) => member.id)
.sort();
return members.length
? [
{
revisionId: iface.revisionId,
...(iface.application ? { definitionId: iface.application.definitionId } : {}),
members,
},
]
: [];
})
.sort((a, b) => a.revisionId.localeCompare(b.revisionId));
for (const module of modules)
for (const argument of index.interfaces.get(module.interfaceRevisionId)?.application?.arguments ?? [])
if (argument.kind === "object" && argument.target.kind === "interface")
used.add(argument.target.interfaceRevisionId);
const providers = new Set<string>(Object.values(pkg.clientPackages ?? {}));
for (const query of pkg.queries ?? [])
for (const imported of query.importedFragments ?? []) providers.add(imported.packageRevisionId);
providers.delete(pkg.revisionId);
const componentSchema: BindingSchema = {
format: "quixos-bindings",
version: 1,
interfaces: interfaces(used),
packages: [
pkg,
...[...providers].map((id) => {
const provider = index.packages.get(id);
if (!provider) throw new ExecutionPlanError("UNKNOWN_BUILD_PROVIDER", id, "Missing declared package provider");
return normalize(provider);
}),
],
};
const definition: PackageBuildContract["definition"] = {
codec: "camino-package-build-v1",
package: shape,
versions: { ...executionVersions },
bindings,
queries: [
...new Map(
pkg.exports.flatMap((entry) =>
entry.dependencyPorts.flatMap((port) => {
if (port.requirement.kind !== "query") return [];
const requirement = port.requirement;
const provider = index.packages.get(requirement.packageRevisionId);
if (!provider)
throw new ExecutionPlanError(
"UNKNOWN_QUERY_PROVIDER",
requirement.packageRevisionId,
"Missing declared query provider",
);
return [
[
JSON.stringify([requirement.packageRevisionId, requirement.queryId]),
publicQueryContract(provider, requirement.queryId),
] as const,
];
}),
),
).entries(),
]
.sort(([a], [b]) => a.localeCompare(b))
.map(([, query]) => query),
...(components.length && !diagnostics.length
? { component: generatePackageComponentBindings(componentSchema, shape) }
: {}),
moduleInterfaces,
diagnostics,
};
const normalized = JSON.parse(canonicalJson(definition)) as typeof definition;
return { definition: normalized, digest: hexDigest(normalized) };
}
export function checkPackageBuildContract(contract: PackageBuildContract): void {
if (contract.definition.codec !== "camino-package-build-v1" || hexDigest(contract.definition) !== contract.digest)
throw new ExecutionPlanError("INVALID_BUILD_CONTRACT", contract.digest, "Package contract digest mismatch");
if (contract.definition.diagnostics.length)
throw new ExecutionPlanError(
"INCOMPLETE_BUILD_CONTRACT",
contract.definition.package.revision,
contract.definition.diagnostics.join("; "),
);
}