45 lines
2.3 KiB
TypeScript
45 lines
2.3 KiB
TypeScript
import { ExecutionPlanError, hexDigest, type ExecutionPlan, type PlannedQuery } from "./plan.js";
|
|||
|
|
/** Contracts promised by a direct result. Interface dependencies are explicit; accidental
|
||
|
|
* overlap among today's implementations does not silently widen an exported interface contract. */
|
||
|
|
export function resultInterfaces(plan: ExecutionPlan, operationKey: string): string[] {
|
||
|
|
const operation = plan.definition.operations.find((operation) => operation.key === operationKey);
|
||
|
|
if (!operation) throw new ExecutionPlanError("UNKNOWN_OPERATION", operationKey, "No installed public operation");
|
||
|
|
if (operation.output.kind !== "object-ref") return [];
|
||
|
|
const expected = operation.output.expectation;
|
||
|
|
if (expected.kind === "atom")
|
||
|
|
return plan.definition.workspace.conformances
|
||
|
|
.filter((c) => c.atomId === expected.atomId)
|
||
|
|
.map((c) => c.interfaceRevisionId)
|
||
|
|
.sort();
|
||
|
|
const interfaces = new Map(plan.definition.workspace.interfaceImports.map((i) => [String(i.revisionId), i]));
|
||
|
|
const result = new Set<string>();
|
||
|
|
const visit = (id: string) => {
|
||
|
|
if (result.has(id)) return;
|
||
|
|
result.add(id);
|
||
|
|
for (const dependency of interfaces.get(id)?.requiredInterfaces ?? []) visit(dependency);
|
||
|
|
};
|
||
|
|
visit(expected.interfaceRevisionId);
|
||
|
|
return [...result].sort();
|
||
|
|
}
|
||
|
|
/** Compile-time observeResult compatibility. Observation does not grant read or mutation authority. */
|
||
|
|
export function checkResultProjection(plan: ExecutionPlan, operationKey: string, queryId: string): PlannedQuery {
|
||
|
|
if (hexDigest(plan.definition) !== plan.digest)
|
||
|
|
throw new ExecutionPlanError("INVALID_PLAN", "projection", "Plan digest mismatch");
|
||
|
|
const query = plan.definition.queries.find((q) => q.id === queryId);
|
||
|
|
if (!query) throw new ExecutionPlanError("UNKNOWN_PROJECTION", queryId, "No installed query");
|
||
|
|
const declaration = query.query.checked.declaration;
|
||
|
|
if (declaration.placement?.kind !== "replica" || declaration.root.kind !== "object")
|
||
|
|
throw new ExecutionPlanError(
|
||
|
|
"INVALID_RESULT_PROJECTION",
|
||
|
|
queryId,
|
||
|
|
"Result observation requires a direct-object replica declaration",
|
||
|
|
);
|
||
|
|
if (!resultInterfaces(plan, operationKey).includes(declaration.root.interface))
|
||
|
|
throw new ExecutionPlanError(
|
||
|
|
"RESULT_PROJECTION_MISMATCH",
|
||
|
|
operationKey,
|
||
|
|
`Result does not promise ${declaration.root.interface}`,
|
||
|
|
);
|
||
|
|
return query;
|
||
|
|
}
|