Add queryable capability contracts and checked GraphQL artifacts

This commit is contained in:
Timothy J. Aveni
2026-09-17 14:34:16 -07:00
parent e3501772c5
commit 61a410f98f
27 changed files with 4798 additions and 2333 deletions
+42
View File
@@ -40,6 +40,8 @@ import { specializePackageExport } from "./generic-packages.js";
import { isDeepStrictEqual } from "node:util";
export type CapabilityValidationIssueCode =
| "invalid-query-contract"
| "query-provider-reason-required"
| "invalid-semantic-major"
| "duplicate-conformance-id"
| "required-value"
@@ -502,6 +504,21 @@ const collectIdentityIndexes = (
const operations = new Map<OperationId, InterfaceOperationEntry>();
for (const [memberIndex, member] of revision.members.entries()) {
const memberPath = `${path}.members[${memberIndex}]`;
if (member.kind !== "operation" && member.queryRead) {
const contract = member.queryRead.execution;
const getter = member.kind === "value" ? "get" : "resolve";
if (contract !== "native" && contract !== "rpc-permitted")
issue(issues, "invalid-query-contract", memberPath, "Unknown query execution contract");
if (member.operations.filter((operation) => operation.displayName === getter).length !== 1)
issue(issues, "invalid-query-contract", memberPath, `Queryable members require exactly one ${getter}`);
if (member.kind === "relationship" && contract === "rpc-permitted")
issue(issues, "invalid-query-contract", memberPath, "RPC query relationships are not supported");
if (member.kind === "value") {
const type = member.valueType.kind === "optional" ? member.valueType.value : member.valueType;
if (type.kind !== "scalar")
issue(issues, "invalid-query-contract", memberPath, "Queryable values must be scalar or optional scalar");
}
}
requireText(issues, member.id, `${memberPath}.id`, "Member ID");
requireText(issues, member.displayName, `${memberPath}.displayName`, "Member name");
if (memberIds.has(member.id)) {
@@ -1424,6 +1441,31 @@ const validateConformances = (
}
const operation = operationEntry.operation;
const binding = entry.binding;
const member = operationEntry.member;
if (
member.kind !== "operation" &&
member.queryRead &&
operation.displayName === (member.kind === "value" ? "get" : "resolve")
) {
const native =
member.kind === "value"
? binding.kind === "state" && binding.primitive === "read"
: binding.kind === "edge" && binding.primitive === "resolve";
if (!native && (member.queryRead.execution === "native" || binding.kind !== "package"))
issue(
issues,
"invalid-query-contract",
bindingPath,
"This queryable contract requires a native read binding; a justification cannot weaken it",
);
else if (!native && !entry.queryReason?.trim())
issue(
issues,
"query-provider-reason-required",
bindingPath,
"Package-backed query reads require a concrete query-reason justification",
);
}
if (operation.scope === "class" && (!conformance.id || operation.mode !== "call")) {
issue(
issues,