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 988e10263a
commit 6227d64b08
12 changed files with 147 additions and 57 deletions
+5 -11
View File
@@ -5,7 +5,7 @@ import { createHash } from "node:crypto";
import { execFileSync } from "node:child_process";
import { pathToFileURL } from "node:url";
const digest = (bytes) => createHash("sha256").update(bytes).digest("hex");
export async function buildComponents({ config, compiler, plan, pkg, output, recordFile, generated }) {
export async function buildComponents({ config, compiler, contract, pkg, output, recordFile, generated }) {
const settings = config.components;
if (!settings) {
if (pkg.components?.length) throw Error("Declared components require checked component build settings");
@@ -108,7 +108,7 @@ export async function buildComponents({ config, compiler, plan, pkg, output, rec
sharedAliases.push("--alias:" + name + "=" + shim, "--external:" + url);
}
const bindingPath = compiler.artifactPath(settings.bindingOutput),
bindings = compiler.generateComponentClientBindings(plan, pkg.revision),
bindings = contract.definition.component,
assets = compiler.componentAssetDeclarations();
await fs.mkdir(path.dirname(path.join(work, bindingPath)), { recursive: true });
await fs.writeFile(path.join(work, bindingPath), bindings);
@@ -126,10 +126,9 @@ export async function buildComponents({ config, compiler, plan, pkg, output, rec
for (const root of settings.contracts ?? []) {
const companion = JSON.parse(await fs.readFile(path.join(root, "react-contracts.json"), "utf8"));
if (companion.schemaVersion !== 1) throw Error("Unsupported React props companion");
const contracts = plan.definition.workspace.interfaceImports.filter(
const contracts = contract.definition.moduleInterfaces.filter(
(iface) =>
iface.revisionId === companion.interfaceRevisionId ||
iface.application?.definitionId === companion.interfaceRevisionId,
iface.revisionId === companion.interfaceRevisionId || iface.definitionId === companion.interfaceRevisionId,
);
if (!contracts.length) continue;
for (const [memberId, entry] of Object.entries(companion.members)) {
@@ -139,12 +138,7 @@ export async function buildComponents({ config, compiler, plan, pkg, output, rec
`import type {${entry.export} as ${alias}} from ${JSON.stringify(path.join(root, "types", compiler.artifactPath(entry.path)))};`,
);
for (const iface of contracts) {
if (
!(iface.template?.members ?? iface.members).some(
(member) => member.kind === "module" && member.id === memberId,
)
)
throw Error("Props companion names an unknown module member");
if (!iface.members.includes(memberId)) throw Error("Props companion names an unknown module member");
const key = JSON.stringify([iface.revisionId, memberId]);
if (seenProps.has(key)) throw Error("Duplicate React props companion for " + key);
seenProps.add(key);