diff --git a/.quixos-subtree-source.json b/.quixos-subtree-source.json index b207eac..726300e 100644 --- a/.quixos-subtree-source.json +++ b/.quixos-subtree-source.json @@ -1,7 +1,7 @@ { "version": 1, "sourceRepo": "https://gitea-external.egads.tutti.syntaxblitz.net/quixos/quixos", - "sourceCommit": "c87835bff85794bc232dbaaddc1eb3b593c2aab8", + "sourceCommit": "37a50c17063a41d23037b39785f7054992d7c708", "sourcePath": "quixos-protocol", "exportName": "quixos-protocol", "mirrorRemote": "https://gitea-external.egads.tutti.syntaxblitz.net/quixos/quixos-protocol.git" diff --git a/flake.nix b/flake.nix index 7a86d3d..80677c9 100644 --- a/flake.nix +++ b/flake.nix @@ -29,6 +29,7 @@ pkgs.jujutsu pkgs.gnutar pkgs.util-linux + pkgs.nix ]; buildPhase = '' runHook preBuild @@ -39,6 +40,8 @@ export QUIXOS_PROTO_PATH="${pkgs.protobuf}/include:$PWD/proto''${QUIXOS_PROTO_PATH:+:$QUIXOS_PROTO_PATH}" patchShebangs node_modules/.bin node_modules/@bufbuild/protoc-gen-es/bin yarn build + esbuild dist/src/execution-world/index.js --bundle --platform=node --target=node24 --format=esm --outfile=execution-world.mjs + esbuild dist/src/react-build/package-queries.js --bundle --platform=node --target=node24 --format=esm --outfile=react-query-generator.mjs esbuild dist/src/bindings/client.js --bundle --platform=node --target=node24 --format=esm --outfile=client-codegen.mjs diff --recursive --unified "$TMPDIR/generated-before/proto" src/gen diff --recursive --unified "$TMPDIR/generated-before/capability" src/capability-language/generated @@ -59,6 +62,7 @@ --bundle --platform=node --target=node24 --format=esm \ --outfile=quixos-lock-check.mjs esbuild dist/src/bindings/cli.js --bundle --platform=node --target=node24 --format=esm --outfile=quixos-codegen-ts.mjs + esbuild dist/src/bindings/portable-client-cli.js --bundle --platform=node --target=node24 --format=esm --outfile=quixos-codegen-client.mjs esbuild dist/src/bindings/react-platform.js --bundle --platform=node --target=node24 --format=esm --outfile=react-platform.mjs esbuild dist/src/capability-language/tool-cli.js --bundle --platform=node --target=node24 --format=esm --outfile=quixos-qx.mjs runHook postBuild @@ -67,20 +71,31 @@ checkPhase = '' runHook preCheck yarn test + node dist/test/emit-portable-reader.js + node dist/test/emit-managed.js runHook postCheck ''; installPhase = '' runHook preInstall mkdir -p "$out" install -Dm644 nix/checked-package.nix "$out/share/checked-package.nix" + install -Dm644 nix/checked-artifacts.nix "$out/share/checked-artifacts.nix" + install -Dm644 nix/package-context.nix "$out/share/package-context.nix" + install -Dm644 nix/lock-source.nix "$out/share/lock-source.nix" + install -Dm644 nix/package-context.mjs "$out/share/package-context.mjs" + install -Dm644 nix/check-package.mjs "$out/share/check-package.mjs" + install -Dm644 nix/check-execution.mjs "$out/share/check-execution.mjs" substitute nix/checked-candidate.nix "$out/share/checked-candidate.nix" \ - --replace-fail '@nixpkgs@' '${pkgs.path}' + --replace-fail '@nixpkgs@' '${pkgs.path}' \ + --replace-fail '@nixpkgsNarHash@' '${nixpkgs.narHash}' cp --reflink=auto --recursive grammar "$out/grammar" cp --reflink=auto --recursive proto "$out/proto" cp --reflink=auto --recursive dist "$out/dist" cp package.json "$out/package.json" + mkdir -p "$out/share/quixos-protocol/qualification" + cp -r dist/portable-reader dist/managed "$out/share/quixos-protocol/qualification/" mkdir -p "$out/bin" - for tool in quixos-codegen-ts quixos-qx; do + for tool in quixos-codegen-ts quixos-codegen-client quixos-qx; do printf '#!/bin/sh\nexec ${pkgs.nodejs_24}/bin/node "%s/libexec/quixos-protocol/%s.mjs" "$@"\n' "$out" "$tool" > "$out/bin/$tool" chmod +x "$out/bin/$tool" done @@ -112,9 +127,12 @@ EOF chmod +x "$out/bin/quixos-lock-check" mkdir -p "$out/libexec/quixos-protocol" + install -m644 execution-world.mjs "$out/libexec/quixos-protocol/execution-world.mjs" + install -m644 react-query-generator.mjs "$out/libexec/quixos-protocol/react-query-generator.mjs" install -m644 client-codegen.mjs "$out/libexec/quixos-protocol/client-codegen.mjs" install -m644 react-platform.mjs "$out/libexec/quixos-protocol/react-platform.mjs" install -m644 quixos-codegen-ts.mjs quixos-qx.mjs "$out/libexec/quixos-protocol/" + install -m644 quixos-codegen-client.mjs "$out/libexec/quixos-protocol/" install -m644 quixos-descriptor-check.mjs "$out/libexec/quixos-protocol/quixos-descriptor-check.mjs" install -m644 quixos-capability-compile.mjs "$out/libexec/quixos-protocol/quixos-capability-compile.mjs" install -m644 quixos-workspace-compile.mjs "$out/libexec/quixos-protocol/quixos-workspace-compile.mjs" diff --git a/grammar/QuixosCapability.g4 b/grammar/QuixosCapability.g4 index fb41ae7..b16081f 100644 --- a/grammar/QuixosCapability.g4 +++ b/grammar/QuixosCapability.g4 @@ -27,6 +27,7 @@ workspaceItem | sharedAttachmentDecl | conformanceDecl | constructorBindingDecl + | presentationDecl | typeAliasDecl ; @@ -43,10 +44,15 @@ externalInterfaceDecl : EXTERNAL INTERFACE identifier REVISION stringLiteral SEMI ; +symbolDecl + : SYMBOL identifier ID stringLiteral SEMI + ; + resourcePreamble : resourceImportDecl | externalAtomDecl | externalInterfaceDecl + | symbolDecl | typeAliasDecl ; @@ -92,21 +98,27 @@ interfaceMember : valueMember | relationshipMember | operationMember + | moduleMember + ; + +moduleMember + : MODULE identifier ID stringLiteral CONTRACT stringLiteral (SUBJECT targetConstraint)? SEMI ; operationMember : STATIC? OPERATION identifier ID stringLiteral COLON valueType ARROW valueType - LBRACE CALL ID stringLiteral SEMI RBRACE + LBRACE CALL ID stringLiteral executionClause? SEMI RBRACE ; valueMember - : queryReadContract? VALUE identifier ID stringLiteral COLON valueType + : queryReadContract? (VALUE | FIELD) identifier ID stringLiteral COLON valueType LBRACE valueMemberOperation* RBRACE ; valueMemberOperation - : GET ID stringLiteral SEMI - | SET ID stringLiteral SEMI + : GET ID stringLiteral executionClause? SEMI + | SET ID stringLiteral executionClause? SEMI + | EDIT ID stringLiteral executionClause? SEMI | WATCH START ID stringLiteral STOP ID stringLiteral SEMI ; @@ -116,13 +128,13 @@ relationshipMember ; queryReadContract - : QUERYABLE RPC? + : QUERYABLE (RPC | PORTABLE)? ; relationshipOperation - : RESOLVE ID stringLiteral SEMI - | CONNECT ID stringLiteral SEMI - | DISCONNECT ID stringLiteral SEMI + : RESOLVE ID stringLiteral executionClause? SEMI + | CONNECT ID stringLiteral executionClause? SEMI + | DISCONNECT ID stringLiteral executionClause? SEMI | WATCH START ID stringLiteral STOP ID stringLiteral SEMI ; @@ -144,11 +156,22 @@ packageExport | packageConstructorExport | packageQuery | packageQuerySpecialization + | moduleExport + ; + +moduleExport + : MODULE identifier ID stringLiteral IMPLEMENTS interfaceType DOT identifier SEMI + ; + +presentationDecl + : PRESENTATION identifier ID stringLiteral FOR interfaceType USING identifier DOT identifier SEMI ; packageQuery - : QUERY identifier typeParameters? ID stringLiteral ROOT interfaceType - DOCUMENT stringLiteral OPERATION stringLiteral LBRACE queryClause* RBRACE + : QUERY FRAGMENT? identifier typeParameters? ID stringLiteral ROOT WORKSPACE? interfaceType + DOCUMENT stringLiteral + (OPERATION stringLiteral LBRACE queryClause* RBRACE + | FRAGMENT stringLiteral (OPERATION stringLiteral)? (LBRACE queryClause* RBRACE | SEMI)) ; packageQuerySpecialization @@ -156,7 +179,10 @@ packageQuerySpecialization ; queryClause - : FRAGMENTS stringLiteral SEMI + : REPLICA SEMI + | LOCAL FROM identifier SEMI + | FRAGMENTS stringLiteral SEMI + | FRAGMENTS identifier DOT identifier (WITH LBRACE fragmentVariable* RBRACE)? SEMI | VIEW OBJECT? identifier AS interfaceType SEMI | MAX identifier INTEGER SEMI | ALLOW interfaceType DOT identifier identifier stringLiteral SEMI @@ -164,14 +190,33 @@ queryClause | POLL INTEGER stringLiteral SEMI ; +fragmentVariable : identifier TO identifier SEMI; + packageOperationExport : OPERATION identifier typeParameters? ID stringLiteral COLON valueType ARROW valueType - MODE operationMode eventClause? RECEIVER receiverRequirement dependencyBlock? SEMI + MODE operationMode executionClause? eventClause? RECEIVER receiverRequirement dependencyBlock? preparationClause? acceptanceClause? SEMI ; packageFunctionExport : FUNCTION identifier typeParameters? ID stringLiteral COLON valueType ARROW valueType - dependencyBlock? SEMI + executionClause? dependencyBlock? preparationClause? acceptanceClause? SEMI + ; + +acceptanceClause + : ACCEPT USING identifier + ; + +preparationClause + : PREPARES CREATION identifier USING stringLiteral + ; + +executionClause + : EXECUTION REMOTE + | EXECUTION (SNAPSHOT | TRANSACTION) (TARGETS LBRACK identifierList RBRACK)? + ; + +selectedOperations + : USING LBRACK memberOperationRef (COMMA memberOperationRef)* RBRACK ; packageConstructorExport @@ -207,9 +252,11 @@ dependencyBlock ; dependencyPort - : STATE identifier ID stringLiteral COLON valueType primitiveList SEMI - | EDGE identifier ID stringLiteral COLON cardinality targetConstraint primitiveList SEMI - | INTERFACE identifier ID stringLiteral COLON identifier typeArguments? SEMI + : REFLECTION identifier ID stringLiteral LBRACK identifierList RBRACK SEMI + | STATE identifier ID stringLiteral COLON valueType primitiveList SEMI + | EDGE identifier ID stringLiteral COLON cardinality targetConstraint (KEYED stringLiteral)? primitiveList SEMI + | INTERFACE identifier ID stringLiteral COLON identifier typeArguments? selectedOperations? (FROM INPUT)? SEMI + | CREATION identifier ID stringLiteral COLON identifier INPUT valueType SEMI | CONSTRUCTOR identifier ID stringLiteral COLON identifier (INPUT valueType)? SEMI | QUERY identifier ID stringLiteral COLON identifier DOT identifier SEMI ; @@ -221,6 +268,7 @@ primitiveList primitive : READ | WRITE + | EDIT | RESOLVE | CONNECT | DISCONNECT @@ -243,8 +291,10 @@ stateDecl ; storagePolicy - : OPTIMISTIC_REGISTER + : REGISTER | CRDT LPAREN valueType RPAREN + | JOURNAL jsonObject + | SUBSCRIPTION LPAREN valueType RPAREN ; edgeDecl @@ -257,15 +307,38 @@ edgeEndpoint ; conformanceDecl - : CONFORM identifier AS identifier typeArguments? (ID stringLiteral)? (SEMANTIC_MAJOR INTEGER)? + : CONFORM typeParameters? identifier conformanceSourceArguments? AS identifier typeArguments? (ID stringLiteral)? (SEMANTIC_MAJOR INTEGER)? LBRACE conformanceItem* RBRACE ; +conformanceSourceArguments + : typeArguments + ; + conformanceItem : PRIVATE attachmentDecl | operationBindingDecl | stateFieldBindingDecl + | initializationDecl | relationshipMaterializationDecl + | moduleBindingDecl + ; + +moduleBindingDecl + : BIND identifier TO identifier DOT identifier SEMI + ; + +initializationDecl + : INITIALIZE LBRACE initializationField* RBRACE SEMI + ; + +initializationField + : identifier TO STATE identifier SEMI + | identifier TO EDGE identifier DOT identifier SEMI + ; + +creationOwnerMapping + : interfaceType FROM INPUT (DOT identifier)* SEMI ; stateFieldBindingDecl @@ -289,6 +362,7 @@ operationName | CALL | GET | SET + | EDIT | RESOLVE | CONNECT | DISCONNECT @@ -299,14 +373,23 @@ operationName ; operationProvider - : STATE identifier DOT statePrimitive + : LIFECYCLE DOT identifier + | JOURNAL identifier DOT identifier + | SUBSCRIPTION identifier DOT identifier + | STATE identifier DOT statePrimitive | EDGE identifier DOT identifier DOT edgePrimitive - | PACKAGE identifier DOT identifier typeArguments? dependencyBindingBlock? + | INTERFACE identifier typeArguments? DOT memberOperationRef + | PACKAGE identifier DOT identifier typeArguments? dependencyBindingBlock? acceptanceBinding? + ; + +acceptanceBinding + : ACCEPT dependencyBindingBlock ; statePrimitive : READ | WRITE + | EDIT | WATCH_START | WATCH_STOP ; @@ -324,11 +407,13 @@ dependencyBindingBlock ; dependencyBinding - : identifier TO STATE identifier (VIA EDGE identifier DOT identifier)? SEMI + : identifier TO WORKSPACE REFLECTION LBRACK identifierList RBRACK SEMI + | identifier TO STATE identifier (VIA EDGE identifier DOT identifier)? SEMI | identifier TO EDGE identifier DOT identifier (VIA EDGE identifier DOT identifier)? SEMI - | identifier TO INTERFACE identifier typeArguments? (VIA EDGE identifier DOT identifier)? SEMI + | identifier TO INTERFACE identifier typeArguments? ((VIA EDGE identifier DOT identifier) | (FROM INPUT))? SEMI + | identifier TO CREATION identifier WITH LBRACE creationOwnerMapping* RBRACE SEMI | identifier TO CONSTRUCTOR identifier SEMI - | identifier TO QUERY identifier DOT identifier (VIA EDGE identifier DOT identifier)? SEMI + | identifier TO WORKSPACE? QUERY identifier DOT identifier (VIA EDGE identifier DOT identifier)? SEMI ; constructorBindingDecl @@ -346,6 +431,7 @@ valueType | OPTIONAL LT valueType GT | LIST LT valueType GT | RECORD LBRACE recordField* RBRACE + | VARIANT ID stringLiteral LBRACE variantCase+ RBRACE | identifier typeArguments? ; @@ -353,6 +439,10 @@ recordField : identifier COLON valueType SEMI ; +variantCase + : identifier ID stringLiteral COLON valueType SEMI + ; + scalarType : BOOL | BYTES @@ -396,12 +486,30 @@ jsonArray identifier : IDENTIFIER + | START + | STOP + | EDIT + | CREATION + | INITIALIZE + | EXECUTION + | SNAPSHOT + | TRANSACTION + | REMOTE + | TARGETS + | PORTABLE + | REPLICA + | LOCAL + | FROM + | REGISTER + | INTERFACES + | REFLECTION | SOURCE | QUERY | SPECIALIZE | ROOT | DOCUMENT | FRAGMENTS + | FRAGMENT | VIEW | MAX | ALLOW @@ -411,17 +519,44 @@ identifier | RESULT_BYTES | RPC_CALLS | DEADLINE_MS + | PRESENTATION + | SUBJECT + | MESSAGE + | JOURNAL + | SUBSCRIPTION + | CONNECT + | FOR ; stringLiteral : STRING_LITERAL ; +EDIT: 'edit'; +MODULE: 'module'; +CONTRACT: 'contract'; +PRESENTATION: 'presentation'; +SUBJECT: 'subject'; +FOR: 'for'; +CREATION: 'creation'; +VARIANT: 'variant'; +PREPARES: 'prepares'; +INITIALIZE: 'initialize'; WORKSPACE: 'workspace'; +EXECUTION: 'execution'; +SNAPSHOT: 'snapshot'; +TRANSACTION: 'transaction'; +REMOTE: 'remote'; +TARGETS: 'targets'; +PORTABLE: 'portable'; +REPLICA: 'replica'; +LOCAL: 'local'; +FROM: 'from'; RESULT_BYTES: 'result-bytes'; RPC_CALLS: 'rpc-calls'; DEADLINE_MS: 'deadline-ms'; QUERY: 'query'; +REFLECTION: 'reflection'; SPECIALIZE: 'specialize'; ROOT: 'root'; DOCUMENT: 'document'; @@ -441,11 +576,13 @@ REF: 'ref'; FRAGMENT: 'fragment'; IMPORT: 'import'; EXTERNAL: 'external'; +SYMBOL: 'symbol'; ATOM: 'atom'; INTERFACE: 'interface'; INTERFACES: 'interfaces'; PACKAGE: 'package'; VALUE: 'value'; +FIELD: 'field'; RELATION: 'relation'; OPERATION: 'operation'; FUNCTION: 'function'; @@ -464,6 +601,7 @@ EDGE: 'edge'; PROJECTION: 'projection'; WITH: 'with'; USING: 'using'; +ACCEPT: 'accept'; VIA: 'via'; MATERIALIZE: 'materialize'; IF: 'if'; @@ -477,6 +615,9 @@ COMMIT: 'commit'; REVISION: 'revision'; SEMANTIC_MAJOR: 'semantic-major'; ON_DELETE: 'on-delete'; +LIFECYCLE: 'lifecycle'; +JOURNAL: 'journal'; +SUBSCRIPTION: 'subscription'; RETAIN_OTHER: 'retain-other'; KEYED: 'keyed'; PUBLIC_TRAVERSAL: 'public-traversal'; @@ -502,7 +643,7 @@ WATCH_START: 'watch-start'; WATCH_STOP: 'watch-stop'; SUBSCRIBE: 'subscribe'; UNSUBSCRIBE: 'unsubscribe'; -OPTIMISTIC_REGISTER: 'optimistic-register'; +REGISTER: 'register'; CRDT: 'crdt'; OPTIONAL_ONE: 'optional-one'; EXACTLY_ONE: 'exactly-one'; diff --git a/grammar/QuixosLock.g4 b/grammar/QuixosLock.g4 index e8b41a1..ae2525e 100644 --- a/grammar/QuixosLock.g4 +++ b/grammar/QuixosLock.g4 @@ -20,6 +20,7 @@ importEntry resourceKind : INTERFACE | PACKAGE + | BUILD ; sourceBlock @@ -58,6 +59,7 @@ QUIXOS: 'quixos'; SOURCE: 'source'; INTERFACE: 'interface'; PACKAGE: 'package'; +BUILD: 'build'; REPOSITORY: 'repository'; COMMIT: 'commit'; POLICY: 'policy'; diff --git a/nix/check-execution.mjs b/nix/check-execution.mjs new file mode 100644 index 0000000..698f3de --- /dev/null +++ b/nix/check-execution.mjs @@ -0,0 +1,47 @@ +// Validate compiler-owned plans and immutable artifact receipts before publishing check outputs. +import { readFile, writeFile } from "node:fs/promises"; +import { createHash } from "node:crypto"; +import { pathToFileURL } from "node:url"; +import { join } from "node:path"; +const [compilerPath, planPath, checksPath, output] = process.argv.slice(2); +const compiler = await import(pathToFileURL(compilerPath)); +const plan = JSON.parse(await readFile(planPath, "utf8")); +const checks = JSON.parse(await readFile(checksPath, "utf8")); +compiler.compileWorkspaceEntry(plan, JSON.parse(await readFile(join(output, "entry.json"), "utf8"))); +const roots = Object.create(null); +for (const check of checks) { + if (Object.hasOwn(roots, check.packageRevisionId)) throw Error("Duplicate checked package revision"); + roots[check.packageRevisionId] = check.artifactPath; +} +const generator = createHash("sha256") + .update(await readFile(compilerPath)) + .digest("hex"); +// These compiler-dependent checks belong to the Nix build, not runtime recovery. +const candidate = JSON.parse(await readFile(join(output, "candidate.json"), "utf8")); +const { executionContracts: _legacyContracts, ...workspace } = candidate; +if (compiler.hexDigest(compiler.compileExecutionPlan(workspace)) !== compiler.hexDigest(plan)) + throw Error("CANDIDATE_PLAN_MISMATCH: build plan differs from source compilation"); +const checked = await compiler.loadCheckedArtifactWorld(plan, roots, generator); +const installation = await compiler.loadExecutionInstallation(plan, roots); +const kernel = compiler.compileKernelProgram(plan), + queries = compiler.compileQueryProgram(plan); +const graph = compiler.graphExecutionRepresentation(plan, installation, kernel, queries); +for (const [file, value] of [ + ["graph-execution.json", graph], + ["execution-world.json", checked.world], + ["installation.json", installation], + ["kernel.json", kernel], + ["queries.json", queries], + [ + "execution-artifacts.json", + { + codec: "camino-checked-execution-v2", + generator, + roots, + manifests: checked.manifests, + execution: compiler.hexDigest(graph), + programs: { kernel: compiler.hexDigest(kernel), queries: compiler.hexDigest(queries) }, + }, + ], +]) + await writeFile(join(output, file), JSON.stringify(value) + "\n"); diff --git a/nix/check-package.mjs b/nix/check-package.mjs new file mode 100644 index 0000000..efeb326 --- /dev/null +++ b/nix/check-package.mjs @@ -0,0 +1,41 @@ +// An independent package check witnesses its contract and bytes, without +// inventing a workspace or granting installation/activation authority. +import { readFile, writeFile } from "node:fs/promises"; +import { createHash } from "node:crypto"; +import { pathToFileURL } from "node:url"; +import { join } from "node:path"; +const [compilerPath, schemaPath, checksPath, output] = process.argv.slice(2); +const compiler = await import(pathToFileURL(compilerPath)); +const schema = JSON.parse(await readFile(schemaPath, "utf8")); +const checks = JSON.parse(await readFile(checksPath, "utf8")); +const generator = createHash("sha256") + .update(await readFile(compilerPath)) + .digest("hex"); +const evidence = []; +for (const check of checks) { + const pkg = schema.packages.find((pkg) => pkg.revisionId === check.packageRevisionId); + if (!pkg) throw Error("Unknown checked package"); + const contract = compiler.packageBuildContract(schema, pkg.revisionId); + const sourceDigest = compiler.hexDigest({ revision: pkg.revisionId, source: pkg.source }); + const manifest = compiler.parsePackageArtifacts( + JSON.parse(await readFile(join(check.artifactPath, "share/quixos/package-artifacts.json"), "utf8")), + ); + compiler.checkPackageCoverage( + { ...contract.definition.package, bindingDigest: contract.digest, sourceDigest }, + manifest, + ); + const receipt = JSON.parse(await readFile(join(check.artifactPath, "share/quixos/build-receipt.json"), "utf8")); + const expected = compiler.expectedPackageReceipt(contract, manifest, sourceDigest, generator); + if (compiler.hexDigest(receipt) !== compiler.hexDigest(expected.receipt)) + throw Error("Invalid package build receipt"); + const read = await compiler.artifactFileReader({ [pkg.revisionId]: check.artifactPath }); + await compiler.checkArtifactFiles(manifest, read); + for (const [file, bytes] of expected.sources) + if (!Buffer.from(bytes).equals(await read(pkg.revisionId, file, bytes.length))) + throw Error("Modified generated binding " + file); + evidence.push({ ...check, contract: contract.digest, sourceDigest, receipt }); +} +await writeFile( + join(output, "package-checks.json"), + JSON.stringify({ codec: "camino-package-checks-v1", generator, packages: evidence }), +); diff --git a/nix/checked-artifacts.nix b/nix/checked-artifacts.nix new file mode 100644 index 0000000..cc7d7c1 --- /dev/null +++ b/nix/checked-artifacts.nix @@ -0,0 +1,49 @@ +# Candidate artifact evaluation. This is separate from the old server activation path; +# the new coordinator consumes its manifest/receipt, not the legacy server ABI. +{ + source, + plan, + generator, + core, + sharedRuntime, + typescript, + packageRevisionId, + system ? builtins.currentSystem, +}: +let + packageSource = builtins.path { + path = /. + source; + name = "quixos-package-source"; + filter = + path: _: + !(builtins.elem (baseNameOf path) [ + ".git" + ".jj" + ]); + }; + package = builtins.getFlake ( + "path:" + builtins.unsafeDiscardStringContext (toString packageSource) + ); + pkgs = import package.inputs.nixpkgs { inherit system; }; + buildContext = import ./package-context.nix { + inherit pkgs packageRevisionId; + protocol = builtins.storePath generator; + plan = builtins.path { + path = /. + plan; + name = "candidate-execution-plan.json"; + }; + }; + checked = + package.quixosPackages.${system}.checkedArtifacts + or (throw "Package ${packageRevisionId} lacks checkedArtifacts for the portable execution world."); +in +checked ( + buildContext + // { + inherit packageRevisionId; + generator = builtins.storePath generator; + core = builtins.storePath core; + sharedRuntime = builtins.storePath sharedRuntime; + typescript = builtins.storePath typescript; + } +) diff --git a/nix/checked-candidate.nix b/nix/checked-candidate.nix index fabe5b9..d657ca0 100644 --- a/nix/checked-candidate.nix +++ b/nix/checked-candidate.nix @@ -9,7 +9,11 @@ system ? builtins.currentSystem, }: let - pkgs = import (/. + "@nixpkgs@") { inherit system; }; + pkgs = import (/. + "@nixpkgs@") { + inherit system; + config = { }; + overlays = [ ]; + }; protocol = builtins.storePath generator; sourceKey = source: "${source.kind}:${source.repository}@${source.commit}"; fetch = @@ -20,93 +24,309 @@ let ref = "refs/tags/quixos-reachability/${source.commit}"; shallow = true; }; - load = - ancestors: source: - if builtins.elem (sourceKey source) ancestors then - throw "Source dependency cycle at ${sourceKey source}" - else if builtins.length ancestors >= 100 then - throw "Source dependency depth exceeds 100" - else - let - directory = fetch source; - lockFile = pkgs.runCommand "qx-source-lock.json" { } '' - ${protocol}/bin/quixos-lock-check ${directory}/quixos.lock > "$out" - ''; - lock = builtins.fromJSON (builtins.readFile lockFile); + # Memoize by exact source identity. A diamond-shaped dependency graph must + # not recursively evaluate every path through the same imported subtree. + loadSource = + source: + let + directory = fetch source; + lockSource = import ./lock-source.nix { source = directory; }; + lockFile = pkgs.runCommand "qx-source-lock.json" { } '' + ${protocol}/bin/quixos-lock-check ${lockSource}/quixos.lock > "$out" + ''; + lock = builtins.fromJSON (builtins.readFile lockFile); + in + source // { inherit directory lock; }; + sourceEntry = source: { + key = sourceKey source; + value = loadSource source; + }; + sourceGraph = builtins.genericClosure { + startSet = [ (sourceEntry { inherit kind repository commit; }) ]; + operator = + entry: + map ( + resource: sourceEntry (resource.source // { inherit (resource) kind; }) + ) entry.value.lock.resources; + }; + nodeTable = builtins.listToAttrs ( + map (entry: { + name = entry.key; + value = entry.value // { children = map ( - entry: load (ancestors ++ [ (sourceKey source) ]) (entry.source // { inherit (entry) kind; }) - ) lock.resources; - in - source // { inherit directory children; }; - root = load [ ] { inherit kind repository commit; }; - flatten = node: [ node ] ++ pkgs.lib.concatMap flatten node.children; - nodes = builtins.attrValues ( - builtins.listToAttrs ( - map (node: { - name = sourceKey node; - value = node; - }) (flatten root) - ) + resource: + nodeTable.${sourceKey (resource.source // { inherit (resource) kind; })} + // { + inherit (resource) binding; + } + ) entry.value.lock.resources; + }; + }) sourceGraph ); - snapshots = pkgs.writeText "qx-nix-source-graph.json" ( - builtins.toJSON { - resources = map (node: { - inherit (node) - kind - repository - commit - directory - ; - }) (builtins.filter (node: node.kind != "workspace") nodes); - } + root = nodeTable.${sourceKey { inherit kind repository commit; }}; + configuration = + if builtins.pathExists (root.directory + "/quixos.check.json") then + builtins.fromJSON (builtins.readFile (root.directory + "/quixos.check.json")) + else + { }; + execution = (configuration.backend or "typescript") == "execution"; + runtime = builtins.getFlake ( + "git+${root.lock.quixos.repository}?rev=${root.lock.quixos.commit}&ref=refs/tags/quixos-reachability/${root.lock.quixos.commit}" ); - compile = + runtimePackages = runtime.packages.${system}; + resourceClosure = + includeBuild: node: + map (entry: entry.value) ( + builtins.genericClosure { + startSet = pkgs.lib.optional (includeBuild || node.kind != "build") { + key = sourceKey node; + value = node; + }; + operator = + entry: + map (child: { + key = sourceKey child; + value = child; + }) (builtins.filter (child: includeBuild || child.kind != "build") entry.value.children); + } + ); + capabilityFlatten = resourceClosure false; + # Frontends are an explicit build choice. The capability assembler itself + # neither discovers React contracts nor loads executable package code. + queryFrontends = node: - pkgs.runCommand "qx-${node.kind}-contract" { } '' + pkgs.lib.unique ( + pkgs.lib.concatMap ( + node: + let + config = node.directory + "/quixos.check.json"; + in + if builtins.pathExists config then + (builtins.fromJSON (builtins.readFile config)).queryFrontends or [ ] + else + [ ] + ) (capabilityFlatten node) + ); + queryGeneratorFlags = + node: + if queryFrontends node == [ ] then + "" + else if queryFrontends node == [ "react" ] then + "--query-generator ${protocol}/libexec/quixos-protocol/react-query-generator.mjs" + else + throw "Unsupported query frontend selection: ${builtins.toJSON (queryFrontends node)}"; + snapshots = + node: + pkgs.writeText "qx-nix-source-graph.json" ( + builtins.toJSON { + resources = map (node: { + inherit (node) + kind + repository + commit + directory + ; + }) (builtins.filter (entry: entry.kind != "workspace") (resourceClosure true node)); + } + ); + # Each resource is checked once per exact source/closure/frontend/toolchain. + # Composition imports these compiler outputs; authoring receipts are never + # build inputs. Missing entries in ordinary CLI use take the full check path. + compiledNodes = builtins.mapAttrs (_: node: compileNode node) nodeTable; + compile = node: compiledNodes.${sourceKey node}; + checkedResources = + node: + pkgs.writeText "qx-checked-resources.json" ( + builtins.toJSON ( + map + (dependency: { + inherit (dependency) kind repository commit; + artifact = compile dependency; + }) + ( + builtins.filter ( + dependency: + dependency.kind != "workspace" + && dependency.kind != "build" + && sourceKey dependency != sourceKey node + ) (resourceClosure true node) + ) + ) + ); + compileNode = + node: + pkgs.runCommand "qx-${node.kind}-contract" { QUIXOS_TRACE_PROFILE = "1"; } '' mkdir -p "$out" ${ if node.kind == "workspace" then '' ${protocol}/bin/quixos-workspace-compile --root ${node.directory} \ + ${queryGeneratorFlags root} \ + --checked-resources ${checkedResources node} \ --source-root-commit ${pkgs.lib.escapeShellArg node.commit} \ - --checkout-root "$TMPDIR/checkouts" --snapshot-map ${snapshots} \ - --graph-out "$out/graph.json" --schemas-out "$out/package-schemas.json" > "$out/candidate.json" + --checkout-root "$TMPDIR/checkouts" --snapshot-map ${snapshots node} \ + --graph-out "$out/graph.json" --schemas-out "$out/package-schemas.json" \ + ${ + pkgs.lib.optionalString (execution && kind == "workspace") + ''--execution-plan-out "$out/execution-plan.json" --build-contexts-out "$out/build-contexts.json"'' + } > "$out/candidate.json" '' else '' ${protocol}/bin/quixos-resource-compile --root ${node.directory} \ + ${queryGeneratorFlags root} \ + --checked-resources ${checkedResources node} \ --kind ${node.kind} --repository ${pkgs.lib.escapeShellArg node.repository} \ --commit ${pkgs.lib.escapeShellArg node.commit} \ - --checkout-root "$TMPDIR/checkouts" --snapshot-map ${snapshots} --snapshot-only true \ + --checkout-root "$TMPDIR/checkouts" --snapshot-map ${snapshots node} --snapshot-only true \ --graph-out "$out/graph.json" --schema-out "$out/bindings.json" > "$out/candidate.json" '' } ''; contract = compile root; + # Parse shared workspace outputs once. Repeating fromJSON in every package + # makes composition quadratic in package count and in the serialized world. + workspacePackages = builtins.listToAttrs ( + map + (pkg: { + name = sourceKey (pkg.source // { kind = "package"; }); + value = pkg; + }) + (builtins.fromJSON (builtins.readFile "${contract}/execution-plan.json")) + .definition.bindingSchema.packages + ); + workspaceContexts = builtins.fromJSON (builtins.readFile "${contract}/build-contexts.json"); + buildDependencies = + node: + builtins.listToAttrs ( + map (dependency: { + name = dependency.binding; + value = { + src = dependency.directory; + source = { inherit (dependency) repository commit; }; + flake = builtins.getFlake "git+${dependency.repository}?rev=${dependency.commit}&ref=refs/tags/quixos-reachability/${dependency.commit}"; + }; + }) (builtins.filter (dependency: dependency.kind == "build") node.children) + ); + reactContracts = + node: moduleInterfaces: + let + interfaces = builtins.attrValues ( + builtins.listToAttrs ( + map (dependency: { + name = sourceKey dependency; + value = dependency; + }) (builtins.filter (dependency: dependency.kind == "interface") (capabilityFlatten node)) + ) + ); + in + pkgs.lib.concatMap ( + dependency: + if !builtins.pathExists (dependency.directory + "/flake.nix") then + [ ] + else + let + source = builtins.getFlake "git+${dependency.repository}?rev=${dependency.commit}&ref=refs/tags/quixos-reachability/${dependency.commit}"; + build = source.quixosInterfaces.${system}.reactTypes or null; + in + if build == null then + [ ] + else if + !(builtins.any ( + entry: + let + resource = builtins.fromJSON (builtins.readFile "${compile dependency}/candidate.json"); + in + entry.revisionId == resource.revision.revisionId + || (entry.definitionId or null) == resource.revision.revisionId + ) moduleInterfaces) + then + [ ] + else + [ + (build { + interface = "${compile dependency}/candidate.json"; + generator = protocol; + typescript = runtimePackages.camino-replica-core.dependencies; + reactSdk = runtimePackages.camino-react; + dependencies = buildDependencies dependency; + }) + ] + ) interfaces; checkPackage = node: let compiled = compile node; - candidate = builtins.fromJSON (builtins.readFile "${compiled}/candidate.json"); + candidate = + if execution && kind == "workspace" then + { + revision = + workspacePackages.${sourceKey node} + or (throw "Source package missing from workspace compilation: ${node.repository}"); + } + else + builtins.fromJSON (builtins.readFile "${compiled}/candidate.json"); + buildContext = import ./package-context.nix { + inherit pkgs protocol; + contexts = if kind == "workspace" then "${contract}/build-contexts.json" else null; + contextData = + if execution && kind == "workspace" then + workspaceContexts.${candidate.revision.revisionId} + else + null; + plan = "${compiled}/bindings.json"; + packageRevisionId = candidate.revision.revisionId; + }; + buildContract = builtins.fromJSON (builtins.readFile buildContext.buildContract); + componentEntries = if execution then buildContract.definition.package.components or [ ] else [ ]; package = builtins.getFlake ( "git+${node.repository}?rev=${node.commit}&ref=refs/tags/quixos-reachability/${node.commit}" ); checked = - package.quixosPackages.${system}.checkedServer - or (throw "Package ${node.repository} lacks checkedServer; use the supported package scaffold."); - artifact = checked { - schema = - if kind == "workspace" then - pkgs.writeText "package-specialization-schema.json" ( - builtins.toJSON - (builtins.fromJSON (builtins.readFile "${contract}/package-schemas.json")) - .${candidate.revision.revisionId} - ) - else - "${compiled}/bindings.json"; - generator = protocol; - packageRevisionId = candidate.revision.revisionId; - }; + if execution then + package.quixosPackages.${system}.checkedArtifacts + or (throw "Package ${node.repository} lacks checkedArtifacts; execution workspaces cannot import legacy servers.") + else + package.quixosPackages.${system}.checkedServer + or (throw "Package ${node.repository} lacks checkedServer; use the supported package scaffold."); + artifact = checked ( + if execution then + buildContext + // { + generator = protocol; + core = runtimePackages.camino-replica-core; + sharedRuntime = runtimePackages.camino-shared-runtime; + typescript = runtimePackages.camino-replica-core.dependencies; + packageRevisionId = candidate.revision.revisionId; + dependencies = buildDependencies node; + buildEnvironment = { + inherit pkgs system; + nixpkgsSource = toString pkgs.path; + narHash = "@nixpkgsNarHash@"; + }; + } + // pkgs.lib.optionalAttrs (componentEntries != [ ]) { + react = { + sdk = runtimePackages.camino-react; + runtime = runtimePackages.camino-react-runtime; + contracts = reactContracts node buildContract.definition.moduleInterfaces; + }; + } + else + { + schema = + if kind == "workspace" then + pkgs.writeText "package-specialization-schema.json" ( + builtins.toJSON + (builtins.fromJSON (builtins.readFile "${contract}/package-schemas.json")) + .${candidate.revision.revisionId} + ) + else + "${compiled}/bindings.json"; + generator = protocol; + packageRevisionId = candidate.revision.revisionId; + } + ); in { packageRevisionId = candidate.revision.revisionId; @@ -116,7 +336,20 @@ let if contractOnly then [ ] else - map checkPackage (builtins.filter (node: node.kind == "package") nodes); + map checkPackage ( + builtins.filter + (node: node.kind == "package" && (kind == "workspace" || sourceKey node == sourceKey root)) + ( + builtins.attrValues ( + builtins.listToAttrs ( + map (node: { + name = sourceKey node; + value = node; + }) (capabilityFlatten root) + ) + ) + ) + ); manifest = pkgs.writeText "qx-candidate-checks.json" (builtins.toJSON checks); bindingOptions = pkgs.writeText "qx-typescript-options.json" ( builtins.toJSON ( @@ -126,13 +359,45 @@ let in pkgs.runCommand (if contractOnly then "qx-contract" else "qx-checked-candidate") { } '' mkdir -p "$out" + cp ${ + builtins.toFile "qx-candidate-source.json" (builtins.toJSON { inherit repository commit kind; }) + } "$out/source.json" cp ${contract}/candidate.json "$out/candidate.json" cp ${contract}/graph.json "$out/graph.json" cp ${manifest} "$out/checks.json" + ${pkgs.lib.optionalString (execution && kind == "workspace") '' + ${ + if builtins.pathExists "${root.directory}/quixos.entry.json" then + '' + cp ${root.directory}/quixos.entry.json "$out/entry.json" + '' + else + '' + echo null > "$out/entry.json" + '' + } + cp ${contract}/execution-plan.json "$out/execution-plan.json" + ${pkgs.lib.optionalString (!contractOnly) '' + ${pkgs.nodejs_24}/bin/node ${./check-execution.mjs} \ + ${protocol}/libexec/quixos-protocol/execution-world.mjs \ + "$out/execution-plan.json" "$out/checks.json" "$out" + mkdir -p "$out/bin" + cat > "$out/bin/run-isolated" < "$out/quixos.lock" + printf '%s' ${pkgs.lib.escapeShellArg extra} > "$out/implementation.ts" + printf '%s' 'quixos-lock fragment version 1 {}' > "$out/nested/dependencies.txt" + ''; + first = fixture "first implementation" base; + edited = fixture "edited implementation" base; + changed = fixture "first implementation" ( + builtins.replaceStrings + [ "1111111111111111111111111111111111111111" ] + [ "2222222222222222222222222222222222222222" ] + base + ); + fragments = fixture "first implementation" ( + builtins.replaceStrings [ "quixos source" ] [ ''import "nested/dependencies.txt"; quixos source'' ] + base + ); + conservative = fixture "first implementation" (base + "\n// import in a comment\n"); + slice = source: import ./lock-source.nix { inherit source; }; + parse = + source: + pkgs.runCommand "qx-source-lock.json" { } '' + ${protocol}/bin/quixos-lock-check ${slice source}/quixos.lock > "$out" + ''; +in +assert slice first == slice edited; +assert (parse first).drvPath == (parse edited).drvPath; +assert slice first != slice changed; +pkgs.runCommand "qx-lock-source-check" { } '' + ${protocol}/bin/quixos-lock-check ${slice first}/quixos.lock > first.json + ${protocol}/bin/quixos-lock-check ${slice edited}/quixos.lock > edited.json + cmp first.json edited.json + ${protocol}/bin/quixos-lock-check ${slice conservative}/quixos.lock > comment.json + cmp first.json comment.json + ${protocol}/bin/quixos-lock-check ${slice fragments}/quixos.lock > "$out" +'' diff --git a/nix/package-context.mjs b/nix/package-context.mjs new file mode 100644 index 0000000..20ba028 --- /dev/null +++ b/nix/package-context.mjs @@ -0,0 +1,15 @@ +import fs from "node:fs/promises"; +import { pathToFileURL } from "node:url"; +const compiler = await import(pathToFileURL(process.argv[2])); +const input = JSON.parse(await fs.readFile(process.argv[3], "utf8")); +const revision = process.argv[4]; +const schema = input.definition?.bindingSchema ?? input; +if (input.definition && compiler.hexDigest(input.definition) !== input.digest) throw Error("Invalid execution plan"); +const pkg = schema.packages.find((entry) => entry.revisionId === revision); +if (!pkg) throw Error("Unknown build package " + revision); +process.stdout.write( + JSON.stringify({ + contract: compiler.packageBuildContract(schema, revision), + sourceDigest: compiler.hexDigest({ revision, source: pkg.source }), + }), +); diff --git a/nix/package-context.nix b/nix/package-context.nix new file mode 100644 index 0000000..ef20489 --- /dev/null +++ b/nix/package-context.nix @@ -0,0 +1,33 @@ +# Workspace composition may reevaluate this projection after any graph edit. +# Only the canonical, source-free value becomes a package compilation input. +{ + pkgs, + protocol, + plan ? null, + contexts ? null, + contextData ? null, + packageRevisionId, +}: +let + projected = pkgs.runCommand "camino-package-context.json" { } '' + ${pkgs.nodejs_24}/bin/node ${./package-context.mjs} \ + ${protocol}/libexec/quixos-protocol/execution-world.mjs ${plan} \ + ${pkgs.lib.escapeShellArg packageRevisionId} > "$out" + ''; + context = + if contextData != null then + contextData + else if contexts == null then + builtins.fromJSON (builtins.readFile projected) + else + (builtins.fromJSON (builtins.readFile contexts)).${packageRevisionId}; +in +{ + # This is typed semantic data (nominal IDs, types and generated source), never + # Nix artifact paths. Remove the projection derivation's string context, not + # any code/toolchain dependency. Those are separate explicit helper arguments. + buildContract = builtins.toFile "camino-package-contract.json" ( + builtins.unsafeDiscardStringContext (builtins.toJSON context.contract) + ); + sourceDigest = builtins.unsafeDiscardStringContext context.sourceDigest; +} diff --git a/package.json b/package.json index c499da9..4aa8a44 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "type": "module", "bin": { "quixos-capability-compile": "dist/src/capability-language/cli.js", + "quixos-codegen-client": "dist/src/bindings/portable-client-cli.js", "quixos-codegen-ts": "dist/src/bindings/cli.js", "quixos-descriptor-check": "dist/src/descriptor-check.js", "quixos-lock-check": "dist/src/resource-lock/cli.js", @@ -18,7 +19,8 @@ "./capability-model": "./dist/src/capability-model/index.js", "./capability-language": "./dist/src/capability-language/index.js", "./resource-lock": "./dist/src/resource-lock/index.js", - "./gen/*": "./dist/src/gen/*" + "./gen/*": "./dist/src/gen/*", + "./execution-world": "./dist/src/execution-world/index.js" }, "scripts": { "build": "yarn generate && tsc -p tsconfig.build.json", diff --git a/proto/camino/api.proto b/proto/camino/api.proto index 6f25c8b..cb59274 100644 --- a/proto/camino/api.proto +++ b/proto/camino/api.proto @@ -241,7 +241,10 @@ message CaminoOp { message QueryRequest { string query_id = 1; - string object_id = 2; + oneof root { + string object_id = 2; + quixos.WorkspaceQueryScope workspace_scope = 5; + } map variables = 3; // Typed callers require this exact checked definition. Administrative/raw // callers may omit it to explicitly select the currently installed definition. @@ -351,7 +354,10 @@ message QueryFieldFailure { } message QueryChangesRequest { string query_id = 1; - string object_id = 2; + oneof root { + string object_id = 2; + quixos.WorkspaceQueryScope workspace_scope = 4; + } string data_version = 3; } message QueryChangesResponse { diff --git a/proto/camino/schema.proto b/proto/camino/schema.proto index d88091b..1e2e123 100644 --- a/proto/camino/schema.proto +++ b/proto/camino/schema.proto @@ -125,6 +125,7 @@ message QueryRelationPath { message QueryFieldOperand { string interface_revision_id = 1; string member_id = 2; + repeated string value_path = 3; } message QueryExpression { repeated QueryPathStep path = 1; @@ -165,6 +166,7 @@ enum QueryComparisonOperator { QUERY_COMPARISON_OPERATOR_LTE = 5; QUERY_COMPARISON_OPERATOR_GT = 6; QUERY_COMPARISON_OPERATOR_GTE = 7; + QUERY_COMPARISON_OPERATOR_CONTAINS_INSENSITIVE = 8; } message QueryComparison { QueryOperand operand = 1; @@ -194,6 +196,10 @@ message QueryReductionPredicate { QueryPredicate where = 3; QueryPredicate having = 4; } +message QueryInterfacePredicate { + string interface_revision_id = 1; + QueryPredicate predicate = 2; +} message QueryPredicate { oneof predicate { QueryPredicateList and = 1; @@ -202,6 +208,7 @@ message QueryPredicate { QueryComparison compare = 4; QueryRelationPredicate relation = 5; QueryReductionPredicate reduce = 6; + QueryInterfacePredicate as = 7; } } message QueryRow { @@ -286,6 +293,15 @@ message QueryReadBinding { Cardinality cardinality = 13; string key_type = 14; } +// Checked schema survives even when no atom currently implements an interface. +message QueryFieldContract { + string interface_revision_id = 1; + string member_id = 2; + string field_name = 3; + string value_type_json = 4; + Cardinality cardinality = 5; + string key_type = 6; +} message QueryBudgets { uint32 rows = 1; uint32 depth = 2; @@ -295,11 +311,20 @@ message QueryBudgets { uint32 concurrency = 6; uint32 deadline_ms = 7; } +message QueryPlacement { + oneof kind { + bool replica = 1; + string local_source_query_id = 2; + } +} message InstalledQuery { string id = 1; string definition_digest = 2; string binding_digest = 3; - string root_interface_revision_id = 4; + oneof root { + string object_interface = 4; + string workspace_interface = 14; + } repeated QuerySelection selection = 5; repeated QueryReadBinding bindings = 6; QueryBudgets budgets = 7; @@ -309,4 +334,7 @@ message InstalledQuery { bool watch = 11; uint32 polling_interval_ms = 12; bool rpc_predicate_or_order = 13; + repeated string root_atom_ids = 15; + repeated QueryFieldContract fields = 16; + QueryPlacement placement = 17; } diff --git a/proto/quixos/orch.proto b/proto/quixos/orch.proto index 4b74f42..90a514f 100644 --- a/proto/quixos/orch.proto +++ b/proto/quixos/orch.proto @@ -9,6 +9,8 @@ import "quixos/refs.proto"; import "quixos/runtime.proto"; service OrchestratorRuntime { + rpc ReadDefinitions(DefinitionRequest) returns (DefinitionPage); + rpc WatchDefinitions(DefinitionRequest) returns (stream DefinitionPage); rpc ExecuteQuery(camino.QueryRequest) returns (camino.QueryResponse); rpc WatchQuery(camino.QueryRequest) returns (stream QueryEvent); rpc TryConform(TryConformRequest) returns (TryConformResponse); @@ -26,6 +28,83 @@ service OrchestratorRuntime { rpc CloseActivation(CloseActivationRequest) returns (CloseActivationResponse); } +// Activated definitions, not runtime metrics or object data. Nested type JSON +// uses the compiler's canonical ValueType/InterfaceRevision public contracts. +message DefinitionSelector { + string kind = 1; // active | atoms | interfaces | conformances | queries | indexes + repeated string ids = 2; + string atom_id = 3; + string interface_revision_id = 4; + string package_revision_id = 5; +} +message DefinitionIdentity { + string workspace_id = 1; + string workspace_revision_id = 2; + string source_root_commit = 3; + string workspace_epoch = 4; + string definition_digest = 5; +} +message DefinitionRequest { + DefinitionSelector selector = 1; + uint32 first = 2; // 1..100; 0 uses 50 + string after = 3; + string snapshot = 4; // issued definition_digest; current serving snapshot only + string workspace_id = 5; // required for injected package authority + string workspace_epoch = 6; // required for invocation-bound package calls +} +message ReflectedAtom { + string id = 1; + string display_name = 2; + string constructor_input_type_json = 3; +} +message ReflectedInterface { + string id = 1; + string interface_id = 2; + string display_name = 3; + string contract_json = 4; // canonical interface contract, excluding source location +} +message ReflectedConformance { + string id = 1; + string atom_id = 2; + string interface_revision_id = 3; + string definition_id = 4; + repeated string operation_ids = 5; + repeated ClassCapability class_capabilities = 6; + bool instance = 7; +} +message ReflectedQuery { + string id = 1; + string name = 2; + string package_revision_id = 3; + string definition_digest = 4; + string binding_digest = 5; + string root_kind = 6; + string interface_revision_id = 7; + string variables_type_json = 8; + string output_type_json = 9; + string effects_json = 10; + bool watch = 11; + WorkspaceQueryScope workspace_scope = 12; +} +message ReflectedIndex { + string id = 1; + string contract_json = 2; +} +message DefinitionRecord { + oneof definition { + ReflectedAtom atom = 1; + ReflectedInterface interface = 2; + ReflectedConformance conformance = 3; + ReflectedQuery query = 4; + ReflectedIndex index = 5; + } +} +message DefinitionPage { + DefinitionIdentity identity = 1; + repeated DefinitionRecord records = 2; + string next_cursor = 3; +} + message QueryEvent { string run_id = 1; uint64 sequence = 2; @@ -145,6 +224,7 @@ message QueryDescription { repeated string source_files = 6; string effects_json = 7; camino.InstalledQuery plan = 8; + WorkspaceQueryScope workspace_scope = 9; } message ClassCapability { string conformance_id = 1; diff --git a/proto/quixos/refs.proto b/proto/quixos/refs.proto index af15c91..6e4a7d0 100644 --- a/proto/quixos/refs.proto +++ b/proto/quixos/refs.proto @@ -44,12 +44,34 @@ message InjectedDependency { string interface_revision_id = 4; string constructor_atom_id = 5; string query_id = 7; + WorkspaceQueryDependency workspace_query = 8; + ReflectionDependency reflection = 9; } // Defaults to the invocation receiver. A checked dependency traversal can // select a related object explicitly before the package runtime starts. string object_id = 6; } +message ReflectionDependency { + string workspace_id = 1; + string workspace_epoch = 2; + repeated string selectors = 3; +} + +// Scope evidence is not authority by itself. The caller must also hold the +// matching workspace-query grant (or the authenticated workspace user grant). +message WorkspaceQueryScope { + string workspace_id = 1; + string workspace_revision_id = 2; + string workspace_epoch = 3; + string interface_revision_id = 4; + string binding_digest = 5; +} +message WorkspaceQueryDependency { + string query_id = 1; + WorkspaceQueryScope scope = 2; +} + message EdgeDependency { string edge_type_id = 1; string projection_id = 2; diff --git a/src/bindings/cli.ts b/src/bindings/cli.ts index d62f844..322c9de 100644 --- a/src/bindings/cli.ts +++ b/src/bindings/cli.ts @@ -1,6 +1,10 @@ #!/usr/bin/env node import { readFile, writeFile } from "node:fs/promises"; -import { generateTypeScriptBindings } from "./index.js"; +import { + generateTypeScriptBindings, + generatePortableTypeScriptBindings, + generateRemoteTypeScriptBindings, +} from "./index.js"; import path from "node:path"; import { reactPlatformTypes } from "./react-platform.js"; import { generateReactBindings } from "./react.js"; @@ -11,7 +15,23 @@ const main = async () => { throw new Error("usage: quixos-codegen-ts SCHEMA.json PACKAGE_REVISION OUTPUT.ts [OPTIONS.json]"); const config = options ? JSON.parse(await readFile(options, "utf8")) : {}; const contracts = JSON.parse(await readFile(schema, "utf8")); - const generated = generateTypeScriptBindings(contracts, revision, config); + if ([config.portable, config.remote, config.react].filter(Boolean).length > 1) + throw new Error("Portable, remote and React bindings are separate artifacts"); + if (config.remote !== undefined && config.remote !== true) throw new Error("Remote binding mode must be true"); + if (config.portable && typeof config.portable.target !== "string") + throw new Error("Portable bindings require an explicit target"); + const generated = config.remote + ? generateRemoteTypeScriptBindings( + contracts, + revision, + config.runtimeModule ? { runtimeModule: config.runtimeModule } : {}, + ) + : config.portable + ? generatePortableTypeScriptBindings(contracts, revision, { + ...config.portable, + ...(config.runtimeModule ? { runtimeModule: config.runtimeModule } : {}), + }) + : generateTypeScriptBindings(contracts, revision, config); await writeFile(output, generated); if (config.react) { await writeFile(path.join(path.dirname(output), "web-studio-react-runtime.d.ts"), reactPlatformTypes); diff --git a/src/bindings/client.ts b/src/bindings/client.ts index cac5353..c3fd941 100644 --- a/src/bindings/client.ts +++ b/src/bindings/client.ts @@ -11,6 +11,8 @@ export const generateClientContracts = ( throw new Error("Host contracts require closed interface applications, not generic definitions"); const type = (value: ValueType): string => { switch (value.kind) { + case "variant": + throw new Error("Variants require the Camino portable value runtime; legacy RPC bindings are unsupported"); case "builtin": return value.name === "unit" ? "undefined" : "string"; case "scalar": @@ -28,6 +30,8 @@ export const generateClientContracts = ( return qualified ? `CapabilityReference<${JSON.stringify(value.expectation.kind === "atom" ? `atom:${value.expectation.atomId}` : `interface:${value.expectation.interfaceRevisionId}`)}>` : `{readonly $quixosRef: string}`; + case "change": + throw new Error("CRDT change values require the prepared-change codec and runtime (LF-19)"); case "optional": return `(${type(value.value)} | null)`; case "list": diff --git a/src/bindings/generics.ts b/src/bindings/generics.ts index fc275a1..e3ec258 100644 --- a/src/bindings/generics.ts +++ b/src/bindings/generics.ts @@ -49,6 +49,8 @@ export const genericImplementationType = ( const value = (type: ValueTypeExpression, scope: Scope, depth = 0): string => { if (depth > 128) throw new Error("Generic type expansion exceeds depth limit"); switch (type.kind) { + case "variant": + throw new Error("Variants require the Camino portable value runtime; legacy RPC bindings are unsupported"); case "parameter": { const bound = scope.arguments.get(type.parameterId); if (bound) return bound; @@ -60,6 +62,8 @@ export const genericImplementationType = ( return `QxObjectRef<${target(type.expectation, scope)}>`; case "record": return object(Object.entries(type.fields).map(([name, field]) => [name, value(field, scope, depth + 1)])); + case "change": + throw new Error("CRDT change values require the prepared-change codec and runtime (LF-19)"); case "optional": return `(${value(type.value, scope, depth + 1)} | null)`; case "list": @@ -79,6 +83,8 @@ export const genericImplementationType = ( const requirement = entry.requirement, scope = rootScope(); switch (requirement.kind) { + case "reflection": + return `QxReflectionPort<${requirement.selectors.map((value) => JSON.stringify(value)).join(" | ")}>`; case "state": return object([ ...requirement.primitives.map((primitive): [string, string] => { @@ -113,6 +119,8 @@ export const genericImplementationType = ( } case "query": return queryPort(requirement); + case "creation": + throw new Error("Creation ports require the portable transaction context generator (LF-09)"); case "constructor": return object([ [ @@ -129,7 +137,12 @@ export const genericImplementationType = ( }; const operations = (contract.template?.members ?? contract.members).flatMap((member) => member.operations - .filter((op) => op.mode === "call" && op.scope !== "class") + .filter( + (op) => + op.mode === "call" && + op.scope !== "class" && + (!requirement.operations || requirement.operations.includes(op.id)), + ) .map((op) => ({ ...op, name: `${member.displayName}.${op.displayName}` })), ); const methods: [string, string][] = [ diff --git a/src/bindings/index.ts b/src/bindings/index.ts index 3891275..23b4a6d 100644 --- a/src/bindings/index.ts +++ b/src/bindings/index.ts @@ -9,6 +9,14 @@ import type { CompiledCapabilityResourceRepository } from "../capability-languag import { genericImplementationType } from "./generics.js"; import { capabilityId } from "../capability-model/types.js"; +/** Source-free package compilation inputs. Nominal references do not import + * implementations or concrete workspace conformances. */ +export type BindingContractSchema = { + format: "quixos-bindings"; + version: 1; + interfaces: Omit[]; + packages: Pick[]; +}; /** Portable generator input. New backends consume this instead of the parser or TS runtime. */ export type BindingSchema = { format: "quixos-bindings"; @@ -17,24 +25,29 @@ export type BindingSchema = { interfaceTemplates?: InterfaceRevision[]; packages: PackageRevision[]; }; -export const bindingSchema = (compiled: Pick): BindingSchema => ({ +export const bindingSchema = ( + compiled: Pick & + Partial>, +): BindingSchema => ({ format: "quixos-bindings", version: 1, - interfaceTemplates: compiled.resources.flatMap((node) => + interfaceTemplates: (compiled.capabilityResources ?? compiled.resources).flatMap((node) => node.resource.kind === "interface" && node.resource.revision.template ? [node.resource.revision] : [], ), interfaces: [ ...new Map( - compiled.resources + (compiled.capabilityResources ?? compiled.resources) .flatMap((node) => [ ...(node.resource.kind === "interface" ? [node.resource.revision] : []), - ...(node.resource.specializations ?? []), + ...(node.resource.kind === "build" ? [] : (node.resource.specializations ?? [])), ]) .filter((entry) => !entry.template) .map((entry) => [entry.revisionId, entry]), ).values(), ], - packages: compiled.resources.flatMap((node) => (node.resource.kind === "package" ? [node.resource.revision] : [])), + packages: (compiled.capabilityResources ?? compiled.resources).flatMap((node) => + node.resource.kind === "package" ? [node.resource.revision] : [], + ), }); export const specializeBindingSchema = ( @@ -107,6 +120,8 @@ export const generateTypeScriptBindings = ( const messages = new Map(); const type = (value: ValueType): string => { switch (value.kind) { + case "variant": + throw new Error("Variants require the Camino portable value runtime; legacy RPC bindings are unsupported"); case "builtin": return value.name === "unit" ? "null" : "QxWatchHandle"; case "scalar": @@ -122,6 +137,8 @@ export const generateTypeScriptBindings = ( }[value.name]; case "object-ref": return `QxObjectRef<${q(value.expectation.kind === "atom" ? `atom:${value.expectation.atomId}` : `interface:${value.expectation.interfaceRevisionId}`)}>`; + case "change": + throw new Error("CRDT change values require the prepared-change codec and runtime (LF-19)"); case "optional": return `(${type(value.value)} | null)`; case "list": @@ -144,6 +161,11 @@ export const generateTypeScriptBindings = ( const port = (entry: DependencyPort): { type: string; spec: unknown } => { const requirement = entry.requirement; switch (requirement.kind) { + case "reflection": + return { + type: `QxReflectionPort<${requirement.selectors.map((value) => q(value)).join(" | ")}>`, + spec: { kind: "reflection", id: entry.id, selectors: requirement.selectors }, + }; case "query": { const query = schema.packages .find((candidate) => candidate.revisionId === requirement.packageRevisionId) @@ -208,7 +230,12 @@ export const generateTypeScriptBindings = ( // Streaming ports need a future streaming ABI; ordinary calls are fully typed today. const operations = contract.members.flatMap((member) => member.operations - .filter((operation) => operation.mode === "call" && operation.scope !== "class") + .filter( + (operation) => + operation.mode === "call" && + operation.scope !== "class" && + (!requirement.operations || requirement.operations.includes(operation.id)), + ) .map((operation) => ({ ...operation, name: `${member.displayName}.${operation.displayName}` })), ); const methods: [string, string][] = [ @@ -239,6 +266,8 @@ export const generateTypeScriptBindings = ( }, }; } + case "creation": + throw new Error("Creation ports require the portable transaction context generator (LF-09)"); case "constructor": { const input = requirement.inputType; if (!input) @@ -321,6 +350,7 @@ export const generateTypeScriptBindings = ( // 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 === "variant") return false; if (value.kind === "record") return Object.values(value.fields).every(hasCodec); if (value.kind === "list" || value.kind === "optional") return hasCodec(value.value); return true; @@ -378,6 +408,7 @@ export const generateTypeScriptBindings = ( "RelationshipCollection", "RelationshipEntry", "QxQuerySnapshot", + "QxReflectionPort", "QxQueryDescriptor", ].filter((name) => new RegExp(`\\b${name}\\b`).test(signatures)); return ( @@ -388,7 +419,7 @@ export const generateTypeScriptBindings = ( `declare const appliedType: unique symbol;\ntype QxApplied = 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 type QueryVariables = ${queryVariables};\nexport type QueryResults = ${queryResults};\n` + - `export const queries: ${queryDescriptors} = ${JSON.stringify(Object.fromEntries((pkg.checkedQueries ?? []).map((query) => [query.declaration.displayName, { id: `${pkg.revisionId}:${query.declaration.id}`, definitionDigest: query.definitionDigest, rootInterfaceRevisionId: query.declaration.root, variables: query.variables, output: query.output, watch: query.declaration.watch }])), null, 2)} as const;\n` + + `export const queries: ${queryDescriptors} = ${JSON.stringify(Object.fromEntries((pkg.checkedQueries ?? []).map((query) => [query.declaration.displayName, { id: `${pkg.revisionId}:${query.declaration.id}`, definitionDigest: query.definitionDigest, root: query.declaration.root, variables: query.variables, output: query.output, watch: query.declaration.watch }])), null, 2)} as const;\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) @@ -406,3 +437,12 @@ export const generateTypeScriptBindings = ( `\n },\n});\n` ); }; + +export { + generatePortableTypeScriptBindings, + generateRemoteTypeScriptBindings, + portableValueSchema, + PortableBindingError, + type PortableBindingOptions, +} from "./portable.js"; +export * from "./portable-client.js"; diff --git a/src/bindings/portable-client-cli.ts b/src/bindings/portable-client-cli.ts new file mode 100644 index 0000000..57ae321 --- /dev/null +++ b/src/bindings/portable-client-cli.ts @@ -0,0 +1,13 @@ +#!/usr/bin/env node +import { readFile, writeFile } from "node:fs/promises"; +import { generatePortableClientBindings } from "./portable-client.js"; +async function main() { + const [plan, output, target = "target:org.quixos.execution.javascript@1", ...rest] = process.argv.slice(2); + if (!plan || !output || rest.length) + throw Error("usage: quixos-codegen-client EXECUTION_PLAN.json OUTPUT.ts [TARGET]"); + await writeFile(output, generatePortableClientBindings(JSON.parse(await readFile(plan, "utf8")), { target })); +} +void main().catch((error) => { + console.error(error instanceof Error ? error.message : error); + process.exitCode = 1; +}); diff --git a/src/bindings/portable-client.ts b/src/bindings/portable-client.ts new file mode 100644 index 0000000..121040d --- /dev/null +++ b/src/bindings/portable-client.ts @@ -0,0 +1,128 @@ +import { compileKernelProgram } from "../execution-world/kernel.js"; +import { hexDigest, type ExecutionPlan } from "../execution-world/plan.js"; +import { resultInterfaces } from "../execution-world/projections.js"; +import { portableValueSchema, tsType, PortableBindingError } from "./portable.js"; +import type { QuerySelection } from "../query/types.js"; +const conditional = (selections: readonly QuerySelection[]): boolean => + selections.some((selection) => selection.conditions.length > 0 || conditional(selection.selection)); +/** Closed-world app descriptors. Handler bindings and application acquisition share the same public identities. */ +export function generatePortableClientBindings( + plan: ExecutionPlan, + options: { target: string; runtimeModule?: string }, +): string { + if (hexDigest(plan.definition) !== plan.digest) throw new PortableBindingError("client", "Invalid plan digest"); + const executionPlan = (compileKernelProgram(plan) as { plan: string }).plan; + const workspace = plan.definition.workspace, + atoms = new Map(), + unavailable: { key: string; reason: string }[] = []; + const seen = new Set(); + for (const operation of plan.definition.operations) { + if (operation.execution.kind === "remote" || !operation.availableTargets.includes(options.target)) { + unavailable.push({ key: operation.key, reason: "OPERATION_NOT_PORTABLE" }); + continue; + } + let input: string, output: string; + try { + input = tsType(operation.input); + output = tsType(operation.output); + } catch (error) { + if (!(error instanceof PortableBindingError)) throw error; + unavailable.push({ key: operation.key, reason: "UNSUPPORTED_OPERATION_CODEC" }); + continue; + } + const atom = workspace.atoms.find((a) => a.id === operation.atomId)!; + const iface = workspace.interfaceImports.find((i) => i.revisionId === operation.interfaceRevisionId)!; + const member = iface.members.find((m) => m.operations.some((o) => o.id === operation.operationId))!; + const facet = member.operations.find((o) => o.id === operation.operationId)!; + const name = `${iface.displayName}.${member.displayName}.${facet.displayName}`; + const alias = JSON.stringify([atom.displayName, name]); + if (seen.has(alias)) throw new PortableBindingError(alias, "Ambiguous application descriptor alias"); + seen.add(alias); + const roots = resultInterfaces(plan, operation.key); + const descriptor = { + key: operation.key, + plan: executionPlan, + effect: operation.execution.kind, + input: portableValueSchema(operation.input), + output: portableValueSchema(operation.output), + resultInterfaces: roots, + }; + const list = atoms.get(atom.displayName) ?? []; + list.push( + `${JSON.stringify(name)}: defineOperationDescriptor<${input}, ${output}, ${roots.map((r) => JSON.stringify(r)).join(" | ") || "never"}, ${JSON.stringify(operation.execution.kind)}>(${JSON.stringify(descriptor)})`, + ); + atoms.set(atom.displayName, list); + } + const queries = new Map(); + const views = new Map(); + const serverProjections = new Map(); + const results = new Map(); + for (const query of plan.definition.queries) { + const declaration = query.query.checked.declaration; + const pkg = workspace.packageImports.find((p) => p.revisionId === query.query.packageRevisionId)!; + const alias = JSON.stringify([pkg.displayName, declaration.displayName]); + if (seen.has(`query:${alias}`)) throw new PortableBindingError(alias, "Ambiguous query alias"); + seen.add(`query:${alias}`); + const addResult = () => { + if (conditional(query.query.checked.selection)) { + unavailable.push({ key: query.id, reason: "CONDITIONAL_RESULT_DECODER_UNSUPPORTED" }); + return; + } + const decoders = results.get(pkg.displayName) ?? []; + decoders.push( + `${JSON.stringify(declaration.displayName)}: (data: Json): ${tsType(query.query.checked.output)} => decodeArgument(${JSON.stringify(portableValueSchema(query.query.checked.output))}, { codec: "camino-value-v1", usage: "argument", value: data }, { workspace: workspaceId(${JSON.stringify(workspace.workspaceId)}), limits: { bytes: ${declaration.budgets.resultBytes + 128}, nodes: 100000, depth: 64 } }) as ${tsType(query.query.checked.output)}`, + ); + results.set(pkg.displayName, decoders); + }; + if (!declaration.placement) { + const operations = query.operations.map( + (key) => plan.definition.operations.find((operation) => operation.key === key)!, + ); + if ( + operations.some( + (operation) => operation.execution.kind !== "snapshot" || !operation.execution.targets.includes("server"), + ) + ) { + unavailable.push({ key: query.id, reason: "QUERY_NOT_SNAPSHOT_PROJECTION" }); + continue; + } + const list = serverProjections.get(pkg.displayName) ?? []; + list.push( + JSON.stringify(declaration.displayName) + + ": " + + JSON.stringify({ + kind: "server-projection", + id: query.id, + plan: executionPlan, + root: declaration.root.interface, + rootKind: declaration.root.kind, + }) + + " as const satisfies ServerProjection<" + + JSON.stringify(declaration.root.interface) + + ", " + + JSON.stringify(declaration.root.kind) + + ">", + ); + serverProjections.set(pkg.displayName, list); + addResult(); + continue; + } + if (declaration.placement.kind === "local") { + const list = views.get(pkg.displayName) ?? []; + list.push( + `${JSON.stringify(declaration.displayName)}: ${JSON.stringify({ id: query.id, plan: executionPlan, source: JSON.stringify([query.query.packageRevisionId, declaration.placement.sourceQueryId]) })} as const satisfies LocalQueryDescriptor`, + ); + views.set(pkg.displayName, list); + addResult(); + continue; + } + const list = queries.get(pkg.displayName) ?? []; + list.push( + `${JSON.stringify(declaration.displayName)}: { id: ${JSON.stringify(query.id)}, plan: ${JSON.stringify(executionPlan)}, root: ${JSON.stringify(declaration.root.interface)}, rootKind: ${JSON.stringify(declaration.root.kind)} } as const satisfies ReplicaProjection<${JSON.stringify(declaration.root.interface)}, ${JSON.stringify(declaration.root.kind)}>`, + ); + queries.set(pkg.displayName, list); + } + const groups = (entries: Map) => + [...entries].map(([name, values]) => `${JSON.stringify(name)}: {${values.join(",\n")}}`).join(",\n"); + return `// Generated application descriptors. Regenerate; do not edit.\nimport { defineOperationDescriptor, decodeArgument, workspaceId, type Json, type PreparedChange, type ManagedReference, type ReplicaProjection, type ServerProjection, type LocalQueryDescriptor } from ${JSON.stringify(options.runtimeModule ?? "@quixos/camino-replica-core")};\nexport const planDigest = ${JSON.stringify(executionPlan)};\nexport const operations = {${groups(atoms)}} as const;\nexport const queries = {${groups(queries)}} as const;\nexport const views = {${groups(views)}} as const;\nexport const serverProjections = {${groups(serverProjections)}} as const;\nexport const results = {${groups(results)}} as const;\nexport const unavailable = ${JSON.stringify(unavailable)} as const;\n`; +} diff --git a/src/bindings/portable.ts b/src/bindings/portable.ts new file mode 100644 index 0000000..fbaff4c --- /dev/null +++ b/src/bindings/portable.ts @@ -0,0 +1,365 @@ +import { portableReflectionType } from "../execution-world/reflection.js"; +import type { BindingContractSchema } from "./index.js"; +import type { + ValueType, + ObjectExpectation, + PackageExport, + DependencyPort, + ExecutionContract, +} from "../capability-model/types.js"; + +export class PortableBindingError extends Error { + constructor( + readonly path: string, + message: string, + ) { + super(`${path}: ${message}`); + this.name = "PortableBindingError"; + } +} +const q = JSON.stringify; +const unit: ValueType = { kind: "builtin", name: "unit" }; +const string: ValueType = { kind: "scalar", name: "string" }; +const bool: ValueType = { kind: "scalar", name: "bool" }; +const isUnit = (t: ValueType) => t.kind === "builtin" && t.name === "unit"; +const expectation = (e: ObjectExpectation) => ({ + kind: e.kind, + id: e.kind === "atom" ? e.atomId : e.interfaceRevisionId, +}); +/** The protocol IR and replica wire schema intentionally share structure, not reference identity. */ +export function portableValueSchema(type: ValueType): unknown { + switch (type.kind) { + case "variant": + return { + kind: "variant", + id: type.id, + cases: Object.fromEntries( + Object.entries(type.cases).map(([label, entry]) => [ + label, + { id: entry.id, value: portableValueSchema(entry.value) }, + ]), + ), + }; + case "change": + return { + kind: "record", + fields: { + codec: { kind: "scalar", name: "string" }, + profile: { kind: "scalar", name: "string" }, + generation: { kind: "scalar", name: "string" }, + changes: { kind: "list", value: { kind: "scalar", name: "bytes" } }, + }, + }; + case "scalar": + return type; + case "builtin": + if (type.name === "unit") return type; + break; + case "object-ref": + return { kind: type.kind, expectation: expectation(type.expectation) }; + case "optional": + case "list": + return { kind: type.kind, value: portableValueSchema(type.value) }; + case "record": + return { + kind: type.kind, + fields: Object.fromEntries( + Object.entries(type.fields).map(([key, value]) => [key, portableValueSchema(value)]), + ), + }; + } + throw new PortableBindingError( + type.kind, + "Unsupported portable value; use a supported record/reference contract (native edits and generic adapters have separate tasks)", + ); +} +export function tsType(type: ValueType): string { + portableValueSchema(type); + switch (type.kind) { + case "variant": + return `(${Object.entries(type.cases) + .map(([label, entry]) => `{ readonly kind: ${q(label)}; readonly value: ${tsType(entry.value)} }`) + .join(" | ")})`; + case "change": + return `PreparedChange<${tsType(type.value)}>`; + case "scalar": + return type.name === "bool" + ? "boolean" + : type.name === "string" + ? "string" + : type.name === "bytes" + ? "Uint8Array" + : ["int64", "uint64"].includes(type.name) + ? "bigint" + : "number"; + case "builtin": + return "null"; + case "object-ref": { + const e = expectation(type.expectation); + return `ManagedReference<${q(e.kind)}, ${q(e.id)}>`; + } + case "optional": + return `(${tsType(type.value)} | null)`; + case "list": + return `ReadonlyArray<${tsType(type.value)}>`; + case "record": + return `{ ${Object.entries(type.fields) + .map(([key, value]) => `readonly ${q(key)}: ${tsType(value)}`) + .join("; ")} }`; + default: + throw new PortableBindingError(type.kind, "Unsupported type"); + } +} +interface Method { + name: string; + portId: string; + portName: string; + action: { kind: string; primitive?: string; operationId?: string; selector?: string }; + input: ValueType; + output: ValueType; + keyed: boolean; + explicitInput?: boolean; +} +function methods( + schema: BindingContractSchema, + entry: PackageExport, + port: DependencyPort, + target: string, + remote = false, +): Method[] { + const req = port.requirement; + const path = `${entry.displayName}.ports.${port.displayName}`; + const method = ( + name: string, + action: Method["action"], + input: ValueType, + output: ValueType, + keyed = false, + ): Method => ({ name, portId: port.id, portName: port.displayName, action, input, output, keyed }); + const writable = () => { + if (remote) + throw new PortableBindingError(path, "Remote writes and creation require a named transaction capability"); + if (entry.execution?.kind !== "transaction") + throw new PortableBindingError(path, "Snapshot context cannot expose mutation ports"); + }; + switch (req.kind) { + case "reflection": + if (remote) + throw new PortableBindingError( + path, + "Use an ordinary portable snapshot capability to read installation metadata", + ); + return req.selectors.map((selector) => + method(selector, { kind: "reflection", selector }, unit, portableReflectionType(selector)), + ); + case "creation": + writable(); + return [ + method( + "create", + { kind: "creation" }, + req.inputType, + { kind: "object-ref", expectation: { kind: "atom", atomId: req.atomId } }, + true, + ), + ]; + case "state": + return req.primitives.map((primitive) => { + if (primitive === "read") return method("get", { kind: "state", primitive }, unit, req.valueType); + if (primitive === "write") { + writable(); + return method("set", { kind: "state", primitive }, req.valueType, unit); + } + if (primitive === "edit") { + writable(); + return method("edit", { kind: "state", primitive }, { kind: "change", value: req.valueType }, unit); + } + throw new PortableBindingError(path, `State ${primitive} is not supported by this portable adapter yet`); + }); + case "edge": { + const ref: ValueType = { kind: "object-ref", expectation: req.target }; + const mapKey: ValueType | undefined = req.keyType + ? { kind: "scalar", name: req.keyType === "boolean" ? "bool" : req.keyType } + : undefined; + const item: ValueType = { kind: "record", fields: { entry: string, target: ref, ...(mapKey ? { mapKey } : {}) } }; + const connectInput: ValueType = mapKey ? { kind: "record", fields: { mapKey, target: ref } } : ref; + return req.primitives.map((primitive) => { + if (primitive === "resolve") + return method("resolve", { kind: "edge", primitive }, unit, { kind: "list", value: item }); + writable(); + if (primitive === "connect") return method("connect", { kind: "edge", primitive }, connectInput, string, true); + if (primitive === "disconnect") return method("disconnect", { kind: "edge", primitive }, string, bool); + throw new PortableBindingError(path, `Edge ${primitive} is not supported by this portable adapter yet`); + }); + } + case "interface": { + const iface = schema.interfaces.find((i) => i.revisionId === req.interfaceRevisionId); + if (!iface) throw new PortableBindingError(path, `Missing interface ${req.interfaceRevisionId}`); + const operations = iface.members.flatMap((member) => member.operations.map((op) => ({ member, op }))); + if (req.operations?.some((id) => !operations.some(({ op }) => op.id === id))) + throw new PortableBindingError(path, "Unknown selected interface operation"); + return operations + .filter(({ op }) => !req.operations || req.operations.includes(op.id)) + .map(({ member, op }) => { + const execution: ExecutionContract = op.execution ?? { kind: "remote" }; + if ( + (execution.kind === "remote" && !remote) || + op.mode !== "call" || + (op.scope === "class" && (remote || req.receiver === "input")) + ) + throw new PortableBindingError( + `${path}.${member.displayName}.${op.displayName}`, + `Selected operation is not an object call available in portable target ${target}; select a portable subset or move this handler to remote execution`, + ); + if (execution.kind === "transaction" && !remote) writable(); + if (req.receiver === "input" && remote) + throw new PortableBindingError(path, "Reference-selected dependencies require portable execution"); + return { + ...method( + `${member.displayName}.${op.displayName}`, + { kind: "interface", operationId: op.id }, + req.receiver === "input" + ? { + kind: "record", + fields: { + receiver: { + kind: "object-ref", + expectation: { kind: "interface", interfaceRevisionId: req.interfaceRevisionId }, + }, + input: op.inputType, + }, + } + : op.inputType, + op.outputType, + execution.kind === "transaction", + ), + ...(op.scope === "class" ? { explicitInput: true } : {}), + }; + }); + } + default: + throw new PortableBindingError(path, `Unsupported ${req.kind} port in portable bindings`); + } +} +export interface PortableBindingOptions { + target: string; + runtimeModule?: string; +} +/** Generate one target's handlers. The same source signatures are used in browser/server builds. */ +export function generatePortableTypeScriptBindings( + schema: BindingContractSchema, + revision: string, + options: PortableBindingOptions, +): string { + return generateBindings(schema, revision, options, false); +} +export function generateRemoteTypeScriptBindings( + schema: BindingContractSchema, + revision: string, + options: { runtimeModule?: string } = {}, +): string { + return generateBindings(schema, revision, { ...options, target: "server" }, true); +} +function generateBindings( + schema: BindingContractSchema, + revision: string, + options: PortableBindingOptions, + remote: boolean, +): string { + const mode = remote ? "Remote" : "Portable"; + const pkg = schema.packages.find((p) => p.revisionId === revision); + if (!pkg) throw new PortableBindingError(revision, "Missing package"); + const entries = pkg.exports.filter((e) => + remote + ? !e.execution || e.execution.kind === "remote" + : e.execution && e.execution.kind !== "remote" && e.execution.targets.includes(options.target), + ); + const names = new Set(); + const contexts: string[] = [], + implementations: string[] = [], + adapters: string[] = [], + descriptors: unknown[] = []; + for (const entry of entries) { + const effect = entry.execution?.kind ?? "remote"; + // The checked binding schema already contains concrete generic applications. + // Each has its own identity, value codecs and specialized dependency ports. + if (entry.kind === "constructor") + throw new PortableBindingError(entry.id, "Legacy constructors are not supported; declare an ordinary factory"); + if (names.has(entry.displayName)) throw new PortableBindingError(entry.id, "Duplicate handler name"); + names.add(entry.displayName); + const portNames = new Set(); + const all: Method[] = [], + facets: string[] = []; + for (const port of entry.dependencyPorts) { + if (portNames.has(port.displayName)) + throw new PortableBindingError(entry.id, `Duplicate port name ${port.displayName}`); + portNames.add(port.displayName); + const selected = methods(schema, entry, port, options.target, remote); + if (new Set(selected.map((m) => m.name)).size !== selected.length) + throw new PortableBindingError(port.id, "Duplicate method name"); + all.push(...selected); + facets.push( + `readonly ${q(port.displayName)}: { ${selected.map((m) => `readonly ${q(m.name)}: (${[...(isUnit(m.input) && !m.explicitInput ? [] : [`input: ${tsType(m.input)}`]), ...(m.keyed ? [remote ? "options: ServerMutationOptions" : "options: MutationKey"] : [])].join(", ")}) => Promise<${tsType(m.output)}>`).join("; ")} }`, + ); + } + const receiver = + entry.kind === "function" + ? null + : entry.receiverRequirement.kind === "exact-atom" + ? { kind: "atom", id: entry.receiverRequirement.atomId } + : "object"; + const receiverType = + receiver === null + ? "null" + : receiver === "object" + ? "ExactReference" + : `ManagedReference<"atom", ${q(receiver.id)}>`; + contexts.push( + `readonly ${q(entry.displayName)}: { readonly effect: ${q(effect)}; ${remote ? "readonly signal: AbortSignal;" : ""} readonly input: ${tsType(entry.inputType)}; readonly receiver: ${receiverType}; readonly ports: { ${facets.join("; ")} } }`, + ); + const output = tsType(entry.outputType); + implementations.push( + `readonly ${q(entry.displayName)}: (context: Contexts[${q(entry.displayName)}]) => ${output} | Promise<${output}>`, + ); + adapters.push( + `${q(entry.id)}: context => installed[${q(entry.displayName)}](context as Contexts[${q(entry.displayName)}])`, + ); + descriptors.push({ + id: entry.id, + name: entry.displayName, + effect, + receiver, + input: portableValueSchema(entry.inputType), + output: portableValueSchema(entry.outputType), + methods: all.map((m) => ({ ...m, input: portableValueSchema(m.input), output: portableValueSchema(m.output) })), + }); + } + const groups = new Map(); + for (const template of pkg.genericExports ?? []) { + if (template.execution?.kind !== "remote" && template.execution?.targets.includes(options.target)) + groups.set(template.displayName, []); + } + for (const entry of entries) { + if (!entry.application) continue; + const template = pkg.genericExports?.find((item) => item.id === entry.application!.exportId); + if (!template) throw new PortableBindingError(entry.id, "Missing generic export template"); + groups.set(template.displayName, [...(groups.get(template.displayName) ?? []), entry]); + } + const generic = [...groups].map(([name, applications]) => { + if (!applications.length) return `${q(name)}: (_handler: unknown) => ({})`; + contexts.push( + `readonly ${q(name)}: ${applications.map((entry) => `Contexts[${q(entry.displayName)}]`).join(" | ")}`, + ); + const type = applications + .map( + (entry) => + `((context: Contexts[${q(entry.displayName)}]) => ${tsType(entry.outputType)} | Promise<${tsType(entry.outputType)}>)`, + ) + .join(" & "); + const handlers = applications + .map((entry) => `${q(entry.displayName)}: (context: Contexts[${q(entry.displayName)}]) => handler(context)`) + .join(","); + return `${q(name)}: (handler: ${type}) => ({${handlers}})`; + }); + return `// Generated ${remote ? "remote" : "portable"} bindings. Regenerate from the checked capability schema; do not edit.\nimport { define${mode}Registry, type ${mode}Export, type PreparedChange, type ManagedReference, type ExactReference, type ${remote ? "ServerMutationOptions" : "MutationKey"} } from ${q(options.runtimeModule ?? "@quixos/camino-replica-core")};\nexport const target = ${q(options.target)};\nexport type Contexts = { ${contexts.join(";\n")} };\nexport type ${mode}Implementation = { ${implementations.join(";\n")} };\nexport const declarations = ${JSON.stringify(descriptors, null, 2)} as const satisfies readonly ${mode}Export[];\nexport const generic = { ${generic.join(",\n")} };\nexport function createRegistry(handlers: ${mode}Implementation) { const installed = { ...handlers }; return define${mode}Registry(declarations, { ${adapters.join(",\n")} }); }\n`; +} diff --git a/src/bindings/presentation-queries.ts b/src/bindings/presentation-queries.ts new file mode 100644 index 0000000..ec793e7 --- /dev/null +++ b/src/bindings/presentation-queries.ts @@ -0,0 +1,83 @@ +import { createHash } from "node:crypto"; +import type { InterfaceRevision, PackageRevision, ValueType } from "../capability-model/types.js"; +import { defaultQueryBudgets, type QueryDeclaration } from "../query/types.js"; + +/** React contract tooling generates ordinary queries before normal checking. + * Neither the query language nor its execution planner knows presentation IDs. */ +export function presentationQueries(pkg: PackageRevision, interfaces: readonly InterfaceRevision[]) { + const documents = new Map(); + const generated: QueryDeclaration[] = []; + const contracts = new Map(interfaces.map((contract) => [contract.revisionId, contract])); + const navigation = new Set(); + for (const module of pkg.modules ?? []) { + const contract = contracts.get(module.interfaceRevisionId); + const member = contract?.members.find((member) => member.id === module.memberId); + if (member?.kind !== "module" || member.contract !== "org.quixos.react.component/2") continue; + const argument = contract?.application?.arguments[1]; + if (argument?.kind !== "object" || argument.target.kind !== "interface") + throw Error("Stateful React presentations require a closed navigation interface"); + navigation.add(argument.target.interfaceRevisionId); + } + const valueSelection = (type: ValueType): string => { + if (type.kind === "optional" || type.kind === "list") return valueSelection(type.value); + return type.kind === "record" + ? `{${Object.entries(type.fields) + .map(([name, type]) => `${name} ${valueSelection(type)}`) + .join(" ")}}` + : ""; + }; + for (const id of [...navigation].sort()) { + const contract = contracts.get(id as InterfaceRevision["revisionId"]); + if (!contract || contract.template) throw Error(`Missing closed presentation navigation ${id}`); + const fields = contract.members.flatMap((member) => { + if ((member.kind !== "value" && member.kind !== "relationship") || !member.queryRead) return []; + if (member.kind === "value") return [`${member.displayName} ${valueSelection(member.valueType)}`]; + if (member.target.kind !== "interface") + throw Error(`Navigation relationship ${member.displayName} must declare an interface view`); + return [ + member.cardinality === "many" || member.cardinality === "many-unique" + ? `${member.displayName}(all:512) {entries {node {_camino {ref}}}}` + : `${member.displayName} {_camino {ref}}`, + ]; + }); + const stem = `PresentationNavigation${createHash("sha256").update(id).digest("hex").slice(0, 12)}`; + const inputs = `presentation:navigation:${id}:inputs`; + for (const local of [false, true]) { + const name = `${stem}${local ? "View" : "Inputs"}`; + const document = `generated/${name}.graphql`; + documents.set( + document, + `query ${name}($presentationMount: QxRef_PresentationMount!) { + root { ${local ? "availableObjects" : "objects"}(all:512, where:{_camino:{as:{PresentationState:{presentationMount:{eq:$presentationMount}}}}}) { + entries { node { ${fields.join("\n")} + ... on PresentationState { + _presentationOccurrence: occurrence + _presentationSource: source {_camino {ref}} + _presentationParent: parent {_camino {ref}} + _presentationMount: presentationMount {_camino {ref}} + } + } } + } } + }`, + ); + generated.push({ + id: local ? `presentation:navigation:${id}:view` : inputs, + displayName: name, + root: { kind: "workspace-interface", interface: contract.revisionId }, + document, + operation: name, + fragments: [], + views: [], + allowances: [], + budgets: { ...defaultQueryBudgets, rows: 5000, candidates: 5000, resultBytes: 8388608 }, + watch: local, + placement: local ? { kind: "local", sourceQueryId: inputs } : { kind: "replica" }, + }); + } + } + for (const query of generated) + if (pkg.queries?.some((existing) => existing.id === query.id || existing.displayName === query.displayName)) + throw Error(`Presentation query conflicts with an authored query: ${query.id}`); + pkg.queries = [...(pkg.queries ?? []), ...generated]; + return documents; +} diff --git a/src/bindings/react-platform.ts b/src/bindings/react-platform.ts index 790ccb4..a028ca2 100644 --- a/src/bindings/react-platform.ts +++ b/src/bindings/react-platform.ts @@ -10,11 +10,16 @@ declare module "@quixos/web-studio-react-runtime" { export type ObjectRef = string & { readonly $quixosAtom: AtomId; }; - export type FieldCapability = {objectId: string; interfaceRevisionId: string; getOperationId: string; watchOperationId?: string; setOperationId?: string; inputKind?: "fields" | "value"}; - export type ReadableField = {readonly value?: T; readonly capability: FieldCapability}; + export type ReadableField = {readonly value?: T; readonly $field: "query" | "independent"}; + export type QueryField = ReadableField & {readonly $field: "query"}; + export type IndependentField = ReadableField & {readonly $field: "independent"}; /** set(T) uses the resolved capability: native CRDT fields send incremental edits; * custom/manual setters receive semantic values. Storage provenance grants no authority. */ - export type WritableField = ReadableField & {readonly $writeType?: (value: T) => T; readonly writable: true; readonly capability: FieldCapability & {setOperationId: string}}; + export type WritableField = ReadableField & {readonly $writeType?: (value: T) => T; readonly writable: true}; + export type WritableQueryField = WritableField & QueryField; + export type WritableIndependentField = WritableField & IndependentField; + export function detachField(field: WritableQueryField): WritableIndependentField; + export function detachField(field: QueryField): IndependentField; export type LiveFieldProp = ReadableField; export type InterfaceReference = {readonly $quixosRef: string; readonly interfaceRevisionId: I; readonly fields: Fields}; export type ReactInterfaceContract = {readonly interfaceRevisionId: string; readonly $viewType?: (view: View) => View}; @@ -25,11 +30,14 @@ declare module "@quixos/web-studio-react-runtime" { export type QueryValueType = {kind: "builtin" | "scalar"; name: string} | {kind: "record"; fields: Record} | {kind: "optional" | "list"; value: QueryValueType} | {kind: "object-ref"; expectation: {kind: "atom"; atomId: string} | {kind: "interface"; interfaceRevisionId: string}}; export type QueryReference = {readonly $quixosRef: string; readonly queryContract: Contract}; export type QueryLiveProjection = {path: readonly string[]; selectionId: string; interfaceRevisionId: string; getOperationId: string; setOperationId?: string; watchOperationId?: string; inputKind: "fields" | "value"; valueType: QueryValueType; conditions: {include: boolean; defaultValue?: boolean; value: {kind: "variable"; name: string} | {kind: "literal"; value: unknown}}[]}; - export interface QueryDescriptor {readonly id: string; readonly definitionDigest: string; readonly rootInterfaceRevisionId: string; readonly variables: QueryValueType; readonly output: QueryValueType; readonly watch: boolean; readonly liveFields?: readonly QueryLiveProjection[]; readonly $types?: (variables: Variables, result: Result) => [Variables, Result]} - export type QueryPartial = T extends {readonly $quixosRef: string} | {readonly capability: {readonly getOperationId: string}} ? T : T extends readonly (infer Item)[] ? QueryPartial[] : T extends object ? {[Key in keyof T]?: QueryPartial} : T; + export interface QueryDescriptor {readonly id: string; readonly definitionDigest: string; readonly root: {readonly kind: Kind; readonly interface: string}; readonly variables: QueryValueType; readonly output: QueryValueType; readonly watch: boolean; readonly liveFields?: readonly QueryLiveProjection[]; readonly $types?: (variables: Variables, result: Result) => [Variables, Result]} + export type QueryPartial = T extends {readonly $quixosRef: string} | ReadableField ? T : T extends readonly (infer Item)[] ? QueryPartial[] : T extends object ? {[Key in keyof T]?: QueryPartial} : T; export type QueryFieldState = {path: readonly (string | number)[]; status: "pending" | "error"; error?: string}; export type QueryState = {refreshing: boolean; fields: QueryFieldState[]; runId?: string; sequence?: bigint; consistency?: string} & ({status: "loading"; data?: undefined; error?: undefined} | {status: "ready"; data: T; error?: undefined} | {status: "partial"; data: QueryPartial; error?: undefined} | {status: "error"; data?: T | QueryPartial; error: Error}); - export function useQuery(descriptor: QueryDescriptor, options: {root: string | {readonly $quixosRef: string}; variables: Variables}): QueryState & {refresh(): void}; + export type WorkspaceQueryScope = {readonly $workspaceQueryScope: true}; + export function getWorkspaceQueryScope(descriptor: QueryDescriptor): Promise; + export function useQuery(descriptor: QueryDescriptor, options: {root: string | {readonly $quixosRef: string}; variables: Variables}): QueryState & {refresh(): void}; + export function useQuery(descriptor: QueryDescriptor, options: {scope: WorkspaceQueryScope; variables: Variables}): QueryState & {refresh(): void}; export interface QueryConnection {entries: {key: string; node: Row; cursor?: string | null}[]; pageInfo: {hasNextPage: boolean; endCursor?: string | null}} export function CollectionView(props: {result: QueryState & {refresh(): void}; connection(data: QueryPartial): QueryPartial> | undefined; renderItem(row: QueryPartial, entryKey: string): React.ReactNode; onCreate?: () => void; onLoadMore?: (cursor: string | null) => void; onReset?: () => void; loadingMore?: boolean; empty?: React.ReactNode; prefetch?: boolean}): React.ReactElement; export type ReactComponentHostProps = { @@ -58,7 +66,8 @@ declare module "@quixos/web-studio-react-runtime" { options?: {clientMutationId?: string; signal?: AbortSignal}, ) => Promise; export const h: typeof React.createElement; - export type LiveFieldState = {refreshing: boolean; stale: boolean; pendingWrites: number; writeError?: Error; refresh(): Promise; discard(): void} & ({status: "loading"; value?: undefined; error?: undefined} | {status: "ready"; value: T; error?: Error} | {status: "error"; value?: undefined; error: Error}); + export type FieldReadState = "connecting" | "live" | "snapshot" | "inactive" | "not-selected" | "error" | "obsolete"; + export type LiveFieldState = {readState: FieldReadState; refreshing: boolean; stale: boolean; pendingWrites: number; writeError?: Error; refresh(): Promise; discard(): void} & ({status: "loading"; value?: undefined; error?: undefined} | {status: "ready"; value: T; error?: Error} | {status: "error"; value?: undefined; error: Error}); export type WritableLiveFieldState = LiveFieldState & {set(value: T): Promise; retry(): Promise}; export function useLiveField(field: ReadableField, options: {id: string; write: (value: T) => Promise}): WritableLiveFieldState; export function useLiveField(field: WritableField): WritableLiveFieldState; diff --git a/src/bindings/react.ts b/src/bindings/react.ts index ab91177..2f2f869 100644 --- a/src/bindings/react.ts +++ b/src/bindings/react.ts @@ -1,6 +1,5 @@ import type { BindingSchema } from "./index.js"; import type { ValueType } from "../capability-model/types.js"; -import { queryPresentation } from "../query/presentation.js"; /** Browser projection of the SAME checked RPC types, not a second props schema. */ export function generateReactBindings( @@ -16,6 +15,8 @@ export function generateReactBindings( const declarations: string[] = []; const type = (value: ValueType): string => { switch (value.kind) { + case "variant": + throw new Error("Variants require the Camino portable value runtime; legacy RPC bindings are unsupported"); case "builtin": if (value.name !== "unit") throw new Error(`Unsupported React props builtin ${value.name}`); return "null"; @@ -30,6 +31,8 @@ export function generateReactBindings( uint32: "number", double: "number", }[value.name]; + case "change": + throw new Error("CRDT change values require the prepared-change codec and runtime (LF-19)"); case "optional": return `(${type(value.value)} | null)`; case "list": @@ -84,6 +87,7 @@ export function generateReactBindings( const browserValue = (value: ValueType, seen: Set): boolean => { if (value.kind === "message") return false; if (value.kind === "builtin") return value.name === "unit"; + if (value.kind === "variant") return false; if (value.kind === "record") return Object.values(value.fields).every((field) => browserValue(field, seen)); if (value.kind === "list" || value.kind === "optional") return browserValue(value.value, seen); if (value.kind === "object-ref" && value.expectation.kind === "interface") { @@ -129,43 +133,26 @@ export function generateReactBindings( return type(value); }; const queries = pkg.checkedQueries ?? []; - const presentedType = ( - value: ValueType, - fields: ReturnType, - path: string[] = [], - ): string => { - const field = fields.find((field) => JSON.stringify(field.path) === JSON.stringify(path)); - if (field) - return `${field.conditions.length ? "(" : ""}${field.setOperationId ? "WritableField" : "ReadableField"}<${type(field.valueType)}>${field.conditions.length ? " | null)" : ""}`; - if (value.kind === "optional") return `(${presentedType(value.value, fields, path)} | null)`; - if (value.kind === "list") return `Array<${presentedType(value.value, fields, [...path, "*"])}>`; - if (value.kind === "record") - return `{${Object.entries(value.fields) - .map(([key, child]) => `${q(key)}: ${presentedType(child, fields, [...path, key])}`) - .join(";")}}`; - return queryType(value); - }; const queryCode = `export type QueryVariables = {${queries.map((query) => `${q(query.declaration.displayName)}: ${queryType(query.variables)}`).join(";")}};\n` + - `export type QueryResults = {${queries.map((query) => `${q(query.declaration.displayName)}: ${presentedType(query.output, queryPresentation(query, schema.interfaces))}`).join(";")}};\n` + - `export const queries: {${queries.map((query) => `${q(query.declaration.displayName)}: QueryDescriptor`).join(";")}} = ${JSON.stringify( + `export type QueryResults = {${queries.map((query) => `${q(query.declaration.displayName)}: ${queryType(query.output)}`).join(";")}};\n` + + `export const queries: {${queries.map((query) => `${q(query.declaration.displayName)}: QueryDescriptor`).join(";")}} = ${JSON.stringify( Object.fromEntries( queries.map((query) => [ query.declaration.displayName, { id: `${pkg.revisionId}:${query.declaration.id}`, definitionDigest: query.definitionDigest, - rootInterfaceRevisionId: query.declaration.root, + root: query.declaration.root, variables: query.variables, output: query.output, - liveFields: queryPresentation(query, schema.interfaces), watch: query.declaration.watch, }, ]), ), )};\n`; return ( - `// Generated from checked QX contracts. Do not edit.\nimport {defineReactInterfaceContract} from "@quixos/web-studio-react-runtime";\nimport type {ObjectRef, InterfaceReference, ReadableField, WritableField, QueryDescriptor, QueryReference} from "@quixos/web-studio-react-runtime";\n${declarations.join("\n")}\nexport type ReactResults = {${results.join(";\n")}};\n` + + `// Generated from checked QX contracts. Do not edit.\nimport {defineReactInterfaceContract} from "@quixos/web-studio-react-runtime";\nimport type {ObjectRef, InterfaceReference, ReadableField, WritableField, QueryField, WritableQueryField, QueryDescriptor, QueryReference} from "@quixos/web-studio-react-runtime";\n${declarations.join("\n")}\nexport type ReactResults = {${results.join(";\n")}};\n` + queryCode + `const contractsData = ${JSON.stringify(schema.interfaces)};\n` + `export const reactContracts = {${contracts.map(({ iface, view }) => `${q(iface.revisionId)}: defineReactInterfaceContract<${view}>(${q(iface.revisionId)}, contractsData)`).join(",\n")}};\n` + diff --git a/src/capability-language/assembly.ts b/src/capability-language/assembly.ts index d388d02..6236e1d 100644 --- a/src/capability-language/assembly.ts +++ b/src/capability-language/assembly.ts @@ -1,9 +1,13 @@ import { readFile, realpath } from "node:fs/promises"; import path from "node:path"; +import { profile } from "./profile.js"; import { loadQxSources, resolveQxSources } from "./source-loader.js"; -import { compileRepositoryQuery } from "../query/compile.js"; +import { compileQuery, compileRepositoryQuery } from "../query/compile.js"; +import type { QueryDeclaration } from "../query/types.js"; +import { compileQueryComposition, type QueryComposition } from "../query/composition.js"; import { checkQueryTemplate } from "../query/templates.js"; import { readRepositorySource } from "./source-loader.js"; +import { parseControllerCatalog } from "../capability-model/controllers.js"; import { linkQueries } from "../query/link.js"; import { capabilityId, @@ -40,13 +44,40 @@ export type CapabilityRepositoryResolver = ( kind: LockedResource["kind"], ) => Promise; +/** Already checked outputs of this compiler, composed by the Nix build DAG. + * Source capture/check reports never grant this authority. */ +export type CheckedResourceResolver = ( + kind: LockedResource["kind"], + source: GitSource, + lock: QuixosRepositoryLock, + dependencies: ReadonlyMap, +) => Promise; + +/** An explicitly selected build frontend supplies ordinary query sources. The + * assembler never chooses a frontend by inspecting an interface/contract ID. + * Generated sources go through the same query and imported-fragment checks. */ +export type PackageQueryGenerator = ((input: { + readonly package: Readonly; + readonly interfaces: readonly InterfaceRevision[]; + readonly readSource: (name: string) => Promise; +}) => Promise<{ + readonly queries: readonly QueryDeclaration[]; + readonly documents: ReadonlyMap; + readonly compositions?: readonly QueryComposition[]; + readonly artifacts?: Readonly>; +}>) & { + /** Optional explicit frontend stage after workspace binding resolution. + * Its output still passes normal workspace/query validation. */ + completeWorkspace?: (workspace: WorkspaceRevision) => Promise; +}; + export type ResolvedCapabilityResource = { key: string; kind: LockedResource["kind"]; source: GitSource; directory: string; lock: QuixosRepositoryLock; - resource: CapabilityResource; + resource: CapabilityResource | { kind: "build" }; dependencies: ReadonlyMap; }; @@ -55,13 +86,15 @@ export type CompiledWorkspaceRepository = { plan: CompiledWorkspaceRevision; lock: QuixosRepositoryLock; resources: ResolvedCapabilityResource[]; + capabilityResources: ResolvedCapabilityResource[]; directResources: ReadonlyMap; }; export type CompiledCapabilityResourceRepository = { - resource: CapabilityResource; + resource: CapabilityResource | { kind: "build" }; lock: QuixosRepositoryLock; resources: ResolvedCapabilityResource[]; + capabilityResources: ResolvedCapabilityResource[]; directResources: ReadonlyMap; }; @@ -78,7 +111,10 @@ const assertImportsMatchLock = ( resources: readonly LockedResource[], ) => { const authored = [...imports].map(importsKey).sort(); - const locked = [...resources].map(importsKey).sort(); + const locked = resources + .filter((entry) => entry.kind !== "build") + .map(importsKey) + .sort(); if (authored.length !== locked.length || authored.some((entry, index) => entry !== locked[index])) { throw new Error( `${label} imports do not match quixos.lock:\n` + @@ -136,13 +172,19 @@ const diagnosticsMessage = ( }) .join("\n")}`; -const revisionFor = (node: ResolvedCapabilityResource) => node.resource.revision; +const revisionFor = (node: ResolvedCapabilityResource) => { + if (node.resource.kind === "build") throw Error("Build sources have no capability revision"); + return node.resource.revision; +}; -const resourceClosure = (roots: readonly ResolvedCapabilityResource[]): ResolvedCapabilityResource[] => { +const resourceClosure = ( + roots: readonly ResolvedCapabilityResource[], + capabilityOnly = false, +): ResolvedCapabilityResource[] => { const result: ResolvedCapabilityResource[] = []; const seen = new Set(); const visit = (node: ResolvedCapabilityResource) => { - if (seen.has(node.key)) return; + if (seen.has(node.key) || (capabilityOnly && node.kind === "build")) return; seen.add(node.key); for (const dependency of node.dependencies.values()) visit(dependency); result.push(node); @@ -154,30 +196,47 @@ const resourceClosure = (roots: readonly ResolvedCapabilityResource[]): Resolved const environmentFor = ( direct: readonly [LockedResource, ResolvedCapabilityResource][], closure: readonly ResolvedCapabilityResource[], -): CapabilityImportEnvironment => ({ - interfaces: new Map( - direct.flatMap(([locked, node]) => - node.resource.kind === "interface" ? [[locked.binding, node.resource.revision] as const] : [], +): CapabilityImportEnvironment => { + closure = resourceClosure( + direct.map(([, node]) => node), + true, + ); + return { + interfaces: new Map( + direct.flatMap(([locked, node]) => + node.resource.kind === "interface" ? [[locked.binding, node.resource.revision] as const] : [], + ), ), - ), - packages: new Map( - direct.flatMap(([locked, node]) => - node.resource.kind === "package" ? [[locked.binding, node.resource.revision] as const] : [], + packages: new Map( + direct.flatMap(([locked, node]) => + node.resource.kind === "package" ? [[locked.binding, node.resource.revision] as const] : [], + ), ), - ), - interfaceClosure: exactRevisions( - closure.flatMap((node) => [ - ...(node.resource.kind === "interface" ? [node.resource.revision] : []), - ...(node.resource.specializations ?? []), - ]), - ), - packageClosure: exactRevisions( - closure.flatMap((node) => (node.resource.kind === "package" ? [node.resource.revision] : [])), - ), - externalAtoms: exactAtoms(closure.flatMap((node) => node.resource.externalAtoms)), -}); + interfaceClosure: exactRevisions( + closure.flatMap((node) => [ + ...(node.resource.kind === "interface" ? [node.resource.revision] : []), + ...(node.resource.kind === "build" ? [] : (node.resource.specializations ?? [])), + ]), + ), + packageClosure: exactRevisions( + closure.flatMap((node) => (node.resource.kind === "package" ? [node.resource.revision] : [])), + ), + externalAtoms: exactAtoms( + closure.flatMap((node) => (node.resource.kind === "build" ? [] : node.resource.externalAtoms)), + ), + }; +}; -const createResourceGraphResolver = (quixosCommit: string, resolveResource: CapabilityRepositoryResolver) => { +const createResourceGraphResolver = ( + quixosCommit: string, + resolveResource: CapabilityRepositoryResolver, + generateQueries?: PackageQueryGenerator, + checkedResource?: CheckedResourceResolver, +) => { + const graphStarted = performance.now(); + let checkedResources = 0, + reusedResources = 0, + checkedQueries = 0; const resolved = new Map>(); const active: string[] = []; @@ -217,7 +276,44 @@ const createResourceGraphResolver = (quixosCommit: string, resolveResource: Capa for (const dependency of lockResult.lock.resources) { directPairs.push([dependency, await visit(dependency)]); } + if (locked.kind === "build") { + return { + key, + kind: locked.kind, + source: locked.source, + directory: snapshot.directory, + lock: lockResult.lock, + resource: { kind: "build" as const }, + dependencies: new Map( + directPairs.map(([dependency, node]) => [bindingKey(dependency.kind, dependency.binding), node]), + ), + }; + } const dependencyClosure = resourceClosure(directPairs.map(([, node]) => node)); + const dependencies = new Map( + directPairs.map(([dependency, node]) => [bindingKey(dependency.kind, dependency.binding), node]), + ); + const checked = await checkedResource?.(locked.kind, locked.source, lockResult.lock, dependencies); + if (checked) { + if ( + checked.kind !== locked.kind || + checked.revision.source.repository !== locked.source.repository || + checked.revision.source.commit !== locked.source.commit + ) + throw new Error("Checked resource identity mismatch"); + assertImportsMatchLock("Checked resource", checked.imports, lockResult.lock.resources); + reusedResources++; + return { + key, + kind: locked.kind, + source: locked.source, + directory: snapshot.directory, + lock: lockResult.lock, + resource: structuredClone(checked), + dependencies, + }; + } + const checkStarted = performance.now(); const environment = environmentFor(directPairs, dependencyClosure); const manifestName = locked.kind === "interface" ? "interface.qx" : "package.qx"; const manifestPath = path.join(snapshot.directory, manifestName); @@ -239,15 +335,86 @@ const createResourceGraphResolver = (quixosCommit: string, resolveResource: Capa ...(environment.interfaceClosure ?? []), ...(compiled.resource.specializations ?? []), ]; + const generated = await generateQueries?.({ + package: structuredClone(compiled.resource.revision), + interfaces: structuredClone(queryInterfaces), + readSource: (name) => readRepositorySource(snapshot.directory, name), + }); + if (generated) { + if (generated.artifacts) compiled.resource.revision.buildArtifacts = structuredClone(generated.artifacts); + const queries = [...(compiled.resource.revision.queries ?? [])]; + for (const query of generated.queries) { + if (queries.some((entry) => entry.id === query.id || entry.displayName === query.displayName)) + throw new Error(`Generated query conflicts with an authored query: ${query.id}`); + if (!generated.documents.has(query.document)) + throw new Error(`Generated query has no source: ${query.document}`); + queries.push(structuredClone(query)); + } + compiled.resource.revision.queries = queries; + } for (const template of compiled.resource.revision.queryTemplates ?? []) await checkQueryTemplate(template, queryInterfaces, (name) => readRepositorySource(snapshot.directory, name), ); - if (compiled.resource.revision.queries?.length) - compiled.resource.revision.checkedQueries = await Promise.all( - compiled.resource.revision.queries.map((query) => - compileRepositoryQuery(snapshot.directory, query, queryInterfaces), - ), + if (compiled.resource.revision.queries?.length) { + const pkg = compiled.resource.revision; + const checked = new Map(); + const compile = async ( + id: string, + parents: readonly string[] = [], + ): Promise => { + if (parents.includes(id)) throw new Error(`Query fragment cycle: ${[...parents, id].join(" -> ")}`); + const previous = checked.get(id); + if (previous) return previous; + const query = pkg.queries!.find((query) => query.id === id); + if (!query) throw new Error(`Unknown fragment ${pkg.revisionId}/${id}`); + const resolveFragment = async (packageId: string, fragmentId: string) => { + if (packageId === pkg.revisionId) return compile(fragmentId, [...parents, id]); + const imported = environment.packageClosure + ?.find((other) => other.revisionId === packageId) + ?.checkedQueries?.find((query) => query.declaration.id === fragmentId); + if (!imported) throw new Error(`Unchecked imported fragment ${packageId}/${fragmentId}`); + return imported; + }; + const result = generated?.documents.has(query.document) + ? await compileQuery( + query, + queryInterfaces, + async (name) => + generated.documents.get(name) ?? (await readRepositorySource(snapshot.directory, name)), + resolveFragment, + ) + : await compileRepositoryQuery(snapshot.directory, query, queryInterfaces, resolveFragment); + checked.set(id, result); + return result; + }; + pkg.checkedQueries = []; + for (const query of pkg.queries!) pkg.checkedQueries.push(await compile(query.id)); + } + if (generated?.compositions?.length) { + const pkg = compiled.resource.revision; + const queries = new Map((pkg.checkedQueries ?? []).map((query) => [query.declaration.id, query])); + for (const dependency of environment.packageClosure ?? []) + for (const query of dependency.checkedQueries ?? []) + queries.set(JSON.stringify([dependency.revisionId, query.declaration.id]), query); + const ids = new Set(); + pkg.queryCompositions = generated.compositions.map((composition) => { + if (ids.has(composition.id)) throw new Error(`Duplicate query composition: ${composition.id}`); + ids.add(composition.id); + return compileQueryComposition(composition, queries); + }); + } + let controllerText: string | undefined; + try { + controllerText = await readRepositorySource(snapshot.directory, "quixos.controllers.json"); + } catch (error) { + if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error; + } + if (controllerText !== undefined) + compiled.resource.revision.controllers = parseControllerCatalog( + controllerText, + queryInterfaces, + compiled.resource.revision.checkedQueries, ); let catalogText: string | undefined; try { @@ -271,6 +438,15 @@ const createResourceGraphResolver = (quixosCommit: string, resolveResource: Capa compiled.resource.revision.migrationCatalog = catalog; } } + checkedResources++; + const queries = + compiled.resource.kind === "package" ? (compiled.resource.revision.checkedQueries?.length ?? 0) : 0; + checkedQueries += queries; + profile("resource-check", checkStarted, { + kind: locked.kind, + resource: compiled.resource.revision.displayName, + queries, + }); return { key, kind: locked.kind, @@ -292,7 +468,11 @@ const createResourceGraphResolver = (quixosCommit: string, resolveResource: Capa return { visit, - nodes: async () => await Promise.all(resolved.values()), + nodes: async () => { + const nodes = await Promise.all(resolved.values()); + profile("resource-graph", graphStarted, { checkedResources, reusedResources, checkedQueries }); + return nodes; + }, }; }; @@ -301,12 +481,19 @@ export const compileCapabilityResourceRepository = async (options: { kind: LockedResource["kind"]; source: GitSource; resolveResource: CapabilityRepositoryResolver; + generateQueries?: PackageQueryGenerator; + checkedResource?: CheckedResourceResolver; }): Promise => { const lockResult = await loadQuixosLock(path.join(options.rootDirectory, "quixos.lock")); if (!lockResult.ok) { throw new Error(diagnosticsMessage("Resource lock", lockResult.diagnostics)); } - const resolver = createResourceGraphResolver(lockResult.lock.quixos.commit, options.resolveResource); + const resolver = createResourceGraphResolver( + lockResult.lock.quixos.commit, + options.resolveResource, + options.generateQueries, + options.checkedResource, + ); const root = await resolver.visit( { kind: options.kind, @@ -319,6 +506,7 @@ export const compileCapabilityResourceRepository = async (options: { resource: root.resource, lock: root.lock, resources: await resolver.nodes(), + capabilityResources: resourceClosure([root], true), directResources: root.dependencies, }; }; @@ -331,13 +519,20 @@ export const compileWorkspaceRepository = async (options: { sourceRootCommit?: string; /** An editor's proposed source snapshot; locks and dependency revisions remain exact. */ readSource?: (name: string) => Promise; + generateQueries?: PackageQueryGenerator; + checkedResource?: CheckedResourceResolver; }): Promise => { const rootLockResult = await loadQuixosLock(path.join(options.rootDirectory, "quixos.lock")); if (!rootLockResult.ok) { throw new Error(diagnosticsMessage("Workspace lock", rootLockResult.diagnostics)); } const rootLock = rootLockResult.lock; - const resolver = createResourceGraphResolver(rootLock.quixos.commit, options.resolveResource); + const resolver = createResourceGraphResolver( + rootLock.quixos.commit, + options.resolveResource, + options.generateQueries, + options.checkedResource, + ); const directPairs: Array<[LockedResource, ResolvedCapabilityResource]> = []; for (const locked of rootLock.resources) { @@ -363,11 +558,13 @@ export const compileWorkspaceRepository = async (options: { ); } assertImportsMatchLock(workspacePath, compiled.imports, rootLock.resources); - const availableInterfaceIds = new Set( - nodes.flatMap((node) => (node.resource.kind === "interface" ? [node.resource.revision.revisionId] : [])), - ); + const availableInterfaceIds = new Set((environment.interfaceClosure ?? []).map((iface) => iface.revisionId)); const availableAtomIds = new Set(compiled.workspace.atoms.map((atom) => atom.id)); - for (const node of nodes) { + for (const node of resourceClosure( + directPairs.map(([, node]) => node), + true, + )) { + if (node.resource.kind === "build") continue; for (const requirement of node.resource.externalInterfaces) { if (!availableInterfaceIds.has(requirement.revisionId)) { throw new Error( @@ -399,6 +596,7 @@ export const compileWorkspaceRepository = async (options: { : {}), ...(options.sourceRootCommit ? { sourceRootCommit: options.sourceRootCommit } : {}), }; + await options.generateQueries?.completeWorkspace?.(workspace); const checked = compileWorkspaceRevision(workspace); if (!checked.ok) { throw new Error( @@ -417,6 +615,10 @@ export const compileWorkspaceRepository = async (options: { plan: checked.plan, lock: rootLock, resources: nodes, + capabilityResources: resourceClosure( + directPairs.map(([, node]) => node), + true, + ), directResources: new Map(directPairs.map(([locked, node]) => [bindingKey(locked.kind, locked.binding), node])), }; }; diff --git a/src/capability-language/authoring-check.ts b/src/capability-language/authoring-check.ts index eb0a94a..b378075 100644 --- a/src/capability-language/authoring-check.ts +++ b/src/capability-language/authoring-check.ts @@ -7,7 +7,12 @@ import type { convergeAuthoring } from "./authoring-converge.js"; import { execFile as callback } from "node:child_process"; import { promisify } from "node:util"; import { buildImmutableCandidate, checkerIdentity } from "./checked-build.js"; -import { planEvolution, type WorkspaceRevision, type EvolutionReview } from "../capability-model/index.js"; +import { + planEvolution, + reviewSemanticMajors, + type WorkspaceRevision, + type EvolutionReview, +} from "../capability-model/index.js"; export const checkRecordName = (directory: string) => createHash("sha256").update(directory).digest("hex") + ".json"; export async function checkAuthoring( @@ -34,6 +39,7 @@ export async function checkAuthoring( phase: string; output: string; compilation?: "passed"; + runtime?: "execution"; activationReadiness?: "preserve" | "migration-required" | "blocked"; migrationRequired?: string[]; } = { @@ -58,7 +64,7 @@ export async function checkAuthoring( // Repository-scoped agents can check separate immutable candidates in parallel. const capture = promisify(callback)("quixos-qx", ["converge", context.workbench, directory], { maxBuffer: 4 * 1024 * 1024, - env: { ...process.env, QUIXOS_JJ_NO_CHECKPOINT: "1", QUIXOS_TRACE_CAPTURE: "1" }, + env: { ...process.env, QUIXOS_JJ_NO_CHECKPOINT: "1", QUIXOS_TRACE_CAPTURE: "1", QUIXOS_TRACE_PROFILE: "1" }, }); console.error(`Capture details: ${path.join(output, "capture.log")}`); capture.child.stderr?.on("data", (chunk) => appendFileSync(path.join(output, "capture.log"), chunk)); @@ -88,6 +94,9 @@ export async function checkAuthoring( resource.kind, path.join(output, "nix.log"), options.contractOnly, + (phase, ms) => { + timings[phase] = ms; + }, ); } finally { timings.immutableCheckMs = Math.round(performance.now() - buildStarted); @@ -98,7 +107,26 @@ export async function checkAuthoring( const candidateText = await fs.readFile(path.join(report.artifactPath, "candidate.json"), "utf8"); await fs.writeFile(path.join(output, "candidate.json"), candidateText); report.compilation = "passed"; - if (resource.kind === "workspace" && !options.contractOnly) { + try { + await fs.access(path.join(report.artifactPath, "execution-plan.json")); + report.runtime = "execution"; + } catch (error) { + if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error; + } + if (resource.kind === "workspace" && !options.contractOnly && report.runtime === "execution") { + report.phase = "semantic-review"; + if (options.reviews && !options.baseline) + throw new Error("Execution semantic reviews require an explicit --baseline candidate"); + const baseline = options.baseline + ? (JSON.parse(await fs.readFile(options.baseline, "utf8")) as WorkspaceRevision) + : null; + const receipts = options.reviews + ? (JSON.parse(await fs.readFile(options.reviews, "utf8")) as EvolutionReview[]) + : []; + const review = reviewSemanticMajors(baseline, JSON.parse(candidateText), receipts); + await fs.writeFile(path.join(output, "semantic-review.json"), JSON.stringify(review, null, 2)); + report.blockers.push(...review.blockers); + } else if (resource.kind === "workspace" && !options.contractOnly) { report.phase = "evolution"; await progress(); let baseline = options.baseline; @@ -147,7 +175,7 @@ export async function checkAuthoring( await fs.writeFile(temporary, JSON.stringify(value, null, 2), { flag: "wx", mode: 0o600 }); await fs.rename(temporary, filename); }; - if (report.artifactPath) { + if (report.artifactPath && report.compilation === "passed" && !report.blockers.length) { const graph = JSON.parse(await fs.readFile(path.join(report.artifactPath, "graph.json"), "utf8")); for (const checked of graph.resources) { const managed = context.resources.find( diff --git a/src/capability-language/authoring-context.ts b/src/capability-language/authoring-context.ts index f91a318..2402292 100644 --- a/src/capability-language/authoring-context.ts +++ b/src/capability-language/authoring-context.ts @@ -3,7 +3,7 @@ import path from "node:path"; import { loadQuixosLock, type GitSource } from "../resource-lock/index.js"; export type AuthoringResource = { - kind: "workspace" | "interface" | "package"; + kind: "workspace" | "interface" | "package" | "build"; directory: string; resourceId?: string; source?: GitSource; @@ -31,7 +31,7 @@ export async function authoringContext(start: string) { if (typeof entry.directory === "string" && path.isAbsolute(entry.directory)) entry.directory = path.relative(workbench, entry.directory); if ( - !["interface", "package"].includes(entry.kind) || + !["interface", "package", "build"].includes(entry.kind) || !entry.source || !/^resources\/[A-Za-z0-9][A-Za-z0-9._-]*$/.test(entry.directory) ) { diff --git a/src/capability-language/authoring-converge.ts b/src/capability-language/authoring-converge.ts index c410152..cba7d94 100644 --- a/src/capability-language/authoring-converge.ts +++ b/src/capability-language/authoring-converge.ts @@ -5,6 +5,7 @@ import { promisify } from "node:util"; import { randomUUID } from "node:crypto"; import { authoringContext } from "./authoring-context.js"; import { snapshotCommit } from "./checked-build.js"; +import { profile } from "./profile.js"; import { loadQuixosLock, parseQuixosLockDocument, @@ -14,6 +15,42 @@ import { } from "../resource-lock/index.js"; const execFile = promisify(callback); +// Bound independent repository operations. One visit owns each repository; +// all visits finish before the final concurrent-edit validation pass. +const concurrency = 4; +async function mapConcurrent(values: readonly T[], action: (value: T) => Promise): Promise { + const results = new Array(values.length); + let next = 0; + await Promise.all( + Array.from({ length: Math.min(concurrency, values.length) }, async () => { + while (next < values.length) { + const index = next++; + results[index] = await action(values[index]); + } + }), + ); + return results; +} +const limiter = () => { + let running = 0; + const waiting: Array<() => void> = []; + return async (action: () => Promise): Promise => { + await new Promise((resolve) => { + if (running < concurrency) { + running++; + resolve(); + } else waiting.push(resolve); + }); + try { + return await action(); + } finally { + const next = waiting.shift(); + if (next) next(); + else running--; + } + }; +}; + const command = async (cwd: string, executable: string, args: string[]) => { const start = performance.now(); // Do not log arguments: transports may contain credentials. Source identities @@ -44,13 +81,14 @@ export type AuthoringBlocker = { * worklist grants typechecking, semantic review or activation approval. * Caller serializes coordinators; package authors may still be editing. */ export async function convergeAuthoring(start: string, target = "root") { + const started = performance.now(); const context = await authoringContext(start); const graphFile = path.join(context.workbench, ".quixos/resource-graph.json"); const graphBefore = await readFile(graphFile, "utf8"); const blockers: AuthoringBlocker[] = []; const nodes = new Map(); const selected = new Map(); - for (const entry of context.resources) { + const inventory = await mapConcurrent(context.resources, async (entry) => { const root = path.join(context.workbench, entry.directory); let repository = entry.source?.repository; try { @@ -64,6 +102,9 @@ export async function convergeAuthoring(start: string, target = "root") { blockers.push({ directory: entry.directory, phase: "source", message: String(error).slice(0, 2000) }); if (!repository) throw error; // The root has no separate registered source. } + return { ...entry, repository }; + }); + for (const { repository, ...entry } of inventory) { const key = identity(entry.kind, repository); if (selected.has(key)) throw new Error(`More than one editable checkout for ${repository}`); selected.set(key, entry.directory); @@ -73,7 +114,7 @@ export async function convergeAuthoring(start: string, target = "root") { dependencies: [], }); } - for (const node of nodes.values()) { + await mapConcurrent([...nodes.values()], async (node) => { try { const lock = await loadQuixosLock(path.join(context.workbench, node.directory, "quixos.lock")); if (!lock.ok) throw new Error(lock.diagnostics.map((d) => `${d.fileName}: ${d.message}`).join("\n")); @@ -88,97 +129,122 @@ export async function convergeAuthoring(start: string, target = "root") { } catch (error) { blockers.push({ directory: node.directory, phase: "resolution", message: String(error) }); } - } - const complete = new Map(), - active = new Set(); - const visited = new Set(); - if (!nodes.has(target)) throw new Error(`Not a registered repository: ${target}`); - const visit = async (directory: string): Promise => { - visited.add(directory); - if (complete.has(directory)) return true; - if (blockers.some((entry) => entry.directory === directory)) return false; - if (active.has(directory)) { + }); + // Detect cycles before starting concurrent futures: two roots awaiting each + // other must fail clearly rather than deadlocking on their cached promises. + const scanned = new Set(); + const detectCycles = (directory: string, parents: string[] = []) => { + if (parents.includes(directory)) { blockers.push({ directory, phase: "dependency", - message: `Source dependency cycle: ${[...active, directory].join(" -> ")}`, + message: `Source dependency cycle: ${[...parents, directory].join(" -> ")}`, }); - return false; + return; } - active.add(directory); - const node = nodes.get(directory)!; - for (const dependency of node.dependencies) - if (!(await visit(dependency))) { - blockers.push({ directory, phase: "dependency", message: `Waiting for ${dependency}` }); - active.delete(directory); + if (scanned.has(directory)) return; + for (const dependency of nodes.get(directory)!.dependencies) detectCycles(dependency, [...parents, directory]); + scanned.add(directory); + }; + for (const directory of nodes.keys()) detectCycles(directory); + const complete = new Map(); + const pending = new Map>(); + const capture = limiter(); + const visited = new Set(); + if (!nodes.has(target)) throw new Error(`Not a registered repository: ${target}`); + const visit = (directory: string): Promise => { + visited.add(directory); + const existing = pending.get(directory); + if (existing) return existing; + const result = (async () => { + if (blockers.some((entry) => entry.directory === directory)) return false; + const node = nodes.get(directory)!; + const ready = await Promise.all(node.dependencies.map(visit)); + const unavailable = node.dependencies.find((_, index) => !ready[index]); + if (unavailable) { + blockers.push({ directory, phase: "dependency", message: `Waiting for ${unavailable}` }); return false; } - const root = path.join(context.workbench, directory); - let phase: AuthoringBlocker["phase"] = "source"; - try { - const lock = await loadQuixosLock(path.join(root, "quixos.lock")); - if (!lock.ok) throw new Error("Lock changed during convergence; retry after joining writers"); - for (const file of lock.lock.sourceFiles ?? ["quixos.lock"]) { - const filename = path.join(root, file), - before = await readFile(filename, "utf8"); - const parsed = parseQuixosLockDocument(before, file); - if (!parsed.ok) throw new Error(`Invalid lock ${file}`); - let changed = false; - for (const dependency of parsed.document.resources) { - const target = selected.get(identity(dependency.kind, dependency.source.repository)); - const source = target ? complete.get(target) : undefined; - if (target && !source) - throw new Error(`Dependencies changed during convergence (${dependency.binding}); join writers and retry`); - if (source && source.commit !== dependency.source.commit) { - dependency.source = source; - changed = true; + return await capture(async () => { + const root = path.join(context.workbench, directory); + let phase: AuthoringBlocker["phase"] = "source"; + try { + const lock = await loadQuixosLock(path.join(root, "quixos.lock")); + if (!lock.ok) throw new Error("Lock changed during convergence; retry after joining writers"); + for (const file of lock.lock.sourceFiles ?? ["quixos.lock"]) { + const filename = path.join(root, file), + before = await readFile(filename, "utf8"); + const parsed = parseQuixosLockDocument(before, file); + if (!parsed.ok) throw new Error(`Invalid lock ${file}`); + let changed = false; + for (const dependency of parsed.document.resources) { + const target = selected.get(identity(dependency.kind, dependency.source.repository)); + const source = target ? complete.get(target) : undefined; + if (target && !source) + throw new Error( + `Dependencies changed during convergence (${dependency.binding}); join writers and retry`, + ); + if (source && source.commit !== dependency.source.commit) { + dependency.source = source; + changed = true; + } + } + if (changed) { + const temporary = `${filename}.${randomUUID()}.tmp`; + try { + await writeFile(temporary, formatQuixosLockDocument(parsed.document), { flag: "wx" }); + if ((await readFile(filename, "utf8")) !== before) + throw new Error(`Concurrent edit to ${file}; retry after joining writers`); + await rename(temporary, filename); + } finally { + await rm(temporary, { force: true }); + } + } } - } - if (changed) { - const temporary = `${filename}.${randomUUID()}.tmp`; - try { - await writeFile(temporary, formatQuixosLockDocument(parsed.document), { flag: "wx" }); - if ((await readFile(filename, "utf8")) !== before) - throw new Error(`Concurrent edit to ${file}; retry after joining writers`); - await rename(temporary, filename); - } finally { - await rm(temporary, { force: true }); + const snapshotStarted = performance.now(); + const commit = await snapshotCommit(root); + if (process.env.QUIXOS_TRACE_CAPTURE === "1") + console.error( + `[${new Date().toISOString()}] Capture: ${directory} snapshot: ${Math.round(performance.now() - snapshotStarted)}ms`, + ); + phase = "publication"; + const ref = retentionTagForCommit(commit); + const remote = await command(root, "git", ["ls-remote", "--refs", node.source.repository, ref]); + if (remote && remote.split(/\s+/)[0] !== commit) + throw new Error(`Conflicting immutable retention ref ${ref}`); + if (!remote) { + await command(root, "git", ["push", node.source.repository, `${commit}:${ref}`]); + if ( + (await command(root, "git", ["ls-remote", "--refs", node.source.repository, ref])).split(/\s+/)[0] !== + commit + ) + throw new Error("Published source retention was not observed"); } + complete.set(directory, { ...node.source, commit }); + } catch (error) { + blockers.push({ directory, phase, message: String(error).slice(0, 4000) }); } - } - const snapshotStarted = performance.now(); - const commit = await snapshotCommit(root); - if (process.env.QUIXOS_TRACE_CAPTURE === "1") - console.error( - `[${new Date().toISOString()}] Capture: ${directory} snapshot: ${Math.round(performance.now() - snapshotStarted)}ms`, - ); - phase = "publication"; - const ref = retentionTagForCommit(commit); - const remote = await command(root, "git", ["ls-remote", "--refs", node.source.repository, ref]); - if (remote && remote.split(/\s+/)[0] !== commit) throw new Error(`Conflicting immutable retention ref ${ref}`); - if (!remote) await command(root, "git", ["push", node.source.repository, `${commit}:${ref}`]); - if ((await command(root, "git", ["ls-remote", "--refs", node.source.repository, ref])).split(/\s+/)[0] !== commit) - throw new Error("Published source retention was not observed"); - complete.set(directory, { ...node.source, commit }); - } catch (error) { - blockers.push({ directory, phase, message: String(error).slice(0, 4000) }); - } - active.delete(directory); - return complete.has(directory); + return complete.has(directory); + }); + })(); + pending.set(directory, result); + return result; }; // Include newly created, not-yet-imported resources, then the root. - if (target === "root") for (const directory of [...nodes.keys()].filter((d) => d !== "root")) await visit(directory); + if (target === "root") await Promise.all([...nodes.keys()].filter((d) => d !== "root").map(visit)); await visit(target); for (let index = blockers.length - 1; index >= 0; index--) if (!visited.has(blockers[index].directory)) blockers.splice(index, 1); - for (const [directory, source] of complete) { + const recheckStarted = performance.now(); + await mapConcurrent([...complete], async ([directory, source]) => { try { if ((await snapshotCommit(path.join(context.workbench, directory))) !== source.commit) throw new Error("Source advanced while converging; join writers and retry"); } catch (error) { blockers.push({ directory, phase: "concurrent-edit", message: String(error) }); } - } + }); + profile("capture-recheck", recheckStarted, { repositories: complete.size }); // Persist successful selections even if another repository is still broken. // Recovery must not depend on all parents succeeding in the same invocation. const graph = JSON.parse(graphBefore); @@ -238,11 +304,14 @@ export async function convergeAuthoring(start: string, target = "root") { await rm(temporary, { force: true }); } } + profile("capture", started, { repositories: nodes.size, snapshots: complete.size * 2, target }); return { workbench: context.workbench, converged: blockers.length === 0, candidate: blockers.length ? null : (complete.get(target) ?? null), - retained: [...complete].map(([directory, source]) => ({ directory, source })), + retained: [...complete] + .sort(([left], [right]) => left.localeCompare(right)) + .map(([directory, source]) => ({ directory, source })), worklist: blockers, verificationEvidence: false, activated: false, diff --git a/src/capability-language/authoring-inspect.ts b/src/capability-language/authoring-inspect.ts index 980bfc8..c81ae0b 100644 --- a/src/capability-language/authoring-inspect.ts +++ b/src/capability-language/authoring-inspect.ts @@ -69,7 +69,9 @@ export async function inspectAuthoringRepository(root: string, historyLimit = 10 declarations: syntax ? [...walkSyntax(syntax.root)] .filter((node) => - /^(?:interface|package|atom|state|edge|method|function|event|conformance)\w*Decl$/.test(node.kind), + /^(?:(?:interface|package|atom|state|edge|method|function|event|conformance|presentation)\w*Decl|module(?:Member|Export))$/.test( + node.kind, + ), ) .map((node) => ({ kind: node.kind, source: source.slice(node.start, node.end) })) : [], diff --git a/src/capability-language/candidate-check.ts b/src/capability-language/candidate-check.ts index 6a67de9..45c9ea2 100644 --- a/src/capability-language/candidate-check.ts +++ b/src/capability-language/candidate-check.ts @@ -209,7 +209,7 @@ export const checkWorkspaceCandidate = async (options: { : []; const evolution = planEvolution(baseline, compiled.workspace, { reviews }); blockers.push(...evolution.blockers); - for (const resource of compiled.resources.filter((entry) => entry.kind === "package")) { + for (const resource of compiled.capabilityResources.filter((entry) => entry.kind === "package")) { // Per-package recursive schema, identical to host activation, not unrelated // workspace declarations that would unnecessarily invalidate build caches. const candidate = await compileCapabilityResourceRepository({ @@ -218,6 +218,7 @@ export const checkWorkspaceCandidate = async (options: { source: resource.source, resolveResource, }); + if (candidate.resource.kind !== "package") throw Error("Expected checked package contract"); const schema = path.join(temporary, "bindings.json"); await fs.writeFile( schema, diff --git a/src/capability-language/checked-build.ts b/src/capability-language/checked-build.ts index d08b50a..7e2e7fd 100644 --- a/src/capability-language/checked-build.ts +++ b/src/capability-language/checked-build.ts @@ -13,13 +13,14 @@ export const checkerIdentity = () => export async function snapshotCommit(root: string): Promise { const run = async (...args: string[]) => (await execFile("jj", args, { cwd: root, env: environment() })).stdout.trim(); - await run("status"); - // jj resolve --list exits 1 on a clean revision. Query structured revision - // metadata instead of depending on diagnostic wording or swallowing errors. - if ((await run("--ignore-working-copy", "log", "--no-graph", "-r", "@", "-T", "conflict")) !== "false") - throw new Error("Resolve source conflicts before verification"); - const commit = await run("--ignore-working-copy", "log", "--no-graph", "-r", "@", "-T", "commit_id"); - if (!/^(?:[a-f0-9]{40}|[a-f0-9]{64})$/.test(commit)) throw new Error("Verification requires an exact jj commit"); + // A single normal log snapshots the working copy and observes both values + // from that exact revision. Separate status/log processes add no evidence. + const [conflict, commit] = (await run("log", "--no-graph", "-r", "@", "-T", 'conflict ++ "\\n" ++ commit_id')).split( + "\n", + ); + if (conflict !== "false") throw new Error("Resolve source conflicts before verification"); + if (!commit || !/^(?:[a-f0-9]{40}|[a-f0-9]{64})$/.test(commit)) + throw new Error("Verification requires an exact jj commit"); await execFile("git", ["diff", "--exit-code", "--no-ext-diff", "--no-textconv", commit, "--"], { cwd: root, env: environment(), @@ -107,9 +108,10 @@ export async function buildCheckedPackage(source: string, schema: string, packag * nested fetches. Keep build noise in a named log, with a bounded failure tail. */ export async function buildImmutableCandidate( source: { repository: string; commit: string }, - kind: "workspace" | "interface" | "package", + kind: "workspace" | "interface" | "package" | "build", logFile: string, contractOnly = false, + observe?: (phase: "nixEvaluationMs" | "nixBuildMs", ms: number) => void, ): Promise { if (!/^(?:[a-f0-9]{40}|[a-f0-9]{64})$/.test(source.commit)) throw new Error("An immutable candidate requires an exact commit"); @@ -126,16 +128,52 @@ export async function buildImmutableCandidate( log.once("open", () => resolve()); log.once("error", reject); }); - return await new Promise((resolve, reject) => { - let output = "", - tail = "", - failure: Error | undefined; - const child = spawn( - "nix", - [ - "build", - "--impure", - "--file", + const run = async (executable: string, args: string[]) => + await new Promise((resolve, reject) => { + let output = "", + tail = "", + failure: Error | undefined; + const child = spawn(executable, args, { env: environment(), stdio: ["ignore", "pipe", "pipe"] }); + const failedLog = (error: Error) => { + failure = error; + child.kill(); + }; + log.on("error", failedLog); + child.stdout.on("data", (chunk) => { + output += chunk; + }); + child.stderr.on("data", (chunk) => { + log.write(chunk); + tail = (tail + String(chunk)).slice(-6000); + }); + child.on("error", (error) => { + failure = error; + }); + child.on("close", (code) => { + log.off("error", failedLog); + if (failure) reject(failure); + else if (code !== 0) reject(new Error(`Candidate Nix check failed (${code}). Full log: ${logFile}\n${tail}`)); + else resolve(output.trim()); + }); + }); + const timed = async (phase: "nixEvaluationMs" | "nixBuildMs", action: () => Promise) => { + const started = performance.now(); + try { + return await action(); + } finally { + observe?.(phase, Math.round(performance.now() - started)); + } + }; + let gcDirectory: string | undefined; + try { + // Evaluation includes import-from-derivation builds (locks and contracts). + // Build the resulting derivation directly, without evaluating the DAG twice. + // Keep the derivation alive across the two processes, including concurrent + // GC. This temporary root is removed on success and on verification failure. + gcDirectory = await fs.mkdtemp(path.join(path.dirname(logFile), ".gc-root-")); + const gcRoot = path.join(gcDirectory, "candidate"); + const derivation = await timed("nixEvaluationMs", async () => { + await run("nix-instantiate", [ builder, "--argstr", "repository", @@ -152,33 +190,22 @@ export async function buildImmutableCandidate( "--arg", "contractOnly", contractOnly ? "true" : "false", - "--no-link", - "--print-out-paths", - "-L", - ], - { env: environment(), stdio: ["ignore", "pipe", "pipe"] }, + "--add-root", + gcRoot, + "--indirect", + ]); + return await fs.readlink(gcRoot); + }); + if (!/^\/nix\/store\/[a-z0-9]{32}-[^\s/]+\.drv$/.test(derivation)) + throw new Error("Nix returned an invalid candidate derivation"); + const artifact = await timed("nixBuildMs", () => + run("nix", ["build", derivation + "^*", "--no-link", "--print-out-paths", "-L"]), ); - log.on("error", (error) => { - failure = error; - child.kill(); - }); - child.stdout.on("data", (chunk) => { - output += chunk; - }); - child.stderr.on("data", (chunk) => { - log.write(chunk); - tail = (tail + String(chunk)).slice(-6000); - }); - child.on("error", (error) => { - failure = error; - }); - child.on("close", (code) => - log.end(() => { - if (failure) reject(failure); - else if (code !== 0 || !/^\/nix\/store\/[a-z0-9]{32}-[^\s/]+$/.test(output.trim())) - reject(new Error(`Candidate Nix check failed (${code}). Full log: ${logFile}\n${tail}`)); - else resolve(output.trim()); - }), - ); - }); + if (!/^\/nix\/store\/[a-z0-9]{32}-[^\s/]+$/.test(artifact)) + throw new Error("Nix returned an invalid candidate artifact"); + return artifact; + } finally { + await new Promise((resolve) => log.end(resolve)); + if (gcDirectory) await fs.rm(gcDirectory, { recursive: true, force: true }); + } } diff --git a/src/capability-language/checked-resources.ts b/src/capability-language/checked-resources.ts new file mode 100644 index 0000000..1d258fa --- /dev/null +++ b/src/capability-language/checked-resources.ts @@ -0,0 +1,40 @@ +import { readFile } from "node:fs/promises"; +import type { CheckedResourceResolver } from "./assembly.js"; +import type { CapabilityResource } from "./parser.js"; + +/** Internal compiler inputs produced by checked-candidate.nix, not authoring + * receipts. The Nix composition owns their derivations and exact toolchain. + * Ordinary compiler callers omit this map and run all checks directly. */ +export async function loadCheckedResources(filename?: string): Promise { + if (!filename) return undefined; + const entries = JSON.parse(await readFile(filename, "utf8")) as Array<{ + kind: string; + repository: string; + commit: string; + artifact: string; + }>; + const resources = new Map(); + for (const entry of entries) { + const key = `${entry.kind}\0${entry.repository}\0${entry.commit}`; + if (resources.has(key)) throw new Error("Repeated checked resource input"); + if (!/^\/nix\/store\/[^/]+$/.test(entry.artifact)) + throw new Error("Checked resources must be immutable Nix outputs"); + resources.set(key, entry.artifact); + } + return async (kind, source, lock, dependencies) => { + const artifact = resources.get(`${kind}\0${source.repository}\0${source.commit}`); + if (!artifact) return undefined; + const graph = JSON.parse(await readFile(`${artifact}/graph.json`, "utf8")); + const expected = [...dependencies].map(([binding, node]) => ({ binding, resourceKey: node.key })); + if ( + graph.quixos.commit !== lock.quixos.commit || + graph.quixos.repository !== lock.quixos.repository || + graph.root.kind !== kind || + graph.root.source.repository !== source.repository || + graph.root.source.commit !== source.commit || + JSON.stringify(graph.root.dependencies) !== JSON.stringify(expected) + ) + throw new Error("Checked resource input does not match its locked dependency graph"); + return JSON.parse(await readFile(`${artifact}/candidate.json`, "utf8")) as CapabilityResource; + }; +} diff --git a/src/capability-language/component-nix.ts b/src/capability-language/component-nix.ts new file mode 100644 index 0000000..201eded --- /dev/null +++ b/src/capability-language/component-nix.ts @@ -0,0 +1,52 @@ +import { execFile as callback } from "node:child_process"; +import { promisify } from "node:util"; +const execFile = promisify(callback); +export const nixLiteral = (value: string): string => JSON.stringify(value).replace(/\$\{/g, "\\${"); + +/** Recognize the deliberately small scaffold edit surface, never evaluate Nix. + * Authors may replace it with arbitrary Nix; then scaffolding declines the edit. */ +export async function componentEntries(source: string): Promise> { + const { stdout } = await execFile("nix-instantiate", ["--parse", "--expr", source], { maxBuffer: 1024 * 1024 }); + const tokens = stdout.match(/"(?:\\.|[^"\\])*"|[A-Za-z_][A-Za-z0-9_-]*|[{}=;]/g) ?? []; + if (stdout.replace(/"(?:\\.|[^"\\])*"|[A-Za-z_][A-Za-z0-9_-]*|[{}=;]|\s+/g, "")) + throw Error("components.nix is outside the supported literal-entry shape"); + let cursor = 0; + const expect = (token: string) => { + if (tokens[cursor++] !== token) throw Error("components.nix is outside the supported literal-entry shape"); + }; + const string = () => { + const token = tokens[cursor++]; + if (!token?.startsWith('"')) throw Error("Component entries must use literal strings"); + const json = token.replace(/(\\*)\$\{/g, (_match, escapes: string) => { + if (escapes.length % 2 === 0) throw Error("Component entries must use literal strings"); + return escapes.slice(1) + "${"; + }); + return JSON.parse(json) as string; + }; + const entries: Record = Object.create(null); + expect("{"); + while (tokens[cursor] !== "}") { + const id = tokens[cursor]?.startsWith('"') ? string() : tokens[cursor++]; + if (!id) throw Error("Missing component identity"); + if (Object.hasOwn(entries, id)) throw Error("Duplicate component entry"); + expect("="); + expect("{"); + const properties: Record = Object.create(null); + while (tokens[cursor] !== "}") { + const key = tokens[cursor]?.startsWith('"') ? string() : tokens[cursor++]; + if (!key || !["entry", "export"].includes(key) || Object.hasOwn(properties, key)) + throw Error("Unsupported component entry property"); + expect("="); + properties[key] = string(); + expect(";"); + } + expect("}"); + expect(";"); + if (!properties.entry || !properties.export) throw Error("Component entry needs entry and export"); + entries[id] = { entry: properties.entry, export: properties.export }; + } + expect("}"); + if (cursor !== tokens.length || !/\n}\s*$/.test(source)) + throw Error("components.nix is outside the supported scaffold shape"); + return entries; +} diff --git a/src/capability-language/component-scaffold.ts b/src/capability-language/component-scaffold.ts new file mode 100644 index 0000000..7d1723b --- /dev/null +++ b/src/capability-language/component-scaffold.ts @@ -0,0 +1,100 @@ +import fs from "node:fs/promises"; +import path from "node:path"; +import { parseQx, walkSyntax } from "./source.js"; +import type { ScaffoldRecipe } from "./scaffold-recipes.js"; +import type { StructuralRequest } from "./structural-plan.js"; +import { componentEntries, nixLiteral } from "./component-nix.js"; + +/** Add one ordinary component entry without regenerating existing application code. */ +export async function scaffoldComponent( + root: string, + prefix: string, + spec: ScaffoldRecipe, +): Promise { + if (!spec.declaration || !spec.name || !/^[A-Z][A-Za-z0-9_]*$/.test(spec.name) || !spec.id) + throw Error("Component scaffold requires a PascalCase name, ID and QX declaration"); + const source = `package Draft id "draft" revision "draft@1" { ${spec.declaration} }`; + const syntax = parseQx(source); + const declarations = [...walkSyntax(syntax.root)].filter((node) => node.kind === "moduleExport"); + const pkg = syntax.root.children.find((node) => node.kind === "packageResourceDecl"); + if ( + syntax.diagnostics.length || + syntax.root.children.length !== 1 || + declarations.length !== 1 || + pkg?.children.filter((node) => node.kind === "packageExport").length !== 1 + ) + throw Error("Expected exactly one component declaration"); + const declaration = declarations[0]!; + const identifier = declaration.children.find((node) => node.kind === "identifier"); + const id = declaration.children.find((node) => node.kind === "stringLiteral"); + if ( + !identifier || + source.slice(identifier.start, identifier.end) !== spec.name || + !id || + JSON.parse(source.slice(id.start, id.end)) !== spec.id + ) + throw Error("Component declaration name/ID must match its registration"); + const authored = await fs.readFile(path.join(root, prefix, "package.qx"), "utf8"); + const existing = parseQx(authored); + const parent = existing.root.children.find((node) => node.kind === "packageResourceDecl"); + if (existing.diagnostics.length || !parent) throw Error("Fix package.qx before adding a component"); + for (const item of parent.children.filter((node) => node.kind === "packageExport").flatMap((node) => node.children)) { + const name = item.children.find((node) => node.kind === "identifier"); + const literal = item.children.find((node) => node.kind === "stringLiteral"); + if ( + (name && authored.slice(name.start, name.end) === spec.name) || + (literal && JSON.parse(authored.slice(literal.start, literal.end)) === spec.id) + ) + throw Error("Component requires a unique name and ID"); + } + const filename = "components.nix"; + const before = await fs.readFile(path.join(root, prefix, filename), "utf8"); + const entries = await componentEntries(before); + if (!entries || typeof entries !== "object" || Array.isArray(entries) || Object.hasOwn(entries, spec.id)) + throw Error("Invalid or duplicate component entry"); + const insertion = ` ${nixLiteral(spec.id)} = { entry = ${nixLiteral(`src/components/${spec.name}.tsx`)}; export = ${nixLiteral(spec.name)}; };\n`; + const after = before.replace(/\n}(\s*)$/, (_match, trailing) => "\n" + insertion + "}" + trailing); + const parentID = parent.children.find((node) => node.kind === "stringLiteral")!; + const configFile = prefix + "quixos.check.json"; + const configBefore = await fs.readFile(path.join(root, configFile), "utf8"); + const config = JSON.parse(configBefore); + config.queryFrontends = [...new Set([...(config.queryFrontends ?? []), "react"])]; + const preparationFile = prefix + "quixos.react.json"; + let preparationBefore: string | undefined; + try { + preparationBefore = await fs.readFile(path.join(root, preparationFile), "utf8"); + } catch (error) { + if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error; + } + const preparation = preparationBefore ? JSON.parse(preparationBefore) : { modules: {} }; + if (Object.hasOwn(preparation.modules, spec.name)) throw Error("Duplicate presentation preparation"); + preparation.modules[spec.name] = { inputs: [] }; + const preparationText = JSON.stringify(preparation, null, 2) + "\n"; + return { + kind: "package", + source: spec.source, + resourceRoot: spec.directory, + validation: "syntax", + files: [ + preparationBefore === undefined + ? { file: preparationFile, create: preparationText } + : { file: preparationFile, expected: preparationBefore, replace: preparationText }, + { file: configFile, expected: configBefore, replace: JSON.stringify(config, null, 2) + "\n" }, + { + file: prefix + "package.qx", + edits: [ + { + operation: "append", + parent: { kind: "packageResourceDecl", id: JSON.parse(authored.slice(parentID.start, parentID.end)) }, + source: spec.declaration, + }, + ], + }, + { file: prefix + filename, expected: before, replace: after }, + { + file: prefix + `src/components/${spec.name}.tsx`, + create: `import type { Modules } from "../gen/client.js";\n\n// Declare initial query inputs in quixos.react.json; use interfaces for actions.\nexport const ${spec.name}: Modules[${JSON.stringify(spec.name)}] = ({subject}) =>

{subject.object}

;\n`, + }, + ], + }; +} diff --git a/src/capability-language/controller-scaffold.ts b/src/capability-language/controller-scaffold.ts new file mode 100644 index 0000000..f98df2a --- /dev/null +++ b/src/capability-language/controller-scaffold.ts @@ -0,0 +1,43 @@ +import fs from "node:fs/promises"; +import path from "node:path"; +import type { ScaffoldRecipe } from "./scaffold-recipes.js"; +import type { StructuralRequest } from "./structural-plan.js"; +export async function scaffoldController( + root: string, + prefix: string, + spec: ScaffoldRecipe, +): Promise { + const entry = { id: spec.id, ...spec.controller }; + const fields = ["id", "subject", "snapshot", "remote", "input"]; + if ( + Object.keys(entry).some((key) => ![...fields, "registry"].includes(key)) || + (entry.registry !== undefined && typeof entry.registry !== "string") || + fields.some((key) => typeof (entry as Record)[key] !== "string") || + !["unit", "value"].includes(entry.input ?? "") + ) + throw Error("Controller requires id, subject, snapshot, remote and input (unit/value)"); + const file = prefix + "quixos.controllers.json"; + let before: string | undefined; + try { + before = await fs.readFile(path.join(root, file), "utf8"); + } catch (error) { + if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error; + } + const catalog = before ? JSON.parse(before) : { schemaVersion: 1, controllers: [] }; + if ( + catalog.schemaVersion !== 1 || + !Array.isArray(catalog.controllers) || + catalog.controllers.length >= 1000 || + catalog.controllers.some((c: { id: string }) => c.id === entry.id) + ) + throw Error("Invalid catalog or duplicate controller ID"); + catalog.controllers.push(entry); + const content = JSON.stringify(catalog, null, 2) + "\n"; + return { + kind: "package", + source: spec.source, + resourceRoot: spec.directory, + validation: "syntax", + files: [before === undefined ? { file, create: content } : { file, expected: before, replace: content }], + }; +} diff --git a/src/capability-language/execution-scaffold.ts b/src/capability-language/execution-scaffold.ts new file mode 100644 index 0000000..226154e --- /dev/null +++ b/src/capability-language/execution-scaffold.ts @@ -0,0 +1,208 @@ +import fs from "node:fs/promises"; +import path from "node:path"; +import type { ScaffoldRecipe } from "./scaffold-recipes.js"; +import type { StructuralRequest } from "./structural-plan.js"; +import { parseQx, walkSyntax } from "./source.js"; +import { addImplementation } from "./implementation-edit.js"; +import { formatQuixosLock } from "../resource-lock/index.js"; +import { javascriptTarget } from "../execution-world/native-backends.js"; +const json = (value: unknown) => JSON.stringify(value, null, 2) + "\n"; +const q = (value: string) => JSON.stringify(value); +const nixString = (value: string) => q(value).replaceAll("${", "\\${"); +export async function isExecutionScaffold(root: string, prefix: string): Promise { + try { + return JSON.parse(await fs.readFile(path.join(root, prefix, "quixos.check.json"), "utf8")).backend === "execution"; + } catch (error) { + if ((error as NodeJS.ErrnoException).code === "ENOENT") return false; + throw error; + } +} +/** Insert ordinary authored code; declarations remain the only export inventory. */ +export async function executionScaffold( + root: string, + command: string, + spec: ScaffoldRecipe, + prefix: string, + nixSource: (source: NonNullable["helpers"]) => string, +): Promise { + if (!spec.name || !/^[A-Za-z_][A-Za-z0-9_]*$/.test(spec.name) || !spec.id) + throw new Error("Execution scaffold requires a simple name and an ID"); + if (spec.initialFiles && command !== "package") throw new Error("Initial files are only for new packages"); + const files: StructuralRequest["files"] = []; + const create = (file: string, content: string) => files.push({ file: prefix + file, create: content }); + if (command === "package") { + if (!spec.tools || !spec.revision) throw new Error("Execution package requires exact tool sources and a revision"); + create( + "package.qx", + `symbol JavaScript id ${q(javascriptTarget)};\npackage ${spec.name} id ${q(spec.id)} revision ${q(spec.revision)} {\n}\n`, + ); + create( + "quixos.lock", + formatQuixosLock({ formatVersion: 1, quixos: { resolver: "git", ...spec.tools.quixos }, resources: [] }), + ); + create("quixos.check.json", json({ backend: "execution" })); + create("components.nix", "{\n}\n"); + create( + "package.json", + json({ + name: `@quixos/${spec.name.toLowerCase()}`, + private: true, + type: "module", + packageManager: "yarn@4.18.0", + scripts: { typecheck: "qx-workspace check" }, + }), + ); + create( + ".gitignore", + "node_modules/\ndist/\nsrc/gen/\n.quixos/\nresult\n.yarn/cache/\n.yarn/install-state.gz\n.yarn/unplugged/\n", + ); + create( + ".yarnrc.yml", + 'enableScripts: true\n\ngenerateDefaultNix: false\n\nindividualNixPackaging: true\n\nnodeLinker: node-modules\n\nsupportedArchitectures:\n os:\n - current\n - linux\n cpu:\n - current\n - x64\n - arm64\n libc:\n - current\n - glibc\n\nplugins:\n - checksum: 271de9a785321a37c564b8e2a6b5cd689850b20572d5fee8ec0a6b8e7a5d4cfb59e533524af2b4a537e00da12828abd062eaec6978556e3dd9149a020db2591b\n path: .yarn/plugins/yarn-plugin-nixify.cjs\n spec: "https://gitea-external.egads.tutti.syntaxblitz.net/quixos/yarn-plugin-nixify-patched/raw/commit/4528fdd20b30d869262443b3f044549810e75fb8/dist/yarn-plugin-nixify.js"\n', + ); + for (const lane of ["browser", "server", "remote"]) + create( + `src/${lane}.ts`, + `import { createRegistry } from "./gen/${lane === "remote" ? "remote" : "qx"}.js";\nexport const registry = createRegistry({});\n`, + ); + create( + "flake.nix", + `{ + inputs.protocol.url = ${nixString(nixSource(spec.tools.protocol))}; + inputs.nixpkgs.follows = "protocol/nixpkgs"; + inputs.flake-utils.follows = "protocol/flake-utils"; + inputs.helpers = { url = ${nixString(nixSource(spec.tools.helpers))}; flake = false; }; + outputs = { nixpkgs, flake-utils, helpers, ... }: + flake-utils.lib.eachDefaultSystem (system: let + pkgs = import nixpkgs { inherit system; }; + camino = import (helpers + "/quixos-package-helpers.nix"); + in { + devShells.default = (import (helpers + "/quixos-package-helpers.nix")).mkCaminoYarnShell { inherit pkgs; }; + quixosPackages.checkedArtifacts = context: +let + pkgs = camino.packageSet { inherit nixpkgs system context; }; + contract = builtins.fromJSON (builtins.readFile context.buildContract); + package = contract.definition.package; + portable = builtins.filter (entry: entry.execution.kind != "remote") package.exports; + remote = builtins.filter (entry: entry.execution.kind == "remote") package.exports; + modules = import ./components.nix; + dependencies = if builtins.pathExists ./yarn-project.nix + then camino.mkCaminoYarnDependencies { inherit pkgs; src = ./.; } else null; +in camino.assembleCaminoPackage { + inherit pkgs context; + artifacts = + pkgs.lib.optional (portable != []) (camino.mkCaminoTypeScriptPackage { + inherit pkgs context dependencies; src = ./.; + entry = "src/server.ts"; entries.browser = "src/browser.ts"; + targets = pkgs.lib.unique (map (target: if target == "target:org.quixos.execution.javascript@1" then "browser" else if target == "server" then "server" else throw "Unsupported TypeScript backend target") (pkgs.lib.concatMap (entry: entry.execution.targets) portable)); + }) ++ pkgs.lib.optional (remote != []) (camino.mkCaminoTypeScriptService { + inherit pkgs context dependencies; src = ./.; entry = "src/remote.ts"; + }) ++ pkgs.lib.optional (modules != {}) (camino.mkCaminoReactPackage { + inherit pkgs context dependencies modules; src = ./.; + }); +}; + }); +} +`, + ); + create( + "README.md", + `# ${spec.name}\n\nThis execution package exports checked browser/server modules and a process service.\nUse scaffold function with an explicit execution contract to add a typed handler.\nSnapshot/transaction targets choose browser.ts and/or server.ts; remote handlers\nuse remote.ts. Shared helpers are ordinary imports. Generated bindings are build\noutputs, not authored source. The Nix composition supplies the canonical package contract\nand explicit runtime dependencies to checkedArtifacts.\n\nRun qx-workspace check from this package to build it independently, or from a\nworkspace to also check installed bindings. Activate the resulting report separately through the workspace\nhost. A source-only structural check is not runtime qualification.\n`, + ); + } else if (command === "function") { + if (!spec.declaration) + throw new Error("Execution functions require a declaration with explicit execution effect and targets"); + const wrapped = `package Scaffold id "scaffold" revision "scaffold@1" { ${spec.declaration} }`; + const parsed = parseQx(wrapped); + const pkg = parsed.root.children.find((node) => node.kind === "packageResourceDecl"); + const exports = [...walkSyntax(parsed.root)].filter((node) => + ["packageFunctionExport", "packageOperationExport"].includes(node.kind), + ); + const declaration = exports[0]; + if ( + parsed.diagnostics.length || + exports.length !== 1 || + !declaration || + parsed.root.children.length !== 1 || + pkg?.children.filter((node) => node.kind === "packageExport").length !== 1 || + pkg?.children.some((node) => !["identifier", "stringLiteral", "packageExport"].includes(node.kind)) + ) + throw new Error("Expected one function or operation export with an execution contract"); + const text = (node: { start: number; end: number }) => wrapped.slice(node.start, node.end); + const name = declaration.children.find((node) => node.kind === "identifier"); + const id = declaration.children.find((node) => node.kind === "stringLiteral"); + const execution = declaration.children.find((node) => node.kind === "executionClause"); + if (!name || text(name) !== spec.name || !id || JSON.parse(text(id)) !== spec.id || !execution) + throw new Error("Declaration name/ID must match; an explicit execution contract is required"); + const tokens = parsed.tokens + .filter((token) => !token.trivia && token.start >= execution.start && token.end <= execution.end) + .map((token) => token.text); + const remote = tokens[1] === "remote"; + const declaredTargets = remote + ? ["remote"] + : [...walkSyntax(execution)].filter((node) => node.kind === "identifier").map(text); + const targets = declaredTargets.length ? declaredTargets : ["server"]; + if ( + !targets.length || + new Set(targets).size !== targets.length || + targets.some((target) => !(remote ? ["remote"] : ["JavaScript", "server"]).includes(target)) + ) + throw new Error("This TypeScript scaffold supports the JavaScript target, server, and remote service handlers"); + const authored = await fs.readFile(path.join(root, prefix, "package.qx"), "utf8"); + const syntax = parseQx(authored); + const packageNode = syntax.root.children.find((node) => node.kind === "packageResourceDecl"); + if (syntax.diagnostics.length || !packageNode) throw new Error("Fix package.qx before adding a handler"); + for (const node of [...walkSyntax(packageNode)].filter((node) => + ["packageFunctionExport", "packageOperationExport", "packageConstructorExport"].includes(node.kind), + )) { + const identifier = node.children.find((child) => child.kind === "identifier"); + const literal = node.children.find((child) => child.kind === "stringLiteral"); + if ( + (identifier && authored.slice(identifier.start, identifier.end) === spec.name) || + (literal && JSON.parse(authored.slice(literal.start, literal.end)) === spec.id) + ) + throw new Error("New export requires a unique name and ID"); + } + const packageID = packageNode.children.find((node) => node.kind === "stringLiteral")!; + files.push({ + file: prefix + "package.qx", + edits: [ + { + operation: "append", + parent: { kind: "packageResourceDecl", id: JSON.parse(authored.slice(packageID.start, packageID.end)) }, + source: spec.declaration, + }, + ], + }); + create( + `src/impl/${spec.name}.ts`, + `import type { ${remote ? "Remote" : "Portable"}Implementation } from "../gen/${remote ? "remote" : "qx"}.js";\nexport const handler: ${remote ? "Remote" : "Portable"}Implementation[${q(spec.name)}] = async (_context) => { throw new Error(${q("Implement " + spec.name)}); };\n`, + ); + for (const target of targets) { + const file = prefix + `src/${target === "JavaScript" ? "browser" : target}.ts`; + const before = await fs.readFile(path.join(root, file), "utf8"); + files.push({ + file, + expected: before, + replace: addImplementation(before, "createRegistry", spec.name, `./impl/${spec.name}.js`), + }); + } + } else + throw new Error("Execution packages use ordinary factory transactions; migration scaffolding is not supported"); + if (spec.initialFiles) { + for (const [file, content] of Object.entries(spec.initialFiles)) { + if ( + typeof content !== "string" || + !/^(?:package\.qx|(?:package|components|flake|yarn-project)\.nix|package\.json|yarn\.lock|\.yarnrc\.yml|\.gitignore|\.yarn\/plugins\/[A-Za-z0-9_.-]+\.cjs|quixos\.controllers\.json|README\.md|(?:src|queries)\/(?:[A-Za-z0-9_-]+\/)*[A-Za-z0-9_.-]+)$/.test( + file, + ) || + file.split("/").some((part) => part === ".." || part === "." || part === "gen") + ) + throw new Error("Not an initial authored execution file: " + file); + const previous = files.findIndex((entry) => entry.file === prefix + file); + if (previous >= 0) files.splice(previous, 1); + create(file, content); + } + } + return { kind: "package", source: spec.source, resourceRoot: spec.directory, validation: "syntax", files }; +} diff --git a/src/capability-language/generated/QuixosCapability.interp b/src/capability-language/generated/QuixosCapability.interp index dc84544..6bdd1e3 100644 --- a/src/capability-language/generated/QuixosCapability.interp +++ b/src/capability-language/generated/QuixosCapability.interp @@ -1,10 +1,30 @@ token literal names: null +'edit' +'module' +'contract' +'presentation' +'subject' +'for' +'creation' +'variant' +'prepares' +'initialize' 'workspace' +'execution' +'snapshot' +'transaction' +'remote' +'targets' +'portable' +'replica' +'local' +'from' 'result-bytes' 'rpc-calls' 'deadline-ms' 'query' +'reflection' 'specialize' 'root' 'document' @@ -24,11 +44,13 @@ null 'fragment' 'import' 'external' +'symbol' 'atom' 'interface' 'interfaces' 'package' 'value' +'field' 'relation' 'operation' 'function' @@ -47,6 +69,7 @@ null 'projection' 'with' 'using' +'accept' 'via' 'materialize' 'if' @@ -60,6 +83,9 @@ null 'revision' 'semantic-major' 'on-delete' +'lifecycle' +'journal' +'subscription' 'retain-other' 'keyed' 'public-traversal' @@ -85,7 +111,7 @@ null 'watch-stop' 'subscribe' 'unsubscribe' -'optimistic-register' +'register' 'crdt' 'optional-one' 'exactly-one' @@ -136,11 +162,31 @@ null token symbolic names: null +EDIT +MODULE +CONTRACT +PRESENTATION +SUBJECT +FOR +CREATION +VARIANT +PREPARES +INITIALIZE WORKSPACE +EXECUTION +SNAPSHOT +TRANSACTION +REMOTE +TARGETS +PORTABLE +REPLICA +LOCAL +FROM RESULT_BYTES RPC_CALLS DEADLINE_MS QUERY +REFLECTION SPECIALIZE ROOT DOCUMENT @@ -160,11 +206,13 @@ REF FRAGMENT IMPORT EXTERNAL +SYMBOL ATOM INTERFACE INTERFACES PACKAGE VALUE +FIELD RELATION OPERATION FUNCTION @@ -183,6 +231,7 @@ EDGE PROJECTION WITH USING +ACCEPT VIA MATERIALIZE IF @@ -196,6 +245,9 @@ COMMIT REVISION SEMANTIC_MAJOR ON_DELETE +LIFECYCLE +JOURNAL +SUBSCRIPTION RETAIN_OTHER KEYED PUBLIC_TRAVERSAL @@ -221,7 +273,7 @@ WATCH_START WATCH_STOP SUBSCRIBE UNSUBSCRIBE -OPTIMISTIC_REGISTER +REGISTER CRDT OPTIONAL_ONE EXACTLY_ONE @@ -279,6 +331,7 @@ workspaceItem resourceImportDecl externalAtomDecl externalInterfaceDecl +symbolDecl resourcePreamble atomDecl interfaceResourceDecl @@ -289,6 +342,7 @@ typeArguments typeArgument typeAliasDecl interfaceMember +moduleMember operationMember valueMember valueMemberOperation @@ -298,11 +352,18 @@ relationshipOperation targetConstraint packageResourceDecl packageExport +moduleExport +presentationDecl packageQuery packageQuerySpecialization queryClause +fragmentVariable packageOperationExport packageFunctionExport +acceptanceClause +preparationClause +executionClause +selectedOperations packageConstructorExport eventClause operationMode @@ -319,13 +380,19 @@ storagePolicy edgeDecl edgeEndpoint conformanceDecl +conformanceSourceArguments conformanceItem +moduleBindingDecl +initializationDecl +initializationField +creationOwnerMapping stateFieldBindingDecl relationshipMaterializationDecl operationBindingDecl memberOperationRef operationName operationProvider +acceptanceBinding statePrimitive edgePrimitive dependencyBindingBlock @@ -333,6 +400,7 @@ dependencyBinding constructorBindingDecl valueType recordField +variantCase scalarType cardinality jsonLiteral @@ -344,4 +412,4 @@ stringLiteral atn: -[4, 1, 133, 1091, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, 4, 2, 5, 7, 5, 2, 6, 7, 6, 2, 7, 7, 7, 2, 8, 7, 8, 2, 9, 7, 9, 2, 10, 7, 10, 2, 11, 7, 11, 2, 12, 7, 12, 2, 13, 7, 13, 2, 14, 7, 14, 2, 15, 7, 15, 2, 16, 7, 16, 2, 17, 7, 17, 2, 18, 7, 18, 2, 19, 7, 19, 2, 20, 7, 20, 2, 21, 7, 21, 2, 22, 7, 22, 2, 23, 7, 23, 2, 24, 7, 24, 2, 25, 7, 25, 2, 26, 7, 26, 2, 27, 7, 27, 2, 28, 7, 28, 2, 29, 7, 29, 2, 30, 7, 30, 2, 31, 7, 31, 2, 32, 7, 32, 2, 33, 7, 33, 2, 34, 7, 34, 2, 35, 7, 35, 2, 36, 7, 36, 2, 37, 7, 37, 2, 38, 7, 38, 2, 39, 7, 39, 2, 40, 7, 40, 2, 41, 7, 41, 2, 42, 7, 42, 2, 43, 7, 43, 2, 44, 7, 44, 2, 45, 7, 45, 2, 46, 7, 46, 2, 47, 7, 47, 2, 48, 7, 48, 2, 49, 7, 49, 2, 50, 7, 50, 2, 51, 7, 51, 2, 52, 7, 52, 2, 53, 7, 53, 2, 54, 7, 54, 2, 55, 7, 55, 2, 56, 7, 56, 2, 57, 7, 57, 2, 58, 7, 58, 2, 59, 7, 59, 2, 60, 7, 60, 2, 61, 7, 61, 2, 62, 7, 62, 2, 63, 7, 63, 2, 64, 7, 64, 2, 65, 7, 65, 2, 66, 7, 66, 2, 67, 7, 67, 2, 68, 7, 68, 2, 69, 7, 69, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 3, 0, 153, 8, 0, 1, 1, 1, 1, 1, 1, 5, 1, 158, 8, 1, 10, 1, 12, 1, 161, 9, 1, 1, 1, 1, 1, 1, 2, 1, 2, 1, 2, 1, 2, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 5, 3, 179, 8, 3, 10, 3, 12, 3, 182, 9, 3, 1, 3, 1, 3, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 3, 4, 193, 8, 4, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 3, 5, 205, 8, 5, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 8, 1, 8, 1, 8, 1, 8, 3, 8, 225, 8, 8, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 3, 9, 233, 8, 9, 1, 9, 1, 9, 1, 10, 5, 10, 238, 8, 10, 10, 10, 12, 10, 241, 9, 10, 1, 10, 1, 10, 1, 10, 3, 10, 246, 8, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 5, 10, 256, 8, 10, 10, 10, 12, 10, 259, 9, 10, 3, 10, 261, 8, 10, 1, 10, 1, 10, 5, 10, 265, 8, 10, 10, 10, 12, 10, 268, 9, 10, 1, 10, 1, 10, 1, 11, 1, 11, 1, 11, 1, 11, 5, 11, 276, 8, 11, 10, 11, 12, 11, 279, 9, 11, 1, 11, 1, 11, 1, 12, 1, 12, 1, 12, 1, 12, 3, 12, 287, 8, 12, 1, 12, 1, 12, 1, 12, 1, 12, 1, 12, 1, 12, 5, 12, 295, 8, 12, 10, 12, 12, 12, 298, 9, 12, 3, 12, 300, 8, 12, 3, 12, 302, 8, 12, 1, 13, 1, 13, 3, 13, 306, 8, 13, 1, 14, 1, 14, 1, 14, 1, 14, 5, 14, 312, 8, 14, 10, 14, 12, 14, 315, 9, 14, 1, 14, 1, 14, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 3, 15, 326, 8, 15, 1, 16, 1, 16, 1, 16, 3, 16, 331, 8, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 17, 1, 17, 1, 17, 3, 17, 340, 8, 17, 1, 18, 3, 18, 343, 8, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 19, 3, 19, 361, 8, 19, 1, 19, 1, 19, 1, 19, 1, 19, 1, 19, 1, 19, 1, 19, 1, 19, 5, 19, 371, 8, 19, 10, 19, 12, 19, 374, 9, 19, 1, 19, 1, 19, 1, 20, 1, 20, 1, 20, 1, 20, 1, 20, 1, 20, 1, 20, 1, 20, 1, 20, 1, 20, 1, 20, 1, 20, 1, 20, 1, 20, 1, 20, 1, 20, 1, 20, 1, 20, 1, 20, 3, 20, 397, 8, 20, 1, 21, 3, 21, 400, 8, 21, 1, 21, 1, 21, 1, 21, 1, 21, 1, 21, 1, 21, 1, 21, 1, 21, 3, 21, 410, 8, 21, 1, 21, 1, 21, 3, 21, 414, 8, 21, 1, 21, 1, 21, 5, 21, 418, 8, 21, 10, 21, 12, 21, 421, 9, 21, 1, 21, 1, 21, 1, 22, 1, 22, 3, 22, 427, 8, 22, 1, 23, 1, 23, 1, 23, 1, 23, 1, 23, 1, 23, 1, 23, 1, 23, 1, 23, 1, 23, 1, 23, 1, 23, 1, 23, 1, 23, 1, 23, 1, 23, 1, 23, 1, 23, 1, 23, 1, 23, 1, 23, 1, 23, 1, 23, 1, 23, 3, 23, 453, 8, 23, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 3, 24, 460, 8, 24, 1, 24, 1, 24, 3, 24, 464, 8, 24, 1, 25, 5, 25, 467, 8, 25, 10, 25, 12, 25, 470, 9, 25, 1, 25, 1, 25, 1, 25, 1, 25, 1, 25, 1, 25, 1, 25, 1, 25, 3, 25, 480, 8, 25, 1, 25, 1, 25, 5, 25, 484, 8, 25, 10, 25, 12, 25, 487, 9, 25, 1, 25, 1, 25, 1, 26, 1, 26, 1, 26, 1, 26, 1, 26, 3, 26, 496, 8, 26, 1, 27, 1, 27, 1, 27, 3, 27, 501, 8, 27, 1, 27, 1, 27, 1, 27, 1, 27, 1, 27, 1, 27, 1, 27, 1, 27, 1, 27, 1, 27, 5, 27, 513, 8, 27, 10, 27, 12, 27, 516, 9, 27, 1, 27, 1, 27, 1, 28, 1, 28, 1, 28, 1, 28, 1, 28, 1, 28, 1, 28, 1, 28, 1, 28, 1, 29, 1, 29, 1, 29, 1, 29, 1, 29, 1, 29, 3, 29, 535, 8, 29, 1, 29, 1, 29, 1, 29, 1, 29, 1, 29, 1, 29, 1, 29, 1, 29, 1, 29, 1, 29, 1, 29, 1, 29, 1, 29, 1, 29, 1, 29, 1, 29, 1, 29, 1, 29, 1, 29, 1, 29, 1, 29, 1, 29, 1, 29, 1, 29, 1, 29, 3, 29, 562, 8, 29, 1, 30, 1, 30, 1, 30, 3, 30, 567, 8, 30, 1, 30, 1, 30, 1, 30, 1, 30, 1, 30, 1, 30, 1, 30, 1, 30, 1, 30, 3, 30, 578, 8, 30, 1, 30, 1, 30, 1, 30, 3, 30, 583, 8, 30, 1, 30, 1, 30, 1, 31, 1, 31, 1, 31, 3, 31, 590, 8, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 3, 31, 599, 8, 31, 1, 31, 1, 31, 1, 32, 1, 32, 1, 32, 1, 32, 1, 32, 1, 32, 1, 32, 1, 32, 1, 32, 3, 32, 612, 8, 32, 1, 32, 1, 32, 1, 33, 1, 33, 1, 33, 1, 34, 1, 34, 1, 35, 1, 35, 1, 35, 1, 35, 1, 35, 1, 35, 1, 35, 1, 35, 1, 35, 1, 35, 5, 35, 631, 8, 35, 10, 35, 12, 35, 634, 9, 35, 3, 35, 636, 8, 35, 1, 35, 3, 35, 639, 8, 35, 1, 36, 1, 36, 1, 36, 5, 36, 644, 8, 36, 10, 36, 12, 36, 647, 9, 36, 1, 37, 1, 37, 1, 37, 5, 37, 652, 8, 37, 10, 37, 12, 37, 655, 9, 37, 1, 37, 1, 37, 1, 38, 1, 38, 1, 38, 1, 38, 1, 38, 1, 38, 1, 38, 1, 38, 1, 38, 1, 38, 1, 38, 1, 38, 1, 38, 1, 38, 1, 38, 1, 38, 1, 38, 1, 38, 1, 38, 1, 38, 1, 38, 1, 38, 1, 38, 1, 38, 1, 38, 1, 38, 3, 38, 685, 8, 38, 1, 38, 1, 38, 1, 38, 1, 38, 1, 38, 1, 38, 1, 38, 1, 38, 1, 38, 1, 38, 3, 38, 697, 8, 38, 1, 38, 1, 38, 1, 38, 1, 38, 1, 38, 1, 38, 1, 38, 1, 38, 1, 38, 1, 38, 1, 38, 1, 38, 3, 38, 711, 8, 38, 1, 39, 1, 39, 1, 39, 1, 39, 5, 39, 717, 8, 39, 10, 39, 12, 39, 720, 9, 39, 1, 39, 1, 39, 1, 40, 1, 40, 1, 41, 1, 41, 1, 41, 1, 42, 1, 42, 3, 42, 731, 8, 42, 1, 43, 1, 43, 1, 43, 1, 43, 1, 43, 1, 43, 1, 43, 1, 43, 1, 43, 1, 43, 1, 43, 1, 43, 3, 43, 745, 8, 43, 1, 43, 1, 43, 1, 44, 1, 44, 1, 44, 1, 44, 1, 44, 1, 44, 3, 44, 755, 8, 44, 1, 45, 1, 45, 1, 45, 1, 45, 1, 45, 1, 45, 1, 45, 1, 45, 1, 45, 1, 46, 1, 46, 1, 46, 1, 46, 1, 46, 1, 46, 1, 46, 3, 46, 773, 8, 46, 1, 46, 1, 46, 3, 46, 777, 8, 46, 1, 46, 3, 46, 780, 8, 46, 1, 46, 1, 46, 3, 46, 784, 8, 46, 1, 46, 3, 46, 787, 8, 46, 1, 46, 1, 46, 1, 47, 1, 47, 1, 47, 1, 47, 1, 47, 3, 47, 796, 8, 47, 1, 47, 1, 47, 3, 47, 800, 8, 47, 1, 47, 1, 47, 3, 47, 804, 8, 47, 1, 47, 1, 47, 5, 47, 808, 8, 47, 10, 47, 12, 47, 811, 9, 47, 1, 47, 1, 47, 1, 48, 1, 48, 1, 48, 1, 48, 1, 48, 3, 48, 820, 8, 48, 1, 49, 1, 49, 1, 49, 1, 49, 1, 49, 1, 49, 1, 49, 1, 50, 1, 50, 1, 50, 1, 50, 1, 50, 1, 50, 1, 50, 1, 50, 1, 50, 1, 50, 1, 50, 1, 50, 1, 50, 1, 50, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 3, 51, 849, 8, 51, 1, 51, 1, 51, 1, 52, 1, 52, 1, 52, 1, 52, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 3, 53, 868, 8, 53, 1, 54, 1, 54, 1, 54, 1, 54, 1, 54, 1, 54, 1, 54, 1, 54, 1, 54, 1, 54, 1, 54, 1, 54, 1, 54, 1, 54, 1, 54, 1, 54, 1, 54, 3, 54, 887, 8, 54, 1, 54, 3, 54, 890, 8, 54, 3, 54, 892, 8, 54, 1, 55, 1, 55, 1, 56, 1, 56, 1, 57, 1, 57, 1, 57, 5, 57, 901, 8, 57, 10, 57, 12, 57, 904, 9, 57, 1, 57, 1, 57, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 3, 58, 918, 8, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 3, 58, 934, 8, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 3, 58, 943, 8, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 3, 58, 951, 8, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 3, 58, 973, 8, 58, 1, 58, 1, 58, 3, 58, 977, 8, 58, 1, 59, 1, 59, 1, 59, 1, 59, 1, 59, 1, 59, 1, 59, 3, 59, 986, 8, 59, 1, 59, 1, 59, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 3, 60, 1004, 8, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 5, 60, 1026, 8, 60, 10, 60, 12, 60, 1029, 9, 60, 1, 60, 1, 60, 1, 60, 3, 60, 1034, 8, 60, 3, 60, 1036, 8, 60, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 1, 62, 1, 62, 1, 63, 1, 63, 1, 64, 1, 64, 1, 64, 1, 64, 1, 64, 1, 64, 1, 64, 1, 64, 3, 64, 1055, 8, 64, 1, 65, 1, 65, 1, 65, 1, 65, 5, 65, 1061, 8, 65, 10, 65, 12, 65, 1064, 9, 65, 3, 65, 1066, 8, 65, 1, 65, 1, 65, 1, 66, 1, 66, 1, 66, 1, 66, 1, 67, 1, 67, 1, 67, 1, 67, 5, 67, 1078, 8, 67, 10, 67, 12, 67, 1081, 9, 67, 3, 67, 1083, 8, 67, 1, 67, 1, 67, 1, 68, 1, 68, 1, 69, 1, 69, 1, 69, 0, 0, 70, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70, 72, 74, 76, 78, 80, 82, 84, 86, 88, 90, 92, 94, 96, 98, 100, 102, 104, 106, 108, 110, 112, 114, 116, 118, 120, 122, 124, 126, 128, 130, 132, 134, 136, 138, 0, 7, 1, 0, 81, 85, 2, 0, 76, 80, 82, 83, 2, 0, 76, 77, 82, 83, 2, 0, 78, 80, 82, 83, 1, 0, 101, 108, 1, 0, 88, 91, 3, 0, 2, 15, 55, 55, 129, 129, 1170, 0, 152, 1, 0, 0, 0, 2, 154, 1, 0, 0, 0, 4, 164, 1, 0, 0, 0, 6, 168, 1, 0, 0, 0, 8, 192, 1, 0, 0, 0, 10, 204, 1, 0, 0, 0, 12, 206, 1, 0, 0, 0, 14, 213, 1, 0, 0, 0, 16, 224, 1, 0, 0, 0, 18, 226, 1, 0, 0, 0, 20, 239, 1, 0, 0, 0, 22, 271, 1, 0, 0, 0, 24, 301, 1, 0, 0, 0, 26, 303, 1, 0, 0, 0, 28, 307, 1, 0, 0, 0, 30, 325, 1, 0, 0, 0, 32, 327, 1, 0, 0, 0, 34, 339, 1, 0, 0, 0, 36, 342, 1, 0, 0, 0, 38, 360, 1, 0, 0, 0, 40, 396, 1, 0, 0, 0, 42, 399, 1, 0, 0, 0, 44, 424, 1, 0, 0, 0, 46, 452, 1, 0, 0, 0, 48, 463, 1, 0, 0, 0, 50, 468, 1, 0, 0, 0, 52, 495, 1, 0, 0, 0, 54, 497, 1, 0, 0, 0, 56, 519, 1, 0, 0, 0, 58, 561, 1, 0, 0, 0, 60, 563, 1, 0, 0, 0, 62, 586, 1, 0, 0, 0, 64, 602, 1, 0, 0, 0, 66, 615, 1, 0, 0, 0, 68, 618, 1, 0, 0, 0, 70, 638, 1, 0, 0, 0, 72, 640, 1, 0, 0, 0, 74, 648, 1, 0, 0, 0, 76, 710, 1, 0, 0, 0, 78, 712, 1, 0, 0, 0, 80, 723, 1, 0, 0, 0, 82, 725, 1, 0, 0, 0, 84, 730, 1, 0, 0, 0, 86, 732, 1, 0, 0, 0, 88, 754, 1, 0, 0, 0, 90, 756, 1, 0, 0, 0, 92, 765, 1, 0, 0, 0, 94, 790, 1, 0, 0, 0, 96, 819, 1, 0, 0, 0, 98, 821, 1, 0, 0, 0, 100, 828, 1, 0, 0, 0, 102, 842, 1, 0, 0, 0, 104, 852, 1, 0, 0, 0, 106, 867, 1, 0, 0, 0, 108, 891, 1, 0, 0, 0, 110, 893, 1, 0, 0, 0, 112, 895, 1, 0, 0, 0, 114, 897, 1, 0, 0, 0, 116, 976, 1, 0, 0, 0, 118, 978, 1, 0, 0, 0, 120, 1035, 1, 0, 0, 0, 122, 1037, 1, 0, 0, 0, 124, 1042, 1, 0, 0, 0, 126, 1044, 1, 0, 0, 0, 128, 1054, 1, 0, 0, 0, 130, 1056, 1, 0, 0, 0, 132, 1069, 1, 0, 0, 0, 134, 1073, 1, 0, 0, 0, 136, 1086, 1, 0, 0, 0, 138, 1088, 1, 0, 0, 0, 140, 141, 3, 6, 3, 0, 141, 142, 5, 0, 0, 1, 142, 153, 1, 0, 0, 0, 143, 144, 3, 20, 10, 0, 144, 145, 5, 0, 0, 1, 145, 153, 1, 0, 0, 0, 146, 147, 3, 50, 25, 0, 147, 148, 5, 0, 0, 1, 148, 153, 1, 0, 0, 0, 149, 150, 3, 2, 1, 0, 150, 151, 5, 0, 0, 1, 151, 153, 1, 0, 0, 0, 152, 140, 1, 0, 0, 0, 152, 143, 1, 0, 0, 0, 152, 146, 1, 0, 0, 0, 152, 149, 1, 0, 0, 0, 153, 1, 1, 0, 0, 0, 154, 155, 5, 22, 0, 0, 155, 159, 5, 117, 0, 0, 156, 158, 3, 8, 4, 0, 157, 156, 1, 0, 0, 0, 158, 161, 1, 0, 0, 0, 159, 157, 1, 0, 0, 0, 159, 160, 1, 0, 0, 0, 160, 162, 1, 0, 0, 0, 161, 159, 1, 0, 0, 0, 162, 163, 5, 118, 0, 0, 163, 3, 1, 0, 0, 0, 164, 165, 5, 23, 0, 0, 165, 166, 3, 138, 69, 0, 166, 167, 5, 114, 0, 0, 167, 5, 1, 0, 0, 0, 168, 169, 5, 1, 0, 0, 169, 170, 3, 136, 68, 0, 170, 171, 5, 64, 0, 0, 171, 172, 3, 138, 69, 0, 172, 173, 5, 58, 0, 0, 173, 174, 3, 138, 69, 0, 174, 175, 5, 57, 0, 0, 175, 176, 3, 138, 69, 0, 176, 180, 5, 117, 0, 0, 177, 179, 3, 8, 4, 0, 178, 177, 1, 0, 0, 0, 179, 182, 1, 0, 0, 0, 180, 178, 1, 0, 0, 0, 180, 181, 1, 0, 0, 0, 181, 183, 1, 0, 0, 0, 182, 180, 1, 0, 0, 0, 183, 184, 5, 118, 0, 0, 184, 7, 1, 0, 0, 0, 185, 193, 3, 4, 2, 0, 186, 193, 3, 18, 9, 0, 187, 193, 3, 10, 5, 0, 188, 193, 3, 82, 41, 0, 189, 193, 3, 94, 47, 0, 190, 193, 3, 118, 59, 0, 191, 193, 3, 32, 16, 0, 192, 185, 1, 0, 0, 0, 192, 186, 1, 0, 0, 0, 192, 187, 1, 0, 0, 0, 192, 188, 1, 0, 0, 0, 192, 189, 1, 0, 0, 0, 192, 190, 1, 0, 0, 0, 192, 191, 1, 0, 0, 0, 193, 9, 1, 0, 0, 0, 194, 195, 5, 23, 0, 0, 195, 196, 5, 26, 0, 0, 196, 197, 3, 136, 68, 0, 197, 198, 5, 114, 0, 0, 198, 205, 1, 0, 0, 0, 199, 200, 5, 23, 0, 0, 200, 201, 5, 28, 0, 0, 201, 202, 3, 136, 68, 0, 202, 203, 5, 114, 0, 0, 203, 205, 1, 0, 0, 0, 204, 194, 1, 0, 0, 0, 204, 199, 1, 0, 0, 0, 205, 11, 1, 0, 0, 0, 206, 207, 5, 24, 0, 0, 207, 208, 5, 25, 0, 0, 208, 209, 3, 136, 68, 0, 209, 210, 5, 64, 0, 0, 210, 211, 3, 138, 69, 0, 211, 212, 5, 114, 0, 0, 212, 13, 1, 0, 0, 0, 213, 214, 5, 24, 0, 0, 214, 215, 5, 26, 0, 0, 215, 216, 3, 136, 68, 0, 216, 217, 5, 58, 0, 0, 217, 218, 3, 138, 69, 0, 218, 219, 5, 114, 0, 0, 219, 15, 1, 0, 0, 0, 220, 225, 3, 10, 5, 0, 221, 225, 3, 12, 6, 0, 222, 225, 3, 14, 7, 0, 223, 225, 3, 32, 16, 0, 224, 220, 1, 0, 0, 0, 224, 221, 1, 0, 0, 0, 224, 222, 1, 0, 0, 0, 224, 223, 1, 0, 0, 0, 225, 17, 1, 0, 0, 0, 226, 227, 5, 25, 0, 0, 227, 228, 3, 136, 68, 0, 228, 229, 5, 64, 0, 0, 229, 232, 3, 138, 69, 0, 230, 231, 5, 65, 0, 0, 231, 233, 3, 138, 69, 0, 232, 230, 1, 0, 0, 0, 232, 233, 1, 0, 0, 0, 233, 234, 1, 0, 0, 0, 234, 235, 5, 114, 0, 0, 235, 19, 1, 0, 0, 0, 236, 238, 3, 16, 8, 0, 237, 236, 1, 0, 0, 0, 238, 241, 1, 0, 0, 0, 239, 237, 1, 0, 0, 0, 239, 240, 1, 0, 0, 0, 240, 242, 1, 0, 0, 0, 241, 239, 1, 0, 0, 0, 242, 243, 5, 26, 0, 0, 243, 245, 3, 136, 68, 0, 244, 246, 3, 22, 11, 0, 245, 244, 1, 0, 0, 0, 245, 246, 1, 0, 0, 0, 246, 247, 1, 0, 0, 0, 247, 248, 5, 64, 0, 0, 248, 249, 3, 138, 69, 0, 249, 250, 5, 58, 0, 0, 250, 260, 3, 138, 69, 0, 251, 252, 5, 69, 0, 0, 252, 257, 3, 26, 13, 0, 253, 254, 5, 115, 0, 0, 254, 256, 3, 26, 13, 0, 255, 253, 1, 0, 0, 0, 256, 259, 1, 0, 0, 0, 257, 255, 1, 0, 0, 0, 257, 258, 1, 0, 0, 0, 258, 261, 1, 0, 0, 0, 259, 257, 1, 0, 0, 0, 260, 251, 1, 0, 0, 0, 260, 261, 1, 0, 0, 0, 261, 262, 1, 0, 0, 0, 262, 266, 5, 117, 0, 0, 263, 265, 3, 34, 17, 0, 264, 263, 1, 0, 0, 0, 265, 268, 1, 0, 0, 0, 266, 264, 1, 0, 0, 0, 266, 267, 1, 0, 0, 0, 267, 269, 1, 0, 0, 0, 268, 266, 1, 0, 0, 0, 269, 270, 5, 118, 0, 0, 270, 21, 1, 0, 0, 0, 271, 272, 5, 123, 0, 0, 272, 277, 3, 24, 12, 0, 273, 274, 5, 115, 0, 0, 274, 276, 3, 24, 12, 0, 275, 273, 1, 0, 0, 0, 276, 279, 1, 0, 0, 0, 277, 275, 1, 0, 0, 0, 277, 278, 1, 0, 0, 0, 278, 280, 1, 0, 0, 0, 279, 277, 1, 0, 0, 0, 280, 281, 5, 124, 0, 0, 281, 23, 1, 0, 0, 0, 282, 283, 5, 29, 0, 0, 283, 286, 3, 136, 68, 0, 284, 285, 5, 113, 0, 0, 285, 287, 5, 19, 0, 0, 286, 284, 1, 0, 0, 0, 286, 287, 1, 0, 0, 0, 287, 302, 1, 0, 0, 0, 288, 289, 5, 18, 0, 0, 289, 299, 3, 136, 68, 0, 290, 291, 5, 20, 0, 0, 291, 296, 3, 26, 13, 0, 292, 293, 5, 125, 0, 0, 293, 295, 3, 26, 13, 0, 294, 292, 1, 0, 0, 0, 295, 298, 1, 0, 0, 0, 296, 294, 1, 0, 0, 0, 296, 297, 1, 0, 0, 0, 297, 300, 1, 0, 0, 0, 298, 296, 1, 0, 0, 0, 299, 290, 1, 0, 0, 0, 299, 300, 1, 0, 0, 0, 300, 302, 1, 0, 0, 0, 301, 282, 1, 0, 0, 0, 301, 288, 1, 0, 0, 0, 302, 25, 1, 0, 0, 0, 303, 305, 3, 136, 68, 0, 304, 306, 3, 28, 14, 0, 305, 304, 1, 0, 0, 0, 305, 306, 1, 0, 0, 0, 306, 27, 1, 0, 0, 0, 307, 308, 5, 123, 0, 0, 308, 313, 3, 30, 15, 0, 309, 310, 5, 115, 0, 0, 310, 312, 3, 30, 15, 0, 311, 309, 1, 0, 0, 0, 312, 315, 1, 0, 0, 0, 313, 311, 1, 0, 0, 0, 313, 314, 1, 0, 0, 0, 314, 316, 1, 0, 0, 0, 315, 313, 1, 0, 0, 0, 316, 317, 5, 124, 0, 0, 317, 29, 1, 0, 0, 0, 318, 319, 5, 25, 0, 0, 319, 326, 3, 136, 68, 0, 320, 321, 5, 26, 0, 0, 321, 326, 3, 26, 13, 0, 322, 323, 5, 18, 0, 0, 323, 326, 3, 136, 68, 0, 324, 326, 3, 120, 60, 0, 325, 318, 1, 0, 0, 0, 325, 320, 1, 0, 0, 0, 325, 322, 1, 0, 0, 0, 325, 324, 1, 0, 0, 0, 326, 31, 1, 0, 0, 0, 327, 328, 5, 17, 0, 0, 328, 330, 3, 136, 68, 0, 329, 331, 3, 22, 11, 0, 330, 329, 1, 0, 0, 0, 330, 331, 1, 0, 0, 0, 331, 332, 1, 0, 0, 0, 332, 333, 5, 126, 0, 0, 333, 334, 3, 120, 60, 0, 334, 335, 5, 114, 0, 0, 335, 33, 1, 0, 0, 0, 336, 340, 3, 38, 19, 0, 337, 340, 3, 42, 21, 0, 338, 340, 3, 36, 18, 0, 339, 336, 1, 0, 0, 0, 339, 337, 1, 0, 0, 0, 339, 338, 1, 0, 0, 0, 340, 35, 1, 0, 0, 0, 341, 343, 5, 39, 0, 0, 342, 341, 1, 0, 0, 0, 342, 343, 1, 0, 0, 0, 343, 344, 1, 0, 0, 0, 344, 345, 5, 31, 0, 0, 345, 346, 3, 136, 68, 0, 346, 347, 5, 64, 0, 0, 347, 348, 3, 138, 69, 0, 348, 349, 5, 113, 0, 0, 349, 350, 3, 120, 60, 0, 350, 351, 5, 112, 0, 0, 351, 352, 3, 120, 60, 0, 352, 353, 5, 117, 0, 0, 353, 354, 5, 81, 0, 0, 354, 355, 5, 64, 0, 0, 355, 356, 3, 138, 69, 0, 356, 357, 5, 114, 0, 0, 357, 358, 5, 118, 0, 0, 358, 37, 1, 0, 0, 0, 359, 361, 3, 44, 22, 0, 360, 359, 1, 0, 0, 0, 360, 361, 1, 0, 0, 0, 361, 362, 1, 0, 0, 0, 362, 363, 5, 29, 0, 0, 363, 364, 3, 136, 68, 0, 364, 365, 5, 64, 0, 0, 365, 366, 3, 138, 69, 0, 366, 367, 5, 113, 0, 0, 367, 368, 3, 120, 60, 0, 368, 372, 5, 117, 0, 0, 369, 371, 3, 40, 20, 0, 370, 369, 1, 0, 0, 0, 371, 374, 1, 0, 0, 0, 372, 370, 1, 0, 0, 0, 372, 373, 1, 0, 0, 0, 373, 375, 1, 0, 0, 0, 374, 372, 1, 0, 0, 0, 375, 376, 5, 118, 0, 0, 376, 39, 1, 0, 0, 0, 377, 378, 5, 71, 0, 0, 378, 379, 5, 64, 0, 0, 379, 380, 3, 138, 69, 0, 380, 381, 5, 114, 0, 0, 381, 397, 1, 0, 0, 0, 382, 383, 5, 72, 0, 0, 383, 384, 5, 64, 0, 0, 384, 385, 3, 138, 69, 0, 385, 386, 5, 114, 0, 0, 386, 397, 1, 0, 0, 0, 387, 388, 5, 73, 0, 0, 388, 389, 5, 74, 0, 0, 389, 390, 5, 64, 0, 0, 390, 391, 3, 138, 69, 0, 391, 392, 5, 75, 0, 0, 392, 393, 5, 64, 0, 0, 393, 394, 3, 138, 69, 0, 394, 395, 5, 114, 0, 0, 395, 397, 1, 0, 0, 0, 396, 377, 1, 0, 0, 0, 396, 382, 1, 0, 0, 0, 396, 387, 1, 0, 0, 0, 397, 41, 1, 0, 0, 0, 398, 400, 3, 44, 22, 0, 399, 398, 1, 0, 0, 0, 399, 400, 1, 0, 0, 0, 400, 401, 1, 0, 0, 0, 401, 402, 5, 30, 0, 0, 402, 403, 3, 136, 68, 0, 403, 404, 5, 64, 0, 0, 404, 405, 3, 138, 69, 0, 405, 406, 5, 113, 0, 0, 406, 407, 3, 126, 63, 0, 407, 409, 3, 48, 24, 0, 408, 410, 5, 92, 0, 0, 409, 408, 1, 0, 0, 0, 409, 410, 1, 0, 0, 0, 410, 413, 1, 0, 0, 0, 411, 412, 5, 62, 0, 0, 412, 414, 3, 138, 69, 0, 413, 411, 1, 0, 0, 0, 413, 414, 1, 0, 0, 0, 414, 415, 1, 0, 0, 0, 415, 419, 5, 117, 0, 0, 416, 418, 3, 46, 23, 0, 417, 416, 1, 0, 0, 0, 418, 421, 1, 0, 0, 0, 419, 417, 1, 0, 0, 0, 419, 420, 1, 0, 0, 0, 420, 422, 1, 0, 0, 0, 421, 419, 1, 0, 0, 0, 422, 423, 5, 118, 0, 0, 423, 43, 1, 0, 0, 0, 424, 426, 5, 14, 0, 0, 425, 427, 5, 15, 0, 0, 426, 425, 1, 0, 0, 0, 426, 427, 1, 0, 0, 0, 427, 45, 1, 0, 0, 0, 428, 429, 5, 78, 0, 0, 429, 430, 5, 64, 0, 0, 430, 431, 3, 138, 69, 0, 431, 432, 5, 114, 0, 0, 432, 453, 1, 0, 0, 0, 433, 434, 5, 79, 0, 0, 434, 435, 5, 64, 0, 0, 435, 436, 3, 138, 69, 0, 436, 437, 5, 114, 0, 0, 437, 453, 1, 0, 0, 0, 438, 439, 5, 80, 0, 0, 439, 440, 5, 64, 0, 0, 440, 441, 3, 138, 69, 0, 441, 442, 5, 114, 0, 0, 442, 453, 1, 0, 0, 0, 443, 444, 5, 73, 0, 0, 444, 445, 5, 74, 0, 0, 445, 446, 5, 64, 0, 0, 446, 447, 3, 138, 69, 0, 447, 448, 5, 75, 0, 0, 448, 449, 5, 64, 0, 0, 449, 450, 3, 138, 69, 0, 450, 451, 5, 114, 0, 0, 451, 453, 1, 0, 0, 0, 452, 428, 1, 0, 0, 0, 452, 433, 1, 0, 0, 0, 452, 438, 1, 0, 0, 0, 452, 443, 1, 0, 0, 0, 453, 47, 1, 0, 0, 0, 454, 455, 5, 25, 0, 0, 455, 464, 3, 136, 68, 0, 456, 457, 5, 26, 0, 0, 457, 459, 3, 136, 68, 0, 458, 460, 3, 28, 14, 0, 459, 458, 1, 0, 0, 0, 459, 460, 1, 0, 0, 0, 460, 464, 1, 0, 0, 0, 461, 462, 5, 18, 0, 0, 462, 464, 3, 136, 68, 0, 463, 454, 1, 0, 0, 0, 463, 456, 1, 0, 0, 0, 463, 461, 1, 0, 0, 0, 464, 49, 1, 0, 0, 0, 465, 467, 3, 16, 8, 0, 466, 465, 1, 0, 0, 0, 467, 470, 1, 0, 0, 0, 468, 466, 1, 0, 0, 0, 468, 469, 1, 0, 0, 0, 469, 471, 1, 0, 0, 0, 470, 468, 1, 0, 0, 0, 471, 472, 5, 28, 0, 0, 472, 473, 3, 136, 68, 0, 473, 474, 5, 64, 0, 0, 474, 475, 3, 138, 69, 0, 475, 476, 5, 58, 0, 0, 476, 479, 3, 138, 69, 0, 477, 478, 5, 59, 0, 0, 478, 480, 5, 127, 0, 0, 479, 477, 1, 0, 0, 0, 479, 480, 1, 0, 0, 0, 480, 481, 1, 0, 0, 0, 481, 485, 5, 117, 0, 0, 482, 484, 3, 52, 26, 0, 483, 482, 1, 0, 0, 0, 484, 487, 1, 0, 0, 0, 485, 483, 1, 0, 0, 0, 485, 486, 1, 0, 0, 0, 486, 488, 1, 0, 0, 0, 487, 485, 1, 0, 0, 0, 488, 489, 5, 118, 0, 0, 489, 51, 1, 0, 0, 0, 490, 496, 3, 60, 30, 0, 491, 496, 3, 62, 31, 0, 492, 496, 3, 64, 32, 0, 493, 496, 3, 54, 27, 0, 494, 496, 3, 56, 28, 0, 495, 490, 1, 0, 0, 0, 495, 491, 1, 0, 0, 0, 495, 492, 1, 0, 0, 0, 495, 493, 1, 0, 0, 0, 495, 494, 1, 0, 0, 0, 496, 53, 1, 0, 0, 0, 497, 498, 5, 5, 0, 0, 498, 500, 3, 136, 68, 0, 499, 501, 3, 22, 11, 0, 500, 499, 1, 0, 0, 0, 500, 501, 1, 0, 0, 0, 501, 502, 1, 0, 0, 0, 502, 503, 5, 64, 0, 0, 503, 504, 3, 138, 69, 0, 504, 505, 5, 7, 0, 0, 505, 506, 3, 26, 13, 0, 506, 507, 5, 8, 0, 0, 507, 508, 3, 138, 69, 0, 508, 509, 5, 31, 0, 0, 509, 510, 3, 138, 69, 0, 510, 514, 5, 117, 0, 0, 511, 513, 3, 58, 29, 0, 512, 511, 1, 0, 0, 0, 513, 516, 1, 0, 0, 0, 514, 512, 1, 0, 0, 0, 514, 515, 1, 0, 0, 0, 515, 517, 1, 0, 0, 0, 516, 514, 1, 0, 0, 0, 517, 518, 5, 118, 0, 0, 518, 55, 1, 0, 0, 0, 519, 520, 5, 5, 0, 0, 520, 521, 3, 136, 68, 0, 521, 522, 5, 64, 0, 0, 522, 523, 3, 138, 69, 0, 523, 524, 5, 6, 0, 0, 524, 525, 3, 136, 68, 0, 525, 526, 3, 28, 14, 0, 526, 527, 5, 114, 0, 0, 527, 57, 1, 0, 0, 0, 528, 529, 5, 9, 0, 0, 529, 530, 3, 138, 69, 0, 530, 531, 5, 114, 0, 0, 531, 562, 1, 0, 0, 0, 532, 534, 5, 10, 0, 0, 533, 535, 5, 18, 0, 0, 534, 533, 1, 0, 0, 0, 534, 535, 1, 0, 0, 0, 535, 536, 1, 0, 0, 0, 536, 537, 3, 136, 68, 0, 537, 538, 5, 37, 0, 0, 538, 539, 3, 26, 13, 0, 539, 540, 5, 114, 0, 0, 540, 562, 1, 0, 0, 0, 541, 542, 5, 11, 0, 0, 542, 543, 3, 136, 68, 0, 543, 544, 5, 127, 0, 0, 544, 545, 5, 114, 0, 0, 545, 562, 1, 0, 0, 0, 546, 547, 5, 12, 0, 0, 547, 548, 3, 26, 13, 0, 548, 549, 5, 116, 0, 0, 549, 550, 3, 136, 68, 0, 550, 551, 3, 136, 68, 0, 551, 552, 3, 138, 69, 0, 552, 553, 5, 114, 0, 0, 553, 562, 1, 0, 0, 0, 554, 555, 5, 73, 0, 0, 555, 562, 5, 114, 0, 0, 556, 557, 5, 13, 0, 0, 557, 558, 5, 127, 0, 0, 558, 559, 3, 138, 69, 0, 559, 560, 5, 114, 0, 0, 560, 562, 1, 0, 0, 0, 561, 528, 1, 0, 0, 0, 561, 532, 1, 0, 0, 0, 561, 541, 1, 0, 0, 0, 561, 546, 1, 0, 0, 0, 561, 554, 1, 0, 0, 0, 561, 556, 1, 0, 0, 0, 562, 59, 1, 0, 0, 0, 563, 564, 5, 31, 0, 0, 564, 566, 3, 136, 68, 0, 565, 567, 3, 22, 11, 0, 566, 565, 1, 0, 0, 0, 566, 567, 1, 0, 0, 0, 567, 568, 1, 0, 0, 0, 568, 569, 5, 64, 0, 0, 569, 570, 3, 138, 69, 0, 570, 571, 5, 113, 0, 0, 571, 572, 3, 120, 60, 0, 572, 573, 5, 112, 0, 0, 573, 574, 3, 120, 60, 0, 574, 575, 5, 66, 0, 0, 575, 577, 3, 68, 34, 0, 576, 578, 3, 66, 33, 0, 577, 576, 1, 0, 0, 0, 577, 578, 1, 0, 0, 0, 578, 579, 1, 0, 0, 0, 579, 580, 5, 68, 0, 0, 580, 582, 3, 70, 35, 0, 581, 583, 3, 74, 37, 0, 582, 581, 1, 0, 0, 0, 582, 583, 1, 0, 0, 0, 583, 584, 1, 0, 0, 0, 584, 585, 5, 114, 0, 0, 585, 61, 1, 0, 0, 0, 586, 587, 5, 32, 0, 0, 587, 589, 3, 136, 68, 0, 588, 590, 3, 22, 11, 0, 589, 588, 1, 0, 0, 0, 589, 590, 1, 0, 0, 0, 590, 591, 1, 0, 0, 0, 591, 592, 5, 64, 0, 0, 592, 593, 3, 138, 69, 0, 593, 594, 5, 113, 0, 0, 594, 595, 3, 120, 60, 0, 595, 596, 5, 112, 0, 0, 596, 598, 3, 120, 60, 0, 597, 599, 3, 74, 37, 0, 598, 597, 1, 0, 0, 0, 598, 599, 1, 0, 0, 0, 599, 600, 1, 0, 0, 0, 600, 601, 5, 114, 0, 0, 601, 63, 1, 0, 0, 0, 602, 603, 5, 33, 0, 0, 603, 604, 3, 136, 68, 0, 604, 605, 5, 64, 0, 0, 605, 606, 3, 138, 69, 0, 606, 607, 5, 34, 0, 0, 607, 608, 3, 136, 68, 0, 608, 609, 5, 113, 0, 0, 609, 611, 3, 120, 60, 0, 610, 612, 3, 74, 37, 0, 611, 610, 1, 0, 0, 0, 611, 612, 1, 0, 0, 0, 612, 613, 1, 0, 0, 0, 613, 614, 5, 114, 0, 0, 614, 65, 1, 0, 0, 0, 615, 616, 5, 67, 0, 0, 616, 617, 3, 120, 60, 0, 617, 67, 1, 0, 0, 0, 618, 619, 7, 0, 0, 0, 619, 69, 1, 0, 0, 0, 620, 639, 5, 70, 0, 0, 621, 622, 5, 25, 0, 0, 622, 639, 3, 136, 68, 0, 623, 624, 5, 18, 0, 0, 624, 639, 3, 136, 68, 0, 625, 626, 5, 27, 0, 0, 626, 635, 5, 119, 0, 0, 627, 632, 3, 26, 13, 0, 628, 629, 5, 115, 0, 0, 629, 631, 3, 26, 13, 0, 630, 628, 1, 0, 0, 0, 631, 634, 1, 0, 0, 0, 632, 630, 1, 0, 0, 0, 632, 633, 1, 0, 0, 0, 633, 636, 1, 0, 0, 0, 634, 632, 1, 0, 0, 0, 635, 627, 1, 0, 0, 0, 635, 636, 1, 0, 0, 0, 636, 637, 1, 0, 0, 0, 637, 639, 5, 120, 0, 0, 638, 620, 1, 0, 0, 0, 638, 621, 1, 0, 0, 0, 638, 623, 1, 0, 0, 0, 638, 625, 1, 0, 0, 0, 639, 71, 1, 0, 0, 0, 640, 645, 3, 136, 68, 0, 641, 642, 5, 115, 0, 0, 642, 644, 3, 136, 68, 0, 643, 641, 1, 0, 0, 0, 644, 647, 1, 0, 0, 0, 645, 643, 1, 0, 0, 0, 645, 646, 1, 0, 0, 0, 646, 73, 1, 0, 0, 0, 647, 645, 1, 0, 0, 0, 648, 649, 5, 69, 0, 0, 649, 653, 5, 117, 0, 0, 650, 652, 3, 76, 38, 0, 651, 650, 1, 0, 0, 0, 652, 655, 1, 0, 0, 0, 653, 651, 1, 0, 0, 0, 653, 654, 1, 0, 0, 0, 654, 656, 1, 0, 0, 0, 655, 653, 1, 0, 0, 0, 656, 657, 5, 118, 0, 0, 657, 75, 1, 0, 0, 0, 658, 659, 5, 43, 0, 0, 659, 660, 3, 136, 68, 0, 660, 661, 5, 64, 0, 0, 661, 662, 3, 138, 69, 0, 662, 663, 5, 113, 0, 0, 663, 664, 3, 120, 60, 0, 664, 665, 3, 78, 39, 0, 665, 666, 5, 114, 0, 0, 666, 711, 1, 0, 0, 0, 667, 668, 5, 44, 0, 0, 668, 669, 3, 136, 68, 0, 669, 670, 5, 64, 0, 0, 670, 671, 3, 138, 69, 0, 671, 672, 5, 113, 0, 0, 672, 673, 3, 126, 63, 0, 673, 674, 3, 48, 24, 0, 674, 675, 3, 78, 39, 0, 675, 676, 5, 114, 0, 0, 676, 711, 1, 0, 0, 0, 677, 678, 5, 26, 0, 0, 678, 679, 3, 136, 68, 0, 679, 680, 5, 64, 0, 0, 680, 681, 3, 138, 69, 0, 681, 682, 5, 113, 0, 0, 682, 684, 3, 136, 68, 0, 683, 685, 3, 28, 14, 0, 684, 683, 1, 0, 0, 0, 684, 685, 1, 0, 0, 0, 685, 686, 1, 0, 0, 0, 686, 687, 5, 114, 0, 0, 687, 711, 1, 0, 0, 0, 688, 689, 5, 33, 0, 0, 689, 690, 3, 136, 68, 0, 690, 691, 5, 64, 0, 0, 691, 692, 3, 138, 69, 0, 692, 693, 5, 113, 0, 0, 693, 696, 3, 136, 68, 0, 694, 695, 5, 35, 0, 0, 695, 697, 3, 120, 60, 0, 696, 694, 1, 0, 0, 0, 696, 697, 1, 0, 0, 0, 697, 698, 1, 0, 0, 0, 698, 699, 5, 114, 0, 0, 699, 711, 1, 0, 0, 0, 700, 701, 5, 5, 0, 0, 701, 702, 3, 136, 68, 0, 702, 703, 5, 64, 0, 0, 703, 704, 3, 138, 69, 0, 704, 705, 5, 113, 0, 0, 705, 706, 3, 136, 68, 0, 706, 707, 5, 116, 0, 0, 707, 708, 3, 136, 68, 0, 708, 709, 5, 114, 0, 0, 709, 711, 1, 0, 0, 0, 710, 658, 1, 0, 0, 0, 710, 667, 1, 0, 0, 0, 710, 677, 1, 0, 0, 0, 710, 688, 1, 0, 0, 0, 710, 700, 1, 0, 0, 0, 711, 77, 1, 0, 0, 0, 712, 713, 5, 119, 0, 0, 713, 718, 3, 80, 40, 0, 714, 715, 5, 115, 0, 0, 715, 717, 3, 80, 40, 0, 716, 714, 1, 0, 0, 0, 717, 720, 1, 0, 0, 0, 718, 716, 1, 0, 0, 0, 718, 719, 1, 0, 0, 0, 719, 721, 1, 0, 0, 0, 720, 718, 1, 0, 0, 0, 721, 722, 5, 120, 0, 0, 722, 79, 1, 0, 0, 0, 723, 724, 7, 1, 0, 0, 724, 81, 1, 0, 0, 0, 725, 726, 5, 42, 0, 0, 726, 727, 3, 84, 42, 0, 727, 83, 1, 0, 0, 0, 728, 731, 3, 86, 43, 0, 729, 731, 3, 90, 45, 0, 730, 728, 1, 0, 0, 0, 730, 729, 1, 0, 0, 0, 731, 85, 1, 0, 0, 0, 732, 733, 5, 43, 0, 0, 733, 734, 3, 136, 68, 0, 734, 735, 5, 64, 0, 0, 735, 736, 3, 138, 69, 0, 736, 737, 5, 52, 0, 0, 737, 738, 3, 136, 68, 0, 738, 739, 5, 113, 0, 0, 739, 740, 3, 120, 60, 0, 740, 741, 5, 53, 0, 0, 741, 744, 3, 88, 44, 0, 742, 743, 5, 54, 0, 0, 743, 745, 3, 128, 64, 0, 744, 742, 1, 0, 0, 0, 744, 745, 1, 0, 0, 0, 745, 746, 1, 0, 0, 0, 746, 747, 5, 114, 0, 0, 747, 87, 1, 0, 0, 0, 748, 755, 5, 86, 0, 0, 749, 750, 5, 87, 0, 0, 750, 751, 5, 121, 0, 0, 751, 752, 3, 120, 60, 0, 752, 753, 5, 122, 0, 0, 753, 755, 1, 0, 0, 0, 754, 748, 1, 0, 0, 0, 754, 749, 1, 0, 0, 0, 755, 89, 1, 0, 0, 0, 756, 757, 5, 44, 0, 0, 757, 758, 3, 136, 68, 0, 758, 759, 5, 64, 0, 0, 759, 760, 3, 138, 69, 0, 760, 761, 5, 117, 0, 0, 761, 762, 3, 92, 46, 0, 762, 763, 3, 92, 46, 0, 763, 764, 5, 118, 0, 0, 764, 91, 1, 0, 0, 0, 765, 766, 3, 48, 24, 0, 766, 767, 5, 45, 0, 0, 767, 768, 3, 136, 68, 0, 768, 769, 5, 64, 0, 0, 769, 770, 3, 138, 69, 0, 770, 772, 3, 126, 63, 0, 771, 773, 5, 92, 0, 0, 772, 771, 1, 0, 0, 0, 772, 773, 1, 0, 0, 0, 773, 776, 1, 0, 0, 0, 774, 775, 5, 60, 0, 0, 775, 777, 3, 138, 69, 0, 776, 774, 1, 0, 0, 0, 776, 777, 1, 0, 0, 0, 777, 779, 1, 0, 0, 0, 778, 780, 5, 61, 0, 0, 779, 778, 1, 0, 0, 0, 779, 780, 1, 0, 0, 0, 780, 783, 1, 0, 0, 0, 781, 782, 5, 62, 0, 0, 782, 784, 3, 138, 69, 0, 783, 781, 1, 0, 0, 0, 783, 784, 1, 0, 0, 0, 784, 786, 1, 0, 0, 0, 785, 787, 5, 63, 0, 0, 786, 785, 1, 0, 0, 0, 786, 787, 1, 0, 0, 0, 787, 788, 1, 0, 0, 0, 788, 789, 5, 114, 0, 0, 789, 93, 1, 0, 0, 0, 790, 791, 5, 36, 0, 0, 791, 792, 3, 136, 68, 0, 792, 793, 5, 37, 0, 0, 793, 795, 3, 136, 68, 0, 794, 796, 3, 28, 14, 0, 795, 794, 1, 0, 0, 0, 795, 796, 1, 0, 0, 0, 796, 799, 1, 0, 0, 0, 797, 798, 5, 64, 0, 0, 798, 800, 3, 138, 69, 0, 799, 797, 1, 0, 0, 0, 799, 800, 1, 0, 0, 0, 800, 803, 1, 0, 0, 0, 801, 802, 5, 59, 0, 0, 802, 804, 5, 127, 0, 0, 803, 801, 1, 0, 0, 0, 803, 804, 1, 0, 0, 0, 804, 805, 1, 0, 0, 0, 805, 809, 5, 117, 0, 0, 806, 808, 3, 96, 48, 0, 807, 806, 1, 0, 0, 0, 808, 811, 1, 0, 0, 0, 809, 807, 1, 0, 0, 0, 809, 810, 1, 0, 0, 0, 810, 812, 1, 0, 0, 0, 811, 809, 1, 0, 0, 0, 812, 813, 5, 118, 0, 0, 813, 95, 1, 0, 0, 0, 814, 815, 5, 41, 0, 0, 815, 820, 3, 84, 42, 0, 816, 820, 3, 102, 51, 0, 817, 820, 3, 98, 49, 0, 818, 820, 3, 100, 50, 0, 819, 814, 1, 0, 0, 0, 819, 816, 1, 0, 0, 0, 819, 817, 1, 0, 0, 0, 819, 818, 1, 0, 0, 0, 820, 97, 1, 0, 0, 0, 821, 822, 5, 38, 0, 0, 822, 823, 3, 136, 68, 0, 823, 824, 5, 40, 0, 0, 824, 825, 5, 43, 0, 0, 825, 826, 3, 136, 68, 0, 826, 827, 5, 114, 0, 0, 827, 99, 1, 0, 0, 0, 828, 829, 5, 49, 0, 0, 829, 830, 3, 136, 68, 0, 830, 831, 5, 50, 0, 0, 831, 832, 5, 51, 0, 0, 832, 833, 5, 47, 0, 0, 833, 834, 5, 33, 0, 0, 834, 835, 3, 136, 68, 0, 835, 836, 5, 48, 0, 0, 836, 837, 5, 44, 0, 0, 837, 838, 3, 136, 68, 0, 838, 839, 5, 116, 0, 0, 839, 840, 3, 136, 68, 0, 840, 841, 5, 114, 0, 0, 841, 101, 1, 0, 0, 0, 842, 843, 5, 38, 0, 0, 843, 844, 3, 104, 52, 0, 844, 845, 5, 40, 0, 0, 845, 848, 3, 108, 54, 0, 846, 847, 5, 16, 0, 0, 847, 849, 3, 138, 69, 0, 848, 846, 1, 0, 0, 0, 848, 849, 1, 0, 0, 0, 849, 850, 1, 0, 0, 0, 850, 851, 5, 114, 0, 0, 851, 103, 1, 0, 0, 0, 852, 853, 3, 136, 68, 0, 853, 854, 5, 116, 0, 0, 854, 855, 3, 106, 53, 0, 855, 105, 1, 0, 0, 0, 856, 868, 3, 136, 68, 0, 857, 868, 5, 81, 0, 0, 858, 868, 5, 71, 0, 0, 859, 868, 5, 72, 0, 0, 860, 868, 5, 78, 0, 0, 861, 868, 5, 79, 0, 0, 862, 868, 5, 80, 0, 0, 863, 868, 5, 82, 0, 0, 864, 868, 5, 83, 0, 0, 865, 868, 5, 84, 0, 0, 866, 868, 5, 85, 0, 0, 867, 856, 1, 0, 0, 0, 867, 857, 1, 0, 0, 0, 867, 858, 1, 0, 0, 0, 867, 859, 1, 0, 0, 0, 867, 860, 1, 0, 0, 0, 867, 861, 1, 0, 0, 0, 867, 862, 1, 0, 0, 0, 867, 863, 1, 0, 0, 0, 867, 864, 1, 0, 0, 0, 867, 865, 1, 0, 0, 0, 867, 866, 1, 0, 0, 0, 868, 107, 1, 0, 0, 0, 869, 870, 5, 43, 0, 0, 870, 871, 3, 136, 68, 0, 871, 872, 5, 116, 0, 0, 872, 873, 3, 110, 55, 0, 873, 892, 1, 0, 0, 0, 874, 875, 5, 44, 0, 0, 875, 876, 3, 136, 68, 0, 876, 877, 5, 116, 0, 0, 877, 878, 3, 136, 68, 0, 878, 879, 5, 116, 0, 0, 879, 880, 3, 112, 56, 0, 880, 892, 1, 0, 0, 0, 881, 882, 5, 28, 0, 0, 882, 883, 3, 136, 68, 0, 883, 884, 5, 116, 0, 0, 884, 886, 3, 136, 68, 0, 885, 887, 3, 28, 14, 0, 886, 885, 1, 0, 0, 0, 886, 887, 1, 0, 0, 0, 887, 889, 1, 0, 0, 0, 888, 890, 3, 114, 57, 0, 889, 888, 1, 0, 0, 0, 889, 890, 1, 0, 0, 0, 890, 892, 1, 0, 0, 0, 891, 869, 1, 0, 0, 0, 891, 874, 1, 0, 0, 0, 891, 881, 1, 0, 0, 0, 892, 109, 1, 0, 0, 0, 893, 894, 7, 2, 0, 0, 894, 111, 1, 0, 0, 0, 895, 896, 7, 3, 0, 0, 896, 113, 1, 0, 0, 0, 897, 898, 5, 46, 0, 0, 898, 902, 5, 117, 0, 0, 899, 901, 3, 116, 58, 0, 900, 899, 1, 0, 0, 0, 901, 904, 1, 0, 0, 0, 902, 900, 1, 0, 0, 0, 902, 903, 1, 0, 0, 0, 903, 905, 1, 0, 0, 0, 904, 902, 1, 0, 0, 0, 905, 906, 5, 118, 0, 0, 906, 115, 1, 0, 0, 0, 907, 908, 3, 136, 68, 0, 908, 909, 5, 40, 0, 0, 909, 910, 5, 43, 0, 0, 910, 917, 3, 136, 68, 0, 911, 912, 5, 48, 0, 0, 912, 913, 5, 44, 0, 0, 913, 914, 3, 136, 68, 0, 914, 915, 5, 116, 0, 0, 915, 916, 3, 136, 68, 0, 916, 918, 1, 0, 0, 0, 917, 911, 1, 0, 0, 0, 917, 918, 1, 0, 0, 0, 918, 919, 1, 0, 0, 0, 919, 920, 5, 114, 0, 0, 920, 977, 1, 0, 0, 0, 921, 922, 3, 136, 68, 0, 922, 923, 5, 40, 0, 0, 923, 924, 5, 44, 0, 0, 924, 925, 3, 136, 68, 0, 925, 926, 5, 116, 0, 0, 926, 933, 3, 136, 68, 0, 927, 928, 5, 48, 0, 0, 928, 929, 5, 44, 0, 0, 929, 930, 3, 136, 68, 0, 930, 931, 5, 116, 0, 0, 931, 932, 3, 136, 68, 0, 932, 934, 1, 0, 0, 0, 933, 927, 1, 0, 0, 0, 933, 934, 1, 0, 0, 0, 934, 935, 1, 0, 0, 0, 935, 936, 5, 114, 0, 0, 936, 977, 1, 0, 0, 0, 937, 938, 3, 136, 68, 0, 938, 939, 5, 40, 0, 0, 939, 940, 5, 26, 0, 0, 940, 942, 3, 136, 68, 0, 941, 943, 3, 28, 14, 0, 942, 941, 1, 0, 0, 0, 942, 943, 1, 0, 0, 0, 943, 950, 1, 0, 0, 0, 944, 945, 5, 48, 0, 0, 945, 946, 5, 44, 0, 0, 946, 947, 3, 136, 68, 0, 947, 948, 5, 116, 0, 0, 948, 949, 3, 136, 68, 0, 949, 951, 1, 0, 0, 0, 950, 944, 1, 0, 0, 0, 950, 951, 1, 0, 0, 0, 951, 952, 1, 0, 0, 0, 952, 953, 5, 114, 0, 0, 953, 977, 1, 0, 0, 0, 954, 955, 3, 136, 68, 0, 955, 956, 5, 40, 0, 0, 956, 957, 5, 33, 0, 0, 957, 958, 3, 136, 68, 0, 958, 959, 5, 114, 0, 0, 959, 977, 1, 0, 0, 0, 960, 961, 3, 136, 68, 0, 961, 962, 5, 40, 0, 0, 962, 963, 5, 5, 0, 0, 963, 964, 3, 136, 68, 0, 964, 965, 5, 116, 0, 0, 965, 972, 3, 136, 68, 0, 966, 967, 5, 48, 0, 0, 967, 968, 5, 44, 0, 0, 968, 969, 3, 136, 68, 0, 969, 970, 5, 116, 0, 0, 970, 971, 3, 136, 68, 0, 971, 973, 1, 0, 0, 0, 972, 966, 1, 0, 0, 0, 972, 973, 1, 0, 0, 0, 973, 974, 1, 0, 0, 0, 974, 975, 5, 114, 0, 0, 975, 977, 1, 0, 0, 0, 976, 907, 1, 0, 0, 0, 976, 921, 1, 0, 0, 0, 976, 937, 1, 0, 0, 0, 976, 954, 1, 0, 0, 0, 976, 960, 1, 0, 0, 0, 977, 117, 1, 0, 0, 0, 978, 979, 5, 33, 0, 0, 979, 980, 3, 136, 68, 0, 980, 981, 5, 40, 0, 0, 981, 982, 3, 136, 68, 0, 982, 983, 5, 116, 0, 0, 983, 985, 3, 136, 68, 0, 984, 986, 3, 114, 57, 0, 985, 984, 1, 0, 0, 0, 985, 986, 1, 0, 0, 0, 986, 987, 1, 0, 0, 0, 987, 988, 5, 114, 0, 0, 988, 119, 1, 0, 0, 0, 989, 1036, 3, 124, 62, 0, 990, 1036, 5, 93, 0, 0, 991, 1036, 5, 94, 0, 0, 992, 993, 5, 95, 0, 0, 993, 1036, 3, 138, 69, 0, 994, 995, 5, 96, 0, 0, 995, 996, 5, 123, 0, 0, 996, 997, 3, 136, 68, 0, 997, 998, 5, 124, 0, 0, 998, 1036, 1, 0, 0, 0, 999, 1000, 5, 97, 0, 0, 1000, 1001, 5, 123, 0, 0, 1001, 1003, 3, 136, 68, 0, 1002, 1004, 3, 28, 14, 0, 1003, 1002, 1, 0, 0, 0, 1003, 1004, 1, 0, 0, 0, 1004, 1005, 1, 0, 0, 0, 1005, 1006, 5, 124, 0, 0, 1006, 1036, 1, 0, 0, 0, 1007, 1008, 5, 21, 0, 0, 1008, 1009, 5, 123, 0, 0, 1009, 1010, 3, 136, 68, 0, 1010, 1011, 5, 124, 0, 0, 1011, 1036, 1, 0, 0, 0, 1012, 1013, 5, 98, 0, 0, 1013, 1014, 5, 123, 0, 0, 1014, 1015, 3, 120, 60, 0, 1015, 1016, 5, 124, 0, 0, 1016, 1036, 1, 0, 0, 0, 1017, 1018, 5, 99, 0, 0, 1018, 1019, 5, 123, 0, 0, 1019, 1020, 3, 120, 60, 0, 1020, 1021, 5, 124, 0, 0, 1021, 1036, 1, 0, 0, 0, 1022, 1023, 5, 100, 0, 0, 1023, 1027, 5, 117, 0, 0, 1024, 1026, 3, 122, 61, 0, 1025, 1024, 1, 0, 0, 0, 1026, 1029, 1, 0, 0, 0, 1027, 1025, 1, 0, 0, 0, 1027, 1028, 1, 0, 0, 0, 1028, 1030, 1, 0, 0, 0, 1029, 1027, 1, 0, 0, 0, 1030, 1036, 5, 118, 0, 0, 1031, 1033, 3, 136, 68, 0, 1032, 1034, 3, 28, 14, 0, 1033, 1032, 1, 0, 0, 0, 1033, 1034, 1, 0, 0, 0, 1034, 1036, 1, 0, 0, 0, 1035, 989, 1, 0, 0, 0, 1035, 990, 1, 0, 0, 0, 1035, 991, 1, 0, 0, 0, 1035, 992, 1, 0, 0, 0, 1035, 994, 1, 0, 0, 0, 1035, 999, 1, 0, 0, 0, 1035, 1007, 1, 0, 0, 0, 1035, 1012, 1, 0, 0, 0, 1035, 1017, 1, 0, 0, 0, 1035, 1022, 1, 0, 0, 0, 1035, 1031, 1, 0, 0, 0, 1036, 121, 1, 0, 0, 0, 1037, 1038, 3, 136, 68, 0, 1038, 1039, 5, 113, 0, 0, 1039, 1040, 3, 120, 60, 0, 1040, 1041, 5, 114, 0, 0, 1041, 123, 1, 0, 0, 0, 1042, 1043, 7, 4, 0, 0, 1043, 125, 1, 0, 0, 0, 1044, 1045, 7, 5, 0, 0, 1045, 127, 1, 0, 0, 0, 1046, 1055, 3, 138, 69, 0, 1047, 1055, 5, 127, 0, 0, 1048, 1055, 5, 128, 0, 0, 1049, 1055, 5, 109, 0, 0, 1050, 1055, 5, 110, 0, 0, 1051, 1055, 5, 111, 0, 0, 1052, 1055, 3, 130, 65, 0, 1053, 1055, 3, 134, 67, 0, 1054, 1046, 1, 0, 0, 0, 1054, 1047, 1, 0, 0, 0, 1054, 1048, 1, 0, 0, 0, 1054, 1049, 1, 0, 0, 0, 1054, 1050, 1, 0, 0, 0, 1054, 1051, 1, 0, 0, 0, 1054, 1052, 1, 0, 0, 0, 1054, 1053, 1, 0, 0, 0, 1055, 129, 1, 0, 0, 0, 1056, 1065, 5, 117, 0, 0, 1057, 1062, 3, 132, 66, 0, 1058, 1059, 5, 115, 0, 0, 1059, 1061, 3, 132, 66, 0, 1060, 1058, 1, 0, 0, 0, 1061, 1064, 1, 0, 0, 0, 1062, 1060, 1, 0, 0, 0, 1062, 1063, 1, 0, 0, 0, 1063, 1066, 1, 0, 0, 0, 1064, 1062, 1, 0, 0, 0, 1065, 1057, 1, 0, 0, 0, 1065, 1066, 1, 0, 0, 0, 1066, 1067, 1, 0, 0, 0, 1067, 1068, 5, 118, 0, 0, 1068, 131, 1, 0, 0, 0, 1069, 1070, 3, 138, 69, 0, 1070, 1071, 5, 113, 0, 0, 1071, 1072, 3, 128, 64, 0, 1072, 133, 1, 0, 0, 0, 1073, 1082, 5, 119, 0, 0, 1074, 1079, 3, 128, 64, 0, 1075, 1076, 5, 115, 0, 0, 1076, 1078, 3, 128, 64, 0, 1077, 1075, 1, 0, 0, 0, 1078, 1081, 1, 0, 0, 0, 1079, 1077, 1, 0, 0, 0, 1079, 1080, 1, 0, 0, 0, 1080, 1083, 1, 0, 0, 0, 1081, 1079, 1, 0, 0, 0, 1082, 1074, 1, 0, 0, 0, 1082, 1083, 1, 0, 0, 0, 1083, 1084, 1, 0, 0, 0, 1084, 1085, 5, 120, 0, 0, 1085, 135, 1, 0, 0, 0, 1086, 1087, 7, 6, 0, 0, 1087, 137, 1, 0, 0, 0, 1088, 1089, 5, 130, 0, 0, 1089, 139, 1, 0, 0, 0, 92, 152, 159, 180, 192, 204, 224, 232, 239, 245, 257, 260, 266, 277, 286, 296, 299, 301, 305, 313, 325, 330, 339, 342, 360, 372, 396, 399, 409, 413, 419, 426, 452, 459, 463, 468, 479, 485, 495, 500, 514, 534, 561, 566, 577, 582, 589, 598, 611, 632, 635, 638, 645, 653, 684, 696, 710, 718, 730, 744, 754, 772, 776, 779, 783, 786, 795, 799, 803, 809, 819, 848, 867, 886, 889, 891, 902, 917, 933, 942, 950, 972, 976, 985, 1003, 1027, 1033, 1035, 1054, 1062, 1065, 1079, 1082] \ No newline at end of file +[4, 1, 159, 1469, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, 4, 2, 5, 7, 5, 2, 6, 7, 6, 2, 7, 7, 7, 2, 8, 7, 8, 2, 9, 7, 9, 2, 10, 7, 10, 2, 11, 7, 11, 2, 12, 7, 12, 2, 13, 7, 13, 2, 14, 7, 14, 2, 15, 7, 15, 2, 16, 7, 16, 2, 17, 7, 17, 2, 18, 7, 18, 2, 19, 7, 19, 2, 20, 7, 20, 2, 21, 7, 21, 2, 22, 7, 22, 2, 23, 7, 23, 2, 24, 7, 24, 2, 25, 7, 25, 2, 26, 7, 26, 2, 27, 7, 27, 2, 28, 7, 28, 2, 29, 7, 29, 2, 30, 7, 30, 2, 31, 7, 31, 2, 32, 7, 32, 2, 33, 7, 33, 2, 34, 7, 34, 2, 35, 7, 35, 2, 36, 7, 36, 2, 37, 7, 37, 2, 38, 7, 38, 2, 39, 7, 39, 2, 40, 7, 40, 2, 41, 7, 41, 2, 42, 7, 42, 2, 43, 7, 43, 2, 44, 7, 44, 2, 45, 7, 45, 2, 46, 7, 46, 2, 47, 7, 47, 2, 48, 7, 48, 2, 49, 7, 49, 2, 50, 7, 50, 2, 51, 7, 51, 2, 52, 7, 52, 2, 53, 7, 53, 2, 54, 7, 54, 2, 55, 7, 55, 2, 56, 7, 56, 2, 57, 7, 57, 2, 58, 7, 58, 2, 59, 7, 59, 2, 60, 7, 60, 2, 61, 7, 61, 2, 62, 7, 62, 2, 63, 7, 63, 2, 64, 7, 64, 2, 65, 7, 65, 2, 66, 7, 66, 2, 67, 7, 67, 2, 68, 7, 68, 2, 69, 7, 69, 2, 70, 7, 70, 2, 71, 7, 71, 2, 72, 7, 72, 2, 73, 7, 73, 2, 74, 7, 74, 2, 75, 7, 75, 2, 76, 7, 76, 2, 77, 7, 77, 2, 78, 7, 78, 2, 79, 7, 79, 2, 80, 7, 80, 2, 81, 7, 81, 2, 82, 7, 82, 2, 83, 7, 83, 2, 84, 7, 84, 2, 85, 7, 85, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 3, 0, 185, 8, 0, 1, 1, 1, 1, 1, 1, 5, 1, 190, 8, 1, 10, 1, 12, 1, 193, 9, 1, 1, 1, 1, 1, 1, 2, 1, 2, 1, 2, 1, 2, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 5, 3, 211, 8, 3, 10, 3, 12, 3, 214, 9, 3, 1, 3, 1, 3, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 3, 4, 226, 8, 4, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 3, 5, 238, 8, 5, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 3, 9, 265, 8, 9, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 3, 10, 273, 8, 10, 1, 10, 1, 10, 1, 11, 5, 11, 278, 8, 11, 10, 11, 12, 11, 281, 9, 11, 1, 11, 1, 11, 1, 11, 3, 11, 286, 8, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 5, 11, 296, 8, 11, 10, 11, 12, 11, 299, 9, 11, 3, 11, 301, 8, 11, 1, 11, 1, 11, 5, 11, 305, 8, 11, 10, 11, 12, 11, 308, 9, 11, 1, 11, 1, 11, 1, 12, 1, 12, 1, 12, 1, 12, 5, 12, 316, 8, 12, 10, 12, 12, 12, 319, 9, 12, 1, 12, 1, 12, 1, 13, 1, 13, 1, 13, 1, 13, 3, 13, 327, 8, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 5, 13, 335, 8, 13, 10, 13, 12, 13, 338, 9, 13, 3, 13, 340, 8, 13, 3, 13, 342, 8, 13, 1, 14, 1, 14, 3, 14, 346, 8, 14, 1, 15, 1, 15, 1, 15, 1, 15, 5, 15, 352, 8, 15, 10, 15, 12, 15, 355, 9, 15, 1, 15, 1, 15, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 3, 16, 366, 8, 16, 1, 17, 1, 17, 1, 17, 3, 17, 371, 8, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 18, 1, 18, 1, 18, 1, 18, 3, 18, 381, 8, 18, 1, 19, 1, 19, 1, 19, 1, 19, 1, 19, 1, 19, 1, 19, 1, 19, 3, 19, 391, 8, 19, 1, 19, 1, 19, 1, 20, 3, 20, 396, 8, 20, 1, 20, 1, 20, 1, 20, 1, 20, 1, 20, 1, 20, 1, 20, 1, 20, 1, 20, 1, 20, 1, 20, 1, 20, 1, 20, 3, 20, 411, 8, 20, 1, 20, 1, 20, 1, 20, 1, 21, 3, 21, 417, 8, 21, 1, 21, 1, 21, 1, 21, 1, 21, 1, 21, 1, 21, 1, 21, 1, 21, 5, 21, 427, 8, 21, 10, 21, 12, 21, 430, 9, 21, 1, 21, 1, 21, 1, 22, 1, 22, 1, 22, 1, 22, 3, 22, 438, 8, 22, 1, 22, 1, 22, 1, 22, 1, 22, 1, 22, 1, 22, 3, 22, 446, 8, 22, 1, 22, 1, 22, 1, 22, 1, 22, 1, 22, 1, 22, 3, 22, 454, 8, 22, 1, 22, 1, 22, 1, 22, 1, 22, 1, 22, 1, 22, 1, 22, 1, 22, 1, 22, 1, 22, 1, 22, 3, 22, 467, 8, 22, 1, 23, 3, 23, 470, 8, 23, 1, 23, 1, 23, 1, 23, 1, 23, 1, 23, 1, 23, 1, 23, 1, 23, 3, 23, 480, 8, 23, 1, 23, 1, 23, 3, 23, 484, 8, 23, 1, 23, 1, 23, 5, 23, 488, 8, 23, 10, 23, 12, 23, 491, 9, 23, 1, 23, 1, 23, 1, 24, 1, 24, 3, 24, 497, 8, 24, 1, 25, 1, 25, 1, 25, 1, 25, 3, 25, 503, 8, 25, 1, 25, 1, 25, 1, 25, 1, 25, 1, 25, 1, 25, 3, 25, 511, 8, 25, 1, 25, 1, 25, 1, 25, 1, 25, 1, 25, 1, 25, 3, 25, 519, 8, 25, 1, 25, 1, 25, 1, 25, 1, 25, 1, 25, 1, 25, 1, 25, 1, 25, 1, 25, 1, 25, 1, 25, 3, 25, 532, 8, 25, 1, 26, 1, 26, 1, 26, 1, 26, 1, 26, 3, 26, 539, 8, 26, 1, 26, 1, 26, 3, 26, 543, 8, 26, 1, 27, 5, 27, 546, 8, 27, 10, 27, 12, 27, 549, 9, 27, 1, 27, 1, 27, 1, 27, 1, 27, 1, 27, 1, 27, 1, 27, 1, 27, 3, 27, 559, 8, 27, 1, 27, 1, 27, 5, 27, 563, 8, 27, 10, 27, 12, 27, 566, 9, 27, 1, 27, 1, 27, 1, 28, 1, 28, 1, 28, 1, 28, 1, 28, 1, 28, 3, 28, 576, 8, 28, 1, 29, 1, 29, 1, 29, 1, 29, 1, 29, 1, 29, 1, 29, 1, 29, 1, 29, 1, 29, 1, 30, 1, 30, 1, 30, 1, 30, 1, 30, 1, 30, 1, 30, 1, 30, 1, 30, 1, 30, 1, 30, 1, 30, 1, 31, 1, 31, 3, 31, 602, 8, 31, 1, 31, 1, 31, 3, 31, 606, 8, 31, 1, 31, 1, 31, 1, 31, 1, 31, 3, 31, 612, 8, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 5, 31, 621, 8, 31, 10, 31, 12, 31, 624, 9, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 3, 31, 632, 8, 31, 1, 31, 1, 31, 5, 31, 636, 8, 31, 10, 31, 12, 31, 639, 9, 31, 1, 31, 1, 31, 3, 31, 643, 8, 31, 3, 31, 645, 8, 31, 1, 32, 1, 32, 1, 32, 1, 32, 1, 32, 1, 32, 1, 32, 1, 32, 1, 32, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 5, 33, 674, 8, 33, 10, 33, 12, 33, 677, 9, 33, 1, 33, 3, 33, 680, 8, 33, 1, 33, 1, 33, 1, 33, 1, 33, 3, 33, 686, 8, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 3, 33, 713, 8, 33, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 35, 1, 35, 1, 35, 3, 35, 723, 8, 35, 1, 35, 1, 35, 1, 35, 1, 35, 1, 35, 1, 35, 1, 35, 1, 35, 1, 35, 3, 35, 734, 8, 35, 1, 35, 3, 35, 737, 8, 35, 1, 35, 1, 35, 1, 35, 3, 35, 742, 8, 35, 1, 35, 3, 35, 745, 8, 35, 1, 35, 3, 35, 748, 8, 35, 1, 35, 1, 35, 1, 36, 1, 36, 1, 36, 3, 36, 755, 8, 36, 1, 36, 1, 36, 1, 36, 1, 36, 1, 36, 1, 36, 1, 36, 3, 36, 764, 8, 36, 1, 36, 3, 36, 767, 8, 36, 1, 36, 3, 36, 770, 8, 36, 1, 36, 3, 36, 773, 8, 36, 1, 36, 1, 36, 1, 37, 1, 37, 1, 37, 1, 37, 1, 38, 1, 38, 1, 38, 1, 38, 1, 38, 1, 38, 1, 39, 1, 39, 1, 39, 1, 39, 1, 39, 1, 39, 1, 39, 1, 39, 1, 39, 3, 39, 796, 8, 39, 3, 39, 798, 8, 39, 1, 40, 1, 40, 1, 40, 1, 40, 1, 40, 5, 40, 805, 8, 40, 10, 40, 12, 40, 808, 9, 40, 1, 40, 1, 40, 1, 41, 1, 41, 1, 41, 1, 41, 1, 41, 1, 41, 1, 41, 1, 41, 1, 41, 3, 41, 821, 8, 41, 1, 41, 1, 41, 1, 42, 1, 42, 1, 42, 1, 43, 1, 43, 1, 44, 1, 44, 1, 44, 1, 44, 1, 44, 1, 44, 1, 44, 1, 44, 1, 44, 1, 44, 5, 44, 840, 8, 44, 10, 44, 12, 44, 843, 9, 44, 3, 44, 845, 8, 44, 1, 44, 3, 44, 848, 8, 44, 1, 45, 1, 45, 1, 45, 5, 45, 853, 8, 45, 10, 45, 12, 45, 856, 9, 45, 1, 46, 1, 46, 1, 46, 5, 46, 861, 8, 46, 10, 46, 12, 46, 864, 9, 46, 1, 46, 1, 46, 1, 47, 1, 47, 1, 47, 1, 47, 1, 47, 1, 47, 1, 47, 1, 47, 1, 47, 1, 47, 1, 47, 1, 47, 1, 47, 1, 47, 1, 47, 1, 47, 1, 47, 1, 47, 1, 47, 1, 47, 1, 47, 1, 47, 1, 47, 1, 47, 1, 47, 1, 47, 1, 47, 3, 47, 895, 8, 47, 1, 47, 1, 47, 1, 47, 1, 47, 1, 47, 1, 47, 1, 47, 1, 47, 1, 47, 1, 47, 3, 47, 907, 8, 47, 1, 47, 3, 47, 910, 8, 47, 1, 47, 1, 47, 3, 47, 914, 8, 47, 1, 47, 1, 47, 1, 47, 1, 47, 1, 47, 1, 47, 1, 47, 1, 47, 1, 47, 1, 47, 1, 47, 1, 47, 1, 47, 1, 47, 1, 47, 1, 47, 1, 47, 1, 47, 1, 47, 1, 47, 3, 47, 936, 8, 47, 1, 47, 1, 47, 1, 47, 1, 47, 1, 47, 1, 47, 1, 47, 1, 47, 1, 47, 1, 47, 1, 47, 1, 47, 3, 47, 950, 8, 47, 1, 48, 1, 48, 1, 48, 1, 48, 5, 48, 956, 8, 48, 10, 48, 12, 48, 959, 9, 48, 1, 48, 1, 48, 1, 49, 1, 49, 1, 50, 1, 50, 1, 50, 1, 51, 1, 51, 3, 51, 970, 8, 51, 1, 52, 1, 52, 1, 52, 1, 52, 1, 52, 1, 52, 1, 52, 1, 52, 1, 52, 1, 52, 1, 52, 1, 52, 3, 52, 984, 8, 52, 1, 52, 1, 52, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 3, 53, 1001, 8, 53, 1, 54, 1, 54, 1, 54, 1, 54, 1, 54, 1, 54, 1, 54, 1, 54, 1, 54, 1, 55, 1, 55, 1, 55, 1, 55, 1, 55, 1, 55, 1, 55, 3, 55, 1019, 8, 55, 1, 55, 1, 55, 3, 55, 1023, 8, 55, 1, 55, 3, 55, 1026, 8, 55, 1, 55, 1, 55, 3, 55, 1030, 8, 55, 1, 55, 3, 55, 1033, 8, 55, 1, 55, 1, 55, 1, 56, 1, 56, 3, 56, 1039, 8, 56, 1, 56, 1, 56, 3, 56, 1043, 8, 56, 1, 56, 1, 56, 1, 56, 3, 56, 1048, 8, 56, 1, 56, 1, 56, 3, 56, 1052, 8, 56, 1, 56, 1, 56, 3, 56, 1056, 8, 56, 1, 56, 1, 56, 5, 56, 1060, 8, 56, 10, 56, 12, 56, 1063, 9, 56, 1, 56, 1, 56, 1, 57, 1, 57, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 3, 58, 1076, 8, 58, 1, 59, 1, 59, 1, 59, 1, 59, 1, 59, 1, 59, 1, 59, 1, 59, 1, 60, 1, 60, 1, 60, 5, 60, 1089, 8, 60, 10, 60, 12, 60, 1092, 9, 60, 1, 60, 1, 60, 1, 60, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 3, 61, 1111, 8, 61, 1, 62, 1, 62, 1, 62, 1, 62, 1, 62, 5, 62, 1118, 8, 62, 10, 62, 12, 62, 1121, 9, 62, 1, 62, 1, 62, 1, 63, 1, 63, 1, 63, 1, 63, 1, 63, 1, 63, 1, 63, 1, 64, 1, 64, 1, 64, 1, 64, 1, 64, 1, 64, 1, 64, 1, 64, 1, 64, 1, 64, 1, 64, 1, 64, 1, 64, 1, 64, 1, 65, 1, 65, 1, 65, 1, 65, 1, 65, 1, 65, 3, 65, 1152, 8, 65, 1, 65, 1, 65, 1, 66, 1, 66, 1, 66, 1, 66, 1, 67, 1, 67, 1, 67, 1, 67, 1, 67, 1, 67, 1, 67, 1, 67, 1, 67, 1, 67, 1, 67, 1, 67, 3, 67, 1172, 8, 67, 1, 68, 1, 68, 1, 68, 1, 68, 1, 68, 1, 68, 1, 68, 1, 68, 1, 68, 1, 68, 1, 68, 1, 68, 1, 68, 1, 68, 1, 68, 1, 68, 1, 68, 1, 68, 1, 68, 1, 68, 1, 68, 1, 68, 1, 68, 1, 68, 1, 68, 1, 68, 1, 68, 1, 68, 3, 68, 1202, 8, 68, 1, 68, 1, 68, 1, 68, 1, 68, 1, 68, 1, 68, 1, 68, 1, 68, 3, 68, 1212, 8, 68, 1, 68, 3, 68, 1215, 8, 68, 1, 68, 3, 68, 1218, 8, 68, 3, 68, 1220, 8, 68, 1, 69, 1, 69, 1, 69, 1, 70, 1, 70, 1, 71, 1, 71, 1, 72, 1, 72, 1, 72, 5, 72, 1232, 8, 72, 10, 72, 12, 72, 1235, 9, 72, 1, 72, 1, 72, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 3, 73, 1258, 8, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 3, 73, 1274, 8, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 3, 73, 1283, 8, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 3, 73, 1293, 8, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 5, 73, 1304, 8, 73, 10, 73, 12, 73, 1307, 9, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 3, 73, 1321, 8, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 3, 73, 1333, 8, 73, 1, 73, 1, 73, 3, 73, 1337, 8, 73, 1, 74, 1, 74, 1, 74, 1, 74, 1, 74, 1, 74, 1, 74, 3, 74, 1346, 8, 74, 1, 74, 1, 74, 1, 75, 1, 75, 1, 75, 1, 75, 1, 75, 1, 75, 1, 75, 1, 75, 1, 75, 1, 75, 1, 75, 1, 75, 1, 75, 1, 75, 3, 75, 1364, 8, 75, 1, 75, 1, 75, 1, 75, 1, 75, 1, 75, 1, 75, 1, 75, 1, 75, 1, 75, 1, 75, 1, 75, 1, 75, 1, 75, 1, 75, 1, 75, 1, 75, 1, 75, 1, 75, 1, 75, 1, 75, 5, 75, 1386, 8, 75, 10, 75, 12, 75, 1389, 9, 75, 1, 75, 1, 75, 1, 75, 1, 75, 1, 75, 1, 75, 4, 75, 1397, 8, 75, 11, 75, 12, 75, 1398, 1, 75, 1, 75, 1, 75, 1, 75, 3, 75, 1405, 8, 75, 3, 75, 1407, 8, 75, 1, 76, 1, 76, 1, 76, 1, 76, 1, 76, 1, 77, 1, 77, 1, 77, 1, 77, 1, 77, 1, 77, 1, 77, 1, 78, 1, 78, 1, 79, 1, 79, 1, 80, 1, 80, 1, 80, 1, 80, 1, 80, 1, 80, 1, 80, 1, 80, 3, 80, 1433, 8, 80, 1, 81, 1, 81, 1, 81, 1, 81, 5, 81, 1439, 8, 81, 10, 81, 12, 81, 1442, 9, 81, 3, 81, 1444, 8, 81, 1, 81, 1, 81, 1, 82, 1, 82, 1, 82, 1, 82, 1, 83, 1, 83, 1, 83, 1, 83, 5, 83, 1456, 8, 83, 10, 83, 12, 83, 1459, 9, 83, 3, 83, 1461, 8, 83, 1, 83, 1, 83, 1, 84, 1, 84, 1, 85, 1, 85, 1, 85, 0, 0, 86, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70, 72, 74, 76, 78, 80, 82, 84, 86, 88, 90, 92, 94, 96, 98, 100, 102, 104, 106, 108, 110, 112, 114, 116, 118, 120, 122, 124, 126, 128, 130, 132, 134, 136, 138, 140, 142, 144, 146, 148, 150, 152, 154, 156, 158, 160, 162, 164, 166, 168, 170, 0, 10, 1, 0, 50, 51, 2, 0, 17, 17, 35, 35, 1, 0, 13, 14, 1, 0, 107, 111, 3, 0, 1, 1, 102, 106, 108, 109, 3, 0, 1, 1, 102, 103, 108, 109, 2, 0, 104, 106, 108, 109, 1, 0, 127, 134, 1, 0, 114, 117, 13, 0, 1, 1, 4, 7, 10, 10, 12, 35, 42, 42, 48, 48, 78, 78, 85, 86, 100, 101, 105, 105, 112, 112, 121, 121, 155, 155, 1593, 0, 184, 1, 0, 0, 0, 2, 186, 1, 0, 0, 0, 4, 196, 1, 0, 0, 0, 6, 200, 1, 0, 0, 0, 8, 225, 1, 0, 0, 0, 10, 237, 1, 0, 0, 0, 12, 239, 1, 0, 0, 0, 14, 246, 1, 0, 0, 0, 16, 253, 1, 0, 0, 0, 18, 264, 1, 0, 0, 0, 20, 266, 1, 0, 0, 0, 22, 279, 1, 0, 0, 0, 24, 311, 1, 0, 0, 0, 26, 341, 1, 0, 0, 0, 28, 343, 1, 0, 0, 0, 30, 347, 1, 0, 0, 0, 32, 365, 1, 0, 0, 0, 34, 367, 1, 0, 0, 0, 36, 380, 1, 0, 0, 0, 38, 382, 1, 0, 0, 0, 40, 395, 1, 0, 0, 0, 42, 416, 1, 0, 0, 0, 44, 466, 1, 0, 0, 0, 46, 469, 1, 0, 0, 0, 48, 494, 1, 0, 0, 0, 50, 531, 1, 0, 0, 0, 52, 542, 1, 0, 0, 0, 54, 547, 1, 0, 0, 0, 56, 575, 1, 0, 0, 0, 58, 577, 1, 0, 0, 0, 60, 587, 1, 0, 0, 0, 62, 599, 1, 0, 0, 0, 64, 646, 1, 0, 0, 0, 66, 712, 1, 0, 0, 0, 68, 714, 1, 0, 0, 0, 70, 719, 1, 0, 0, 0, 72, 751, 1, 0, 0, 0, 74, 776, 1, 0, 0, 0, 76, 780, 1, 0, 0, 0, 78, 797, 1, 0, 0, 0, 80, 799, 1, 0, 0, 0, 82, 811, 1, 0, 0, 0, 84, 824, 1, 0, 0, 0, 86, 827, 1, 0, 0, 0, 88, 847, 1, 0, 0, 0, 90, 849, 1, 0, 0, 0, 92, 857, 1, 0, 0, 0, 94, 949, 1, 0, 0, 0, 96, 951, 1, 0, 0, 0, 98, 962, 1, 0, 0, 0, 100, 964, 1, 0, 0, 0, 102, 969, 1, 0, 0, 0, 104, 971, 1, 0, 0, 0, 106, 1000, 1, 0, 0, 0, 108, 1002, 1, 0, 0, 0, 110, 1011, 1, 0, 0, 0, 112, 1036, 1, 0, 0, 0, 114, 1066, 1, 0, 0, 0, 116, 1075, 1, 0, 0, 0, 118, 1077, 1, 0, 0, 0, 120, 1085, 1, 0, 0, 0, 122, 1110, 1, 0, 0, 0, 124, 1112, 1, 0, 0, 0, 126, 1124, 1, 0, 0, 0, 128, 1131, 1, 0, 0, 0, 130, 1145, 1, 0, 0, 0, 132, 1155, 1, 0, 0, 0, 134, 1171, 1, 0, 0, 0, 136, 1219, 1, 0, 0, 0, 138, 1221, 1, 0, 0, 0, 140, 1224, 1, 0, 0, 0, 142, 1226, 1, 0, 0, 0, 144, 1228, 1, 0, 0, 0, 146, 1336, 1, 0, 0, 0, 148, 1338, 1, 0, 0, 0, 150, 1406, 1, 0, 0, 0, 152, 1408, 1, 0, 0, 0, 154, 1413, 1, 0, 0, 0, 156, 1420, 1, 0, 0, 0, 158, 1422, 1, 0, 0, 0, 160, 1432, 1, 0, 0, 0, 162, 1434, 1, 0, 0, 0, 164, 1447, 1, 0, 0, 0, 166, 1451, 1, 0, 0, 0, 168, 1464, 1, 0, 0, 0, 170, 1466, 1, 0, 0, 0, 172, 173, 3, 6, 3, 0, 173, 174, 5, 0, 0, 1, 174, 185, 1, 0, 0, 0, 175, 176, 3, 22, 11, 0, 176, 177, 5, 0, 0, 1, 177, 185, 1, 0, 0, 0, 178, 179, 3, 54, 27, 0, 179, 180, 5, 0, 0, 1, 180, 185, 1, 0, 0, 0, 181, 182, 3, 2, 1, 0, 182, 183, 5, 0, 0, 1, 183, 185, 1, 0, 0, 0, 184, 172, 1, 0, 0, 0, 184, 175, 1, 0, 0, 0, 184, 178, 1, 0, 0, 0, 184, 181, 1, 0, 0, 0, 185, 1, 1, 0, 0, 0, 186, 187, 5, 42, 0, 0, 187, 191, 5, 143, 0, 0, 188, 190, 3, 8, 4, 0, 189, 188, 1, 0, 0, 0, 190, 193, 1, 0, 0, 0, 191, 189, 1, 0, 0, 0, 191, 192, 1, 0, 0, 0, 192, 194, 1, 0, 0, 0, 193, 191, 1, 0, 0, 0, 194, 195, 5, 144, 0, 0, 195, 3, 1, 0, 0, 0, 196, 197, 5, 43, 0, 0, 197, 198, 3, 170, 85, 0, 198, 199, 5, 140, 0, 0, 199, 5, 1, 0, 0, 0, 200, 201, 5, 11, 0, 0, 201, 202, 3, 168, 84, 0, 202, 203, 5, 90, 0, 0, 203, 204, 3, 170, 85, 0, 204, 205, 5, 81, 0, 0, 205, 206, 3, 170, 85, 0, 206, 207, 5, 80, 0, 0, 207, 208, 3, 170, 85, 0, 208, 212, 5, 143, 0, 0, 209, 211, 3, 8, 4, 0, 210, 209, 1, 0, 0, 0, 211, 214, 1, 0, 0, 0, 212, 210, 1, 0, 0, 0, 212, 213, 1, 0, 0, 0, 213, 215, 1, 0, 0, 0, 214, 212, 1, 0, 0, 0, 215, 216, 5, 144, 0, 0, 216, 7, 1, 0, 0, 0, 217, 226, 3, 4, 2, 0, 218, 226, 3, 20, 10, 0, 219, 226, 3, 10, 5, 0, 220, 226, 3, 100, 50, 0, 221, 226, 3, 112, 56, 0, 222, 226, 3, 148, 74, 0, 223, 226, 3, 60, 30, 0, 224, 226, 3, 34, 17, 0, 225, 217, 1, 0, 0, 0, 225, 218, 1, 0, 0, 0, 225, 219, 1, 0, 0, 0, 225, 220, 1, 0, 0, 0, 225, 221, 1, 0, 0, 0, 225, 222, 1, 0, 0, 0, 225, 223, 1, 0, 0, 0, 225, 224, 1, 0, 0, 0, 226, 9, 1, 0, 0, 0, 227, 228, 5, 43, 0, 0, 228, 229, 5, 47, 0, 0, 229, 230, 3, 168, 84, 0, 230, 231, 5, 140, 0, 0, 231, 238, 1, 0, 0, 0, 232, 233, 5, 43, 0, 0, 233, 234, 5, 49, 0, 0, 234, 235, 3, 168, 84, 0, 235, 236, 5, 140, 0, 0, 236, 238, 1, 0, 0, 0, 237, 227, 1, 0, 0, 0, 237, 232, 1, 0, 0, 0, 238, 11, 1, 0, 0, 0, 239, 240, 5, 44, 0, 0, 240, 241, 5, 46, 0, 0, 241, 242, 3, 168, 84, 0, 242, 243, 5, 90, 0, 0, 243, 244, 3, 170, 85, 0, 244, 245, 5, 140, 0, 0, 245, 13, 1, 0, 0, 0, 246, 247, 5, 44, 0, 0, 247, 248, 5, 47, 0, 0, 248, 249, 3, 168, 84, 0, 249, 250, 5, 81, 0, 0, 250, 251, 3, 170, 85, 0, 251, 252, 5, 140, 0, 0, 252, 15, 1, 0, 0, 0, 253, 254, 5, 45, 0, 0, 254, 255, 3, 168, 84, 0, 255, 256, 5, 90, 0, 0, 256, 257, 3, 170, 85, 0, 257, 258, 5, 140, 0, 0, 258, 17, 1, 0, 0, 0, 259, 265, 3, 10, 5, 0, 260, 265, 3, 12, 6, 0, 261, 265, 3, 14, 7, 0, 262, 265, 3, 16, 8, 0, 263, 265, 3, 34, 17, 0, 264, 259, 1, 0, 0, 0, 264, 260, 1, 0, 0, 0, 264, 261, 1, 0, 0, 0, 264, 262, 1, 0, 0, 0, 264, 263, 1, 0, 0, 0, 265, 19, 1, 0, 0, 0, 266, 267, 5, 46, 0, 0, 267, 268, 3, 168, 84, 0, 268, 269, 5, 90, 0, 0, 269, 272, 3, 170, 85, 0, 270, 271, 5, 91, 0, 0, 271, 273, 3, 170, 85, 0, 272, 270, 1, 0, 0, 0, 272, 273, 1, 0, 0, 0, 273, 274, 1, 0, 0, 0, 274, 275, 5, 140, 0, 0, 275, 21, 1, 0, 0, 0, 276, 278, 3, 18, 9, 0, 277, 276, 1, 0, 0, 0, 278, 281, 1, 0, 0, 0, 279, 277, 1, 0, 0, 0, 279, 280, 1, 0, 0, 0, 280, 282, 1, 0, 0, 0, 281, 279, 1, 0, 0, 0, 282, 283, 5, 47, 0, 0, 283, 285, 3, 168, 84, 0, 284, 286, 3, 24, 12, 0, 285, 284, 1, 0, 0, 0, 285, 286, 1, 0, 0, 0, 286, 287, 1, 0, 0, 0, 287, 288, 5, 90, 0, 0, 288, 289, 3, 170, 85, 0, 289, 290, 5, 81, 0, 0, 290, 300, 3, 170, 85, 0, 291, 292, 5, 95, 0, 0, 292, 297, 3, 28, 14, 0, 293, 294, 5, 141, 0, 0, 294, 296, 3, 28, 14, 0, 295, 293, 1, 0, 0, 0, 296, 299, 1, 0, 0, 0, 297, 295, 1, 0, 0, 0, 297, 298, 1, 0, 0, 0, 298, 301, 1, 0, 0, 0, 299, 297, 1, 0, 0, 0, 300, 291, 1, 0, 0, 0, 300, 301, 1, 0, 0, 0, 301, 302, 1, 0, 0, 0, 302, 306, 5, 143, 0, 0, 303, 305, 3, 36, 18, 0, 304, 303, 1, 0, 0, 0, 305, 308, 1, 0, 0, 0, 306, 304, 1, 0, 0, 0, 306, 307, 1, 0, 0, 0, 307, 309, 1, 0, 0, 0, 308, 306, 1, 0, 0, 0, 309, 310, 5, 144, 0, 0, 310, 23, 1, 0, 0, 0, 311, 312, 5, 149, 0, 0, 312, 317, 3, 26, 13, 0, 313, 314, 5, 141, 0, 0, 314, 316, 3, 26, 13, 0, 315, 313, 1, 0, 0, 0, 316, 319, 1, 0, 0, 0, 317, 315, 1, 0, 0, 0, 317, 318, 1, 0, 0, 0, 318, 320, 1, 0, 0, 0, 319, 317, 1, 0, 0, 0, 320, 321, 5, 150, 0, 0, 321, 25, 1, 0, 0, 0, 322, 323, 5, 50, 0, 0, 323, 326, 3, 168, 84, 0, 324, 325, 5, 139, 0, 0, 325, 327, 5, 39, 0, 0, 326, 324, 1, 0, 0, 0, 326, 327, 1, 0, 0, 0, 327, 342, 1, 0, 0, 0, 328, 329, 5, 38, 0, 0, 329, 339, 3, 168, 84, 0, 330, 331, 5, 40, 0, 0, 331, 336, 3, 28, 14, 0, 332, 333, 5, 151, 0, 0, 333, 335, 3, 28, 14, 0, 334, 332, 1, 0, 0, 0, 335, 338, 1, 0, 0, 0, 336, 334, 1, 0, 0, 0, 336, 337, 1, 0, 0, 0, 337, 340, 1, 0, 0, 0, 338, 336, 1, 0, 0, 0, 339, 330, 1, 0, 0, 0, 339, 340, 1, 0, 0, 0, 340, 342, 1, 0, 0, 0, 341, 322, 1, 0, 0, 0, 341, 328, 1, 0, 0, 0, 342, 27, 1, 0, 0, 0, 343, 345, 3, 168, 84, 0, 344, 346, 3, 30, 15, 0, 345, 344, 1, 0, 0, 0, 345, 346, 1, 0, 0, 0, 346, 29, 1, 0, 0, 0, 347, 348, 5, 149, 0, 0, 348, 353, 3, 32, 16, 0, 349, 350, 5, 141, 0, 0, 350, 352, 3, 32, 16, 0, 351, 349, 1, 0, 0, 0, 352, 355, 1, 0, 0, 0, 353, 351, 1, 0, 0, 0, 353, 354, 1, 0, 0, 0, 354, 356, 1, 0, 0, 0, 355, 353, 1, 0, 0, 0, 356, 357, 5, 150, 0, 0, 357, 31, 1, 0, 0, 0, 358, 359, 5, 46, 0, 0, 359, 366, 3, 168, 84, 0, 360, 361, 5, 47, 0, 0, 361, 366, 3, 28, 14, 0, 362, 363, 5, 38, 0, 0, 363, 366, 3, 168, 84, 0, 364, 366, 3, 150, 75, 0, 365, 358, 1, 0, 0, 0, 365, 360, 1, 0, 0, 0, 365, 362, 1, 0, 0, 0, 365, 364, 1, 0, 0, 0, 366, 33, 1, 0, 0, 0, 367, 368, 5, 37, 0, 0, 368, 370, 3, 168, 84, 0, 369, 371, 3, 24, 12, 0, 370, 369, 1, 0, 0, 0, 370, 371, 1, 0, 0, 0, 371, 372, 1, 0, 0, 0, 372, 373, 5, 152, 0, 0, 373, 374, 3, 150, 75, 0, 374, 375, 5, 140, 0, 0, 375, 35, 1, 0, 0, 0, 376, 381, 3, 42, 21, 0, 377, 381, 3, 46, 23, 0, 378, 381, 3, 40, 20, 0, 379, 381, 3, 38, 19, 0, 380, 376, 1, 0, 0, 0, 380, 377, 1, 0, 0, 0, 380, 378, 1, 0, 0, 0, 380, 379, 1, 0, 0, 0, 381, 37, 1, 0, 0, 0, 382, 383, 5, 2, 0, 0, 383, 384, 3, 168, 84, 0, 384, 385, 5, 90, 0, 0, 385, 386, 3, 170, 85, 0, 386, 387, 5, 3, 0, 0, 387, 390, 3, 170, 85, 0, 388, 389, 5, 5, 0, 0, 389, 391, 3, 52, 26, 0, 390, 388, 1, 0, 0, 0, 390, 391, 1, 0, 0, 0, 391, 392, 1, 0, 0, 0, 392, 393, 5, 140, 0, 0, 393, 39, 1, 0, 0, 0, 394, 396, 5, 61, 0, 0, 395, 394, 1, 0, 0, 0, 395, 396, 1, 0, 0, 0, 396, 397, 1, 0, 0, 0, 397, 398, 5, 53, 0, 0, 398, 399, 3, 168, 84, 0, 399, 400, 5, 90, 0, 0, 400, 401, 3, 170, 85, 0, 401, 402, 5, 139, 0, 0, 402, 403, 3, 150, 75, 0, 403, 404, 5, 138, 0, 0, 404, 405, 3, 150, 75, 0, 405, 406, 5, 143, 0, 0, 406, 407, 5, 107, 0, 0, 407, 408, 5, 90, 0, 0, 408, 410, 3, 170, 85, 0, 409, 411, 3, 78, 39, 0, 410, 409, 1, 0, 0, 0, 410, 411, 1, 0, 0, 0, 411, 412, 1, 0, 0, 0, 412, 413, 5, 140, 0, 0, 413, 414, 5, 144, 0, 0, 414, 41, 1, 0, 0, 0, 415, 417, 3, 48, 24, 0, 416, 415, 1, 0, 0, 0, 416, 417, 1, 0, 0, 0, 417, 418, 1, 0, 0, 0, 418, 419, 7, 0, 0, 0, 419, 420, 3, 168, 84, 0, 420, 421, 5, 90, 0, 0, 421, 422, 3, 170, 85, 0, 422, 423, 5, 139, 0, 0, 423, 424, 3, 150, 75, 0, 424, 428, 5, 143, 0, 0, 425, 427, 3, 44, 22, 0, 426, 425, 1, 0, 0, 0, 427, 430, 1, 0, 0, 0, 428, 426, 1, 0, 0, 0, 428, 429, 1, 0, 0, 0, 429, 431, 1, 0, 0, 0, 430, 428, 1, 0, 0, 0, 431, 432, 5, 144, 0, 0, 432, 43, 1, 0, 0, 0, 433, 434, 5, 97, 0, 0, 434, 435, 5, 90, 0, 0, 435, 437, 3, 170, 85, 0, 436, 438, 3, 78, 39, 0, 437, 436, 1, 0, 0, 0, 437, 438, 1, 0, 0, 0, 438, 439, 1, 0, 0, 0, 439, 440, 5, 140, 0, 0, 440, 467, 1, 0, 0, 0, 441, 442, 5, 98, 0, 0, 442, 443, 5, 90, 0, 0, 443, 445, 3, 170, 85, 0, 444, 446, 3, 78, 39, 0, 445, 444, 1, 0, 0, 0, 445, 446, 1, 0, 0, 0, 446, 447, 1, 0, 0, 0, 447, 448, 5, 140, 0, 0, 448, 467, 1, 0, 0, 0, 449, 450, 5, 1, 0, 0, 450, 451, 5, 90, 0, 0, 451, 453, 3, 170, 85, 0, 452, 454, 3, 78, 39, 0, 453, 452, 1, 0, 0, 0, 453, 454, 1, 0, 0, 0, 454, 455, 1, 0, 0, 0, 455, 456, 5, 140, 0, 0, 456, 467, 1, 0, 0, 0, 457, 458, 5, 99, 0, 0, 458, 459, 5, 100, 0, 0, 459, 460, 5, 90, 0, 0, 460, 461, 3, 170, 85, 0, 461, 462, 5, 101, 0, 0, 462, 463, 5, 90, 0, 0, 463, 464, 3, 170, 85, 0, 464, 465, 5, 140, 0, 0, 465, 467, 1, 0, 0, 0, 466, 433, 1, 0, 0, 0, 466, 441, 1, 0, 0, 0, 466, 449, 1, 0, 0, 0, 466, 457, 1, 0, 0, 0, 467, 45, 1, 0, 0, 0, 468, 470, 3, 48, 24, 0, 469, 468, 1, 0, 0, 0, 469, 470, 1, 0, 0, 0, 470, 471, 1, 0, 0, 0, 471, 472, 5, 52, 0, 0, 472, 473, 3, 168, 84, 0, 473, 474, 5, 90, 0, 0, 474, 475, 3, 170, 85, 0, 475, 476, 5, 139, 0, 0, 476, 477, 3, 158, 79, 0, 477, 479, 3, 52, 26, 0, 478, 480, 5, 118, 0, 0, 479, 478, 1, 0, 0, 0, 479, 480, 1, 0, 0, 0, 480, 483, 1, 0, 0, 0, 481, 482, 5, 88, 0, 0, 482, 484, 3, 170, 85, 0, 483, 481, 1, 0, 0, 0, 483, 484, 1, 0, 0, 0, 484, 485, 1, 0, 0, 0, 485, 489, 5, 143, 0, 0, 486, 488, 3, 50, 25, 0, 487, 486, 1, 0, 0, 0, 488, 491, 1, 0, 0, 0, 489, 487, 1, 0, 0, 0, 489, 490, 1, 0, 0, 0, 490, 492, 1, 0, 0, 0, 491, 489, 1, 0, 0, 0, 492, 493, 5, 144, 0, 0, 493, 47, 1, 0, 0, 0, 494, 496, 5, 34, 0, 0, 495, 497, 7, 1, 0, 0, 496, 495, 1, 0, 0, 0, 496, 497, 1, 0, 0, 0, 497, 49, 1, 0, 0, 0, 498, 499, 5, 104, 0, 0, 499, 500, 5, 90, 0, 0, 500, 502, 3, 170, 85, 0, 501, 503, 3, 78, 39, 0, 502, 501, 1, 0, 0, 0, 502, 503, 1, 0, 0, 0, 503, 504, 1, 0, 0, 0, 504, 505, 5, 140, 0, 0, 505, 532, 1, 0, 0, 0, 506, 507, 5, 105, 0, 0, 507, 508, 5, 90, 0, 0, 508, 510, 3, 170, 85, 0, 509, 511, 3, 78, 39, 0, 510, 509, 1, 0, 0, 0, 510, 511, 1, 0, 0, 0, 511, 512, 1, 0, 0, 0, 512, 513, 5, 140, 0, 0, 513, 532, 1, 0, 0, 0, 514, 515, 5, 106, 0, 0, 515, 516, 5, 90, 0, 0, 516, 518, 3, 170, 85, 0, 517, 519, 3, 78, 39, 0, 518, 517, 1, 0, 0, 0, 518, 519, 1, 0, 0, 0, 519, 520, 1, 0, 0, 0, 520, 521, 5, 140, 0, 0, 521, 532, 1, 0, 0, 0, 522, 523, 5, 99, 0, 0, 523, 524, 5, 100, 0, 0, 524, 525, 5, 90, 0, 0, 525, 526, 3, 170, 85, 0, 526, 527, 5, 101, 0, 0, 527, 528, 5, 90, 0, 0, 528, 529, 3, 170, 85, 0, 529, 530, 5, 140, 0, 0, 530, 532, 1, 0, 0, 0, 531, 498, 1, 0, 0, 0, 531, 506, 1, 0, 0, 0, 531, 514, 1, 0, 0, 0, 531, 522, 1, 0, 0, 0, 532, 51, 1, 0, 0, 0, 533, 534, 5, 46, 0, 0, 534, 543, 3, 168, 84, 0, 535, 536, 5, 47, 0, 0, 536, 538, 3, 168, 84, 0, 537, 539, 3, 30, 15, 0, 538, 537, 1, 0, 0, 0, 538, 539, 1, 0, 0, 0, 539, 543, 1, 0, 0, 0, 540, 541, 5, 38, 0, 0, 541, 543, 3, 168, 84, 0, 542, 533, 1, 0, 0, 0, 542, 535, 1, 0, 0, 0, 542, 540, 1, 0, 0, 0, 543, 53, 1, 0, 0, 0, 544, 546, 3, 18, 9, 0, 545, 544, 1, 0, 0, 0, 546, 549, 1, 0, 0, 0, 547, 545, 1, 0, 0, 0, 547, 548, 1, 0, 0, 0, 548, 550, 1, 0, 0, 0, 549, 547, 1, 0, 0, 0, 550, 551, 5, 49, 0, 0, 551, 552, 3, 168, 84, 0, 552, 553, 5, 90, 0, 0, 553, 554, 3, 170, 85, 0, 554, 555, 5, 81, 0, 0, 555, 558, 3, 170, 85, 0, 556, 557, 5, 82, 0, 0, 557, 559, 5, 153, 0, 0, 558, 556, 1, 0, 0, 0, 558, 559, 1, 0, 0, 0, 559, 560, 1, 0, 0, 0, 560, 564, 5, 143, 0, 0, 561, 563, 3, 56, 28, 0, 562, 561, 1, 0, 0, 0, 563, 566, 1, 0, 0, 0, 564, 562, 1, 0, 0, 0, 564, 565, 1, 0, 0, 0, 565, 567, 1, 0, 0, 0, 566, 564, 1, 0, 0, 0, 567, 568, 5, 144, 0, 0, 568, 55, 1, 0, 0, 0, 569, 576, 3, 70, 35, 0, 570, 576, 3, 72, 36, 0, 571, 576, 3, 82, 41, 0, 572, 576, 3, 62, 31, 0, 573, 576, 3, 64, 32, 0, 574, 576, 3, 58, 29, 0, 575, 569, 1, 0, 0, 0, 575, 570, 1, 0, 0, 0, 575, 571, 1, 0, 0, 0, 575, 572, 1, 0, 0, 0, 575, 573, 1, 0, 0, 0, 575, 574, 1, 0, 0, 0, 576, 57, 1, 0, 0, 0, 577, 578, 5, 2, 0, 0, 578, 579, 3, 168, 84, 0, 579, 580, 5, 90, 0, 0, 580, 581, 3, 170, 85, 0, 581, 582, 5, 40, 0, 0, 582, 583, 3, 28, 14, 0, 583, 584, 5, 142, 0, 0, 584, 585, 3, 168, 84, 0, 585, 586, 5, 140, 0, 0, 586, 59, 1, 0, 0, 0, 587, 588, 5, 4, 0, 0, 588, 589, 3, 168, 84, 0, 589, 590, 5, 90, 0, 0, 590, 591, 3, 170, 85, 0, 591, 592, 5, 6, 0, 0, 592, 593, 3, 28, 14, 0, 593, 594, 5, 69, 0, 0, 594, 595, 3, 168, 84, 0, 595, 596, 5, 142, 0, 0, 596, 597, 3, 168, 84, 0, 597, 598, 5, 140, 0, 0, 598, 61, 1, 0, 0, 0, 599, 601, 5, 24, 0, 0, 600, 602, 5, 42, 0, 0, 601, 600, 1, 0, 0, 0, 601, 602, 1, 0, 0, 0, 602, 603, 1, 0, 0, 0, 603, 605, 3, 168, 84, 0, 604, 606, 3, 24, 12, 0, 605, 604, 1, 0, 0, 0, 605, 606, 1, 0, 0, 0, 606, 607, 1, 0, 0, 0, 607, 608, 5, 90, 0, 0, 608, 609, 3, 170, 85, 0, 609, 611, 5, 27, 0, 0, 610, 612, 5, 11, 0, 0, 611, 610, 1, 0, 0, 0, 611, 612, 1, 0, 0, 0, 612, 613, 1, 0, 0, 0, 613, 614, 3, 28, 14, 0, 614, 615, 5, 28, 0, 0, 615, 644, 3, 170, 85, 0, 616, 617, 5, 53, 0, 0, 617, 618, 3, 170, 85, 0, 618, 622, 5, 143, 0, 0, 619, 621, 3, 66, 33, 0, 620, 619, 1, 0, 0, 0, 621, 624, 1, 0, 0, 0, 622, 620, 1, 0, 0, 0, 622, 623, 1, 0, 0, 0, 623, 625, 1, 0, 0, 0, 624, 622, 1, 0, 0, 0, 625, 626, 5, 144, 0, 0, 626, 645, 1, 0, 0, 0, 627, 628, 5, 42, 0, 0, 628, 631, 3, 170, 85, 0, 629, 630, 5, 53, 0, 0, 630, 632, 3, 170, 85, 0, 631, 629, 1, 0, 0, 0, 631, 632, 1, 0, 0, 0, 632, 642, 1, 0, 0, 0, 633, 637, 5, 143, 0, 0, 634, 636, 3, 66, 33, 0, 635, 634, 1, 0, 0, 0, 636, 639, 1, 0, 0, 0, 637, 635, 1, 0, 0, 0, 637, 638, 1, 0, 0, 0, 638, 640, 1, 0, 0, 0, 639, 637, 1, 0, 0, 0, 640, 643, 5, 144, 0, 0, 641, 643, 5, 140, 0, 0, 642, 633, 1, 0, 0, 0, 642, 641, 1, 0, 0, 0, 643, 645, 1, 0, 0, 0, 644, 616, 1, 0, 0, 0, 644, 627, 1, 0, 0, 0, 645, 63, 1, 0, 0, 0, 646, 647, 5, 24, 0, 0, 647, 648, 3, 168, 84, 0, 648, 649, 5, 90, 0, 0, 649, 650, 3, 170, 85, 0, 650, 651, 5, 26, 0, 0, 651, 652, 3, 168, 84, 0, 652, 653, 3, 30, 15, 0, 653, 654, 5, 140, 0, 0, 654, 65, 1, 0, 0, 0, 655, 656, 5, 18, 0, 0, 656, 713, 5, 140, 0, 0, 657, 658, 5, 19, 0, 0, 658, 659, 5, 20, 0, 0, 659, 660, 3, 168, 84, 0, 660, 661, 5, 140, 0, 0, 661, 713, 1, 0, 0, 0, 662, 663, 5, 29, 0, 0, 663, 664, 3, 170, 85, 0, 664, 665, 5, 140, 0, 0, 665, 713, 1, 0, 0, 0, 666, 667, 5, 29, 0, 0, 667, 668, 3, 168, 84, 0, 668, 669, 5, 142, 0, 0, 669, 679, 3, 168, 84, 0, 670, 671, 5, 68, 0, 0, 671, 675, 5, 143, 0, 0, 672, 674, 3, 68, 34, 0, 673, 672, 1, 0, 0, 0, 674, 677, 1, 0, 0, 0, 675, 673, 1, 0, 0, 0, 675, 676, 1, 0, 0, 0, 676, 678, 1, 0, 0, 0, 677, 675, 1, 0, 0, 0, 678, 680, 5, 144, 0, 0, 679, 670, 1, 0, 0, 0, 679, 680, 1, 0, 0, 0, 680, 681, 1, 0, 0, 0, 681, 682, 5, 140, 0, 0, 682, 713, 1, 0, 0, 0, 683, 685, 5, 30, 0, 0, 684, 686, 5, 38, 0, 0, 685, 684, 1, 0, 0, 0, 685, 686, 1, 0, 0, 0, 686, 687, 1, 0, 0, 0, 687, 688, 3, 168, 84, 0, 688, 689, 5, 59, 0, 0, 689, 690, 3, 28, 14, 0, 690, 691, 5, 140, 0, 0, 691, 713, 1, 0, 0, 0, 692, 693, 5, 31, 0, 0, 693, 694, 3, 168, 84, 0, 694, 695, 5, 153, 0, 0, 695, 696, 5, 140, 0, 0, 696, 713, 1, 0, 0, 0, 697, 698, 5, 32, 0, 0, 698, 699, 3, 28, 14, 0, 699, 700, 5, 142, 0, 0, 700, 701, 3, 168, 84, 0, 701, 702, 3, 168, 84, 0, 702, 703, 3, 170, 85, 0, 703, 704, 5, 140, 0, 0, 704, 713, 1, 0, 0, 0, 705, 706, 5, 99, 0, 0, 706, 713, 5, 140, 0, 0, 707, 708, 5, 33, 0, 0, 708, 709, 5, 153, 0, 0, 709, 710, 3, 170, 85, 0, 710, 711, 5, 140, 0, 0, 711, 713, 1, 0, 0, 0, 712, 655, 1, 0, 0, 0, 712, 657, 1, 0, 0, 0, 712, 662, 1, 0, 0, 0, 712, 666, 1, 0, 0, 0, 712, 683, 1, 0, 0, 0, 712, 692, 1, 0, 0, 0, 712, 697, 1, 0, 0, 0, 712, 705, 1, 0, 0, 0, 712, 707, 1, 0, 0, 0, 713, 67, 1, 0, 0, 0, 714, 715, 3, 168, 84, 0, 715, 716, 5, 62, 0, 0, 716, 717, 3, 168, 84, 0, 717, 718, 5, 140, 0, 0, 718, 69, 1, 0, 0, 0, 719, 720, 5, 53, 0, 0, 720, 722, 3, 168, 84, 0, 721, 723, 3, 24, 12, 0, 722, 721, 1, 0, 0, 0, 722, 723, 1, 0, 0, 0, 723, 724, 1, 0, 0, 0, 724, 725, 5, 90, 0, 0, 725, 726, 3, 170, 85, 0, 726, 727, 5, 139, 0, 0, 727, 728, 3, 150, 75, 0, 728, 729, 5, 138, 0, 0, 729, 730, 3, 150, 75, 0, 730, 731, 5, 92, 0, 0, 731, 733, 3, 86, 43, 0, 732, 734, 3, 78, 39, 0, 733, 732, 1, 0, 0, 0, 733, 734, 1, 0, 0, 0, 734, 736, 1, 0, 0, 0, 735, 737, 3, 84, 42, 0, 736, 735, 1, 0, 0, 0, 736, 737, 1, 0, 0, 0, 737, 738, 1, 0, 0, 0, 738, 739, 5, 94, 0, 0, 739, 741, 3, 88, 44, 0, 740, 742, 3, 92, 46, 0, 741, 740, 1, 0, 0, 0, 741, 742, 1, 0, 0, 0, 742, 744, 1, 0, 0, 0, 743, 745, 3, 76, 38, 0, 744, 743, 1, 0, 0, 0, 744, 745, 1, 0, 0, 0, 745, 747, 1, 0, 0, 0, 746, 748, 3, 74, 37, 0, 747, 746, 1, 0, 0, 0, 747, 748, 1, 0, 0, 0, 748, 749, 1, 0, 0, 0, 749, 750, 5, 140, 0, 0, 750, 71, 1, 0, 0, 0, 751, 752, 5, 54, 0, 0, 752, 754, 3, 168, 84, 0, 753, 755, 3, 24, 12, 0, 754, 753, 1, 0, 0, 0, 754, 755, 1, 0, 0, 0, 755, 756, 1, 0, 0, 0, 756, 757, 5, 90, 0, 0, 757, 758, 3, 170, 85, 0, 758, 759, 5, 139, 0, 0, 759, 760, 3, 150, 75, 0, 760, 761, 5, 138, 0, 0, 761, 763, 3, 150, 75, 0, 762, 764, 3, 78, 39, 0, 763, 762, 1, 0, 0, 0, 763, 764, 1, 0, 0, 0, 764, 766, 1, 0, 0, 0, 765, 767, 3, 92, 46, 0, 766, 765, 1, 0, 0, 0, 766, 767, 1, 0, 0, 0, 767, 769, 1, 0, 0, 0, 768, 770, 3, 76, 38, 0, 769, 768, 1, 0, 0, 0, 769, 770, 1, 0, 0, 0, 770, 772, 1, 0, 0, 0, 771, 773, 3, 74, 37, 0, 772, 771, 1, 0, 0, 0, 772, 773, 1, 0, 0, 0, 773, 774, 1, 0, 0, 0, 774, 775, 5, 140, 0, 0, 775, 73, 1, 0, 0, 0, 776, 777, 5, 70, 0, 0, 777, 778, 5, 69, 0, 0, 778, 779, 3, 168, 84, 0, 779, 75, 1, 0, 0, 0, 780, 781, 5, 9, 0, 0, 781, 782, 5, 7, 0, 0, 782, 783, 3, 168, 84, 0, 783, 784, 5, 69, 0, 0, 784, 785, 3, 170, 85, 0, 785, 77, 1, 0, 0, 0, 786, 787, 5, 12, 0, 0, 787, 798, 5, 15, 0, 0, 788, 789, 5, 12, 0, 0, 789, 795, 7, 2, 0, 0, 790, 791, 5, 16, 0, 0, 791, 792, 5, 145, 0, 0, 792, 793, 3, 90, 45, 0, 793, 794, 5, 146, 0, 0, 794, 796, 1, 0, 0, 0, 795, 790, 1, 0, 0, 0, 795, 796, 1, 0, 0, 0, 796, 798, 1, 0, 0, 0, 797, 786, 1, 0, 0, 0, 797, 788, 1, 0, 0, 0, 798, 79, 1, 0, 0, 0, 799, 800, 5, 69, 0, 0, 800, 801, 5, 145, 0, 0, 801, 806, 3, 132, 66, 0, 802, 803, 5, 141, 0, 0, 803, 805, 3, 132, 66, 0, 804, 802, 1, 0, 0, 0, 805, 808, 1, 0, 0, 0, 806, 804, 1, 0, 0, 0, 806, 807, 1, 0, 0, 0, 807, 809, 1, 0, 0, 0, 808, 806, 1, 0, 0, 0, 809, 810, 5, 146, 0, 0, 810, 81, 1, 0, 0, 0, 811, 812, 5, 55, 0, 0, 812, 813, 3, 168, 84, 0, 813, 814, 5, 90, 0, 0, 814, 815, 3, 170, 85, 0, 815, 816, 5, 56, 0, 0, 816, 817, 3, 168, 84, 0, 817, 818, 5, 139, 0, 0, 818, 820, 3, 150, 75, 0, 819, 821, 3, 92, 46, 0, 820, 819, 1, 0, 0, 0, 820, 821, 1, 0, 0, 0, 821, 822, 1, 0, 0, 0, 822, 823, 5, 140, 0, 0, 823, 83, 1, 0, 0, 0, 824, 825, 5, 93, 0, 0, 825, 826, 3, 150, 75, 0, 826, 85, 1, 0, 0, 0, 827, 828, 7, 3, 0, 0, 828, 87, 1, 0, 0, 0, 829, 848, 5, 96, 0, 0, 830, 831, 5, 46, 0, 0, 831, 848, 3, 168, 84, 0, 832, 833, 5, 38, 0, 0, 833, 848, 3, 168, 84, 0, 834, 835, 5, 48, 0, 0, 835, 844, 5, 145, 0, 0, 836, 841, 3, 28, 14, 0, 837, 838, 5, 141, 0, 0, 838, 840, 3, 28, 14, 0, 839, 837, 1, 0, 0, 0, 840, 843, 1, 0, 0, 0, 841, 839, 1, 0, 0, 0, 841, 842, 1, 0, 0, 0, 842, 845, 1, 0, 0, 0, 843, 841, 1, 0, 0, 0, 844, 836, 1, 0, 0, 0, 844, 845, 1, 0, 0, 0, 845, 846, 1, 0, 0, 0, 846, 848, 5, 146, 0, 0, 847, 829, 1, 0, 0, 0, 847, 830, 1, 0, 0, 0, 847, 832, 1, 0, 0, 0, 847, 834, 1, 0, 0, 0, 848, 89, 1, 0, 0, 0, 849, 854, 3, 168, 84, 0, 850, 851, 5, 141, 0, 0, 851, 853, 3, 168, 84, 0, 852, 850, 1, 0, 0, 0, 853, 856, 1, 0, 0, 0, 854, 852, 1, 0, 0, 0, 854, 855, 1, 0, 0, 0, 855, 91, 1, 0, 0, 0, 856, 854, 1, 0, 0, 0, 857, 858, 5, 95, 0, 0, 858, 862, 5, 143, 0, 0, 859, 861, 3, 94, 47, 0, 860, 859, 1, 0, 0, 0, 861, 864, 1, 0, 0, 0, 862, 860, 1, 0, 0, 0, 862, 863, 1, 0, 0, 0, 863, 865, 1, 0, 0, 0, 864, 862, 1, 0, 0, 0, 865, 866, 5, 144, 0, 0, 866, 93, 1, 0, 0, 0, 867, 868, 5, 25, 0, 0, 868, 869, 3, 168, 84, 0, 869, 870, 5, 90, 0, 0, 870, 871, 3, 170, 85, 0, 871, 872, 5, 145, 0, 0, 872, 873, 3, 90, 45, 0, 873, 874, 5, 146, 0, 0, 874, 875, 5, 140, 0, 0, 875, 950, 1, 0, 0, 0, 876, 877, 5, 65, 0, 0, 877, 878, 3, 168, 84, 0, 878, 879, 5, 90, 0, 0, 879, 880, 3, 170, 85, 0, 880, 881, 5, 139, 0, 0, 881, 882, 3, 150, 75, 0, 882, 883, 3, 96, 48, 0, 883, 884, 5, 140, 0, 0, 884, 950, 1, 0, 0, 0, 885, 886, 5, 66, 0, 0, 886, 887, 3, 168, 84, 0, 887, 888, 5, 90, 0, 0, 888, 889, 3, 170, 85, 0, 889, 890, 5, 139, 0, 0, 890, 891, 3, 158, 79, 0, 891, 894, 3, 52, 26, 0, 892, 893, 5, 88, 0, 0, 893, 895, 3, 170, 85, 0, 894, 892, 1, 0, 0, 0, 894, 895, 1, 0, 0, 0, 895, 896, 1, 0, 0, 0, 896, 897, 3, 96, 48, 0, 897, 898, 5, 140, 0, 0, 898, 950, 1, 0, 0, 0, 899, 900, 5, 47, 0, 0, 900, 901, 3, 168, 84, 0, 901, 902, 5, 90, 0, 0, 902, 903, 3, 170, 85, 0, 903, 904, 5, 139, 0, 0, 904, 906, 3, 168, 84, 0, 905, 907, 3, 30, 15, 0, 906, 905, 1, 0, 0, 0, 906, 907, 1, 0, 0, 0, 907, 909, 1, 0, 0, 0, 908, 910, 3, 80, 40, 0, 909, 908, 1, 0, 0, 0, 909, 910, 1, 0, 0, 0, 910, 913, 1, 0, 0, 0, 911, 912, 5, 20, 0, 0, 912, 914, 5, 57, 0, 0, 913, 911, 1, 0, 0, 0, 913, 914, 1, 0, 0, 0, 914, 915, 1, 0, 0, 0, 915, 916, 5, 140, 0, 0, 916, 950, 1, 0, 0, 0, 917, 918, 5, 7, 0, 0, 918, 919, 3, 168, 84, 0, 919, 920, 5, 90, 0, 0, 920, 921, 3, 170, 85, 0, 921, 922, 5, 139, 0, 0, 922, 923, 3, 168, 84, 0, 923, 924, 5, 57, 0, 0, 924, 925, 3, 150, 75, 0, 925, 926, 5, 140, 0, 0, 926, 950, 1, 0, 0, 0, 927, 928, 5, 55, 0, 0, 928, 929, 3, 168, 84, 0, 929, 930, 5, 90, 0, 0, 930, 931, 3, 170, 85, 0, 931, 932, 5, 139, 0, 0, 932, 935, 3, 168, 84, 0, 933, 934, 5, 57, 0, 0, 934, 936, 3, 150, 75, 0, 935, 933, 1, 0, 0, 0, 935, 936, 1, 0, 0, 0, 936, 937, 1, 0, 0, 0, 937, 938, 5, 140, 0, 0, 938, 950, 1, 0, 0, 0, 939, 940, 5, 24, 0, 0, 940, 941, 3, 168, 84, 0, 941, 942, 5, 90, 0, 0, 942, 943, 3, 170, 85, 0, 943, 944, 5, 139, 0, 0, 944, 945, 3, 168, 84, 0, 945, 946, 5, 142, 0, 0, 946, 947, 3, 168, 84, 0, 947, 948, 5, 140, 0, 0, 948, 950, 1, 0, 0, 0, 949, 867, 1, 0, 0, 0, 949, 876, 1, 0, 0, 0, 949, 885, 1, 0, 0, 0, 949, 899, 1, 0, 0, 0, 949, 917, 1, 0, 0, 0, 949, 927, 1, 0, 0, 0, 949, 939, 1, 0, 0, 0, 950, 95, 1, 0, 0, 0, 951, 952, 5, 145, 0, 0, 952, 957, 3, 98, 49, 0, 953, 954, 5, 141, 0, 0, 954, 956, 3, 98, 49, 0, 955, 953, 1, 0, 0, 0, 956, 959, 1, 0, 0, 0, 957, 955, 1, 0, 0, 0, 957, 958, 1, 0, 0, 0, 958, 960, 1, 0, 0, 0, 959, 957, 1, 0, 0, 0, 960, 961, 5, 146, 0, 0, 961, 97, 1, 0, 0, 0, 962, 963, 7, 4, 0, 0, 963, 99, 1, 0, 0, 0, 964, 965, 5, 64, 0, 0, 965, 966, 3, 102, 51, 0, 966, 101, 1, 0, 0, 0, 967, 970, 3, 104, 52, 0, 968, 970, 3, 108, 54, 0, 969, 967, 1, 0, 0, 0, 969, 968, 1, 0, 0, 0, 970, 103, 1, 0, 0, 0, 971, 972, 5, 65, 0, 0, 972, 973, 3, 168, 84, 0, 973, 974, 5, 90, 0, 0, 974, 975, 3, 170, 85, 0, 975, 976, 5, 75, 0, 0, 976, 977, 3, 168, 84, 0, 977, 978, 5, 139, 0, 0, 978, 979, 3, 150, 75, 0, 979, 980, 5, 76, 0, 0, 980, 983, 3, 106, 53, 0, 981, 982, 5, 77, 0, 0, 982, 984, 3, 160, 80, 0, 983, 981, 1, 0, 0, 0, 983, 984, 1, 0, 0, 0, 984, 985, 1, 0, 0, 0, 985, 986, 5, 140, 0, 0, 986, 105, 1, 0, 0, 0, 987, 1001, 5, 112, 0, 0, 988, 989, 5, 113, 0, 0, 989, 990, 5, 147, 0, 0, 990, 991, 3, 150, 75, 0, 991, 992, 5, 148, 0, 0, 992, 1001, 1, 0, 0, 0, 993, 994, 5, 85, 0, 0, 994, 1001, 3, 162, 81, 0, 995, 996, 5, 86, 0, 0, 996, 997, 5, 147, 0, 0, 997, 998, 3, 150, 75, 0, 998, 999, 5, 148, 0, 0, 999, 1001, 1, 0, 0, 0, 1000, 987, 1, 0, 0, 0, 1000, 988, 1, 0, 0, 0, 1000, 993, 1, 0, 0, 0, 1000, 995, 1, 0, 0, 0, 1001, 107, 1, 0, 0, 0, 1002, 1003, 5, 66, 0, 0, 1003, 1004, 3, 168, 84, 0, 1004, 1005, 5, 90, 0, 0, 1005, 1006, 3, 170, 85, 0, 1006, 1007, 5, 143, 0, 0, 1007, 1008, 3, 110, 55, 0, 1008, 1009, 3, 110, 55, 0, 1009, 1010, 5, 144, 0, 0, 1010, 109, 1, 0, 0, 0, 1011, 1012, 3, 52, 26, 0, 1012, 1013, 5, 67, 0, 0, 1013, 1014, 3, 168, 84, 0, 1014, 1015, 5, 90, 0, 0, 1015, 1016, 3, 170, 85, 0, 1016, 1018, 3, 158, 79, 0, 1017, 1019, 5, 118, 0, 0, 1018, 1017, 1, 0, 0, 0, 1018, 1019, 1, 0, 0, 0, 1019, 1022, 1, 0, 0, 0, 1020, 1021, 5, 83, 0, 0, 1021, 1023, 3, 170, 85, 0, 1022, 1020, 1, 0, 0, 0, 1022, 1023, 1, 0, 0, 0, 1023, 1025, 1, 0, 0, 0, 1024, 1026, 5, 87, 0, 0, 1025, 1024, 1, 0, 0, 0, 1025, 1026, 1, 0, 0, 0, 1026, 1029, 1, 0, 0, 0, 1027, 1028, 5, 88, 0, 0, 1028, 1030, 3, 170, 85, 0, 1029, 1027, 1, 0, 0, 0, 1029, 1030, 1, 0, 0, 0, 1030, 1032, 1, 0, 0, 0, 1031, 1033, 5, 89, 0, 0, 1032, 1031, 1, 0, 0, 0, 1032, 1033, 1, 0, 0, 0, 1033, 1034, 1, 0, 0, 0, 1034, 1035, 5, 140, 0, 0, 1035, 111, 1, 0, 0, 0, 1036, 1038, 5, 58, 0, 0, 1037, 1039, 3, 24, 12, 0, 1038, 1037, 1, 0, 0, 0, 1038, 1039, 1, 0, 0, 0, 1039, 1040, 1, 0, 0, 0, 1040, 1042, 3, 168, 84, 0, 1041, 1043, 3, 114, 57, 0, 1042, 1041, 1, 0, 0, 0, 1042, 1043, 1, 0, 0, 0, 1043, 1044, 1, 0, 0, 0, 1044, 1045, 5, 59, 0, 0, 1045, 1047, 3, 168, 84, 0, 1046, 1048, 3, 30, 15, 0, 1047, 1046, 1, 0, 0, 0, 1047, 1048, 1, 0, 0, 0, 1048, 1051, 1, 0, 0, 0, 1049, 1050, 5, 90, 0, 0, 1050, 1052, 3, 170, 85, 0, 1051, 1049, 1, 0, 0, 0, 1051, 1052, 1, 0, 0, 0, 1052, 1055, 1, 0, 0, 0, 1053, 1054, 5, 82, 0, 0, 1054, 1056, 5, 153, 0, 0, 1055, 1053, 1, 0, 0, 0, 1055, 1056, 1, 0, 0, 0, 1056, 1057, 1, 0, 0, 0, 1057, 1061, 5, 143, 0, 0, 1058, 1060, 3, 116, 58, 0, 1059, 1058, 1, 0, 0, 0, 1060, 1063, 1, 0, 0, 0, 1061, 1059, 1, 0, 0, 0, 1061, 1062, 1, 0, 0, 0, 1062, 1064, 1, 0, 0, 0, 1063, 1061, 1, 0, 0, 0, 1064, 1065, 5, 144, 0, 0, 1065, 113, 1, 0, 0, 0, 1066, 1067, 3, 30, 15, 0, 1067, 115, 1, 0, 0, 0, 1068, 1069, 5, 63, 0, 0, 1069, 1076, 3, 102, 51, 0, 1070, 1076, 3, 130, 65, 0, 1071, 1076, 3, 126, 63, 0, 1072, 1076, 3, 120, 60, 0, 1073, 1076, 3, 128, 64, 0, 1074, 1076, 3, 118, 59, 0, 1075, 1068, 1, 0, 0, 0, 1075, 1070, 1, 0, 0, 0, 1075, 1071, 1, 0, 0, 0, 1075, 1072, 1, 0, 0, 0, 1075, 1073, 1, 0, 0, 0, 1075, 1074, 1, 0, 0, 0, 1076, 117, 1, 0, 0, 0, 1077, 1078, 5, 60, 0, 0, 1078, 1079, 3, 168, 84, 0, 1079, 1080, 5, 62, 0, 0, 1080, 1081, 3, 168, 84, 0, 1081, 1082, 5, 142, 0, 0, 1082, 1083, 3, 168, 84, 0, 1083, 1084, 5, 140, 0, 0, 1084, 119, 1, 0, 0, 0, 1085, 1086, 5, 10, 0, 0, 1086, 1090, 5, 143, 0, 0, 1087, 1089, 3, 122, 61, 0, 1088, 1087, 1, 0, 0, 0, 1089, 1092, 1, 0, 0, 0, 1090, 1088, 1, 0, 0, 0, 1090, 1091, 1, 0, 0, 0, 1091, 1093, 1, 0, 0, 0, 1092, 1090, 1, 0, 0, 0, 1093, 1094, 5, 144, 0, 0, 1094, 1095, 5, 140, 0, 0, 1095, 121, 1, 0, 0, 0, 1096, 1097, 3, 168, 84, 0, 1097, 1098, 5, 62, 0, 0, 1098, 1099, 5, 65, 0, 0, 1099, 1100, 3, 168, 84, 0, 1100, 1101, 5, 140, 0, 0, 1101, 1111, 1, 0, 0, 0, 1102, 1103, 3, 168, 84, 0, 1103, 1104, 5, 62, 0, 0, 1104, 1105, 5, 66, 0, 0, 1105, 1106, 3, 168, 84, 0, 1106, 1107, 5, 142, 0, 0, 1107, 1108, 3, 168, 84, 0, 1108, 1109, 5, 140, 0, 0, 1109, 1111, 1, 0, 0, 0, 1110, 1096, 1, 0, 0, 0, 1110, 1102, 1, 0, 0, 0, 1111, 123, 1, 0, 0, 0, 1112, 1113, 3, 28, 14, 0, 1113, 1114, 5, 20, 0, 0, 1114, 1119, 5, 57, 0, 0, 1115, 1116, 5, 142, 0, 0, 1116, 1118, 3, 168, 84, 0, 1117, 1115, 1, 0, 0, 0, 1118, 1121, 1, 0, 0, 0, 1119, 1117, 1, 0, 0, 0, 1119, 1120, 1, 0, 0, 0, 1120, 1122, 1, 0, 0, 0, 1121, 1119, 1, 0, 0, 0, 1122, 1123, 5, 140, 0, 0, 1123, 125, 1, 0, 0, 0, 1124, 1125, 5, 60, 0, 0, 1125, 1126, 3, 168, 84, 0, 1126, 1127, 5, 62, 0, 0, 1127, 1128, 5, 65, 0, 0, 1128, 1129, 3, 168, 84, 0, 1129, 1130, 5, 140, 0, 0, 1130, 127, 1, 0, 0, 0, 1131, 1132, 5, 72, 0, 0, 1132, 1133, 3, 168, 84, 0, 1133, 1134, 5, 73, 0, 0, 1134, 1135, 5, 74, 0, 0, 1135, 1136, 5, 69, 0, 0, 1136, 1137, 5, 55, 0, 0, 1137, 1138, 3, 168, 84, 0, 1138, 1139, 5, 71, 0, 0, 1139, 1140, 5, 66, 0, 0, 1140, 1141, 3, 168, 84, 0, 1141, 1142, 5, 142, 0, 0, 1142, 1143, 3, 168, 84, 0, 1143, 1144, 5, 140, 0, 0, 1144, 129, 1, 0, 0, 0, 1145, 1146, 5, 60, 0, 0, 1146, 1147, 3, 132, 66, 0, 1147, 1148, 5, 62, 0, 0, 1148, 1151, 3, 136, 68, 0, 1149, 1150, 5, 36, 0, 0, 1150, 1152, 3, 170, 85, 0, 1151, 1149, 1, 0, 0, 0, 1151, 1152, 1, 0, 0, 0, 1152, 1153, 1, 0, 0, 0, 1153, 1154, 5, 140, 0, 0, 1154, 131, 1, 0, 0, 0, 1155, 1156, 3, 168, 84, 0, 1156, 1157, 5, 142, 0, 0, 1157, 1158, 3, 134, 67, 0, 1158, 133, 1, 0, 0, 0, 1159, 1172, 3, 168, 84, 0, 1160, 1172, 5, 107, 0, 0, 1161, 1172, 5, 97, 0, 0, 1162, 1172, 5, 98, 0, 0, 1163, 1172, 5, 1, 0, 0, 1164, 1172, 5, 104, 0, 0, 1165, 1172, 5, 105, 0, 0, 1166, 1172, 5, 106, 0, 0, 1167, 1172, 5, 108, 0, 0, 1168, 1172, 5, 109, 0, 0, 1169, 1172, 5, 110, 0, 0, 1170, 1172, 5, 111, 0, 0, 1171, 1159, 1, 0, 0, 0, 1171, 1160, 1, 0, 0, 0, 1171, 1161, 1, 0, 0, 0, 1171, 1162, 1, 0, 0, 0, 1171, 1163, 1, 0, 0, 0, 1171, 1164, 1, 0, 0, 0, 1171, 1165, 1, 0, 0, 0, 1171, 1166, 1, 0, 0, 0, 1171, 1167, 1, 0, 0, 0, 1171, 1168, 1, 0, 0, 0, 1171, 1169, 1, 0, 0, 0, 1171, 1170, 1, 0, 0, 0, 1172, 135, 1, 0, 0, 0, 1173, 1174, 5, 84, 0, 0, 1174, 1175, 5, 142, 0, 0, 1175, 1220, 3, 168, 84, 0, 1176, 1177, 5, 85, 0, 0, 1177, 1178, 3, 168, 84, 0, 1178, 1179, 5, 142, 0, 0, 1179, 1180, 3, 168, 84, 0, 1180, 1220, 1, 0, 0, 0, 1181, 1182, 5, 86, 0, 0, 1182, 1183, 3, 168, 84, 0, 1183, 1184, 5, 142, 0, 0, 1184, 1185, 3, 168, 84, 0, 1185, 1220, 1, 0, 0, 0, 1186, 1187, 5, 65, 0, 0, 1187, 1188, 3, 168, 84, 0, 1188, 1189, 5, 142, 0, 0, 1189, 1190, 3, 140, 70, 0, 1190, 1220, 1, 0, 0, 0, 1191, 1192, 5, 66, 0, 0, 1192, 1193, 3, 168, 84, 0, 1193, 1194, 5, 142, 0, 0, 1194, 1195, 3, 168, 84, 0, 1195, 1196, 5, 142, 0, 0, 1196, 1197, 3, 142, 71, 0, 1197, 1220, 1, 0, 0, 0, 1198, 1199, 5, 47, 0, 0, 1199, 1201, 3, 168, 84, 0, 1200, 1202, 3, 30, 15, 0, 1201, 1200, 1, 0, 0, 0, 1201, 1202, 1, 0, 0, 0, 1202, 1203, 1, 0, 0, 0, 1203, 1204, 5, 142, 0, 0, 1204, 1205, 3, 132, 66, 0, 1205, 1220, 1, 0, 0, 0, 1206, 1207, 5, 49, 0, 0, 1207, 1208, 3, 168, 84, 0, 1208, 1209, 5, 142, 0, 0, 1209, 1211, 3, 168, 84, 0, 1210, 1212, 3, 30, 15, 0, 1211, 1210, 1, 0, 0, 0, 1211, 1212, 1, 0, 0, 0, 1212, 1214, 1, 0, 0, 0, 1213, 1215, 3, 144, 72, 0, 1214, 1213, 1, 0, 0, 0, 1214, 1215, 1, 0, 0, 0, 1215, 1217, 1, 0, 0, 0, 1216, 1218, 3, 138, 69, 0, 1217, 1216, 1, 0, 0, 0, 1217, 1218, 1, 0, 0, 0, 1218, 1220, 1, 0, 0, 0, 1219, 1173, 1, 0, 0, 0, 1219, 1176, 1, 0, 0, 0, 1219, 1181, 1, 0, 0, 0, 1219, 1186, 1, 0, 0, 0, 1219, 1191, 1, 0, 0, 0, 1219, 1198, 1, 0, 0, 0, 1219, 1206, 1, 0, 0, 0, 1220, 137, 1, 0, 0, 0, 1221, 1222, 5, 70, 0, 0, 1222, 1223, 3, 144, 72, 0, 1223, 139, 1, 0, 0, 0, 1224, 1225, 7, 5, 0, 0, 1225, 141, 1, 0, 0, 0, 1226, 1227, 7, 6, 0, 0, 1227, 143, 1, 0, 0, 0, 1228, 1229, 5, 68, 0, 0, 1229, 1233, 5, 143, 0, 0, 1230, 1232, 3, 146, 73, 0, 1231, 1230, 1, 0, 0, 0, 1232, 1235, 1, 0, 0, 0, 1233, 1231, 1, 0, 0, 0, 1233, 1234, 1, 0, 0, 0, 1234, 1236, 1, 0, 0, 0, 1235, 1233, 1, 0, 0, 0, 1236, 1237, 5, 144, 0, 0, 1237, 145, 1, 0, 0, 0, 1238, 1239, 3, 168, 84, 0, 1239, 1240, 5, 62, 0, 0, 1240, 1241, 5, 11, 0, 0, 1241, 1242, 5, 25, 0, 0, 1242, 1243, 5, 145, 0, 0, 1243, 1244, 3, 90, 45, 0, 1244, 1245, 5, 146, 0, 0, 1245, 1246, 5, 140, 0, 0, 1246, 1337, 1, 0, 0, 0, 1247, 1248, 3, 168, 84, 0, 1248, 1249, 5, 62, 0, 0, 1249, 1250, 5, 65, 0, 0, 1250, 1257, 3, 168, 84, 0, 1251, 1252, 5, 71, 0, 0, 1252, 1253, 5, 66, 0, 0, 1253, 1254, 3, 168, 84, 0, 1254, 1255, 5, 142, 0, 0, 1255, 1256, 3, 168, 84, 0, 1256, 1258, 1, 0, 0, 0, 1257, 1251, 1, 0, 0, 0, 1257, 1258, 1, 0, 0, 0, 1258, 1259, 1, 0, 0, 0, 1259, 1260, 5, 140, 0, 0, 1260, 1337, 1, 0, 0, 0, 1261, 1262, 3, 168, 84, 0, 1262, 1263, 5, 62, 0, 0, 1263, 1264, 5, 66, 0, 0, 1264, 1265, 3, 168, 84, 0, 1265, 1266, 5, 142, 0, 0, 1266, 1273, 3, 168, 84, 0, 1267, 1268, 5, 71, 0, 0, 1268, 1269, 5, 66, 0, 0, 1269, 1270, 3, 168, 84, 0, 1270, 1271, 5, 142, 0, 0, 1271, 1272, 3, 168, 84, 0, 1272, 1274, 1, 0, 0, 0, 1273, 1267, 1, 0, 0, 0, 1273, 1274, 1, 0, 0, 0, 1274, 1275, 1, 0, 0, 0, 1275, 1276, 5, 140, 0, 0, 1276, 1337, 1, 0, 0, 0, 1277, 1278, 3, 168, 84, 0, 1278, 1279, 5, 62, 0, 0, 1279, 1280, 5, 47, 0, 0, 1280, 1282, 3, 168, 84, 0, 1281, 1283, 3, 30, 15, 0, 1282, 1281, 1, 0, 0, 0, 1282, 1283, 1, 0, 0, 0, 1283, 1292, 1, 0, 0, 0, 1284, 1285, 5, 71, 0, 0, 1285, 1286, 5, 66, 0, 0, 1286, 1287, 3, 168, 84, 0, 1287, 1288, 5, 142, 0, 0, 1288, 1289, 3, 168, 84, 0, 1289, 1293, 1, 0, 0, 0, 1290, 1291, 5, 20, 0, 0, 1291, 1293, 5, 57, 0, 0, 1292, 1284, 1, 0, 0, 0, 1292, 1290, 1, 0, 0, 0, 1292, 1293, 1, 0, 0, 0, 1293, 1294, 1, 0, 0, 0, 1294, 1295, 5, 140, 0, 0, 1295, 1337, 1, 0, 0, 0, 1296, 1297, 3, 168, 84, 0, 1297, 1298, 5, 62, 0, 0, 1298, 1299, 5, 7, 0, 0, 1299, 1300, 3, 168, 84, 0, 1300, 1301, 5, 68, 0, 0, 1301, 1305, 5, 143, 0, 0, 1302, 1304, 3, 124, 62, 0, 1303, 1302, 1, 0, 0, 0, 1304, 1307, 1, 0, 0, 0, 1305, 1303, 1, 0, 0, 0, 1305, 1306, 1, 0, 0, 0, 1306, 1308, 1, 0, 0, 0, 1307, 1305, 1, 0, 0, 0, 1308, 1309, 5, 144, 0, 0, 1309, 1310, 5, 140, 0, 0, 1310, 1337, 1, 0, 0, 0, 1311, 1312, 3, 168, 84, 0, 1312, 1313, 5, 62, 0, 0, 1313, 1314, 5, 55, 0, 0, 1314, 1315, 3, 168, 84, 0, 1315, 1316, 5, 140, 0, 0, 1316, 1337, 1, 0, 0, 0, 1317, 1318, 3, 168, 84, 0, 1318, 1320, 5, 62, 0, 0, 1319, 1321, 5, 11, 0, 0, 1320, 1319, 1, 0, 0, 0, 1320, 1321, 1, 0, 0, 0, 1321, 1322, 1, 0, 0, 0, 1322, 1323, 5, 24, 0, 0, 1323, 1324, 3, 168, 84, 0, 1324, 1325, 5, 142, 0, 0, 1325, 1332, 3, 168, 84, 0, 1326, 1327, 5, 71, 0, 0, 1327, 1328, 5, 66, 0, 0, 1328, 1329, 3, 168, 84, 0, 1329, 1330, 5, 142, 0, 0, 1330, 1331, 3, 168, 84, 0, 1331, 1333, 1, 0, 0, 0, 1332, 1326, 1, 0, 0, 0, 1332, 1333, 1, 0, 0, 0, 1333, 1334, 1, 0, 0, 0, 1334, 1335, 5, 140, 0, 0, 1335, 1337, 1, 0, 0, 0, 1336, 1238, 1, 0, 0, 0, 1336, 1247, 1, 0, 0, 0, 1336, 1261, 1, 0, 0, 0, 1336, 1277, 1, 0, 0, 0, 1336, 1296, 1, 0, 0, 0, 1336, 1311, 1, 0, 0, 0, 1336, 1317, 1, 0, 0, 0, 1337, 147, 1, 0, 0, 0, 1338, 1339, 5, 55, 0, 0, 1339, 1340, 3, 168, 84, 0, 1340, 1341, 5, 62, 0, 0, 1341, 1342, 3, 168, 84, 0, 1342, 1343, 5, 142, 0, 0, 1343, 1345, 3, 168, 84, 0, 1344, 1346, 3, 144, 72, 0, 1345, 1344, 1, 0, 0, 0, 1345, 1346, 1, 0, 0, 0, 1346, 1347, 1, 0, 0, 0, 1347, 1348, 5, 140, 0, 0, 1348, 149, 1, 0, 0, 0, 1349, 1407, 3, 156, 78, 0, 1350, 1407, 5, 119, 0, 0, 1351, 1407, 5, 120, 0, 0, 1352, 1353, 5, 121, 0, 0, 1353, 1407, 3, 170, 85, 0, 1354, 1355, 5, 122, 0, 0, 1355, 1356, 5, 149, 0, 0, 1356, 1357, 3, 168, 84, 0, 1357, 1358, 5, 150, 0, 0, 1358, 1407, 1, 0, 0, 0, 1359, 1360, 5, 123, 0, 0, 1360, 1361, 5, 149, 0, 0, 1361, 1363, 3, 168, 84, 0, 1362, 1364, 3, 30, 15, 0, 1363, 1362, 1, 0, 0, 0, 1363, 1364, 1, 0, 0, 0, 1364, 1365, 1, 0, 0, 0, 1365, 1366, 5, 150, 0, 0, 1366, 1407, 1, 0, 0, 0, 1367, 1368, 5, 41, 0, 0, 1368, 1369, 5, 149, 0, 0, 1369, 1370, 3, 168, 84, 0, 1370, 1371, 5, 150, 0, 0, 1371, 1407, 1, 0, 0, 0, 1372, 1373, 5, 124, 0, 0, 1373, 1374, 5, 149, 0, 0, 1374, 1375, 3, 150, 75, 0, 1375, 1376, 5, 150, 0, 0, 1376, 1407, 1, 0, 0, 0, 1377, 1378, 5, 125, 0, 0, 1378, 1379, 5, 149, 0, 0, 1379, 1380, 3, 150, 75, 0, 1380, 1381, 5, 150, 0, 0, 1381, 1407, 1, 0, 0, 0, 1382, 1383, 5, 126, 0, 0, 1383, 1387, 5, 143, 0, 0, 1384, 1386, 3, 152, 76, 0, 1385, 1384, 1, 0, 0, 0, 1386, 1389, 1, 0, 0, 0, 1387, 1385, 1, 0, 0, 0, 1387, 1388, 1, 0, 0, 0, 1388, 1390, 1, 0, 0, 0, 1389, 1387, 1, 0, 0, 0, 1390, 1407, 5, 144, 0, 0, 1391, 1392, 5, 8, 0, 0, 1392, 1393, 5, 90, 0, 0, 1393, 1394, 3, 170, 85, 0, 1394, 1396, 5, 143, 0, 0, 1395, 1397, 3, 154, 77, 0, 1396, 1395, 1, 0, 0, 0, 1397, 1398, 1, 0, 0, 0, 1398, 1396, 1, 0, 0, 0, 1398, 1399, 1, 0, 0, 0, 1399, 1400, 1, 0, 0, 0, 1400, 1401, 5, 144, 0, 0, 1401, 1407, 1, 0, 0, 0, 1402, 1404, 3, 168, 84, 0, 1403, 1405, 3, 30, 15, 0, 1404, 1403, 1, 0, 0, 0, 1404, 1405, 1, 0, 0, 0, 1405, 1407, 1, 0, 0, 0, 1406, 1349, 1, 0, 0, 0, 1406, 1350, 1, 0, 0, 0, 1406, 1351, 1, 0, 0, 0, 1406, 1352, 1, 0, 0, 0, 1406, 1354, 1, 0, 0, 0, 1406, 1359, 1, 0, 0, 0, 1406, 1367, 1, 0, 0, 0, 1406, 1372, 1, 0, 0, 0, 1406, 1377, 1, 0, 0, 0, 1406, 1382, 1, 0, 0, 0, 1406, 1391, 1, 0, 0, 0, 1406, 1402, 1, 0, 0, 0, 1407, 151, 1, 0, 0, 0, 1408, 1409, 3, 168, 84, 0, 1409, 1410, 5, 139, 0, 0, 1410, 1411, 3, 150, 75, 0, 1411, 1412, 5, 140, 0, 0, 1412, 153, 1, 0, 0, 0, 1413, 1414, 3, 168, 84, 0, 1414, 1415, 5, 90, 0, 0, 1415, 1416, 3, 170, 85, 0, 1416, 1417, 5, 139, 0, 0, 1417, 1418, 3, 150, 75, 0, 1418, 1419, 5, 140, 0, 0, 1419, 155, 1, 0, 0, 0, 1420, 1421, 7, 7, 0, 0, 1421, 157, 1, 0, 0, 0, 1422, 1423, 7, 8, 0, 0, 1423, 159, 1, 0, 0, 0, 1424, 1433, 3, 170, 85, 0, 1425, 1433, 5, 153, 0, 0, 1426, 1433, 5, 154, 0, 0, 1427, 1433, 5, 135, 0, 0, 1428, 1433, 5, 136, 0, 0, 1429, 1433, 5, 137, 0, 0, 1430, 1433, 3, 162, 81, 0, 1431, 1433, 3, 166, 83, 0, 1432, 1424, 1, 0, 0, 0, 1432, 1425, 1, 0, 0, 0, 1432, 1426, 1, 0, 0, 0, 1432, 1427, 1, 0, 0, 0, 1432, 1428, 1, 0, 0, 0, 1432, 1429, 1, 0, 0, 0, 1432, 1430, 1, 0, 0, 0, 1432, 1431, 1, 0, 0, 0, 1433, 161, 1, 0, 0, 0, 1434, 1443, 5, 143, 0, 0, 1435, 1440, 3, 164, 82, 0, 1436, 1437, 5, 141, 0, 0, 1437, 1439, 3, 164, 82, 0, 1438, 1436, 1, 0, 0, 0, 1439, 1442, 1, 0, 0, 0, 1440, 1438, 1, 0, 0, 0, 1440, 1441, 1, 0, 0, 0, 1441, 1444, 1, 0, 0, 0, 1442, 1440, 1, 0, 0, 0, 1443, 1435, 1, 0, 0, 0, 1443, 1444, 1, 0, 0, 0, 1444, 1445, 1, 0, 0, 0, 1445, 1446, 5, 144, 0, 0, 1446, 163, 1, 0, 0, 0, 1447, 1448, 3, 170, 85, 0, 1448, 1449, 5, 139, 0, 0, 1449, 1450, 3, 160, 80, 0, 1450, 165, 1, 0, 0, 0, 1451, 1460, 5, 145, 0, 0, 1452, 1457, 3, 160, 80, 0, 1453, 1454, 5, 141, 0, 0, 1454, 1456, 3, 160, 80, 0, 1455, 1453, 1, 0, 0, 0, 1456, 1459, 1, 0, 0, 0, 1457, 1455, 1, 0, 0, 0, 1457, 1458, 1, 0, 0, 0, 1458, 1461, 1, 0, 0, 0, 1459, 1457, 1, 0, 0, 0, 1460, 1452, 1, 0, 0, 0, 1460, 1461, 1, 0, 0, 0, 1461, 1462, 1, 0, 0, 0, 1462, 1463, 5, 146, 0, 0, 1463, 167, 1, 0, 0, 0, 1464, 1465, 7, 9, 0, 0, 1465, 169, 1, 0, 0, 0, 1466, 1467, 5, 156, 0, 0, 1467, 171, 1, 0, 0, 0, 130, 184, 191, 212, 225, 237, 264, 272, 279, 285, 297, 300, 306, 317, 326, 336, 339, 341, 345, 353, 365, 370, 380, 390, 395, 410, 416, 428, 437, 445, 453, 466, 469, 479, 483, 489, 496, 502, 510, 518, 531, 538, 542, 547, 558, 564, 575, 601, 605, 611, 622, 631, 637, 642, 644, 675, 679, 685, 712, 722, 733, 736, 741, 744, 747, 754, 763, 766, 769, 772, 795, 797, 806, 820, 841, 844, 847, 854, 862, 894, 906, 909, 913, 935, 949, 957, 969, 983, 1000, 1018, 1022, 1025, 1029, 1032, 1038, 1042, 1047, 1051, 1055, 1061, 1075, 1090, 1110, 1119, 1151, 1171, 1201, 1211, 1214, 1217, 1219, 1233, 1257, 1273, 1282, 1292, 1305, 1320, 1332, 1336, 1345, 1363, 1387, 1398, 1404, 1406, 1432, 1440, 1443, 1457, 1460] \ No newline at end of file diff --git a/src/capability-language/generated/QuixosCapability.tokens b/src/capability-language/generated/QuixosCapability.tokens index c90ad75..ec69fb8 100644 --- a/src/capability-language/generated/QuixosCapability.tokens +++ b/src/capability-language/generated/QuixosCapability.tokens @@ -1,259 +1,311 @@ -WORKSPACE=1 -RESULT_BYTES=2 -RPC_CALLS=3 -DEADLINE_MS=4 -QUERY=5 -SPECIALIZE=6 -ROOT=7 -DOCUMENT=8 -FRAGMENTS=9 -VIEW=10 -MAX=11 -ALLOW=12 -POLL=13 -QUERYABLE=14 -RPC=15 -QUERY_REASON=16 -TYPE=17 -OBJECT=18 -STORABLE=19 -IMPLEMENTS=20 -REF=21 -FRAGMENT=22 -IMPORT=23 -EXTERNAL=24 -ATOM=25 -INTERFACE=26 -INTERFACES=27 -PACKAGE=28 -VALUE=29 -RELATION=30 -OPERATION=31 -FUNCTION=32 -CONSTRUCTOR=33 -CONSTRUCTS=34 -INPUT=35 -CONFORM=36 -AS=37 -BIND=38 -STATIC=39 -TO=40 -PRIVATE=41 -SHARED=42 -STATE=43 -EDGE=44 -PROJECTION=45 -WITH=46 -USING=47 -VIA=48 -MATERIALIZE=49 -IF=50 -ABSENT=51 -ON=52 -POLICY=53 -DEFAULT=54 -SOURCE=55 -REPOSITORY=56 -COMMIT=57 -REVISION=58 -SEMANTIC_MAJOR=59 -ON_DELETE=60 -RETAIN_OTHER=61 -KEYED=62 -PUBLIC_TRAVERSAL=63 -ID=64 -DOC=65 -MODE=66 -EMITS=67 -RECEIVER=68 -REQUIRES=69 -ANY=70 -GET=71 -SET=72 -WATCH=73 -START=74 -STOP=75 -READ=76 -WRITE=77 -RESOLVE=78 -CONNECT=79 -DISCONNECT=80 -CALL=81 -WATCH_START=82 -WATCH_STOP=83 -SUBSCRIBE=84 -UNSUBSCRIBE=85 -OPTIMISTIC_REGISTER=86 -CRDT=87 -OPTIONAL_ONE=88 -EXACTLY_ONE=89 -MANY_UNIQUE=90 -MANY=91 -ORDERED=92 -UNIT=93 -WATCH_HANDLE=94 -MESSAGE=95 -ATOM_REF=96 -INTERFACE_REF=97 -OPTIONAL=98 -LIST=99 -RECORD=100 -BOOL=101 -BYTES=102 -DOUBLE=103 -INT32=104 -INT64=105 -STRING=106 -UINT32=107 -UINT64=108 -TRUE=109 -FALSE=110 -NULL=111 -ARROW=112 -COLON=113 -SEMI=114 -COMMA=115 -DOT=116 -LBRACE=117 -RBRACE=118 -LBRACK=119 -RBRACK=120 -LPAREN=121 -RPAREN=122 -LT=123 -GT=124 -AMP=125 -EQUAL=126 -INTEGER=127 -JSON_NUMBER=128 -IDENTIFIER=129 -STRING_LITERAL=130 -LINE_COMMENT=131 -BLOCK_COMMENT=132 -WS=133 -'workspace'=1 -'result-bytes'=2 -'rpc-calls'=3 -'deadline-ms'=4 -'query'=5 -'specialize'=6 -'root'=7 -'document'=8 -'fragments'=9 -'view'=10 -'max'=11 -'allow'=12 -'poll'=13 -'queryable'=14 -'rpc'=15 -'query-reason'=16 -'type'=17 -'object'=18 -'storable'=19 -'implements'=20 -'ref'=21 -'fragment'=22 -'import'=23 -'external'=24 -'atom'=25 -'interface'=26 -'interfaces'=27 -'package'=28 -'value'=29 -'relation'=30 -'operation'=31 -'function'=32 -'constructor'=33 -'constructs'=34 -'input'=35 -'conform'=36 -'as'=37 -'bind'=38 -'static'=39 -'to'=40 -'private'=41 -'shared'=42 -'state'=43 -'edge'=44 -'projection'=45 -'with'=46 -'using'=47 -'via'=48 -'materialize'=49 -'if'=50 -'absent'=51 -'on'=52 -'policy'=53 -'default'=54 -'source'=55 -'repository'=56 -'commit'=57 -'revision'=58 -'semantic-major'=59 -'on-delete'=60 -'retain-other'=61 -'keyed'=62 -'public-traversal'=63 -'id'=64 -'doc'=65 -'mode'=66 -'emits'=67 -'receiver'=68 -'requires'=69 -'any'=70 -'get'=71 -'set'=72 -'watch'=73 -'start'=74 -'stop'=75 -'read'=76 -'write'=77 -'resolve'=78 -'connect'=79 -'disconnect'=80 -'call'=81 -'watch-start'=82 -'watch-stop'=83 -'subscribe'=84 -'unsubscribe'=85 -'optimistic-register'=86 -'crdt'=87 -'optional-one'=88 -'exactly-one'=89 -'many-unique'=90 -'many'=91 -'ordered'=92 -'unit'=93 -'watch-handle'=94 -'message'=95 -'atom-ref'=96 -'interface-ref'=97 -'optional'=98 -'list'=99 -'record'=100 -'bool'=101 -'bytes'=102 -'double'=103 -'int32'=104 -'int64'=105 -'string'=106 -'uint32'=107 -'uint64'=108 -'true'=109 -'false'=110 -'null'=111 -'->'=112 -':'=113 -';'=114 -','=115 -'.'=116 -'{'=117 -'}'=118 -'['=119 -']'=120 -'('=121 -')'=122 -'<'=123 -'>'=124 -'&'=125 -'='=126 +EDIT=1 +MODULE=2 +CONTRACT=3 +PRESENTATION=4 +SUBJECT=5 +FOR=6 +CREATION=7 +VARIANT=8 +PREPARES=9 +INITIALIZE=10 +WORKSPACE=11 +EXECUTION=12 +SNAPSHOT=13 +TRANSACTION=14 +REMOTE=15 +TARGETS=16 +PORTABLE=17 +REPLICA=18 +LOCAL=19 +FROM=20 +RESULT_BYTES=21 +RPC_CALLS=22 +DEADLINE_MS=23 +QUERY=24 +REFLECTION=25 +SPECIALIZE=26 +ROOT=27 +DOCUMENT=28 +FRAGMENTS=29 +VIEW=30 +MAX=31 +ALLOW=32 +POLL=33 +QUERYABLE=34 +RPC=35 +QUERY_REASON=36 +TYPE=37 +OBJECT=38 +STORABLE=39 +IMPLEMENTS=40 +REF=41 +FRAGMENT=42 +IMPORT=43 +EXTERNAL=44 +SYMBOL=45 +ATOM=46 +INTERFACE=47 +INTERFACES=48 +PACKAGE=49 +VALUE=50 +FIELD=51 +RELATION=52 +OPERATION=53 +FUNCTION=54 +CONSTRUCTOR=55 +CONSTRUCTS=56 +INPUT=57 +CONFORM=58 +AS=59 +BIND=60 +STATIC=61 +TO=62 +PRIVATE=63 +SHARED=64 +STATE=65 +EDGE=66 +PROJECTION=67 +WITH=68 +USING=69 +ACCEPT=70 +VIA=71 +MATERIALIZE=72 +IF=73 +ABSENT=74 +ON=75 +POLICY=76 +DEFAULT=77 +SOURCE=78 +REPOSITORY=79 +COMMIT=80 +REVISION=81 +SEMANTIC_MAJOR=82 +ON_DELETE=83 +LIFECYCLE=84 +JOURNAL=85 +SUBSCRIPTION=86 +RETAIN_OTHER=87 +KEYED=88 +PUBLIC_TRAVERSAL=89 +ID=90 +DOC=91 +MODE=92 +EMITS=93 +RECEIVER=94 +REQUIRES=95 +ANY=96 +GET=97 +SET=98 +WATCH=99 +START=100 +STOP=101 +READ=102 +WRITE=103 +RESOLVE=104 +CONNECT=105 +DISCONNECT=106 +CALL=107 +WATCH_START=108 +WATCH_STOP=109 +SUBSCRIBE=110 +UNSUBSCRIBE=111 +REGISTER=112 +CRDT=113 +OPTIONAL_ONE=114 +EXACTLY_ONE=115 +MANY_UNIQUE=116 +MANY=117 +ORDERED=118 +UNIT=119 +WATCH_HANDLE=120 +MESSAGE=121 +ATOM_REF=122 +INTERFACE_REF=123 +OPTIONAL=124 +LIST=125 +RECORD=126 +BOOL=127 +BYTES=128 +DOUBLE=129 +INT32=130 +INT64=131 +STRING=132 +UINT32=133 +UINT64=134 +TRUE=135 +FALSE=136 +NULL=137 +ARROW=138 +COLON=139 +SEMI=140 +COMMA=141 +DOT=142 +LBRACE=143 +RBRACE=144 +LBRACK=145 +RBRACK=146 +LPAREN=147 +RPAREN=148 +LT=149 +GT=150 +AMP=151 +EQUAL=152 +INTEGER=153 +JSON_NUMBER=154 +IDENTIFIER=155 +STRING_LITERAL=156 +LINE_COMMENT=157 +BLOCK_COMMENT=158 +WS=159 +'edit'=1 +'module'=2 +'contract'=3 +'presentation'=4 +'subject'=5 +'for'=6 +'creation'=7 +'variant'=8 +'prepares'=9 +'initialize'=10 +'workspace'=11 +'execution'=12 +'snapshot'=13 +'transaction'=14 +'remote'=15 +'targets'=16 +'portable'=17 +'replica'=18 +'local'=19 +'from'=20 +'result-bytes'=21 +'rpc-calls'=22 +'deadline-ms'=23 +'query'=24 +'reflection'=25 +'specialize'=26 +'root'=27 +'document'=28 +'fragments'=29 +'view'=30 +'max'=31 +'allow'=32 +'poll'=33 +'queryable'=34 +'rpc'=35 +'query-reason'=36 +'type'=37 +'object'=38 +'storable'=39 +'implements'=40 +'ref'=41 +'fragment'=42 +'import'=43 +'external'=44 +'symbol'=45 +'atom'=46 +'interface'=47 +'interfaces'=48 +'package'=49 +'value'=50 +'field'=51 +'relation'=52 +'operation'=53 +'function'=54 +'constructor'=55 +'constructs'=56 +'input'=57 +'conform'=58 +'as'=59 +'bind'=60 +'static'=61 +'to'=62 +'private'=63 +'shared'=64 +'state'=65 +'edge'=66 +'projection'=67 +'with'=68 +'using'=69 +'accept'=70 +'via'=71 +'materialize'=72 +'if'=73 +'absent'=74 +'on'=75 +'policy'=76 +'default'=77 +'source'=78 +'repository'=79 +'commit'=80 +'revision'=81 +'semantic-major'=82 +'on-delete'=83 +'lifecycle'=84 +'journal'=85 +'subscription'=86 +'retain-other'=87 +'keyed'=88 +'public-traversal'=89 +'id'=90 +'doc'=91 +'mode'=92 +'emits'=93 +'receiver'=94 +'requires'=95 +'any'=96 +'get'=97 +'set'=98 +'watch'=99 +'start'=100 +'stop'=101 +'read'=102 +'write'=103 +'resolve'=104 +'connect'=105 +'disconnect'=106 +'call'=107 +'watch-start'=108 +'watch-stop'=109 +'subscribe'=110 +'unsubscribe'=111 +'register'=112 +'crdt'=113 +'optional-one'=114 +'exactly-one'=115 +'many-unique'=116 +'many'=117 +'ordered'=118 +'unit'=119 +'watch-handle'=120 +'message'=121 +'atom-ref'=122 +'interface-ref'=123 +'optional'=124 +'list'=125 +'record'=126 +'bool'=127 +'bytes'=128 +'double'=129 +'int32'=130 +'int64'=131 +'string'=132 +'uint32'=133 +'uint64'=134 +'true'=135 +'false'=136 +'null'=137 +'->'=138 +':'=139 +';'=140 +','=141 +'.'=142 +'{'=143 +'}'=144 +'['=145 +']'=146 +'('=147 +')'=148 +'<'=149 +'>'=150 +'&'=151 +'='=152 diff --git a/src/capability-language/generated/QuixosCapabilityLexer.interp b/src/capability-language/generated/QuixosCapabilityLexer.interp index dc285d9..25cf672 100644 --- a/src/capability-language/generated/QuixosCapabilityLexer.interp +++ b/src/capability-language/generated/QuixosCapabilityLexer.interp @@ -1,10 +1,30 @@ token literal names: null +'edit' +'module' +'contract' +'presentation' +'subject' +'for' +'creation' +'variant' +'prepares' +'initialize' 'workspace' +'execution' +'snapshot' +'transaction' +'remote' +'targets' +'portable' +'replica' +'local' +'from' 'result-bytes' 'rpc-calls' 'deadline-ms' 'query' +'reflection' 'specialize' 'root' 'document' @@ -24,11 +44,13 @@ null 'fragment' 'import' 'external' +'symbol' 'atom' 'interface' 'interfaces' 'package' 'value' +'field' 'relation' 'operation' 'function' @@ -47,6 +69,7 @@ null 'projection' 'with' 'using' +'accept' 'via' 'materialize' 'if' @@ -60,6 +83,9 @@ null 'revision' 'semantic-major' 'on-delete' +'lifecycle' +'journal' +'subscription' 'retain-other' 'keyed' 'public-traversal' @@ -85,7 +111,7 @@ null 'watch-stop' 'subscribe' 'unsubscribe' -'optimistic-register' +'register' 'crdt' 'optional-one' 'exactly-one' @@ -136,11 +162,31 @@ null token symbolic names: null +EDIT +MODULE +CONTRACT +PRESENTATION +SUBJECT +FOR +CREATION +VARIANT +PREPARES +INITIALIZE WORKSPACE +EXECUTION +SNAPSHOT +TRANSACTION +REMOTE +TARGETS +PORTABLE +REPLICA +LOCAL +FROM RESULT_BYTES RPC_CALLS DEADLINE_MS QUERY +REFLECTION SPECIALIZE ROOT DOCUMENT @@ -160,11 +206,13 @@ REF FRAGMENT IMPORT EXTERNAL +SYMBOL ATOM INTERFACE INTERFACES PACKAGE VALUE +FIELD RELATION OPERATION FUNCTION @@ -183,6 +231,7 @@ EDGE PROJECTION WITH USING +ACCEPT VIA MATERIALIZE IF @@ -196,6 +245,9 @@ COMMIT REVISION SEMANTIC_MAJOR ON_DELETE +LIFECYCLE +JOURNAL +SUBSCRIPTION RETAIN_OTHER KEYED PUBLIC_TRAVERSAL @@ -221,7 +273,7 @@ WATCH_START WATCH_STOP SUBSCRIBE UNSUBSCRIBE -OPTIMISTIC_REGISTER +REGISTER CRDT OPTIONAL_ONE EXACTLY_ONE @@ -271,11 +323,31 @@ BLOCK_COMMENT WS rule names: +EDIT +MODULE +CONTRACT +PRESENTATION +SUBJECT +FOR +CREATION +VARIANT +PREPARES +INITIALIZE WORKSPACE +EXECUTION +SNAPSHOT +TRANSACTION +REMOTE +TARGETS +PORTABLE +REPLICA +LOCAL +FROM RESULT_BYTES RPC_CALLS DEADLINE_MS QUERY +REFLECTION SPECIALIZE ROOT DOCUMENT @@ -295,11 +367,13 @@ REF FRAGMENT IMPORT EXTERNAL +SYMBOL ATOM INTERFACE INTERFACES PACKAGE VALUE +FIELD RELATION OPERATION FUNCTION @@ -318,6 +392,7 @@ EDGE PROJECTION WITH USING +ACCEPT VIA MATERIALIZE IF @@ -331,6 +406,9 @@ COMMIT REVISION SEMANTIC_MAJOR ON_DELETE +LIFECYCLE +JOURNAL +SUBSCRIPTION RETAIN_OTHER KEYED PUBLIC_TRAVERSAL @@ -356,7 +434,7 @@ WATCH_START WATCH_STOP SUBSCRIBE UNSUBSCRIBE -OPTIMISTIC_REGISTER +REGISTER CRDT OPTIONAL_ONE EXACTLY_ONE @@ -415,4 +493,4 @@ mode names: DEFAULT_MODE atn: -[4, 0, 133, 1272, 6, -1, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, 4, 2, 5, 7, 5, 2, 6, 7, 6, 2, 7, 7, 7, 2, 8, 7, 8, 2, 9, 7, 9, 2, 10, 7, 10, 2, 11, 7, 11, 2, 12, 7, 12, 2, 13, 7, 13, 2, 14, 7, 14, 2, 15, 7, 15, 2, 16, 7, 16, 2, 17, 7, 17, 2, 18, 7, 18, 2, 19, 7, 19, 2, 20, 7, 20, 2, 21, 7, 21, 2, 22, 7, 22, 2, 23, 7, 23, 2, 24, 7, 24, 2, 25, 7, 25, 2, 26, 7, 26, 2, 27, 7, 27, 2, 28, 7, 28, 2, 29, 7, 29, 2, 30, 7, 30, 2, 31, 7, 31, 2, 32, 7, 32, 2, 33, 7, 33, 2, 34, 7, 34, 2, 35, 7, 35, 2, 36, 7, 36, 2, 37, 7, 37, 2, 38, 7, 38, 2, 39, 7, 39, 2, 40, 7, 40, 2, 41, 7, 41, 2, 42, 7, 42, 2, 43, 7, 43, 2, 44, 7, 44, 2, 45, 7, 45, 2, 46, 7, 46, 2, 47, 7, 47, 2, 48, 7, 48, 2, 49, 7, 49, 2, 50, 7, 50, 2, 51, 7, 51, 2, 52, 7, 52, 2, 53, 7, 53, 2, 54, 7, 54, 2, 55, 7, 55, 2, 56, 7, 56, 2, 57, 7, 57, 2, 58, 7, 58, 2, 59, 7, 59, 2, 60, 7, 60, 2, 61, 7, 61, 2, 62, 7, 62, 2, 63, 7, 63, 2, 64, 7, 64, 2, 65, 7, 65, 2, 66, 7, 66, 2, 67, 7, 67, 2, 68, 7, 68, 2, 69, 7, 69, 2, 70, 7, 70, 2, 71, 7, 71, 2, 72, 7, 72, 2, 73, 7, 73, 2, 74, 7, 74, 2, 75, 7, 75, 2, 76, 7, 76, 2, 77, 7, 77, 2, 78, 7, 78, 2, 79, 7, 79, 2, 80, 7, 80, 2, 81, 7, 81, 2, 82, 7, 82, 2, 83, 7, 83, 2, 84, 7, 84, 2, 85, 7, 85, 2, 86, 7, 86, 2, 87, 7, 87, 2, 88, 7, 88, 2, 89, 7, 89, 2, 90, 7, 90, 2, 91, 7, 91, 2, 92, 7, 92, 2, 93, 7, 93, 2, 94, 7, 94, 2, 95, 7, 95, 2, 96, 7, 96, 2, 97, 7, 97, 2, 98, 7, 98, 2, 99, 7, 99, 2, 100, 7, 100, 2, 101, 7, 101, 2, 102, 7, 102, 2, 103, 7, 103, 2, 104, 7, 104, 2, 105, 7, 105, 2, 106, 7, 106, 2, 107, 7, 107, 2, 108, 7, 108, 2, 109, 7, 109, 2, 110, 7, 110, 2, 111, 7, 111, 2, 112, 7, 112, 2, 113, 7, 113, 2, 114, 7, 114, 2, 115, 7, 115, 2, 116, 7, 116, 2, 117, 7, 117, 2, 118, 7, 118, 2, 119, 7, 119, 2, 120, 7, 120, 2, 121, 7, 121, 2, 122, 7, 122, 2, 123, 7, 123, 2, 124, 7, 124, 2, 125, 7, 125, 2, 126, 7, 126, 2, 127, 7, 127, 2, 128, 7, 128, 2, 129, 7, 129, 2, 130, 7, 130, 2, 131, 7, 131, 2, 132, 7, 132, 2, 133, 7, 133, 2, 134, 7, 134, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 10, 1, 10, 1, 10, 1, 10, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 12, 1, 12, 1, 12, 1, 12, 1, 12, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 14, 1, 14, 1, 14, 1, 14, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 19, 1, 19, 1, 19, 1, 19, 1, 19, 1, 19, 1, 19, 1, 19, 1, 19, 1, 19, 1, 19, 1, 20, 1, 20, 1, 20, 1, 20, 1, 21, 1, 21, 1, 21, 1, 21, 1, 21, 1, 21, 1, 21, 1, 21, 1, 21, 1, 22, 1, 22, 1, 22, 1, 22, 1, 22, 1, 22, 1, 22, 1, 23, 1, 23, 1, 23, 1, 23, 1, 23, 1, 23, 1, 23, 1, 23, 1, 23, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 25, 1, 25, 1, 25, 1, 25, 1, 25, 1, 25, 1, 25, 1, 25, 1, 25, 1, 25, 1, 26, 1, 26, 1, 26, 1, 26, 1, 26, 1, 26, 1, 26, 1, 26, 1, 26, 1, 26, 1, 26, 1, 27, 1, 27, 1, 27, 1, 27, 1, 27, 1, 27, 1, 27, 1, 27, 1, 28, 1, 28, 1, 28, 1, 28, 1, 28, 1, 28, 1, 29, 1, 29, 1, 29, 1, 29, 1, 29, 1, 29, 1, 29, 1, 29, 1, 29, 1, 30, 1, 30, 1, 30, 1, 30, 1, 30, 1, 30, 1, 30, 1, 30, 1, 30, 1, 30, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 32, 1, 32, 1, 32, 1, 32, 1, 32, 1, 32, 1, 32, 1, 32, 1, 32, 1, 32, 1, 32, 1, 32, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 35, 1, 35, 1, 35, 1, 35, 1, 35, 1, 35, 1, 35, 1, 35, 1, 36, 1, 36, 1, 36, 1, 37, 1, 37, 1, 37, 1, 37, 1, 37, 1, 38, 1, 38, 1, 38, 1, 38, 1, 38, 1, 38, 1, 38, 1, 39, 1, 39, 1, 39, 1, 40, 1, 40, 1, 40, 1, 40, 1, 40, 1, 40, 1, 40, 1, 40, 1, 41, 1, 41, 1, 41, 1, 41, 1, 41, 1, 41, 1, 41, 1, 42, 1, 42, 1, 42, 1, 42, 1, 42, 1, 42, 1, 43, 1, 43, 1, 43, 1, 43, 1, 43, 1, 44, 1, 44, 1, 44, 1, 44, 1, 44, 1, 44, 1, 44, 1, 44, 1, 44, 1, 44, 1, 44, 1, 45, 1, 45, 1, 45, 1, 45, 1, 45, 1, 46, 1, 46, 1, 46, 1, 46, 1, 46, 1, 46, 1, 47, 1, 47, 1, 47, 1, 47, 1, 48, 1, 48, 1, 48, 1, 48, 1, 48, 1, 48, 1, 48, 1, 48, 1, 48, 1, 48, 1, 48, 1, 48, 1, 49, 1, 49, 1, 49, 1, 50, 1, 50, 1, 50, 1, 50, 1, 50, 1, 50, 1, 50, 1, 51, 1, 51, 1, 51, 1, 52, 1, 52, 1, 52, 1, 52, 1, 52, 1, 52, 1, 52, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 54, 1, 54, 1, 54, 1, 54, 1, 54, 1, 54, 1, 54, 1, 55, 1, 55, 1, 55, 1, 55, 1, 55, 1, 55, 1, 55, 1, 55, 1, 55, 1, 55, 1, 55, 1, 56, 1, 56, 1, 56, 1, 56, 1, 56, 1, 56, 1, 56, 1, 57, 1, 57, 1, 57, 1, 57, 1, 57, 1, 57, 1, 57, 1, 57, 1, 57, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 59, 1, 59, 1, 59, 1, 59, 1, 59, 1, 59, 1, 59, 1, 59, 1, 59, 1, 59, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 1, 62, 1, 62, 1, 62, 1, 62, 1, 62, 1, 62, 1, 62, 1, 62, 1, 62, 1, 62, 1, 62, 1, 62, 1, 62, 1, 62, 1, 62, 1, 62, 1, 62, 1, 63, 1, 63, 1, 63, 1, 64, 1, 64, 1, 64, 1, 64, 1, 65, 1, 65, 1, 65, 1, 65, 1, 65, 1, 66, 1, 66, 1, 66, 1, 66, 1, 66, 1, 66, 1, 67, 1, 67, 1, 67, 1, 67, 1, 67, 1, 67, 1, 67, 1, 67, 1, 67, 1, 68, 1, 68, 1, 68, 1, 68, 1, 68, 1, 68, 1, 68, 1, 68, 1, 68, 1, 69, 1, 69, 1, 69, 1, 69, 1, 70, 1, 70, 1, 70, 1, 70, 1, 71, 1, 71, 1, 71, 1, 71, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 74, 1, 74, 1, 74, 1, 74, 1, 74, 1, 75, 1, 75, 1, 75, 1, 75, 1, 75, 1, 76, 1, 76, 1, 76, 1, 76, 1, 76, 1, 76, 1, 77, 1, 77, 1, 77, 1, 77, 1, 77, 1, 77, 1, 77, 1, 77, 1, 78, 1, 78, 1, 78, 1, 78, 1, 78, 1, 78, 1, 78, 1, 78, 1, 79, 1, 79, 1, 79, 1, 79, 1, 79, 1, 79, 1, 79, 1, 79, 1, 79, 1, 79, 1, 79, 1, 80, 1, 80, 1, 80, 1, 80, 1, 80, 1, 81, 1, 81, 1, 81, 1, 81, 1, 81, 1, 81, 1, 81, 1, 81, 1, 81, 1, 81, 1, 81, 1, 81, 1, 82, 1, 82, 1, 82, 1, 82, 1, 82, 1, 82, 1, 82, 1, 82, 1, 82, 1, 82, 1, 82, 1, 83, 1, 83, 1, 83, 1, 83, 1, 83, 1, 83, 1, 83, 1, 83, 1, 83, 1, 83, 1, 84, 1, 84, 1, 84, 1, 84, 1, 84, 1, 84, 1, 84, 1, 84, 1, 84, 1, 84, 1, 84, 1, 84, 1, 85, 1, 85, 1, 85, 1, 85, 1, 85, 1, 85, 1, 85, 1, 85, 1, 85, 1, 85, 1, 85, 1, 85, 1, 85, 1, 85, 1, 85, 1, 85, 1, 85, 1, 85, 1, 85, 1, 85, 1, 86, 1, 86, 1, 86, 1, 86, 1, 86, 1, 87, 1, 87, 1, 87, 1, 87, 1, 87, 1, 87, 1, 87, 1, 87, 1, 87, 1, 87, 1, 87, 1, 87, 1, 87, 1, 88, 1, 88, 1, 88, 1, 88, 1, 88, 1, 88, 1, 88, 1, 88, 1, 88, 1, 88, 1, 88, 1, 88, 1, 89, 1, 89, 1, 89, 1, 89, 1, 89, 1, 89, 1, 89, 1, 89, 1, 89, 1, 89, 1, 89, 1, 89, 1, 90, 1, 90, 1, 90, 1, 90, 1, 90, 1, 91, 1, 91, 1, 91, 1, 91, 1, 91, 1, 91, 1, 91, 1, 91, 1, 92, 1, 92, 1, 92, 1, 92, 1, 92, 1, 93, 1, 93, 1, 93, 1, 93, 1, 93, 1, 93, 1, 93, 1, 93, 1, 93, 1, 93, 1, 93, 1, 93, 1, 93, 1, 94, 1, 94, 1, 94, 1, 94, 1, 94, 1, 94, 1, 94, 1, 94, 1, 95, 1, 95, 1, 95, 1, 95, 1, 95, 1, 95, 1, 95, 1, 95, 1, 95, 1, 96, 1, 96, 1, 96, 1, 96, 1, 96, 1, 96, 1, 96, 1, 96, 1, 96, 1, 96, 1, 96, 1, 96, 1, 96, 1, 96, 1, 97, 1, 97, 1, 97, 1, 97, 1, 97, 1, 97, 1, 97, 1, 97, 1, 97, 1, 98, 1, 98, 1, 98, 1, 98, 1, 98, 1, 99, 1, 99, 1, 99, 1, 99, 1, 99, 1, 99, 1, 99, 1, 100, 1, 100, 1, 100, 1, 100, 1, 100, 1, 101, 1, 101, 1, 101, 1, 101, 1, 101, 1, 101, 1, 102, 1, 102, 1, 102, 1, 102, 1, 102, 1, 102, 1, 102, 1, 103, 1, 103, 1, 103, 1, 103, 1, 103, 1, 103, 1, 104, 1, 104, 1, 104, 1, 104, 1, 104, 1, 104, 1, 105, 1, 105, 1, 105, 1, 105, 1, 105, 1, 105, 1, 105, 1, 106, 1, 106, 1, 106, 1, 106, 1, 106, 1, 106, 1, 106, 1, 107, 1, 107, 1, 107, 1, 107, 1, 107, 1, 107, 1, 107, 1, 108, 1, 108, 1, 108, 1, 108, 1, 108, 1, 109, 1, 109, 1, 109, 1, 109, 1, 109, 1, 109, 1, 110, 1, 110, 1, 110, 1, 110, 1, 110, 1, 111, 1, 111, 1, 111, 1, 112, 1, 112, 1, 113, 1, 113, 1, 114, 1, 114, 1, 115, 1, 115, 1, 116, 1, 116, 1, 117, 1, 117, 1, 118, 1, 118, 1, 119, 1, 119, 1, 120, 1, 120, 1, 121, 1, 121, 1, 122, 1, 122, 1, 123, 1, 123, 1, 124, 1, 124, 1, 125, 1, 125, 1, 126, 3, 126, 1173, 8, 126, 1, 126, 4, 126, 1176, 8, 126, 11, 126, 12, 126, 1177, 1, 127, 3, 127, 1181, 8, 127, 1, 127, 1, 127, 1, 127, 5, 127, 1186, 8, 127, 10, 127, 12, 127, 1189, 9, 127, 3, 127, 1191, 8, 127, 1, 127, 1, 127, 4, 127, 1195, 8, 127, 11, 127, 12, 127, 1196, 3, 127, 1199, 8, 127, 1, 127, 1, 127, 3, 127, 1203, 8, 127, 1, 127, 4, 127, 1206, 8, 127, 11, 127, 12, 127, 1207, 3, 127, 1210, 8, 127, 1, 128, 1, 128, 5, 128, 1214, 8, 128, 10, 128, 12, 128, 1217, 9, 128, 1, 129, 1, 129, 1, 129, 5, 129, 1222, 8, 129, 10, 129, 12, 129, 1225, 9, 129, 1, 129, 1, 129, 1, 130, 1, 130, 1, 130, 1, 130, 1, 130, 1, 130, 1, 130, 1, 130, 3, 130, 1237, 8, 130, 1, 131, 1, 131, 1, 132, 1, 132, 1, 132, 1, 132, 5, 132, 1245, 8, 132, 10, 132, 12, 132, 1248, 9, 132, 1, 132, 1, 132, 1, 133, 1, 133, 1, 133, 1, 133, 5, 133, 1256, 8, 133, 10, 133, 12, 133, 1259, 9, 133, 1, 133, 1, 133, 1, 133, 1, 133, 1, 133, 1, 134, 4, 134, 1267, 8, 134, 11, 134, 12, 134, 1268, 1, 134, 1, 134, 1, 1257, 0, 135, 1, 1, 3, 2, 5, 3, 7, 4, 9, 5, 11, 6, 13, 7, 15, 8, 17, 9, 19, 10, 21, 11, 23, 12, 25, 13, 27, 14, 29, 15, 31, 16, 33, 17, 35, 18, 37, 19, 39, 20, 41, 21, 43, 22, 45, 23, 47, 24, 49, 25, 51, 26, 53, 27, 55, 28, 57, 29, 59, 30, 61, 31, 63, 32, 65, 33, 67, 34, 69, 35, 71, 36, 73, 37, 75, 38, 77, 39, 79, 40, 81, 41, 83, 42, 85, 43, 87, 44, 89, 45, 91, 46, 93, 47, 95, 48, 97, 49, 99, 50, 101, 51, 103, 52, 105, 53, 107, 54, 109, 55, 111, 56, 113, 57, 115, 58, 117, 59, 119, 60, 121, 61, 123, 62, 125, 63, 127, 64, 129, 65, 131, 66, 133, 67, 135, 68, 137, 69, 139, 70, 141, 71, 143, 72, 145, 73, 147, 74, 149, 75, 151, 76, 153, 77, 155, 78, 157, 79, 159, 80, 161, 81, 163, 82, 165, 83, 167, 84, 169, 85, 171, 86, 173, 87, 175, 88, 177, 89, 179, 90, 181, 91, 183, 92, 185, 93, 187, 94, 189, 95, 191, 96, 193, 97, 195, 98, 197, 99, 199, 100, 201, 101, 203, 102, 205, 103, 207, 104, 209, 105, 211, 106, 213, 107, 215, 108, 217, 109, 219, 110, 221, 111, 223, 112, 225, 113, 227, 114, 229, 115, 231, 116, 233, 117, 235, 118, 237, 119, 239, 120, 241, 121, 243, 122, 245, 123, 247, 124, 249, 125, 251, 126, 253, 127, 255, 128, 257, 129, 259, 130, 261, 0, 263, 0, 265, 131, 267, 132, 269, 133, 1, 0, 11, 1, 0, 48, 57, 1, 0, 49, 57, 2, 0, 69, 69, 101, 101, 2, 0, 43, 43, 45, 45, 3, 0, 65, 90, 95, 95, 97, 122, 4, 0, 48, 57, 65, 90, 95, 95, 97, 122, 4, 0, 10, 10, 13, 13, 34, 34, 92, 92, 8, 0, 34, 34, 47, 47, 92, 92, 98, 98, 102, 102, 110, 110, 114, 114, 116, 116, 3, 0, 48, 57, 65, 70, 97, 102, 2, 0, 10, 10, 13, 13, 3, 0, 9, 10, 13, 13, 32, 32, 1286, 0, 1, 1, 0, 0, 0, 0, 3, 1, 0, 0, 0, 0, 5, 1, 0, 0, 0, 0, 7, 1, 0, 0, 0, 0, 9, 1, 0, 0, 0, 0, 11, 1, 0, 0, 0, 0, 13, 1, 0, 0, 0, 0, 15, 1, 0, 0, 0, 0, 17, 1, 0, 0, 0, 0, 19, 1, 0, 0, 0, 0, 21, 1, 0, 0, 0, 0, 23, 1, 0, 0, 0, 0, 25, 1, 0, 0, 0, 0, 27, 1, 0, 0, 0, 0, 29, 1, 0, 0, 0, 0, 31, 1, 0, 0, 0, 0, 33, 1, 0, 0, 0, 0, 35, 1, 0, 0, 0, 0, 37, 1, 0, 0, 0, 0, 39, 1, 0, 0, 0, 0, 41, 1, 0, 0, 0, 0, 43, 1, 0, 0, 0, 0, 45, 1, 0, 0, 0, 0, 47, 1, 0, 0, 0, 0, 49, 1, 0, 0, 0, 0, 51, 1, 0, 0, 0, 0, 53, 1, 0, 0, 0, 0, 55, 1, 0, 0, 0, 0, 57, 1, 0, 0, 0, 0, 59, 1, 0, 0, 0, 0, 61, 1, 0, 0, 0, 0, 63, 1, 0, 0, 0, 0, 65, 1, 0, 0, 0, 0, 67, 1, 0, 0, 0, 0, 69, 1, 0, 0, 0, 0, 71, 1, 0, 0, 0, 0, 73, 1, 0, 0, 0, 0, 75, 1, 0, 0, 0, 0, 77, 1, 0, 0, 0, 0, 79, 1, 0, 0, 0, 0, 81, 1, 0, 0, 0, 0, 83, 1, 0, 0, 0, 0, 85, 1, 0, 0, 0, 0, 87, 1, 0, 0, 0, 0, 89, 1, 0, 0, 0, 0, 91, 1, 0, 0, 0, 0, 93, 1, 0, 0, 0, 0, 95, 1, 0, 0, 0, 0, 97, 1, 0, 0, 0, 0, 99, 1, 0, 0, 0, 0, 101, 1, 0, 0, 0, 0, 103, 1, 0, 0, 0, 0, 105, 1, 0, 0, 0, 0, 107, 1, 0, 0, 0, 0, 109, 1, 0, 0, 0, 0, 111, 1, 0, 0, 0, 0, 113, 1, 0, 0, 0, 0, 115, 1, 0, 0, 0, 0, 117, 1, 0, 0, 0, 0, 119, 1, 0, 0, 0, 0, 121, 1, 0, 0, 0, 0, 123, 1, 0, 0, 0, 0, 125, 1, 0, 0, 0, 0, 127, 1, 0, 0, 0, 0, 129, 1, 0, 0, 0, 0, 131, 1, 0, 0, 0, 0, 133, 1, 0, 0, 0, 0, 135, 1, 0, 0, 0, 0, 137, 1, 0, 0, 0, 0, 139, 1, 0, 0, 0, 0, 141, 1, 0, 0, 0, 0, 143, 1, 0, 0, 0, 0, 145, 1, 0, 0, 0, 0, 147, 1, 0, 0, 0, 0, 149, 1, 0, 0, 0, 0, 151, 1, 0, 0, 0, 0, 153, 1, 0, 0, 0, 0, 155, 1, 0, 0, 0, 0, 157, 1, 0, 0, 0, 0, 159, 1, 0, 0, 0, 0, 161, 1, 0, 0, 0, 0, 163, 1, 0, 0, 0, 0, 165, 1, 0, 0, 0, 0, 167, 1, 0, 0, 0, 0, 169, 1, 0, 0, 0, 0, 171, 1, 0, 0, 0, 0, 173, 1, 0, 0, 0, 0, 175, 1, 0, 0, 0, 0, 177, 1, 0, 0, 0, 0, 179, 1, 0, 0, 0, 0, 181, 1, 0, 0, 0, 0, 183, 1, 0, 0, 0, 0, 185, 1, 0, 0, 0, 0, 187, 1, 0, 0, 0, 0, 189, 1, 0, 0, 0, 0, 191, 1, 0, 0, 0, 0, 193, 1, 0, 0, 0, 0, 195, 1, 0, 0, 0, 0, 197, 1, 0, 0, 0, 0, 199, 1, 0, 0, 0, 0, 201, 1, 0, 0, 0, 0, 203, 1, 0, 0, 0, 0, 205, 1, 0, 0, 0, 0, 207, 1, 0, 0, 0, 0, 209, 1, 0, 0, 0, 0, 211, 1, 0, 0, 0, 0, 213, 1, 0, 0, 0, 0, 215, 1, 0, 0, 0, 0, 217, 1, 0, 0, 0, 0, 219, 1, 0, 0, 0, 0, 221, 1, 0, 0, 0, 0, 223, 1, 0, 0, 0, 0, 225, 1, 0, 0, 0, 0, 227, 1, 0, 0, 0, 0, 229, 1, 0, 0, 0, 0, 231, 1, 0, 0, 0, 0, 233, 1, 0, 0, 0, 0, 235, 1, 0, 0, 0, 0, 237, 1, 0, 0, 0, 0, 239, 1, 0, 0, 0, 0, 241, 1, 0, 0, 0, 0, 243, 1, 0, 0, 0, 0, 245, 1, 0, 0, 0, 0, 247, 1, 0, 0, 0, 0, 249, 1, 0, 0, 0, 0, 251, 1, 0, 0, 0, 0, 253, 1, 0, 0, 0, 0, 255, 1, 0, 0, 0, 0, 257, 1, 0, 0, 0, 0, 259, 1, 0, 0, 0, 0, 265, 1, 0, 0, 0, 0, 267, 1, 0, 0, 0, 0, 269, 1, 0, 0, 0, 1, 271, 1, 0, 0, 0, 3, 281, 1, 0, 0, 0, 5, 294, 1, 0, 0, 0, 7, 304, 1, 0, 0, 0, 9, 316, 1, 0, 0, 0, 11, 322, 1, 0, 0, 0, 13, 333, 1, 0, 0, 0, 15, 338, 1, 0, 0, 0, 17, 347, 1, 0, 0, 0, 19, 357, 1, 0, 0, 0, 21, 362, 1, 0, 0, 0, 23, 366, 1, 0, 0, 0, 25, 372, 1, 0, 0, 0, 27, 377, 1, 0, 0, 0, 29, 387, 1, 0, 0, 0, 31, 391, 1, 0, 0, 0, 33, 404, 1, 0, 0, 0, 35, 409, 1, 0, 0, 0, 37, 416, 1, 0, 0, 0, 39, 425, 1, 0, 0, 0, 41, 436, 1, 0, 0, 0, 43, 440, 1, 0, 0, 0, 45, 449, 1, 0, 0, 0, 47, 456, 1, 0, 0, 0, 49, 465, 1, 0, 0, 0, 51, 470, 1, 0, 0, 0, 53, 480, 1, 0, 0, 0, 55, 491, 1, 0, 0, 0, 57, 499, 1, 0, 0, 0, 59, 505, 1, 0, 0, 0, 61, 514, 1, 0, 0, 0, 63, 524, 1, 0, 0, 0, 65, 533, 1, 0, 0, 0, 67, 545, 1, 0, 0, 0, 69, 556, 1, 0, 0, 0, 71, 562, 1, 0, 0, 0, 73, 570, 1, 0, 0, 0, 75, 573, 1, 0, 0, 0, 77, 578, 1, 0, 0, 0, 79, 585, 1, 0, 0, 0, 81, 588, 1, 0, 0, 0, 83, 596, 1, 0, 0, 0, 85, 603, 1, 0, 0, 0, 87, 609, 1, 0, 0, 0, 89, 614, 1, 0, 0, 0, 91, 625, 1, 0, 0, 0, 93, 630, 1, 0, 0, 0, 95, 636, 1, 0, 0, 0, 97, 640, 1, 0, 0, 0, 99, 652, 1, 0, 0, 0, 101, 655, 1, 0, 0, 0, 103, 662, 1, 0, 0, 0, 105, 665, 1, 0, 0, 0, 107, 672, 1, 0, 0, 0, 109, 680, 1, 0, 0, 0, 111, 687, 1, 0, 0, 0, 113, 698, 1, 0, 0, 0, 115, 705, 1, 0, 0, 0, 117, 714, 1, 0, 0, 0, 119, 729, 1, 0, 0, 0, 121, 739, 1, 0, 0, 0, 123, 752, 1, 0, 0, 0, 125, 758, 1, 0, 0, 0, 127, 775, 1, 0, 0, 0, 129, 778, 1, 0, 0, 0, 131, 782, 1, 0, 0, 0, 133, 787, 1, 0, 0, 0, 135, 793, 1, 0, 0, 0, 137, 802, 1, 0, 0, 0, 139, 811, 1, 0, 0, 0, 141, 815, 1, 0, 0, 0, 143, 819, 1, 0, 0, 0, 145, 823, 1, 0, 0, 0, 147, 829, 1, 0, 0, 0, 149, 835, 1, 0, 0, 0, 151, 840, 1, 0, 0, 0, 153, 845, 1, 0, 0, 0, 155, 851, 1, 0, 0, 0, 157, 859, 1, 0, 0, 0, 159, 867, 1, 0, 0, 0, 161, 878, 1, 0, 0, 0, 163, 883, 1, 0, 0, 0, 165, 895, 1, 0, 0, 0, 167, 906, 1, 0, 0, 0, 169, 916, 1, 0, 0, 0, 171, 928, 1, 0, 0, 0, 173, 948, 1, 0, 0, 0, 175, 953, 1, 0, 0, 0, 177, 966, 1, 0, 0, 0, 179, 978, 1, 0, 0, 0, 181, 990, 1, 0, 0, 0, 183, 995, 1, 0, 0, 0, 185, 1003, 1, 0, 0, 0, 187, 1008, 1, 0, 0, 0, 189, 1021, 1, 0, 0, 0, 191, 1029, 1, 0, 0, 0, 193, 1038, 1, 0, 0, 0, 195, 1052, 1, 0, 0, 0, 197, 1061, 1, 0, 0, 0, 199, 1066, 1, 0, 0, 0, 201, 1073, 1, 0, 0, 0, 203, 1078, 1, 0, 0, 0, 205, 1084, 1, 0, 0, 0, 207, 1091, 1, 0, 0, 0, 209, 1097, 1, 0, 0, 0, 211, 1103, 1, 0, 0, 0, 213, 1110, 1, 0, 0, 0, 215, 1117, 1, 0, 0, 0, 217, 1124, 1, 0, 0, 0, 219, 1129, 1, 0, 0, 0, 221, 1135, 1, 0, 0, 0, 223, 1140, 1, 0, 0, 0, 225, 1143, 1, 0, 0, 0, 227, 1145, 1, 0, 0, 0, 229, 1147, 1, 0, 0, 0, 231, 1149, 1, 0, 0, 0, 233, 1151, 1, 0, 0, 0, 235, 1153, 1, 0, 0, 0, 237, 1155, 1, 0, 0, 0, 239, 1157, 1, 0, 0, 0, 241, 1159, 1, 0, 0, 0, 243, 1161, 1, 0, 0, 0, 245, 1163, 1, 0, 0, 0, 247, 1165, 1, 0, 0, 0, 249, 1167, 1, 0, 0, 0, 251, 1169, 1, 0, 0, 0, 253, 1172, 1, 0, 0, 0, 255, 1180, 1, 0, 0, 0, 257, 1211, 1, 0, 0, 0, 259, 1218, 1, 0, 0, 0, 261, 1228, 1, 0, 0, 0, 263, 1238, 1, 0, 0, 0, 265, 1240, 1, 0, 0, 0, 267, 1251, 1, 0, 0, 0, 269, 1266, 1, 0, 0, 0, 271, 272, 5, 119, 0, 0, 272, 273, 5, 111, 0, 0, 273, 274, 5, 114, 0, 0, 274, 275, 5, 107, 0, 0, 275, 276, 5, 115, 0, 0, 276, 277, 5, 112, 0, 0, 277, 278, 5, 97, 0, 0, 278, 279, 5, 99, 0, 0, 279, 280, 5, 101, 0, 0, 280, 2, 1, 0, 0, 0, 281, 282, 5, 114, 0, 0, 282, 283, 5, 101, 0, 0, 283, 284, 5, 115, 0, 0, 284, 285, 5, 117, 0, 0, 285, 286, 5, 108, 0, 0, 286, 287, 5, 116, 0, 0, 287, 288, 5, 45, 0, 0, 288, 289, 5, 98, 0, 0, 289, 290, 5, 121, 0, 0, 290, 291, 5, 116, 0, 0, 291, 292, 5, 101, 0, 0, 292, 293, 5, 115, 0, 0, 293, 4, 1, 0, 0, 0, 294, 295, 5, 114, 0, 0, 295, 296, 5, 112, 0, 0, 296, 297, 5, 99, 0, 0, 297, 298, 5, 45, 0, 0, 298, 299, 5, 99, 0, 0, 299, 300, 5, 97, 0, 0, 300, 301, 5, 108, 0, 0, 301, 302, 5, 108, 0, 0, 302, 303, 5, 115, 0, 0, 303, 6, 1, 0, 0, 0, 304, 305, 5, 100, 0, 0, 305, 306, 5, 101, 0, 0, 306, 307, 5, 97, 0, 0, 307, 308, 5, 100, 0, 0, 308, 309, 5, 108, 0, 0, 309, 310, 5, 105, 0, 0, 310, 311, 5, 110, 0, 0, 311, 312, 5, 101, 0, 0, 312, 313, 5, 45, 0, 0, 313, 314, 5, 109, 0, 0, 314, 315, 5, 115, 0, 0, 315, 8, 1, 0, 0, 0, 316, 317, 5, 113, 0, 0, 317, 318, 5, 117, 0, 0, 318, 319, 5, 101, 0, 0, 319, 320, 5, 114, 0, 0, 320, 321, 5, 121, 0, 0, 321, 10, 1, 0, 0, 0, 322, 323, 5, 115, 0, 0, 323, 324, 5, 112, 0, 0, 324, 325, 5, 101, 0, 0, 325, 326, 5, 99, 0, 0, 326, 327, 5, 105, 0, 0, 327, 328, 5, 97, 0, 0, 328, 329, 5, 108, 0, 0, 329, 330, 5, 105, 0, 0, 330, 331, 5, 122, 0, 0, 331, 332, 5, 101, 0, 0, 332, 12, 1, 0, 0, 0, 333, 334, 5, 114, 0, 0, 334, 335, 5, 111, 0, 0, 335, 336, 5, 111, 0, 0, 336, 337, 5, 116, 0, 0, 337, 14, 1, 0, 0, 0, 338, 339, 5, 100, 0, 0, 339, 340, 5, 111, 0, 0, 340, 341, 5, 99, 0, 0, 341, 342, 5, 117, 0, 0, 342, 343, 5, 109, 0, 0, 343, 344, 5, 101, 0, 0, 344, 345, 5, 110, 0, 0, 345, 346, 5, 116, 0, 0, 346, 16, 1, 0, 0, 0, 347, 348, 5, 102, 0, 0, 348, 349, 5, 114, 0, 0, 349, 350, 5, 97, 0, 0, 350, 351, 5, 103, 0, 0, 351, 352, 5, 109, 0, 0, 352, 353, 5, 101, 0, 0, 353, 354, 5, 110, 0, 0, 354, 355, 5, 116, 0, 0, 355, 356, 5, 115, 0, 0, 356, 18, 1, 0, 0, 0, 357, 358, 5, 118, 0, 0, 358, 359, 5, 105, 0, 0, 359, 360, 5, 101, 0, 0, 360, 361, 5, 119, 0, 0, 361, 20, 1, 0, 0, 0, 362, 363, 5, 109, 0, 0, 363, 364, 5, 97, 0, 0, 364, 365, 5, 120, 0, 0, 365, 22, 1, 0, 0, 0, 366, 367, 5, 97, 0, 0, 367, 368, 5, 108, 0, 0, 368, 369, 5, 108, 0, 0, 369, 370, 5, 111, 0, 0, 370, 371, 5, 119, 0, 0, 371, 24, 1, 0, 0, 0, 372, 373, 5, 112, 0, 0, 373, 374, 5, 111, 0, 0, 374, 375, 5, 108, 0, 0, 375, 376, 5, 108, 0, 0, 376, 26, 1, 0, 0, 0, 377, 378, 5, 113, 0, 0, 378, 379, 5, 117, 0, 0, 379, 380, 5, 101, 0, 0, 380, 381, 5, 114, 0, 0, 381, 382, 5, 121, 0, 0, 382, 383, 5, 97, 0, 0, 383, 384, 5, 98, 0, 0, 384, 385, 5, 108, 0, 0, 385, 386, 5, 101, 0, 0, 386, 28, 1, 0, 0, 0, 387, 388, 5, 114, 0, 0, 388, 389, 5, 112, 0, 0, 389, 390, 5, 99, 0, 0, 390, 30, 1, 0, 0, 0, 391, 392, 5, 113, 0, 0, 392, 393, 5, 117, 0, 0, 393, 394, 5, 101, 0, 0, 394, 395, 5, 114, 0, 0, 395, 396, 5, 121, 0, 0, 396, 397, 5, 45, 0, 0, 397, 398, 5, 114, 0, 0, 398, 399, 5, 101, 0, 0, 399, 400, 5, 97, 0, 0, 400, 401, 5, 115, 0, 0, 401, 402, 5, 111, 0, 0, 402, 403, 5, 110, 0, 0, 403, 32, 1, 0, 0, 0, 404, 405, 5, 116, 0, 0, 405, 406, 5, 121, 0, 0, 406, 407, 5, 112, 0, 0, 407, 408, 5, 101, 0, 0, 408, 34, 1, 0, 0, 0, 409, 410, 5, 111, 0, 0, 410, 411, 5, 98, 0, 0, 411, 412, 5, 106, 0, 0, 412, 413, 5, 101, 0, 0, 413, 414, 5, 99, 0, 0, 414, 415, 5, 116, 0, 0, 415, 36, 1, 0, 0, 0, 416, 417, 5, 115, 0, 0, 417, 418, 5, 116, 0, 0, 418, 419, 5, 111, 0, 0, 419, 420, 5, 114, 0, 0, 420, 421, 5, 97, 0, 0, 421, 422, 5, 98, 0, 0, 422, 423, 5, 108, 0, 0, 423, 424, 5, 101, 0, 0, 424, 38, 1, 0, 0, 0, 425, 426, 5, 105, 0, 0, 426, 427, 5, 109, 0, 0, 427, 428, 5, 112, 0, 0, 428, 429, 5, 108, 0, 0, 429, 430, 5, 101, 0, 0, 430, 431, 5, 109, 0, 0, 431, 432, 5, 101, 0, 0, 432, 433, 5, 110, 0, 0, 433, 434, 5, 116, 0, 0, 434, 435, 5, 115, 0, 0, 435, 40, 1, 0, 0, 0, 436, 437, 5, 114, 0, 0, 437, 438, 5, 101, 0, 0, 438, 439, 5, 102, 0, 0, 439, 42, 1, 0, 0, 0, 440, 441, 5, 102, 0, 0, 441, 442, 5, 114, 0, 0, 442, 443, 5, 97, 0, 0, 443, 444, 5, 103, 0, 0, 444, 445, 5, 109, 0, 0, 445, 446, 5, 101, 0, 0, 446, 447, 5, 110, 0, 0, 447, 448, 5, 116, 0, 0, 448, 44, 1, 0, 0, 0, 449, 450, 5, 105, 0, 0, 450, 451, 5, 109, 0, 0, 451, 452, 5, 112, 0, 0, 452, 453, 5, 111, 0, 0, 453, 454, 5, 114, 0, 0, 454, 455, 5, 116, 0, 0, 455, 46, 1, 0, 0, 0, 456, 457, 5, 101, 0, 0, 457, 458, 5, 120, 0, 0, 458, 459, 5, 116, 0, 0, 459, 460, 5, 101, 0, 0, 460, 461, 5, 114, 0, 0, 461, 462, 5, 110, 0, 0, 462, 463, 5, 97, 0, 0, 463, 464, 5, 108, 0, 0, 464, 48, 1, 0, 0, 0, 465, 466, 5, 97, 0, 0, 466, 467, 5, 116, 0, 0, 467, 468, 5, 111, 0, 0, 468, 469, 5, 109, 0, 0, 469, 50, 1, 0, 0, 0, 470, 471, 5, 105, 0, 0, 471, 472, 5, 110, 0, 0, 472, 473, 5, 116, 0, 0, 473, 474, 5, 101, 0, 0, 474, 475, 5, 114, 0, 0, 475, 476, 5, 102, 0, 0, 476, 477, 5, 97, 0, 0, 477, 478, 5, 99, 0, 0, 478, 479, 5, 101, 0, 0, 479, 52, 1, 0, 0, 0, 480, 481, 5, 105, 0, 0, 481, 482, 5, 110, 0, 0, 482, 483, 5, 116, 0, 0, 483, 484, 5, 101, 0, 0, 484, 485, 5, 114, 0, 0, 485, 486, 5, 102, 0, 0, 486, 487, 5, 97, 0, 0, 487, 488, 5, 99, 0, 0, 488, 489, 5, 101, 0, 0, 489, 490, 5, 115, 0, 0, 490, 54, 1, 0, 0, 0, 491, 492, 5, 112, 0, 0, 492, 493, 5, 97, 0, 0, 493, 494, 5, 99, 0, 0, 494, 495, 5, 107, 0, 0, 495, 496, 5, 97, 0, 0, 496, 497, 5, 103, 0, 0, 497, 498, 5, 101, 0, 0, 498, 56, 1, 0, 0, 0, 499, 500, 5, 118, 0, 0, 500, 501, 5, 97, 0, 0, 501, 502, 5, 108, 0, 0, 502, 503, 5, 117, 0, 0, 503, 504, 5, 101, 0, 0, 504, 58, 1, 0, 0, 0, 505, 506, 5, 114, 0, 0, 506, 507, 5, 101, 0, 0, 507, 508, 5, 108, 0, 0, 508, 509, 5, 97, 0, 0, 509, 510, 5, 116, 0, 0, 510, 511, 5, 105, 0, 0, 511, 512, 5, 111, 0, 0, 512, 513, 5, 110, 0, 0, 513, 60, 1, 0, 0, 0, 514, 515, 5, 111, 0, 0, 515, 516, 5, 112, 0, 0, 516, 517, 5, 101, 0, 0, 517, 518, 5, 114, 0, 0, 518, 519, 5, 97, 0, 0, 519, 520, 5, 116, 0, 0, 520, 521, 5, 105, 0, 0, 521, 522, 5, 111, 0, 0, 522, 523, 5, 110, 0, 0, 523, 62, 1, 0, 0, 0, 524, 525, 5, 102, 0, 0, 525, 526, 5, 117, 0, 0, 526, 527, 5, 110, 0, 0, 527, 528, 5, 99, 0, 0, 528, 529, 5, 116, 0, 0, 529, 530, 5, 105, 0, 0, 530, 531, 5, 111, 0, 0, 531, 532, 5, 110, 0, 0, 532, 64, 1, 0, 0, 0, 533, 534, 5, 99, 0, 0, 534, 535, 5, 111, 0, 0, 535, 536, 5, 110, 0, 0, 536, 537, 5, 115, 0, 0, 537, 538, 5, 116, 0, 0, 538, 539, 5, 114, 0, 0, 539, 540, 5, 117, 0, 0, 540, 541, 5, 99, 0, 0, 541, 542, 5, 116, 0, 0, 542, 543, 5, 111, 0, 0, 543, 544, 5, 114, 0, 0, 544, 66, 1, 0, 0, 0, 545, 546, 5, 99, 0, 0, 546, 547, 5, 111, 0, 0, 547, 548, 5, 110, 0, 0, 548, 549, 5, 115, 0, 0, 549, 550, 5, 116, 0, 0, 550, 551, 5, 114, 0, 0, 551, 552, 5, 117, 0, 0, 552, 553, 5, 99, 0, 0, 553, 554, 5, 116, 0, 0, 554, 555, 5, 115, 0, 0, 555, 68, 1, 0, 0, 0, 556, 557, 5, 105, 0, 0, 557, 558, 5, 110, 0, 0, 558, 559, 5, 112, 0, 0, 559, 560, 5, 117, 0, 0, 560, 561, 5, 116, 0, 0, 561, 70, 1, 0, 0, 0, 562, 563, 5, 99, 0, 0, 563, 564, 5, 111, 0, 0, 564, 565, 5, 110, 0, 0, 565, 566, 5, 102, 0, 0, 566, 567, 5, 111, 0, 0, 567, 568, 5, 114, 0, 0, 568, 569, 5, 109, 0, 0, 569, 72, 1, 0, 0, 0, 570, 571, 5, 97, 0, 0, 571, 572, 5, 115, 0, 0, 572, 74, 1, 0, 0, 0, 573, 574, 5, 98, 0, 0, 574, 575, 5, 105, 0, 0, 575, 576, 5, 110, 0, 0, 576, 577, 5, 100, 0, 0, 577, 76, 1, 0, 0, 0, 578, 579, 5, 115, 0, 0, 579, 580, 5, 116, 0, 0, 580, 581, 5, 97, 0, 0, 581, 582, 5, 116, 0, 0, 582, 583, 5, 105, 0, 0, 583, 584, 5, 99, 0, 0, 584, 78, 1, 0, 0, 0, 585, 586, 5, 116, 0, 0, 586, 587, 5, 111, 0, 0, 587, 80, 1, 0, 0, 0, 588, 589, 5, 112, 0, 0, 589, 590, 5, 114, 0, 0, 590, 591, 5, 105, 0, 0, 591, 592, 5, 118, 0, 0, 592, 593, 5, 97, 0, 0, 593, 594, 5, 116, 0, 0, 594, 595, 5, 101, 0, 0, 595, 82, 1, 0, 0, 0, 596, 597, 5, 115, 0, 0, 597, 598, 5, 104, 0, 0, 598, 599, 5, 97, 0, 0, 599, 600, 5, 114, 0, 0, 600, 601, 5, 101, 0, 0, 601, 602, 5, 100, 0, 0, 602, 84, 1, 0, 0, 0, 603, 604, 5, 115, 0, 0, 604, 605, 5, 116, 0, 0, 605, 606, 5, 97, 0, 0, 606, 607, 5, 116, 0, 0, 607, 608, 5, 101, 0, 0, 608, 86, 1, 0, 0, 0, 609, 610, 5, 101, 0, 0, 610, 611, 5, 100, 0, 0, 611, 612, 5, 103, 0, 0, 612, 613, 5, 101, 0, 0, 613, 88, 1, 0, 0, 0, 614, 615, 5, 112, 0, 0, 615, 616, 5, 114, 0, 0, 616, 617, 5, 111, 0, 0, 617, 618, 5, 106, 0, 0, 618, 619, 5, 101, 0, 0, 619, 620, 5, 99, 0, 0, 620, 621, 5, 116, 0, 0, 621, 622, 5, 105, 0, 0, 622, 623, 5, 111, 0, 0, 623, 624, 5, 110, 0, 0, 624, 90, 1, 0, 0, 0, 625, 626, 5, 119, 0, 0, 626, 627, 5, 105, 0, 0, 627, 628, 5, 116, 0, 0, 628, 629, 5, 104, 0, 0, 629, 92, 1, 0, 0, 0, 630, 631, 5, 117, 0, 0, 631, 632, 5, 115, 0, 0, 632, 633, 5, 105, 0, 0, 633, 634, 5, 110, 0, 0, 634, 635, 5, 103, 0, 0, 635, 94, 1, 0, 0, 0, 636, 637, 5, 118, 0, 0, 637, 638, 5, 105, 0, 0, 638, 639, 5, 97, 0, 0, 639, 96, 1, 0, 0, 0, 640, 641, 5, 109, 0, 0, 641, 642, 5, 97, 0, 0, 642, 643, 5, 116, 0, 0, 643, 644, 5, 101, 0, 0, 644, 645, 5, 114, 0, 0, 645, 646, 5, 105, 0, 0, 646, 647, 5, 97, 0, 0, 647, 648, 5, 108, 0, 0, 648, 649, 5, 105, 0, 0, 649, 650, 5, 122, 0, 0, 650, 651, 5, 101, 0, 0, 651, 98, 1, 0, 0, 0, 652, 653, 5, 105, 0, 0, 653, 654, 5, 102, 0, 0, 654, 100, 1, 0, 0, 0, 655, 656, 5, 97, 0, 0, 656, 657, 5, 98, 0, 0, 657, 658, 5, 115, 0, 0, 658, 659, 5, 101, 0, 0, 659, 660, 5, 110, 0, 0, 660, 661, 5, 116, 0, 0, 661, 102, 1, 0, 0, 0, 662, 663, 5, 111, 0, 0, 663, 664, 5, 110, 0, 0, 664, 104, 1, 0, 0, 0, 665, 666, 5, 112, 0, 0, 666, 667, 5, 111, 0, 0, 667, 668, 5, 108, 0, 0, 668, 669, 5, 105, 0, 0, 669, 670, 5, 99, 0, 0, 670, 671, 5, 121, 0, 0, 671, 106, 1, 0, 0, 0, 672, 673, 5, 100, 0, 0, 673, 674, 5, 101, 0, 0, 674, 675, 5, 102, 0, 0, 675, 676, 5, 97, 0, 0, 676, 677, 5, 117, 0, 0, 677, 678, 5, 108, 0, 0, 678, 679, 5, 116, 0, 0, 679, 108, 1, 0, 0, 0, 680, 681, 5, 115, 0, 0, 681, 682, 5, 111, 0, 0, 682, 683, 5, 117, 0, 0, 683, 684, 5, 114, 0, 0, 684, 685, 5, 99, 0, 0, 685, 686, 5, 101, 0, 0, 686, 110, 1, 0, 0, 0, 687, 688, 5, 114, 0, 0, 688, 689, 5, 101, 0, 0, 689, 690, 5, 112, 0, 0, 690, 691, 5, 111, 0, 0, 691, 692, 5, 115, 0, 0, 692, 693, 5, 105, 0, 0, 693, 694, 5, 116, 0, 0, 694, 695, 5, 111, 0, 0, 695, 696, 5, 114, 0, 0, 696, 697, 5, 121, 0, 0, 697, 112, 1, 0, 0, 0, 698, 699, 5, 99, 0, 0, 699, 700, 5, 111, 0, 0, 700, 701, 5, 109, 0, 0, 701, 702, 5, 109, 0, 0, 702, 703, 5, 105, 0, 0, 703, 704, 5, 116, 0, 0, 704, 114, 1, 0, 0, 0, 705, 706, 5, 114, 0, 0, 706, 707, 5, 101, 0, 0, 707, 708, 5, 118, 0, 0, 708, 709, 5, 105, 0, 0, 709, 710, 5, 115, 0, 0, 710, 711, 5, 105, 0, 0, 711, 712, 5, 111, 0, 0, 712, 713, 5, 110, 0, 0, 713, 116, 1, 0, 0, 0, 714, 715, 5, 115, 0, 0, 715, 716, 5, 101, 0, 0, 716, 717, 5, 109, 0, 0, 717, 718, 5, 97, 0, 0, 718, 719, 5, 110, 0, 0, 719, 720, 5, 116, 0, 0, 720, 721, 5, 105, 0, 0, 721, 722, 5, 99, 0, 0, 722, 723, 5, 45, 0, 0, 723, 724, 5, 109, 0, 0, 724, 725, 5, 97, 0, 0, 725, 726, 5, 106, 0, 0, 726, 727, 5, 111, 0, 0, 727, 728, 5, 114, 0, 0, 728, 118, 1, 0, 0, 0, 729, 730, 5, 111, 0, 0, 730, 731, 5, 110, 0, 0, 731, 732, 5, 45, 0, 0, 732, 733, 5, 100, 0, 0, 733, 734, 5, 101, 0, 0, 734, 735, 5, 108, 0, 0, 735, 736, 5, 101, 0, 0, 736, 737, 5, 116, 0, 0, 737, 738, 5, 101, 0, 0, 738, 120, 1, 0, 0, 0, 739, 740, 5, 114, 0, 0, 740, 741, 5, 101, 0, 0, 741, 742, 5, 116, 0, 0, 742, 743, 5, 97, 0, 0, 743, 744, 5, 105, 0, 0, 744, 745, 5, 110, 0, 0, 745, 746, 5, 45, 0, 0, 746, 747, 5, 111, 0, 0, 747, 748, 5, 116, 0, 0, 748, 749, 5, 104, 0, 0, 749, 750, 5, 101, 0, 0, 750, 751, 5, 114, 0, 0, 751, 122, 1, 0, 0, 0, 752, 753, 5, 107, 0, 0, 753, 754, 5, 101, 0, 0, 754, 755, 5, 121, 0, 0, 755, 756, 5, 101, 0, 0, 756, 757, 5, 100, 0, 0, 757, 124, 1, 0, 0, 0, 758, 759, 5, 112, 0, 0, 759, 760, 5, 117, 0, 0, 760, 761, 5, 98, 0, 0, 761, 762, 5, 108, 0, 0, 762, 763, 5, 105, 0, 0, 763, 764, 5, 99, 0, 0, 764, 765, 5, 45, 0, 0, 765, 766, 5, 116, 0, 0, 766, 767, 5, 114, 0, 0, 767, 768, 5, 97, 0, 0, 768, 769, 5, 118, 0, 0, 769, 770, 5, 101, 0, 0, 770, 771, 5, 114, 0, 0, 771, 772, 5, 115, 0, 0, 772, 773, 5, 97, 0, 0, 773, 774, 5, 108, 0, 0, 774, 126, 1, 0, 0, 0, 775, 776, 5, 105, 0, 0, 776, 777, 5, 100, 0, 0, 777, 128, 1, 0, 0, 0, 778, 779, 5, 100, 0, 0, 779, 780, 5, 111, 0, 0, 780, 781, 5, 99, 0, 0, 781, 130, 1, 0, 0, 0, 782, 783, 5, 109, 0, 0, 783, 784, 5, 111, 0, 0, 784, 785, 5, 100, 0, 0, 785, 786, 5, 101, 0, 0, 786, 132, 1, 0, 0, 0, 787, 788, 5, 101, 0, 0, 788, 789, 5, 109, 0, 0, 789, 790, 5, 105, 0, 0, 790, 791, 5, 116, 0, 0, 791, 792, 5, 115, 0, 0, 792, 134, 1, 0, 0, 0, 793, 794, 5, 114, 0, 0, 794, 795, 5, 101, 0, 0, 795, 796, 5, 99, 0, 0, 796, 797, 5, 101, 0, 0, 797, 798, 5, 105, 0, 0, 798, 799, 5, 118, 0, 0, 799, 800, 5, 101, 0, 0, 800, 801, 5, 114, 0, 0, 801, 136, 1, 0, 0, 0, 802, 803, 5, 114, 0, 0, 803, 804, 5, 101, 0, 0, 804, 805, 5, 113, 0, 0, 805, 806, 5, 117, 0, 0, 806, 807, 5, 105, 0, 0, 807, 808, 5, 114, 0, 0, 808, 809, 5, 101, 0, 0, 809, 810, 5, 115, 0, 0, 810, 138, 1, 0, 0, 0, 811, 812, 5, 97, 0, 0, 812, 813, 5, 110, 0, 0, 813, 814, 5, 121, 0, 0, 814, 140, 1, 0, 0, 0, 815, 816, 5, 103, 0, 0, 816, 817, 5, 101, 0, 0, 817, 818, 5, 116, 0, 0, 818, 142, 1, 0, 0, 0, 819, 820, 5, 115, 0, 0, 820, 821, 5, 101, 0, 0, 821, 822, 5, 116, 0, 0, 822, 144, 1, 0, 0, 0, 823, 824, 5, 119, 0, 0, 824, 825, 5, 97, 0, 0, 825, 826, 5, 116, 0, 0, 826, 827, 5, 99, 0, 0, 827, 828, 5, 104, 0, 0, 828, 146, 1, 0, 0, 0, 829, 830, 5, 115, 0, 0, 830, 831, 5, 116, 0, 0, 831, 832, 5, 97, 0, 0, 832, 833, 5, 114, 0, 0, 833, 834, 5, 116, 0, 0, 834, 148, 1, 0, 0, 0, 835, 836, 5, 115, 0, 0, 836, 837, 5, 116, 0, 0, 837, 838, 5, 111, 0, 0, 838, 839, 5, 112, 0, 0, 839, 150, 1, 0, 0, 0, 840, 841, 5, 114, 0, 0, 841, 842, 5, 101, 0, 0, 842, 843, 5, 97, 0, 0, 843, 844, 5, 100, 0, 0, 844, 152, 1, 0, 0, 0, 845, 846, 5, 119, 0, 0, 846, 847, 5, 114, 0, 0, 847, 848, 5, 105, 0, 0, 848, 849, 5, 116, 0, 0, 849, 850, 5, 101, 0, 0, 850, 154, 1, 0, 0, 0, 851, 852, 5, 114, 0, 0, 852, 853, 5, 101, 0, 0, 853, 854, 5, 115, 0, 0, 854, 855, 5, 111, 0, 0, 855, 856, 5, 108, 0, 0, 856, 857, 5, 118, 0, 0, 857, 858, 5, 101, 0, 0, 858, 156, 1, 0, 0, 0, 859, 860, 5, 99, 0, 0, 860, 861, 5, 111, 0, 0, 861, 862, 5, 110, 0, 0, 862, 863, 5, 110, 0, 0, 863, 864, 5, 101, 0, 0, 864, 865, 5, 99, 0, 0, 865, 866, 5, 116, 0, 0, 866, 158, 1, 0, 0, 0, 867, 868, 5, 100, 0, 0, 868, 869, 5, 105, 0, 0, 869, 870, 5, 115, 0, 0, 870, 871, 5, 99, 0, 0, 871, 872, 5, 111, 0, 0, 872, 873, 5, 110, 0, 0, 873, 874, 5, 110, 0, 0, 874, 875, 5, 101, 0, 0, 875, 876, 5, 99, 0, 0, 876, 877, 5, 116, 0, 0, 877, 160, 1, 0, 0, 0, 878, 879, 5, 99, 0, 0, 879, 880, 5, 97, 0, 0, 880, 881, 5, 108, 0, 0, 881, 882, 5, 108, 0, 0, 882, 162, 1, 0, 0, 0, 883, 884, 5, 119, 0, 0, 884, 885, 5, 97, 0, 0, 885, 886, 5, 116, 0, 0, 886, 887, 5, 99, 0, 0, 887, 888, 5, 104, 0, 0, 888, 889, 5, 45, 0, 0, 889, 890, 5, 115, 0, 0, 890, 891, 5, 116, 0, 0, 891, 892, 5, 97, 0, 0, 892, 893, 5, 114, 0, 0, 893, 894, 5, 116, 0, 0, 894, 164, 1, 0, 0, 0, 895, 896, 5, 119, 0, 0, 896, 897, 5, 97, 0, 0, 897, 898, 5, 116, 0, 0, 898, 899, 5, 99, 0, 0, 899, 900, 5, 104, 0, 0, 900, 901, 5, 45, 0, 0, 901, 902, 5, 115, 0, 0, 902, 903, 5, 116, 0, 0, 903, 904, 5, 111, 0, 0, 904, 905, 5, 112, 0, 0, 905, 166, 1, 0, 0, 0, 906, 907, 5, 115, 0, 0, 907, 908, 5, 117, 0, 0, 908, 909, 5, 98, 0, 0, 909, 910, 5, 115, 0, 0, 910, 911, 5, 99, 0, 0, 911, 912, 5, 114, 0, 0, 912, 913, 5, 105, 0, 0, 913, 914, 5, 98, 0, 0, 914, 915, 5, 101, 0, 0, 915, 168, 1, 0, 0, 0, 916, 917, 5, 117, 0, 0, 917, 918, 5, 110, 0, 0, 918, 919, 5, 115, 0, 0, 919, 920, 5, 117, 0, 0, 920, 921, 5, 98, 0, 0, 921, 922, 5, 115, 0, 0, 922, 923, 5, 99, 0, 0, 923, 924, 5, 114, 0, 0, 924, 925, 5, 105, 0, 0, 925, 926, 5, 98, 0, 0, 926, 927, 5, 101, 0, 0, 927, 170, 1, 0, 0, 0, 928, 929, 5, 111, 0, 0, 929, 930, 5, 112, 0, 0, 930, 931, 5, 116, 0, 0, 931, 932, 5, 105, 0, 0, 932, 933, 5, 109, 0, 0, 933, 934, 5, 105, 0, 0, 934, 935, 5, 115, 0, 0, 935, 936, 5, 116, 0, 0, 936, 937, 5, 105, 0, 0, 937, 938, 5, 99, 0, 0, 938, 939, 5, 45, 0, 0, 939, 940, 5, 114, 0, 0, 940, 941, 5, 101, 0, 0, 941, 942, 5, 103, 0, 0, 942, 943, 5, 105, 0, 0, 943, 944, 5, 115, 0, 0, 944, 945, 5, 116, 0, 0, 945, 946, 5, 101, 0, 0, 946, 947, 5, 114, 0, 0, 947, 172, 1, 0, 0, 0, 948, 949, 5, 99, 0, 0, 949, 950, 5, 114, 0, 0, 950, 951, 5, 100, 0, 0, 951, 952, 5, 116, 0, 0, 952, 174, 1, 0, 0, 0, 953, 954, 5, 111, 0, 0, 954, 955, 5, 112, 0, 0, 955, 956, 5, 116, 0, 0, 956, 957, 5, 105, 0, 0, 957, 958, 5, 111, 0, 0, 958, 959, 5, 110, 0, 0, 959, 960, 5, 97, 0, 0, 960, 961, 5, 108, 0, 0, 961, 962, 5, 45, 0, 0, 962, 963, 5, 111, 0, 0, 963, 964, 5, 110, 0, 0, 964, 965, 5, 101, 0, 0, 965, 176, 1, 0, 0, 0, 966, 967, 5, 101, 0, 0, 967, 968, 5, 120, 0, 0, 968, 969, 5, 97, 0, 0, 969, 970, 5, 99, 0, 0, 970, 971, 5, 116, 0, 0, 971, 972, 5, 108, 0, 0, 972, 973, 5, 121, 0, 0, 973, 974, 5, 45, 0, 0, 974, 975, 5, 111, 0, 0, 975, 976, 5, 110, 0, 0, 976, 977, 5, 101, 0, 0, 977, 178, 1, 0, 0, 0, 978, 979, 5, 109, 0, 0, 979, 980, 5, 97, 0, 0, 980, 981, 5, 110, 0, 0, 981, 982, 5, 121, 0, 0, 982, 983, 5, 45, 0, 0, 983, 984, 5, 117, 0, 0, 984, 985, 5, 110, 0, 0, 985, 986, 5, 105, 0, 0, 986, 987, 5, 113, 0, 0, 987, 988, 5, 117, 0, 0, 988, 989, 5, 101, 0, 0, 989, 180, 1, 0, 0, 0, 990, 991, 5, 109, 0, 0, 991, 992, 5, 97, 0, 0, 992, 993, 5, 110, 0, 0, 993, 994, 5, 121, 0, 0, 994, 182, 1, 0, 0, 0, 995, 996, 5, 111, 0, 0, 996, 997, 5, 114, 0, 0, 997, 998, 5, 100, 0, 0, 998, 999, 5, 101, 0, 0, 999, 1000, 5, 114, 0, 0, 1000, 1001, 5, 101, 0, 0, 1001, 1002, 5, 100, 0, 0, 1002, 184, 1, 0, 0, 0, 1003, 1004, 5, 117, 0, 0, 1004, 1005, 5, 110, 0, 0, 1005, 1006, 5, 105, 0, 0, 1006, 1007, 5, 116, 0, 0, 1007, 186, 1, 0, 0, 0, 1008, 1009, 5, 119, 0, 0, 1009, 1010, 5, 97, 0, 0, 1010, 1011, 5, 116, 0, 0, 1011, 1012, 5, 99, 0, 0, 1012, 1013, 5, 104, 0, 0, 1013, 1014, 5, 45, 0, 0, 1014, 1015, 5, 104, 0, 0, 1015, 1016, 5, 97, 0, 0, 1016, 1017, 5, 110, 0, 0, 1017, 1018, 5, 100, 0, 0, 1018, 1019, 5, 108, 0, 0, 1019, 1020, 5, 101, 0, 0, 1020, 188, 1, 0, 0, 0, 1021, 1022, 5, 109, 0, 0, 1022, 1023, 5, 101, 0, 0, 1023, 1024, 5, 115, 0, 0, 1024, 1025, 5, 115, 0, 0, 1025, 1026, 5, 97, 0, 0, 1026, 1027, 5, 103, 0, 0, 1027, 1028, 5, 101, 0, 0, 1028, 190, 1, 0, 0, 0, 1029, 1030, 5, 97, 0, 0, 1030, 1031, 5, 116, 0, 0, 1031, 1032, 5, 111, 0, 0, 1032, 1033, 5, 109, 0, 0, 1033, 1034, 5, 45, 0, 0, 1034, 1035, 5, 114, 0, 0, 1035, 1036, 5, 101, 0, 0, 1036, 1037, 5, 102, 0, 0, 1037, 192, 1, 0, 0, 0, 1038, 1039, 5, 105, 0, 0, 1039, 1040, 5, 110, 0, 0, 1040, 1041, 5, 116, 0, 0, 1041, 1042, 5, 101, 0, 0, 1042, 1043, 5, 114, 0, 0, 1043, 1044, 5, 102, 0, 0, 1044, 1045, 5, 97, 0, 0, 1045, 1046, 5, 99, 0, 0, 1046, 1047, 5, 101, 0, 0, 1047, 1048, 5, 45, 0, 0, 1048, 1049, 5, 114, 0, 0, 1049, 1050, 5, 101, 0, 0, 1050, 1051, 5, 102, 0, 0, 1051, 194, 1, 0, 0, 0, 1052, 1053, 5, 111, 0, 0, 1053, 1054, 5, 112, 0, 0, 1054, 1055, 5, 116, 0, 0, 1055, 1056, 5, 105, 0, 0, 1056, 1057, 5, 111, 0, 0, 1057, 1058, 5, 110, 0, 0, 1058, 1059, 5, 97, 0, 0, 1059, 1060, 5, 108, 0, 0, 1060, 196, 1, 0, 0, 0, 1061, 1062, 5, 108, 0, 0, 1062, 1063, 5, 105, 0, 0, 1063, 1064, 5, 115, 0, 0, 1064, 1065, 5, 116, 0, 0, 1065, 198, 1, 0, 0, 0, 1066, 1067, 5, 114, 0, 0, 1067, 1068, 5, 101, 0, 0, 1068, 1069, 5, 99, 0, 0, 1069, 1070, 5, 111, 0, 0, 1070, 1071, 5, 114, 0, 0, 1071, 1072, 5, 100, 0, 0, 1072, 200, 1, 0, 0, 0, 1073, 1074, 5, 98, 0, 0, 1074, 1075, 5, 111, 0, 0, 1075, 1076, 5, 111, 0, 0, 1076, 1077, 5, 108, 0, 0, 1077, 202, 1, 0, 0, 0, 1078, 1079, 5, 98, 0, 0, 1079, 1080, 5, 121, 0, 0, 1080, 1081, 5, 116, 0, 0, 1081, 1082, 5, 101, 0, 0, 1082, 1083, 5, 115, 0, 0, 1083, 204, 1, 0, 0, 0, 1084, 1085, 5, 100, 0, 0, 1085, 1086, 5, 111, 0, 0, 1086, 1087, 5, 117, 0, 0, 1087, 1088, 5, 98, 0, 0, 1088, 1089, 5, 108, 0, 0, 1089, 1090, 5, 101, 0, 0, 1090, 206, 1, 0, 0, 0, 1091, 1092, 5, 105, 0, 0, 1092, 1093, 5, 110, 0, 0, 1093, 1094, 5, 116, 0, 0, 1094, 1095, 5, 51, 0, 0, 1095, 1096, 5, 50, 0, 0, 1096, 208, 1, 0, 0, 0, 1097, 1098, 5, 105, 0, 0, 1098, 1099, 5, 110, 0, 0, 1099, 1100, 5, 116, 0, 0, 1100, 1101, 5, 54, 0, 0, 1101, 1102, 5, 52, 0, 0, 1102, 210, 1, 0, 0, 0, 1103, 1104, 5, 115, 0, 0, 1104, 1105, 5, 116, 0, 0, 1105, 1106, 5, 114, 0, 0, 1106, 1107, 5, 105, 0, 0, 1107, 1108, 5, 110, 0, 0, 1108, 1109, 5, 103, 0, 0, 1109, 212, 1, 0, 0, 0, 1110, 1111, 5, 117, 0, 0, 1111, 1112, 5, 105, 0, 0, 1112, 1113, 5, 110, 0, 0, 1113, 1114, 5, 116, 0, 0, 1114, 1115, 5, 51, 0, 0, 1115, 1116, 5, 50, 0, 0, 1116, 214, 1, 0, 0, 0, 1117, 1118, 5, 117, 0, 0, 1118, 1119, 5, 105, 0, 0, 1119, 1120, 5, 110, 0, 0, 1120, 1121, 5, 116, 0, 0, 1121, 1122, 5, 54, 0, 0, 1122, 1123, 5, 52, 0, 0, 1123, 216, 1, 0, 0, 0, 1124, 1125, 5, 116, 0, 0, 1125, 1126, 5, 114, 0, 0, 1126, 1127, 5, 117, 0, 0, 1127, 1128, 5, 101, 0, 0, 1128, 218, 1, 0, 0, 0, 1129, 1130, 5, 102, 0, 0, 1130, 1131, 5, 97, 0, 0, 1131, 1132, 5, 108, 0, 0, 1132, 1133, 5, 115, 0, 0, 1133, 1134, 5, 101, 0, 0, 1134, 220, 1, 0, 0, 0, 1135, 1136, 5, 110, 0, 0, 1136, 1137, 5, 117, 0, 0, 1137, 1138, 5, 108, 0, 0, 1138, 1139, 5, 108, 0, 0, 1139, 222, 1, 0, 0, 0, 1140, 1141, 5, 45, 0, 0, 1141, 1142, 5, 62, 0, 0, 1142, 224, 1, 0, 0, 0, 1143, 1144, 5, 58, 0, 0, 1144, 226, 1, 0, 0, 0, 1145, 1146, 5, 59, 0, 0, 1146, 228, 1, 0, 0, 0, 1147, 1148, 5, 44, 0, 0, 1148, 230, 1, 0, 0, 0, 1149, 1150, 5, 46, 0, 0, 1150, 232, 1, 0, 0, 0, 1151, 1152, 5, 123, 0, 0, 1152, 234, 1, 0, 0, 0, 1153, 1154, 5, 125, 0, 0, 1154, 236, 1, 0, 0, 0, 1155, 1156, 5, 91, 0, 0, 1156, 238, 1, 0, 0, 0, 1157, 1158, 5, 93, 0, 0, 1158, 240, 1, 0, 0, 0, 1159, 1160, 5, 40, 0, 0, 1160, 242, 1, 0, 0, 0, 1161, 1162, 5, 41, 0, 0, 1162, 244, 1, 0, 0, 0, 1163, 1164, 5, 60, 0, 0, 1164, 246, 1, 0, 0, 0, 1165, 1166, 5, 62, 0, 0, 1166, 248, 1, 0, 0, 0, 1167, 1168, 5, 38, 0, 0, 1168, 250, 1, 0, 0, 0, 1169, 1170, 5, 61, 0, 0, 1170, 252, 1, 0, 0, 0, 1171, 1173, 5, 45, 0, 0, 1172, 1171, 1, 0, 0, 0, 1172, 1173, 1, 0, 0, 0, 1173, 1175, 1, 0, 0, 0, 1174, 1176, 7, 0, 0, 0, 1175, 1174, 1, 0, 0, 0, 1176, 1177, 1, 0, 0, 0, 1177, 1175, 1, 0, 0, 0, 1177, 1178, 1, 0, 0, 0, 1178, 254, 1, 0, 0, 0, 1179, 1181, 5, 45, 0, 0, 1180, 1179, 1, 0, 0, 0, 1180, 1181, 1, 0, 0, 0, 1181, 1190, 1, 0, 0, 0, 1182, 1191, 5, 48, 0, 0, 1183, 1187, 7, 1, 0, 0, 1184, 1186, 7, 0, 0, 0, 1185, 1184, 1, 0, 0, 0, 1186, 1189, 1, 0, 0, 0, 1187, 1185, 1, 0, 0, 0, 1187, 1188, 1, 0, 0, 0, 1188, 1191, 1, 0, 0, 0, 1189, 1187, 1, 0, 0, 0, 1190, 1182, 1, 0, 0, 0, 1190, 1183, 1, 0, 0, 0, 1191, 1198, 1, 0, 0, 0, 1192, 1194, 5, 46, 0, 0, 1193, 1195, 7, 0, 0, 0, 1194, 1193, 1, 0, 0, 0, 1195, 1196, 1, 0, 0, 0, 1196, 1194, 1, 0, 0, 0, 1196, 1197, 1, 0, 0, 0, 1197, 1199, 1, 0, 0, 0, 1198, 1192, 1, 0, 0, 0, 1198, 1199, 1, 0, 0, 0, 1199, 1209, 1, 0, 0, 0, 1200, 1202, 7, 2, 0, 0, 1201, 1203, 7, 3, 0, 0, 1202, 1201, 1, 0, 0, 0, 1202, 1203, 1, 0, 0, 0, 1203, 1205, 1, 0, 0, 0, 1204, 1206, 7, 0, 0, 0, 1205, 1204, 1, 0, 0, 0, 1206, 1207, 1, 0, 0, 0, 1207, 1205, 1, 0, 0, 0, 1207, 1208, 1, 0, 0, 0, 1208, 1210, 1, 0, 0, 0, 1209, 1200, 1, 0, 0, 0, 1209, 1210, 1, 0, 0, 0, 1210, 256, 1, 0, 0, 0, 1211, 1215, 7, 4, 0, 0, 1212, 1214, 7, 5, 0, 0, 1213, 1212, 1, 0, 0, 0, 1214, 1217, 1, 0, 0, 0, 1215, 1213, 1, 0, 0, 0, 1215, 1216, 1, 0, 0, 0, 1216, 258, 1, 0, 0, 0, 1217, 1215, 1, 0, 0, 0, 1218, 1223, 5, 34, 0, 0, 1219, 1222, 3, 261, 130, 0, 1220, 1222, 8, 6, 0, 0, 1221, 1219, 1, 0, 0, 0, 1221, 1220, 1, 0, 0, 0, 1222, 1225, 1, 0, 0, 0, 1223, 1221, 1, 0, 0, 0, 1223, 1224, 1, 0, 0, 0, 1224, 1226, 1, 0, 0, 0, 1225, 1223, 1, 0, 0, 0, 1226, 1227, 5, 34, 0, 0, 1227, 260, 1, 0, 0, 0, 1228, 1236, 5, 92, 0, 0, 1229, 1237, 7, 7, 0, 0, 1230, 1231, 5, 117, 0, 0, 1231, 1232, 3, 263, 131, 0, 1232, 1233, 3, 263, 131, 0, 1233, 1234, 3, 263, 131, 0, 1234, 1235, 3, 263, 131, 0, 1235, 1237, 1, 0, 0, 0, 1236, 1229, 1, 0, 0, 0, 1236, 1230, 1, 0, 0, 0, 1237, 262, 1, 0, 0, 0, 1238, 1239, 7, 8, 0, 0, 1239, 264, 1, 0, 0, 0, 1240, 1241, 5, 47, 0, 0, 1241, 1242, 5, 47, 0, 0, 1242, 1246, 1, 0, 0, 0, 1243, 1245, 8, 9, 0, 0, 1244, 1243, 1, 0, 0, 0, 1245, 1248, 1, 0, 0, 0, 1246, 1244, 1, 0, 0, 0, 1246, 1247, 1, 0, 0, 0, 1247, 1249, 1, 0, 0, 0, 1248, 1246, 1, 0, 0, 0, 1249, 1250, 6, 132, 0, 0, 1250, 266, 1, 0, 0, 0, 1251, 1252, 5, 47, 0, 0, 1252, 1253, 5, 42, 0, 0, 1253, 1257, 1, 0, 0, 0, 1254, 1256, 9, 0, 0, 0, 1255, 1254, 1, 0, 0, 0, 1256, 1259, 1, 0, 0, 0, 1257, 1258, 1, 0, 0, 0, 1257, 1255, 1, 0, 0, 0, 1258, 1260, 1, 0, 0, 0, 1259, 1257, 1, 0, 0, 0, 1260, 1261, 5, 42, 0, 0, 1261, 1262, 5, 47, 0, 0, 1262, 1263, 1, 0, 0, 0, 1263, 1264, 6, 133, 0, 0, 1264, 268, 1, 0, 0, 0, 1265, 1267, 7, 10, 0, 0, 1266, 1265, 1, 0, 0, 0, 1267, 1268, 1, 0, 0, 0, 1268, 1266, 1, 0, 0, 0, 1268, 1269, 1, 0, 0, 0, 1269, 1270, 1, 0, 0, 0, 1270, 1271, 6, 134, 0, 0, 1271, 270, 1, 0, 0, 0, 18, 0, 1172, 1177, 1180, 1187, 1190, 1196, 1198, 1202, 1207, 1209, 1215, 1221, 1223, 1236, 1246, 1257, 1268, 1, 0, 1, 0] \ No newline at end of file +[4, 0, 159, 1532, 6, -1, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, 4, 2, 5, 7, 5, 2, 6, 7, 6, 2, 7, 7, 7, 2, 8, 7, 8, 2, 9, 7, 9, 2, 10, 7, 10, 2, 11, 7, 11, 2, 12, 7, 12, 2, 13, 7, 13, 2, 14, 7, 14, 2, 15, 7, 15, 2, 16, 7, 16, 2, 17, 7, 17, 2, 18, 7, 18, 2, 19, 7, 19, 2, 20, 7, 20, 2, 21, 7, 21, 2, 22, 7, 22, 2, 23, 7, 23, 2, 24, 7, 24, 2, 25, 7, 25, 2, 26, 7, 26, 2, 27, 7, 27, 2, 28, 7, 28, 2, 29, 7, 29, 2, 30, 7, 30, 2, 31, 7, 31, 2, 32, 7, 32, 2, 33, 7, 33, 2, 34, 7, 34, 2, 35, 7, 35, 2, 36, 7, 36, 2, 37, 7, 37, 2, 38, 7, 38, 2, 39, 7, 39, 2, 40, 7, 40, 2, 41, 7, 41, 2, 42, 7, 42, 2, 43, 7, 43, 2, 44, 7, 44, 2, 45, 7, 45, 2, 46, 7, 46, 2, 47, 7, 47, 2, 48, 7, 48, 2, 49, 7, 49, 2, 50, 7, 50, 2, 51, 7, 51, 2, 52, 7, 52, 2, 53, 7, 53, 2, 54, 7, 54, 2, 55, 7, 55, 2, 56, 7, 56, 2, 57, 7, 57, 2, 58, 7, 58, 2, 59, 7, 59, 2, 60, 7, 60, 2, 61, 7, 61, 2, 62, 7, 62, 2, 63, 7, 63, 2, 64, 7, 64, 2, 65, 7, 65, 2, 66, 7, 66, 2, 67, 7, 67, 2, 68, 7, 68, 2, 69, 7, 69, 2, 70, 7, 70, 2, 71, 7, 71, 2, 72, 7, 72, 2, 73, 7, 73, 2, 74, 7, 74, 2, 75, 7, 75, 2, 76, 7, 76, 2, 77, 7, 77, 2, 78, 7, 78, 2, 79, 7, 79, 2, 80, 7, 80, 2, 81, 7, 81, 2, 82, 7, 82, 2, 83, 7, 83, 2, 84, 7, 84, 2, 85, 7, 85, 2, 86, 7, 86, 2, 87, 7, 87, 2, 88, 7, 88, 2, 89, 7, 89, 2, 90, 7, 90, 2, 91, 7, 91, 2, 92, 7, 92, 2, 93, 7, 93, 2, 94, 7, 94, 2, 95, 7, 95, 2, 96, 7, 96, 2, 97, 7, 97, 2, 98, 7, 98, 2, 99, 7, 99, 2, 100, 7, 100, 2, 101, 7, 101, 2, 102, 7, 102, 2, 103, 7, 103, 2, 104, 7, 104, 2, 105, 7, 105, 2, 106, 7, 106, 2, 107, 7, 107, 2, 108, 7, 108, 2, 109, 7, 109, 2, 110, 7, 110, 2, 111, 7, 111, 2, 112, 7, 112, 2, 113, 7, 113, 2, 114, 7, 114, 2, 115, 7, 115, 2, 116, 7, 116, 2, 117, 7, 117, 2, 118, 7, 118, 2, 119, 7, 119, 2, 120, 7, 120, 2, 121, 7, 121, 2, 122, 7, 122, 2, 123, 7, 123, 2, 124, 7, 124, 2, 125, 7, 125, 2, 126, 7, 126, 2, 127, 7, 127, 2, 128, 7, 128, 2, 129, 7, 129, 2, 130, 7, 130, 2, 131, 7, 131, 2, 132, 7, 132, 2, 133, 7, 133, 2, 134, 7, 134, 2, 135, 7, 135, 2, 136, 7, 136, 2, 137, 7, 137, 2, 138, 7, 138, 2, 139, 7, 139, 2, 140, 7, 140, 2, 141, 7, 141, 2, 142, 7, 142, 2, 143, 7, 143, 2, 144, 7, 144, 2, 145, 7, 145, 2, 146, 7, 146, 2, 147, 7, 147, 2, 148, 7, 148, 2, 149, 7, 149, 2, 150, 7, 150, 2, 151, 7, 151, 2, 152, 7, 152, 2, 153, 7, 153, 2, 154, 7, 154, 2, 155, 7, 155, 2, 156, 7, 156, 2, 157, 7, 157, 2, 158, 7, 158, 2, 159, 7, 159, 2, 160, 7, 160, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 5, 1, 5, 1, 5, 1, 5, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 12, 1, 12, 1, 12, 1, 12, 1, 12, 1, 12, 1, 12, 1, 12, 1, 12, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 19, 1, 19, 1, 19, 1, 19, 1, 19, 1, 20, 1, 20, 1, 20, 1, 20, 1, 20, 1, 20, 1, 20, 1, 20, 1, 20, 1, 20, 1, 20, 1, 20, 1, 20, 1, 21, 1, 21, 1, 21, 1, 21, 1, 21, 1, 21, 1, 21, 1, 21, 1, 21, 1, 21, 1, 22, 1, 22, 1, 22, 1, 22, 1, 22, 1, 22, 1, 22, 1, 22, 1, 22, 1, 22, 1, 22, 1, 22, 1, 23, 1, 23, 1, 23, 1, 23, 1, 23, 1, 23, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 25, 1, 25, 1, 25, 1, 25, 1, 25, 1, 25, 1, 25, 1, 25, 1, 25, 1, 25, 1, 25, 1, 26, 1, 26, 1, 26, 1, 26, 1, 26, 1, 27, 1, 27, 1, 27, 1, 27, 1, 27, 1, 27, 1, 27, 1, 27, 1, 27, 1, 28, 1, 28, 1, 28, 1, 28, 1, 28, 1, 28, 1, 28, 1, 28, 1, 28, 1, 28, 1, 29, 1, 29, 1, 29, 1, 29, 1, 29, 1, 30, 1, 30, 1, 30, 1, 30, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 32, 1, 32, 1, 32, 1, 32, 1, 32, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 34, 1, 34, 1, 34, 1, 34, 1, 35, 1, 35, 1, 35, 1, 35, 1, 35, 1, 35, 1, 35, 1, 35, 1, 35, 1, 35, 1, 35, 1, 35, 1, 35, 1, 36, 1, 36, 1, 36, 1, 36, 1, 36, 1, 37, 1, 37, 1, 37, 1, 37, 1, 37, 1, 37, 1, 37, 1, 38, 1, 38, 1, 38, 1, 38, 1, 38, 1, 38, 1, 38, 1, 38, 1, 38, 1, 39, 1, 39, 1, 39, 1, 39, 1, 39, 1, 39, 1, 39, 1, 39, 1, 39, 1, 39, 1, 39, 1, 40, 1, 40, 1, 40, 1, 40, 1, 41, 1, 41, 1, 41, 1, 41, 1, 41, 1, 41, 1, 41, 1, 41, 1, 41, 1, 42, 1, 42, 1, 42, 1, 42, 1, 42, 1, 42, 1, 42, 1, 43, 1, 43, 1, 43, 1, 43, 1, 43, 1, 43, 1, 43, 1, 43, 1, 43, 1, 44, 1, 44, 1, 44, 1, 44, 1, 44, 1, 44, 1, 44, 1, 45, 1, 45, 1, 45, 1, 45, 1, 45, 1, 46, 1, 46, 1, 46, 1, 46, 1, 46, 1, 46, 1, 46, 1, 46, 1, 46, 1, 46, 1, 47, 1, 47, 1, 47, 1, 47, 1, 47, 1, 47, 1, 47, 1, 47, 1, 47, 1, 47, 1, 47, 1, 48, 1, 48, 1, 48, 1, 48, 1, 48, 1, 48, 1, 48, 1, 48, 1, 49, 1, 49, 1, 49, 1, 49, 1, 49, 1, 49, 1, 50, 1, 50, 1, 50, 1, 50, 1, 50, 1, 50, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 52, 1, 52, 1, 52, 1, 52, 1, 52, 1, 52, 1, 52, 1, 52, 1, 52, 1, 52, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 54, 1, 54, 1, 54, 1, 54, 1, 54, 1, 54, 1, 54, 1, 54, 1, 54, 1, 54, 1, 54, 1, 54, 1, 55, 1, 55, 1, 55, 1, 55, 1, 55, 1, 55, 1, 55, 1, 55, 1, 55, 1, 55, 1, 55, 1, 56, 1, 56, 1, 56, 1, 56, 1, 56, 1, 56, 1, 57, 1, 57, 1, 57, 1, 57, 1, 57, 1, 57, 1, 57, 1, 57, 1, 58, 1, 58, 1, 58, 1, 59, 1, 59, 1, 59, 1, 59, 1, 59, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 61, 1, 61, 1, 61, 1, 62, 1, 62, 1, 62, 1, 62, 1, 62, 1, 62, 1, 62, 1, 62, 1, 63, 1, 63, 1, 63, 1, 63, 1, 63, 1, 63, 1, 63, 1, 64, 1, 64, 1, 64, 1, 64, 1, 64, 1, 64, 1, 65, 1, 65, 1, 65, 1, 65, 1, 65, 1, 66, 1, 66, 1, 66, 1, 66, 1, 66, 1, 66, 1, 66, 1, 66, 1, 66, 1, 66, 1, 66, 1, 67, 1, 67, 1, 67, 1, 67, 1, 67, 1, 68, 1, 68, 1, 68, 1, 68, 1, 68, 1, 68, 1, 69, 1, 69, 1, 69, 1, 69, 1, 69, 1, 69, 1, 69, 1, 70, 1, 70, 1, 70, 1, 70, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 72, 1, 72, 1, 72, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 74, 1, 74, 1, 74, 1, 75, 1, 75, 1, 75, 1, 75, 1, 75, 1, 75, 1, 75, 1, 76, 1, 76, 1, 76, 1, 76, 1, 76, 1, 76, 1, 76, 1, 76, 1, 77, 1, 77, 1, 77, 1, 77, 1, 77, 1, 77, 1, 77, 1, 78, 1, 78, 1, 78, 1, 78, 1, 78, 1, 78, 1, 78, 1, 78, 1, 78, 1, 78, 1, 78, 1, 79, 1, 79, 1, 79, 1, 79, 1, 79, 1, 79, 1, 79, 1, 80, 1, 80, 1, 80, 1, 80, 1, 80, 1, 80, 1, 80, 1, 80, 1, 80, 1, 81, 1, 81, 1, 81, 1, 81, 1, 81, 1, 81, 1, 81, 1, 81, 1, 81, 1, 81, 1, 81, 1, 81, 1, 81, 1, 81, 1, 81, 1, 82, 1, 82, 1, 82, 1, 82, 1, 82, 1, 82, 1, 82, 1, 82, 1, 82, 1, 82, 1, 83, 1, 83, 1, 83, 1, 83, 1, 83, 1, 83, 1, 83, 1, 83, 1, 83, 1, 83, 1, 84, 1, 84, 1, 84, 1, 84, 1, 84, 1, 84, 1, 84, 1, 84, 1, 85, 1, 85, 1, 85, 1, 85, 1, 85, 1, 85, 1, 85, 1, 85, 1, 85, 1, 85, 1, 85, 1, 85, 1, 85, 1, 86, 1, 86, 1, 86, 1, 86, 1, 86, 1, 86, 1, 86, 1, 86, 1, 86, 1, 86, 1, 86, 1, 86, 1, 86, 1, 87, 1, 87, 1, 87, 1, 87, 1, 87, 1, 87, 1, 88, 1, 88, 1, 88, 1, 88, 1, 88, 1, 88, 1, 88, 1, 88, 1, 88, 1, 88, 1, 88, 1, 88, 1, 88, 1, 88, 1, 88, 1, 88, 1, 88, 1, 89, 1, 89, 1, 89, 1, 90, 1, 90, 1, 90, 1, 90, 1, 91, 1, 91, 1, 91, 1, 91, 1, 91, 1, 92, 1, 92, 1, 92, 1, 92, 1, 92, 1, 92, 1, 93, 1, 93, 1, 93, 1, 93, 1, 93, 1, 93, 1, 93, 1, 93, 1, 93, 1, 94, 1, 94, 1, 94, 1, 94, 1, 94, 1, 94, 1, 94, 1, 94, 1, 94, 1, 95, 1, 95, 1, 95, 1, 95, 1, 96, 1, 96, 1, 96, 1, 96, 1, 97, 1, 97, 1, 97, 1, 97, 1, 98, 1, 98, 1, 98, 1, 98, 1, 98, 1, 98, 1, 99, 1, 99, 1, 99, 1, 99, 1, 99, 1, 99, 1, 100, 1, 100, 1, 100, 1, 100, 1, 100, 1, 101, 1, 101, 1, 101, 1, 101, 1, 101, 1, 102, 1, 102, 1, 102, 1, 102, 1, 102, 1, 102, 1, 103, 1, 103, 1, 103, 1, 103, 1, 103, 1, 103, 1, 103, 1, 103, 1, 104, 1, 104, 1, 104, 1, 104, 1, 104, 1, 104, 1, 104, 1, 104, 1, 105, 1, 105, 1, 105, 1, 105, 1, 105, 1, 105, 1, 105, 1, 105, 1, 105, 1, 105, 1, 105, 1, 106, 1, 106, 1, 106, 1, 106, 1, 106, 1, 107, 1, 107, 1, 107, 1, 107, 1, 107, 1, 107, 1, 107, 1, 107, 1, 107, 1, 107, 1, 107, 1, 107, 1, 108, 1, 108, 1, 108, 1, 108, 1, 108, 1, 108, 1, 108, 1, 108, 1, 108, 1, 108, 1, 108, 1, 109, 1, 109, 1, 109, 1, 109, 1, 109, 1, 109, 1, 109, 1, 109, 1, 109, 1, 109, 1, 110, 1, 110, 1, 110, 1, 110, 1, 110, 1, 110, 1, 110, 1, 110, 1, 110, 1, 110, 1, 110, 1, 110, 1, 111, 1, 111, 1, 111, 1, 111, 1, 111, 1, 111, 1, 111, 1, 111, 1, 111, 1, 112, 1, 112, 1, 112, 1, 112, 1, 112, 1, 113, 1, 113, 1, 113, 1, 113, 1, 113, 1, 113, 1, 113, 1, 113, 1, 113, 1, 113, 1, 113, 1, 113, 1, 113, 1, 114, 1, 114, 1, 114, 1, 114, 1, 114, 1, 114, 1, 114, 1, 114, 1, 114, 1, 114, 1, 114, 1, 114, 1, 115, 1, 115, 1, 115, 1, 115, 1, 115, 1, 115, 1, 115, 1, 115, 1, 115, 1, 115, 1, 115, 1, 115, 1, 116, 1, 116, 1, 116, 1, 116, 1, 116, 1, 117, 1, 117, 1, 117, 1, 117, 1, 117, 1, 117, 1, 117, 1, 117, 1, 118, 1, 118, 1, 118, 1, 118, 1, 118, 1, 119, 1, 119, 1, 119, 1, 119, 1, 119, 1, 119, 1, 119, 1, 119, 1, 119, 1, 119, 1, 119, 1, 119, 1, 119, 1, 120, 1, 120, 1, 120, 1, 120, 1, 120, 1, 120, 1, 120, 1, 120, 1, 121, 1, 121, 1, 121, 1, 121, 1, 121, 1, 121, 1, 121, 1, 121, 1, 121, 1, 122, 1, 122, 1, 122, 1, 122, 1, 122, 1, 122, 1, 122, 1, 122, 1, 122, 1, 122, 1, 122, 1, 122, 1, 122, 1, 122, 1, 123, 1, 123, 1, 123, 1, 123, 1, 123, 1, 123, 1, 123, 1, 123, 1, 123, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 125, 1, 125, 1, 125, 1, 125, 1, 125, 1, 125, 1, 125, 1, 126, 1, 126, 1, 126, 1, 126, 1, 126, 1, 127, 1, 127, 1, 127, 1, 127, 1, 127, 1, 127, 1, 128, 1, 128, 1, 128, 1, 128, 1, 128, 1, 128, 1, 128, 1, 129, 1, 129, 1, 129, 1, 129, 1, 129, 1, 129, 1, 130, 1, 130, 1, 130, 1, 130, 1, 130, 1, 130, 1, 131, 1, 131, 1, 131, 1, 131, 1, 131, 1, 131, 1, 131, 1, 132, 1, 132, 1, 132, 1, 132, 1, 132, 1, 132, 1, 132, 1, 133, 1, 133, 1, 133, 1, 133, 1, 133, 1, 133, 1, 133, 1, 134, 1, 134, 1, 134, 1, 134, 1, 134, 1, 135, 1, 135, 1, 135, 1, 135, 1, 135, 1, 135, 1, 136, 1, 136, 1, 136, 1, 136, 1, 136, 1, 137, 1, 137, 1, 137, 1, 138, 1, 138, 1, 139, 1, 139, 1, 140, 1, 140, 1, 141, 1, 141, 1, 142, 1, 142, 1, 143, 1, 143, 1, 144, 1, 144, 1, 145, 1, 145, 1, 146, 1, 146, 1, 147, 1, 147, 1, 148, 1, 148, 1, 149, 1, 149, 1, 150, 1, 150, 1, 151, 1, 151, 1, 152, 3, 152, 1433, 8, 152, 1, 152, 4, 152, 1436, 8, 152, 11, 152, 12, 152, 1437, 1, 153, 3, 153, 1441, 8, 153, 1, 153, 1, 153, 1, 153, 5, 153, 1446, 8, 153, 10, 153, 12, 153, 1449, 9, 153, 3, 153, 1451, 8, 153, 1, 153, 1, 153, 4, 153, 1455, 8, 153, 11, 153, 12, 153, 1456, 3, 153, 1459, 8, 153, 1, 153, 1, 153, 3, 153, 1463, 8, 153, 1, 153, 4, 153, 1466, 8, 153, 11, 153, 12, 153, 1467, 3, 153, 1470, 8, 153, 1, 154, 1, 154, 5, 154, 1474, 8, 154, 10, 154, 12, 154, 1477, 9, 154, 1, 155, 1, 155, 1, 155, 5, 155, 1482, 8, 155, 10, 155, 12, 155, 1485, 9, 155, 1, 155, 1, 155, 1, 156, 1, 156, 1, 156, 1, 156, 1, 156, 1, 156, 1, 156, 1, 156, 3, 156, 1497, 8, 156, 1, 157, 1, 157, 1, 158, 1, 158, 1, 158, 1, 158, 5, 158, 1505, 8, 158, 10, 158, 12, 158, 1508, 9, 158, 1, 158, 1, 158, 1, 159, 1, 159, 1, 159, 1, 159, 5, 159, 1516, 8, 159, 10, 159, 12, 159, 1519, 9, 159, 1, 159, 1, 159, 1, 159, 1, 159, 1, 159, 1, 160, 4, 160, 1527, 8, 160, 11, 160, 12, 160, 1528, 1, 160, 1, 160, 1, 1517, 0, 161, 1, 1, 3, 2, 5, 3, 7, 4, 9, 5, 11, 6, 13, 7, 15, 8, 17, 9, 19, 10, 21, 11, 23, 12, 25, 13, 27, 14, 29, 15, 31, 16, 33, 17, 35, 18, 37, 19, 39, 20, 41, 21, 43, 22, 45, 23, 47, 24, 49, 25, 51, 26, 53, 27, 55, 28, 57, 29, 59, 30, 61, 31, 63, 32, 65, 33, 67, 34, 69, 35, 71, 36, 73, 37, 75, 38, 77, 39, 79, 40, 81, 41, 83, 42, 85, 43, 87, 44, 89, 45, 91, 46, 93, 47, 95, 48, 97, 49, 99, 50, 101, 51, 103, 52, 105, 53, 107, 54, 109, 55, 111, 56, 113, 57, 115, 58, 117, 59, 119, 60, 121, 61, 123, 62, 125, 63, 127, 64, 129, 65, 131, 66, 133, 67, 135, 68, 137, 69, 139, 70, 141, 71, 143, 72, 145, 73, 147, 74, 149, 75, 151, 76, 153, 77, 155, 78, 157, 79, 159, 80, 161, 81, 163, 82, 165, 83, 167, 84, 169, 85, 171, 86, 173, 87, 175, 88, 177, 89, 179, 90, 181, 91, 183, 92, 185, 93, 187, 94, 189, 95, 191, 96, 193, 97, 195, 98, 197, 99, 199, 100, 201, 101, 203, 102, 205, 103, 207, 104, 209, 105, 211, 106, 213, 107, 215, 108, 217, 109, 219, 110, 221, 111, 223, 112, 225, 113, 227, 114, 229, 115, 231, 116, 233, 117, 235, 118, 237, 119, 239, 120, 241, 121, 243, 122, 245, 123, 247, 124, 249, 125, 251, 126, 253, 127, 255, 128, 257, 129, 259, 130, 261, 131, 263, 132, 265, 133, 267, 134, 269, 135, 271, 136, 273, 137, 275, 138, 277, 139, 279, 140, 281, 141, 283, 142, 285, 143, 287, 144, 289, 145, 291, 146, 293, 147, 295, 148, 297, 149, 299, 150, 301, 151, 303, 152, 305, 153, 307, 154, 309, 155, 311, 156, 313, 0, 315, 0, 317, 157, 319, 158, 321, 159, 1, 0, 11, 1, 0, 48, 57, 1, 0, 49, 57, 2, 0, 69, 69, 101, 101, 2, 0, 43, 43, 45, 45, 3, 0, 65, 90, 95, 95, 97, 122, 4, 0, 48, 57, 65, 90, 95, 95, 97, 122, 4, 0, 10, 10, 13, 13, 34, 34, 92, 92, 8, 0, 34, 34, 47, 47, 92, 92, 98, 98, 102, 102, 110, 110, 114, 114, 116, 116, 3, 0, 48, 57, 65, 70, 97, 102, 2, 0, 10, 10, 13, 13, 3, 0, 9, 10, 13, 13, 32, 32, 1546, 0, 1, 1, 0, 0, 0, 0, 3, 1, 0, 0, 0, 0, 5, 1, 0, 0, 0, 0, 7, 1, 0, 0, 0, 0, 9, 1, 0, 0, 0, 0, 11, 1, 0, 0, 0, 0, 13, 1, 0, 0, 0, 0, 15, 1, 0, 0, 0, 0, 17, 1, 0, 0, 0, 0, 19, 1, 0, 0, 0, 0, 21, 1, 0, 0, 0, 0, 23, 1, 0, 0, 0, 0, 25, 1, 0, 0, 0, 0, 27, 1, 0, 0, 0, 0, 29, 1, 0, 0, 0, 0, 31, 1, 0, 0, 0, 0, 33, 1, 0, 0, 0, 0, 35, 1, 0, 0, 0, 0, 37, 1, 0, 0, 0, 0, 39, 1, 0, 0, 0, 0, 41, 1, 0, 0, 0, 0, 43, 1, 0, 0, 0, 0, 45, 1, 0, 0, 0, 0, 47, 1, 0, 0, 0, 0, 49, 1, 0, 0, 0, 0, 51, 1, 0, 0, 0, 0, 53, 1, 0, 0, 0, 0, 55, 1, 0, 0, 0, 0, 57, 1, 0, 0, 0, 0, 59, 1, 0, 0, 0, 0, 61, 1, 0, 0, 0, 0, 63, 1, 0, 0, 0, 0, 65, 1, 0, 0, 0, 0, 67, 1, 0, 0, 0, 0, 69, 1, 0, 0, 0, 0, 71, 1, 0, 0, 0, 0, 73, 1, 0, 0, 0, 0, 75, 1, 0, 0, 0, 0, 77, 1, 0, 0, 0, 0, 79, 1, 0, 0, 0, 0, 81, 1, 0, 0, 0, 0, 83, 1, 0, 0, 0, 0, 85, 1, 0, 0, 0, 0, 87, 1, 0, 0, 0, 0, 89, 1, 0, 0, 0, 0, 91, 1, 0, 0, 0, 0, 93, 1, 0, 0, 0, 0, 95, 1, 0, 0, 0, 0, 97, 1, 0, 0, 0, 0, 99, 1, 0, 0, 0, 0, 101, 1, 0, 0, 0, 0, 103, 1, 0, 0, 0, 0, 105, 1, 0, 0, 0, 0, 107, 1, 0, 0, 0, 0, 109, 1, 0, 0, 0, 0, 111, 1, 0, 0, 0, 0, 113, 1, 0, 0, 0, 0, 115, 1, 0, 0, 0, 0, 117, 1, 0, 0, 0, 0, 119, 1, 0, 0, 0, 0, 121, 1, 0, 0, 0, 0, 123, 1, 0, 0, 0, 0, 125, 1, 0, 0, 0, 0, 127, 1, 0, 0, 0, 0, 129, 1, 0, 0, 0, 0, 131, 1, 0, 0, 0, 0, 133, 1, 0, 0, 0, 0, 135, 1, 0, 0, 0, 0, 137, 1, 0, 0, 0, 0, 139, 1, 0, 0, 0, 0, 141, 1, 0, 0, 0, 0, 143, 1, 0, 0, 0, 0, 145, 1, 0, 0, 0, 0, 147, 1, 0, 0, 0, 0, 149, 1, 0, 0, 0, 0, 151, 1, 0, 0, 0, 0, 153, 1, 0, 0, 0, 0, 155, 1, 0, 0, 0, 0, 157, 1, 0, 0, 0, 0, 159, 1, 0, 0, 0, 0, 161, 1, 0, 0, 0, 0, 163, 1, 0, 0, 0, 0, 165, 1, 0, 0, 0, 0, 167, 1, 0, 0, 0, 0, 169, 1, 0, 0, 0, 0, 171, 1, 0, 0, 0, 0, 173, 1, 0, 0, 0, 0, 175, 1, 0, 0, 0, 0, 177, 1, 0, 0, 0, 0, 179, 1, 0, 0, 0, 0, 181, 1, 0, 0, 0, 0, 183, 1, 0, 0, 0, 0, 185, 1, 0, 0, 0, 0, 187, 1, 0, 0, 0, 0, 189, 1, 0, 0, 0, 0, 191, 1, 0, 0, 0, 0, 193, 1, 0, 0, 0, 0, 195, 1, 0, 0, 0, 0, 197, 1, 0, 0, 0, 0, 199, 1, 0, 0, 0, 0, 201, 1, 0, 0, 0, 0, 203, 1, 0, 0, 0, 0, 205, 1, 0, 0, 0, 0, 207, 1, 0, 0, 0, 0, 209, 1, 0, 0, 0, 0, 211, 1, 0, 0, 0, 0, 213, 1, 0, 0, 0, 0, 215, 1, 0, 0, 0, 0, 217, 1, 0, 0, 0, 0, 219, 1, 0, 0, 0, 0, 221, 1, 0, 0, 0, 0, 223, 1, 0, 0, 0, 0, 225, 1, 0, 0, 0, 0, 227, 1, 0, 0, 0, 0, 229, 1, 0, 0, 0, 0, 231, 1, 0, 0, 0, 0, 233, 1, 0, 0, 0, 0, 235, 1, 0, 0, 0, 0, 237, 1, 0, 0, 0, 0, 239, 1, 0, 0, 0, 0, 241, 1, 0, 0, 0, 0, 243, 1, 0, 0, 0, 0, 245, 1, 0, 0, 0, 0, 247, 1, 0, 0, 0, 0, 249, 1, 0, 0, 0, 0, 251, 1, 0, 0, 0, 0, 253, 1, 0, 0, 0, 0, 255, 1, 0, 0, 0, 0, 257, 1, 0, 0, 0, 0, 259, 1, 0, 0, 0, 0, 261, 1, 0, 0, 0, 0, 263, 1, 0, 0, 0, 0, 265, 1, 0, 0, 0, 0, 267, 1, 0, 0, 0, 0, 269, 1, 0, 0, 0, 0, 271, 1, 0, 0, 0, 0, 273, 1, 0, 0, 0, 0, 275, 1, 0, 0, 0, 0, 277, 1, 0, 0, 0, 0, 279, 1, 0, 0, 0, 0, 281, 1, 0, 0, 0, 0, 283, 1, 0, 0, 0, 0, 285, 1, 0, 0, 0, 0, 287, 1, 0, 0, 0, 0, 289, 1, 0, 0, 0, 0, 291, 1, 0, 0, 0, 0, 293, 1, 0, 0, 0, 0, 295, 1, 0, 0, 0, 0, 297, 1, 0, 0, 0, 0, 299, 1, 0, 0, 0, 0, 301, 1, 0, 0, 0, 0, 303, 1, 0, 0, 0, 0, 305, 1, 0, 0, 0, 0, 307, 1, 0, 0, 0, 0, 309, 1, 0, 0, 0, 0, 311, 1, 0, 0, 0, 0, 317, 1, 0, 0, 0, 0, 319, 1, 0, 0, 0, 0, 321, 1, 0, 0, 0, 1, 323, 1, 0, 0, 0, 3, 328, 1, 0, 0, 0, 5, 335, 1, 0, 0, 0, 7, 344, 1, 0, 0, 0, 9, 357, 1, 0, 0, 0, 11, 365, 1, 0, 0, 0, 13, 369, 1, 0, 0, 0, 15, 378, 1, 0, 0, 0, 17, 386, 1, 0, 0, 0, 19, 395, 1, 0, 0, 0, 21, 406, 1, 0, 0, 0, 23, 416, 1, 0, 0, 0, 25, 426, 1, 0, 0, 0, 27, 435, 1, 0, 0, 0, 29, 447, 1, 0, 0, 0, 31, 454, 1, 0, 0, 0, 33, 462, 1, 0, 0, 0, 35, 471, 1, 0, 0, 0, 37, 479, 1, 0, 0, 0, 39, 485, 1, 0, 0, 0, 41, 490, 1, 0, 0, 0, 43, 503, 1, 0, 0, 0, 45, 513, 1, 0, 0, 0, 47, 525, 1, 0, 0, 0, 49, 531, 1, 0, 0, 0, 51, 542, 1, 0, 0, 0, 53, 553, 1, 0, 0, 0, 55, 558, 1, 0, 0, 0, 57, 567, 1, 0, 0, 0, 59, 577, 1, 0, 0, 0, 61, 582, 1, 0, 0, 0, 63, 586, 1, 0, 0, 0, 65, 592, 1, 0, 0, 0, 67, 597, 1, 0, 0, 0, 69, 607, 1, 0, 0, 0, 71, 611, 1, 0, 0, 0, 73, 624, 1, 0, 0, 0, 75, 629, 1, 0, 0, 0, 77, 636, 1, 0, 0, 0, 79, 645, 1, 0, 0, 0, 81, 656, 1, 0, 0, 0, 83, 660, 1, 0, 0, 0, 85, 669, 1, 0, 0, 0, 87, 676, 1, 0, 0, 0, 89, 685, 1, 0, 0, 0, 91, 692, 1, 0, 0, 0, 93, 697, 1, 0, 0, 0, 95, 707, 1, 0, 0, 0, 97, 718, 1, 0, 0, 0, 99, 726, 1, 0, 0, 0, 101, 732, 1, 0, 0, 0, 103, 738, 1, 0, 0, 0, 105, 747, 1, 0, 0, 0, 107, 757, 1, 0, 0, 0, 109, 766, 1, 0, 0, 0, 111, 778, 1, 0, 0, 0, 113, 789, 1, 0, 0, 0, 115, 795, 1, 0, 0, 0, 117, 803, 1, 0, 0, 0, 119, 806, 1, 0, 0, 0, 121, 811, 1, 0, 0, 0, 123, 818, 1, 0, 0, 0, 125, 821, 1, 0, 0, 0, 127, 829, 1, 0, 0, 0, 129, 836, 1, 0, 0, 0, 131, 842, 1, 0, 0, 0, 133, 847, 1, 0, 0, 0, 135, 858, 1, 0, 0, 0, 137, 863, 1, 0, 0, 0, 139, 869, 1, 0, 0, 0, 141, 876, 1, 0, 0, 0, 143, 880, 1, 0, 0, 0, 145, 892, 1, 0, 0, 0, 147, 895, 1, 0, 0, 0, 149, 902, 1, 0, 0, 0, 151, 905, 1, 0, 0, 0, 153, 912, 1, 0, 0, 0, 155, 920, 1, 0, 0, 0, 157, 927, 1, 0, 0, 0, 159, 938, 1, 0, 0, 0, 161, 945, 1, 0, 0, 0, 163, 954, 1, 0, 0, 0, 165, 969, 1, 0, 0, 0, 167, 979, 1, 0, 0, 0, 169, 989, 1, 0, 0, 0, 171, 997, 1, 0, 0, 0, 173, 1010, 1, 0, 0, 0, 175, 1023, 1, 0, 0, 0, 177, 1029, 1, 0, 0, 0, 179, 1046, 1, 0, 0, 0, 181, 1049, 1, 0, 0, 0, 183, 1053, 1, 0, 0, 0, 185, 1058, 1, 0, 0, 0, 187, 1064, 1, 0, 0, 0, 189, 1073, 1, 0, 0, 0, 191, 1082, 1, 0, 0, 0, 193, 1086, 1, 0, 0, 0, 195, 1090, 1, 0, 0, 0, 197, 1094, 1, 0, 0, 0, 199, 1100, 1, 0, 0, 0, 201, 1106, 1, 0, 0, 0, 203, 1111, 1, 0, 0, 0, 205, 1116, 1, 0, 0, 0, 207, 1122, 1, 0, 0, 0, 209, 1130, 1, 0, 0, 0, 211, 1138, 1, 0, 0, 0, 213, 1149, 1, 0, 0, 0, 215, 1154, 1, 0, 0, 0, 217, 1166, 1, 0, 0, 0, 219, 1177, 1, 0, 0, 0, 221, 1187, 1, 0, 0, 0, 223, 1199, 1, 0, 0, 0, 225, 1208, 1, 0, 0, 0, 227, 1213, 1, 0, 0, 0, 229, 1226, 1, 0, 0, 0, 231, 1238, 1, 0, 0, 0, 233, 1250, 1, 0, 0, 0, 235, 1255, 1, 0, 0, 0, 237, 1263, 1, 0, 0, 0, 239, 1268, 1, 0, 0, 0, 241, 1281, 1, 0, 0, 0, 243, 1289, 1, 0, 0, 0, 245, 1298, 1, 0, 0, 0, 247, 1312, 1, 0, 0, 0, 249, 1321, 1, 0, 0, 0, 251, 1326, 1, 0, 0, 0, 253, 1333, 1, 0, 0, 0, 255, 1338, 1, 0, 0, 0, 257, 1344, 1, 0, 0, 0, 259, 1351, 1, 0, 0, 0, 261, 1357, 1, 0, 0, 0, 263, 1363, 1, 0, 0, 0, 265, 1370, 1, 0, 0, 0, 267, 1377, 1, 0, 0, 0, 269, 1384, 1, 0, 0, 0, 271, 1389, 1, 0, 0, 0, 273, 1395, 1, 0, 0, 0, 275, 1400, 1, 0, 0, 0, 277, 1403, 1, 0, 0, 0, 279, 1405, 1, 0, 0, 0, 281, 1407, 1, 0, 0, 0, 283, 1409, 1, 0, 0, 0, 285, 1411, 1, 0, 0, 0, 287, 1413, 1, 0, 0, 0, 289, 1415, 1, 0, 0, 0, 291, 1417, 1, 0, 0, 0, 293, 1419, 1, 0, 0, 0, 295, 1421, 1, 0, 0, 0, 297, 1423, 1, 0, 0, 0, 299, 1425, 1, 0, 0, 0, 301, 1427, 1, 0, 0, 0, 303, 1429, 1, 0, 0, 0, 305, 1432, 1, 0, 0, 0, 307, 1440, 1, 0, 0, 0, 309, 1471, 1, 0, 0, 0, 311, 1478, 1, 0, 0, 0, 313, 1488, 1, 0, 0, 0, 315, 1498, 1, 0, 0, 0, 317, 1500, 1, 0, 0, 0, 319, 1511, 1, 0, 0, 0, 321, 1526, 1, 0, 0, 0, 323, 324, 5, 101, 0, 0, 324, 325, 5, 100, 0, 0, 325, 326, 5, 105, 0, 0, 326, 327, 5, 116, 0, 0, 327, 2, 1, 0, 0, 0, 328, 329, 5, 109, 0, 0, 329, 330, 5, 111, 0, 0, 330, 331, 5, 100, 0, 0, 331, 332, 5, 117, 0, 0, 332, 333, 5, 108, 0, 0, 333, 334, 5, 101, 0, 0, 334, 4, 1, 0, 0, 0, 335, 336, 5, 99, 0, 0, 336, 337, 5, 111, 0, 0, 337, 338, 5, 110, 0, 0, 338, 339, 5, 116, 0, 0, 339, 340, 5, 114, 0, 0, 340, 341, 5, 97, 0, 0, 341, 342, 5, 99, 0, 0, 342, 343, 5, 116, 0, 0, 343, 6, 1, 0, 0, 0, 344, 345, 5, 112, 0, 0, 345, 346, 5, 114, 0, 0, 346, 347, 5, 101, 0, 0, 347, 348, 5, 115, 0, 0, 348, 349, 5, 101, 0, 0, 349, 350, 5, 110, 0, 0, 350, 351, 5, 116, 0, 0, 351, 352, 5, 97, 0, 0, 352, 353, 5, 116, 0, 0, 353, 354, 5, 105, 0, 0, 354, 355, 5, 111, 0, 0, 355, 356, 5, 110, 0, 0, 356, 8, 1, 0, 0, 0, 357, 358, 5, 115, 0, 0, 358, 359, 5, 117, 0, 0, 359, 360, 5, 98, 0, 0, 360, 361, 5, 106, 0, 0, 361, 362, 5, 101, 0, 0, 362, 363, 5, 99, 0, 0, 363, 364, 5, 116, 0, 0, 364, 10, 1, 0, 0, 0, 365, 366, 5, 102, 0, 0, 366, 367, 5, 111, 0, 0, 367, 368, 5, 114, 0, 0, 368, 12, 1, 0, 0, 0, 369, 370, 5, 99, 0, 0, 370, 371, 5, 114, 0, 0, 371, 372, 5, 101, 0, 0, 372, 373, 5, 97, 0, 0, 373, 374, 5, 116, 0, 0, 374, 375, 5, 105, 0, 0, 375, 376, 5, 111, 0, 0, 376, 377, 5, 110, 0, 0, 377, 14, 1, 0, 0, 0, 378, 379, 5, 118, 0, 0, 379, 380, 5, 97, 0, 0, 380, 381, 5, 114, 0, 0, 381, 382, 5, 105, 0, 0, 382, 383, 5, 97, 0, 0, 383, 384, 5, 110, 0, 0, 384, 385, 5, 116, 0, 0, 385, 16, 1, 0, 0, 0, 386, 387, 5, 112, 0, 0, 387, 388, 5, 114, 0, 0, 388, 389, 5, 101, 0, 0, 389, 390, 5, 112, 0, 0, 390, 391, 5, 97, 0, 0, 391, 392, 5, 114, 0, 0, 392, 393, 5, 101, 0, 0, 393, 394, 5, 115, 0, 0, 394, 18, 1, 0, 0, 0, 395, 396, 5, 105, 0, 0, 396, 397, 5, 110, 0, 0, 397, 398, 5, 105, 0, 0, 398, 399, 5, 116, 0, 0, 399, 400, 5, 105, 0, 0, 400, 401, 5, 97, 0, 0, 401, 402, 5, 108, 0, 0, 402, 403, 5, 105, 0, 0, 403, 404, 5, 122, 0, 0, 404, 405, 5, 101, 0, 0, 405, 20, 1, 0, 0, 0, 406, 407, 5, 119, 0, 0, 407, 408, 5, 111, 0, 0, 408, 409, 5, 114, 0, 0, 409, 410, 5, 107, 0, 0, 410, 411, 5, 115, 0, 0, 411, 412, 5, 112, 0, 0, 412, 413, 5, 97, 0, 0, 413, 414, 5, 99, 0, 0, 414, 415, 5, 101, 0, 0, 415, 22, 1, 0, 0, 0, 416, 417, 5, 101, 0, 0, 417, 418, 5, 120, 0, 0, 418, 419, 5, 101, 0, 0, 419, 420, 5, 99, 0, 0, 420, 421, 5, 117, 0, 0, 421, 422, 5, 116, 0, 0, 422, 423, 5, 105, 0, 0, 423, 424, 5, 111, 0, 0, 424, 425, 5, 110, 0, 0, 425, 24, 1, 0, 0, 0, 426, 427, 5, 115, 0, 0, 427, 428, 5, 110, 0, 0, 428, 429, 5, 97, 0, 0, 429, 430, 5, 112, 0, 0, 430, 431, 5, 115, 0, 0, 431, 432, 5, 104, 0, 0, 432, 433, 5, 111, 0, 0, 433, 434, 5, 116, 0, 0, 434, 26, 1, 0, 0, 0, 435, 436, 5, 116, 0, 0, 436, 437, 5, 114, 0, 0, 437, 438, 5, 97, 0, 0, 438, 439, 5, 110, 0, 0, 439, 440, 5, 115, 0, 0, 440, 441, 5, 97, 0, 0, 441, 442, 5, 99, 0, 0, 442, 443, 5, 116, 0, 0, 443, 444, 5, 105, 0, 0, 444, 445, 5, 111, 0, 0, 445, 446, 5, 110, 0, 0, 446, 28, 1, 0, 0, 0, 447, 448, 5, 114, 0, 0, 448, 449, 5, 101, 0, 0, 449, 450, 5, 109, 0, 0, 450, 451, 5, 111, 0, 0, 451, 452, 5, 116, 0, 0, 452, 453, 5, 101, 0, 0, 453, 30, 1, 0, 0, 0, 454, 455, 5, 116, 0, 0, 455, 456, 5, 97, 0, 0, 456, 457, 5, 114, 0, 0, 457, 458, 5, 103, 0, 0, 458, 459, 5, 101, 0, 0, 459, 460, 5, 116, 0, 0, 460, 461, 5, 115, 0, 0, 461, 32, 1, 0, 0, 0, 462, 463, 5, 112, 0, 0, 463, 464, 5, 111, 0, 0, 464, 465, 5, 114, 0, 0, 465, 466, 5, 116, 0, 0, 466, 467, 5, 97, 0, 0, 467, 468, 5, 98, 0, 0, 468, 469, 5, 108, 0, 0, 469, 470, 5, 101, 0, 0, 470, 34, 1, 0, 0, 0, 471, 472, 5, 114, 0, 0, 472, 473, 5, 101, 0, 0, 473, 474, 5, 112, 0, 0, 474, 475, 5, 108, 0, 0, 475, 476, 5, 105, 0, 0, 476, 477, 5, 99, 0, 0, 477, 478, 5, 97, 0, 0, 478, 36, 1, 0, 0, 0, 479, 480, 5, 108, 0, 0, 480, 481, 5, 111, 0, 0, 481, 482, 5, 99, 0, 0, 482, 483, 5, 97, 0, 0, 483, 484, 5, 108, 0, 0, 484, 38, 1, 0, 0, 0, 485, 486, 5, 102, 0, 0, 486, 487, 5, 114, 0, 0, 487, 488, 5, 111, 0, 0, 488, 489, 5, 109, 0, 0, 489, 40, 1, 0, 0, 0, 490, 491, 5, 114, 0, 0, 491, 492, 5, 101, 0, 0, 492, 493, 5, 115, 0, 0, 493, 494, 5, 117, 0, 0, 494, 495, 5, 108, 0, 0, 495, 496, 5, 116, 0, 0, 496, 497, 5, 45, 0, 0, 497, 498, 5, 98, 0, 0, 498, 499, 5, 121, 0, 0, 499, 500, 5, 116, 0, 0, 500, 501, 5, 101, 0, 0, 501, 502, 5, 115, 0, 0, 502, 42, 1, 0, 0, 0, 503, 504, 5, 114, 0, 0, 504, 505, 5, 112, 0, 0, 505, 506, 5, 99, 0, 0, 506, 507, 5, 45, 0, 0, 507, 508, 5, 99, 0, 0, 508, 509, 5, 97, 0, 0, 509, 510, 5, 108, 0, 0, 510, 511, 5, 108, 0, 0, 511, 512, 5, 115, 0, 0, 512, 44, 1, 0, 0, 0, 513, 514, 5, 100, 0, 0, 514, 515, 5, 101, 0, 0, 515, 516, 5, 97, 0, 0, 516, 517, 5, 100, 0, 0, 517, 518, 5, 108, 0, 0, 518, 519, 5, 105, 0, 0, 519, 520, 5, 110, 0, 0, 520, 521, 5, 101, 0, 0, 521, 522, 5, 45, 0, 0, 522, 523, 5, 109, 0, 0, 523, 524, 5, 115, 0, 0, 524, 46, 1, 0, 0, 0, 525, 526, 5, 113, 0, 0, 526, 527, 5, 117, 0, 0, 527, 528, 5, 101, 0, 0, 528, 529, 5, 114, 0, 0, 529, 530, 5, 121, 0, 0, 530, 48, 1, 0, 0, 0, 531, 532, 5, 114, 0, 0, 532, 533, 5, 101, 0, 0, 533, 534, 5, 102, 0, 0, 534, 535, 5, 108, 0, 0, 535, 536, 5, 101, 0, 0, 536, 537, 5, 99, 0, 0, 537, 538, 5, 116, 0, 0, 538, 539, 5, 105, 0, 0, 539, 540, 5, 111, 0, 0, 540, 541, 5, 110, 0, 0, 541, 50, 1, 0, 0, 0, 542, 543, 5, 115, 0, 0, 543, 544, 5, 112, 0, 0, 544, 545, 5, 101, 0, 0, 545, 546, 5, 99, 0, 0, 546, 547, 5, 105, 0, 0, 547, 548, 5, 97, 0, 0, 548, 549, 5, 108, 0, 0, 549, 550, 5, 105, 0, 0, 550, 551, 5, 122, 0, 0, 551, 552, 5, 101, 0, 0, 552, 52, 1, 0, 0, 0, 553, 554, 5, 114, 0, 0, 554, 555, 5, 111, 0, 0, 555, 556, 5, 111, 0, 0, 556, 557, 5, 116, 0, 0, 557, 54, 1, 0, 0, 0, 558, 559, 5, 100, 0, 0, 559, 560, 5, 111, 0, 0, 560, 561, 5, 99, 0, 0, 561, 562, 5, 117, 0, 0, 562, 563, 5, 109, 0, 0, 563, 564, 5, 101, 0, 0, 564, 565, 5, 110, 0, 0, 565, 566, 5, 116, 0, 0, 566, 56, 1, 0, 0, 0, 567, 568, 5, 102, 0, 0, 568, 569, 5, 114, 0, 0, 569, 570, 5, 97, 0, 0, 570, 571, 5, 103, 0, 0, 571, 572, 5, 109, 0, 0, 572, 573, 5, 101, 0, 0, 573, 574, 5, 110, 0, 0, 574, 575, 5, 116, 0, 0, 575, 576, 5, 115, 0, 0, 576, 58, 1, 0, 0, 0, 577, 578, 5, 118, 0, 0, 578, 579, 5, 105, 0, 0, 579, 580, 5, 101, 0, 0, 580, 581, 5, 119, 0, 0, 581, 60, 1, 0, 0, 0, 582, 583, 5, 109, 0, 0, 583, 584, 5, 97, 0, 0, 584, 585, 5, 120, 0, 0, 585, 62, 1, 0, 0, 0, 586, 587, 5, 97, 0, 0, 587, 588, 5, 108, 0, 0, 588, 589, 5, 108, 0, 0, 589, 590, 5, 111, 0, 0, 590, 591, 5, 119, 0, 0, 591, 64, 1, 0, 0, 0, 592, 593, 5, 112, 0, 0, 593, 594, 5, 111, 0, 0, 594, 595, 5, 108, 0, 0, 595, 596, 5, 108, 0, 0, 596, 66, 1, 0, 0, 0, 597, 598, 5, 113, 0, 0, 598, 599, 5, 117, 0, 0, 599, 600, 5, 101, 0, 0, 600, 601, 5, 114, 0, 0, 601, 602, 5, 121, 0, 0, 602, 603, 5, 97, 0, 0, 603, 604, 5, 98, 0, 0, 604, 605, 5, 108, 0, 0, 605, 606, 5, 101, 0, 0, 606, 68, 1, 0, 0, 0, 607, 608, 5, 114, 0, 0, 608, 609, 5, 112, 0, 0, 609, 610, 5, 99, 0, 0, 610, 70, 1, 0, 0, 0, 611, 612, 5, 113, 0, 0, 612, 613, 5, 117, 0, 0, 613, 614, 5, 101, 0, 0, 614, 615, 5, 114, 0, 0, 615, 616, 5, 121, 0, 0, 616, 617, 5, 45, 0, 0, 617, 618, 5, 114, 0, 0, 618, 619, 5, 101, 0, 0, 619, 620, 5, 97, 0, 0, 620, 621, 5, 115, 0, 0, 621, 622, 5, 111, 0, 0, 622, 623, 5, 110, 0, 0, 623, 72, 1, 0, 0, 0, 624, 625, 5, 116, 0, 0, 625, 626, 5, 121, 0, 0, 626, 627, 5, 112, 0, 0, 627, 628, 5, 101, 0, 0, 628, 74, 1, 0, 0, 0, 629, 630, 5, 111, 0, 0, 630, 631, 5, 98, 0, 0, 631, 632, 5, 106, 0, 0, 632, 633, 5, 101, 0, 0, 633, 634, 5, 99, 0, 0, 634, 635, 5, 116, 0, 0, 635, 76, 1, 0, 0, 0, 636, 637, 5, 115, 0, 0, 637, 638, 5, 116, 0, 0, 638, 639, 5, 111, 0, 0, 639, 640, 5, 114, 0, 0, 640, 641, 5, 97, 0, 0, 641, 642, 5, 98, 0, 0, 642, 643, 5, 108, 0, 0, 643, 644, 5, 101, 0, 0, 644, 78, 1, 0, 0, 0, 645, 646, 5, 105, 0, 0, 646, 647, 5, 109, 0, 0, 647, 648, 5, 112, 0, 0, 648, 649, 5, 108, 0, 0, 649, 650, 5, 101, 0, 0, 650, 651, 5, 109, 0, 0, 651, 652, 5, 101, 0, 0, 652, 653, 5, 110, 0, 0, 653, 654, 5, 116, 0, 0, 654, 655, 5, 115, 0, 0, 655, 80, 1, 0, 0, 0, 656, 657, 5, 114, 0, 0, 657, 658, 5, 101, 0, 0, 658, 659, 5, 102, 0, 0, 659, 82, 1, 0, 0, 0, 660, 661, 5, 102, 0, 0, 661, 662, 5, 114, 0, 0, 662, 663, 5, 97, 0, 0, 663, 664, 5, 103, 0, 0, 664, 665, 5, 109, 0, 0, 665, 666, 5, 101, 0, 0, 666, 667, 5, 110, 0, 0, 667, 668, 5, 116, 0, 0, 668, 84, 1, 0, 0, 0, 669, 670, 5, 105, 0, 0, 670, 671, 5, 109, 0, 0, 671, 672, 5, 112, 0, 0, 672, 673, 5, 111, 0, 0, 673, 674, 5, 114, 0, 0, 674, 675, 5, 116, 0, 0, 675, 86, 1, 0, 0, 0, 676, 677, 5, 101, 0, 0, 677, 678, 5, 120, 0, 0, 678, 679, 5, 116, 0, 0, 679, 680, 5, 101, 0, 0, 680, 681, 5, 114, 0, 0, 681, 682, 5, 110, 0, 0, 682, 683, 5, 97, 0, 0, 683, 684, 5, 108, 0, 0, 684, 88, 1, 0, 0, 0, 685, 686, 5, 115, 0, 0, 686, 687, 5, 121, 0, 0, 687, 688, 5, 109, 0, 0, 688, 689, 5, 98, 0, 0, 689, 690, 5, 111, 0, 0, 690, 691, 5, 108, 0, 0, 691, 90, 1, 0, 0, 0, 692, 693, 5, 97, 0, 0, 693, 694, 5, 116, 0, 0, 694, 695, 5, 111, 0, 0, 695, 696, 5, 109, 0, 0, 696, 92, 1, 0, 0, 0, 697, 698, 5, 105, 0, 0, 698, 699, 5, 110, 0, 0, 699, 700, 5, 116, 0, 0, 700, 701, 5, 101, 0, 0, 701, 702, 5, 114, 0, 0, 702, 703, 5, 102, 0, 0, 703, 704, 5, 97, 0, 0, 704, 705, 5, 99, 0, 0, 705, 706, 5, 101, 0, 0, 706, 94, 1, 0, 0, 0, 707, 708, 5, 105, 0, 0, 708, 709, 5, 110, 0, 0, 709, 710, 5, 116, 0, 0, 710, 711, 5, 101, 0, 0, 711, 712, 5, 114, 0, 0, 712, 713, 5, 102, 0, 0, 713, 714, 5, 97, 0, 0, 714, 715, 5, 99, 0, 0, 715, 716, 5, 101, 0, 0, 716, 717, 5, 115, 0, 0, 717, 96, 1, 0, 0, 0, 718, 719, 5, 112, 0, 0, 719, 720, 5, 97, 0, 0, 720, 721, 5, 99, 0, 0, 721, 722, 5, 107, 0, 0, 722, 723, 5, 97, 0, 0, 723, 724, 5, 103, 0, 0, 724, 725, 5, 101, 0, 0, 725, 98, 1, 0, 0, 0, 726, 727, 5, 118, 0, 0, 727, 728, 5, 97, 0, 0, 728, 729, 5, 108, 0, 0, 729, 730, 5, 117, 0, 0, 730, 731, 5, 101, 0, 0, 731, 100, 1, 0, 0, 0, 732, 733, 5, 102, 0, 0, 733, 734, 5, 105, 0, 0, 734, 735, 5, 101, 0, 0, 735, 736, 5, 108, 0, 0, 736, 737, 5, 100, 0, 0, 737, 102, 1, 0, 0, 0, 738, 739, 5, 114, 0, 0, 739, 740, 5, 101, 0, 0, 740, 741, 5, 108, 0, 0, 741, 742, 5, 97, 0, 0, 742, 743, 5, 116, 0, 0, 743, 744, 5, 105, 0, 0, 744, 745, 5, 111, 0, 0, 745, 746, 5, 110, 0, 0, 746, 104, 1, 0, 0, 0, 747, 748, 5, 111, 0, 0, 748, 749, 5, 112, 0, 0, 749, 750, 5, 101, 0, 0, 750, 751, 5, 114, 0, 0, 751, 752, 5, 97, 0, 0, 752, 753, 5, 116, 0, 0, 753, 754, 5, 105, 0, 0, 754, 755, 5, 111, 0, 0, 755, 756, 5, 110, 0, 0, 756, 106, 1, 0, 0, 0, 757, 758, 5, 102, 0, 0, 758, 759, 5, 117, 0, 0, 759, 760, 5, 110, 0, 0, 760, 761, 5, 99, 0, 0, 761, 762, 5, 116, 0, 0, 762, 763, 5, 105, 0, 0, 763, 764, 5, 111, 0, 0, 764, 765, 5, 110, 0, 0, 765, 108, 1, 0, 0, 0, 766, 767, 5, 99, 0, 0, 767, 768, 5, 111, 0, 0, 768, 769, 5, 110, 0, 0, 769, 770, 5, 115, 0, 0, 770, 771, 5, 116, 0, 0, 771, 772, 5, 114, 0, 0, 772, 773, 5, 117, 0, 0, 773, 774, 5, 99, 0, 0, 774, 775, 5, 116, 0, 0, 775, 776, 5, 111, 0, 0, 776, 777, 5, 114, 0, 0, 777, 110, 1, 0, 0, 0, 778, 779, 5, 99, 0, 0, 779, 780, 5, 111, 0, 0, 780, 781, 5, 110, 0, 0, 781, 782, 5, 115, 0, 0, 782, 783, 5, 116, 0, 0, 783, 784, 5, 114, 0, 0, 784, 785, 5, 117, 0, 0, 785, 786, 5, 99, 0, 0, 786, 787, 5, 116, 0, 0, 787, 788, 5, 115, 0, 0, 788, 112, 1, 0, 0, 0, 789, 790, 5, 105, 0, 0, 790, 791, 5, 110, 0, 0, 791, 792, 5, 112, 0, 0, 792, 793, 5, 117, 0, 0, 793, 794, 5, 116, 0, 0, 794, 114, 1, 0, 0, 0, 795, 796, 5, 99, 0, 0, 796, 797, 5, 111, 0, 0, 797, 798, 5, 110, 0, 0, 798, 799, 5, 102, 0, 0, 799, 800, 5, 111, 0, 0, 800, 801, 5, 114, 0, 0, 801, 802, 5, 109, 0, 0, 802, 116, 1, 0, 0, 0, 803, 804, 5, 97, 0, 0, 804, 805, 5, 115, 0, 0, 805, 118, 1, 0, 0, 0, 806, 807, 5, 98, 0, 0, 807, 808, 5, 105, 0, 0, 808, 809, 5, 110, 0, 0, 809, 810, 5, 100, 0, 0, 810, 120, 1, 0, 0, 0, 811, 812, 5, 115, 0, 0, 812, 813, 5, 116, 0, 0, 813, 814, 5, 97, 0, 0, 814, 815, 5, 116, 0, 0, 815, 816, 5, 105, 0, 0, 816, 817, 5, 99, 0, 0, 817, 122, 1, 0, 0, 0, 818, 819, 5, 116, 0, 0, 819, 820, 5, 111, 0, 0, 820, 124, 1, 0, 0, 0, 821, 822, 5, 112, 0, 0, 822, 823, 5, 114, 0, 0, 823, 824, 5, 105, 0, 0, 824, 825, 5, 118, 0, 0, 825, 826, 5, 97, 0, 0, 826, 827, 5, 116, 0, 0, 827, 828, 5, 101, 0, 0, 828, 126, 1, 0, 0, 0, 829, 830, 5, 115, 0, 0, 830, 831, 5, 104, 0, 0, 831, 832, 5, 97, 0, 0, 832, 833, 5, 114, 0, 0, 833, 834, 5, 101, 0, 0, 834, 835, 5, 100, 0, 0, 835, 128, 1, 0, 0, 0, 836, 837, 5, 115, 0, 0, 837, 838, 5, 116, 0, 0, 838, 839, 5, 97, 0, 0, 839, 840, 5, 116, 0, 0, 840, 841, 5, 101, 0, 0, 841, 130, 1, 0, 0, 0, 842, 843, 5, 101, 0, 0, 843, 844, 5, 100, 0, 0, 844, 845, 5, 103, 0, 0, 845, 846, 5, 101, 0, 0, 846, 132, 1, 0, 0, 0, 847, 848, 5, 112, 0, 0, 848, 849, 5, 114, 0, 0, 849, 850, 5, 111, 0, 0, 850, 851, 5, 106, 0, 0, 851, 852, 5, 101, 0, 0, 852, 853, 5, 99, 0, 0, 853, 854, 5, 116, 0, 0, 854, 855, 5, 105, 0, 0, 855, 856, 5, 111, 0, 0, 856, 857, 5, 110, 0, 0, 857, 134, 1, 0, 0, 0, 858, 859, 5, 119, 0, 0, 859, 860, 5, 105, 0, 0, 860, 861, 5, 116, 0, 0, 861, 862, 5, 104, 0, 0, 862, 136, 1, 0, 0, 0, 863, 864, 5, 117, 0, 0, 864, 865, 5, 115, 0, 0, 865, 866, 5, 105, 0, 0, 866, 867, 5, 110, 0, 0, 867, 868, 5, 103, 0, 0, 868, 138, 1, 0, 0, 0, 869, 870, 5, 97, 0, 0, 870, 871, 5, 99, 0, 0, 871, 872, 5, 99, 0, 0, 872, 873, 5, 101, 0, 0, 873, 874, 5, 112, 0, 0, 874, 875, 5, 116, 0, 0, 875, 140, 1, 0, 0, 0, 876, 877, 5, 118, 0, 0, 877, 878, 5, 105, 0, 0, 878, 879, 5, 97, 0, 0, 879, 142, 1, 0, 0, 0, 880, 881, 5, 109, 0, 0, 881, 882, 5, 97, 0, 0, 882, 883, 5, 116, 0, 0, 883, 884, 5, 101, 0, 0, 884, 885, 5, 114, 0, 0, 885, 886, 5, 105, 0, 0, 886, 887, 5, 97, 0, 0, 887, 888, 5, 108, 0, 0, 888, 889, 5, 105, 0, 0, 889, 890, 5, 122, 0, 0, 890, 891, 5, 101, 0, 0, 891, 144, 1, 0, 0, 0, 892, 893, 5, 105, 0, 0, 893, 894, 5, 102, 0, 0, 894, 146, 1, 0, 0, 0, 895, 896, 5, 97, 0, 0, 896, 897, 5, 98, 0, 0, 897, 898, 5, 115, 0, 0, 898, 899, 5, 101, 0, 0, 899, 900, 5, 110, 0, 0, 900, 901, 5, 116, 0, 0, 901, 148, 1, 0, 0, 0, 902, 903, 5, 111, 0, 0, 903, 904, 5, 110, 0, 0, 904, 150, 1, 0, 0, 0, 905, 906, 5, 112, 0, 0, 906, 907, 5, 111, 0, 0, 907, 908, 5, 108, 0, 0, 908, 909, 5, 105, 0, 0, 909, 910, 5, 99, 0, 0, 910, 911, 5, 121, 0, 0, 911, 152, 1, 0, 0, 0, 912, 913, 5, 100, 0, 0, 913, 914, 5, 101, 0, 0, 914, 915, 5, 102, 0, 0, 915, 916, 5, 97, 0, 0, 916, 917, 5, 117, 0, 0, 917, 918, 5, 108, 0, 0, 918, 919, 5, 116, 0, 0, 919, 154, 1, 0, 0, 0, 920, 921, 5, 115, 0, 0, 921, 922, 5, 111, 0, 0, 922, 923, 5, 117, 0, 0, 923, 924, 5, 114, 0, 0, 924, 925, 5, 99, 0, 0, 925, 926, 5, 101, 0, 0, 926, 156, 1, 0, 0, 0, 927, 928, 5, 114, 0, 0, 928, 929, 5, 101, 0, 0, 929, 930, 5, 112, 0, 0, 930, 931, 5, 111, 0, 0, 931, 932, 5, 115, 0, 0, 932, 933, 5, 105, 0, 0, 933, 934, 5, 116, 0, 0, 934, 935, 5, 111, 0, 0, 935, 936, 5, 114, 0, 0, 936, 937, 5, 121, 0, 0, 937, 158, 1, 0, 0, 0, 938, 939, 5, 99, 0, 0, 939, 940, 5, 111, 0, 0, 940, 941, 5, 109, 0, 0, 941, 942, 5, 109, 0, 0, 942, 943, 5, 105, 0, 0, 943, 944, 5, 116, 0, 0, 944, 160, 1, 0, 0, 0, 945, 946, 5, 114, 0, 0, 946, 947, 5, 101, 0, 0, 947, 948, 5, 118, 0, 0, 948, 949, 5, 105, 0, 0, 949, 950, 5, 115, 0, 0, 950, 951, 5, 105, 0, 0, 951, 952, 5, 111, 0, 0, 952, 953, 5, 110, 0, 0, 953, 162, 1, 0, 0, 0, 954, 955, 5, 115, 0, 0, 955, 956, 5, 101, 0, 0, 956, 957, 5, 109, 0, 0, 957, 958, 5, 97, 0, 0, 958, 959, 5, 110, 0, 0, 959, 960, 5, 116, 0, 0, 960, 961, 5, 105, 0, 0, 961, 962, 5, 99, 0, 0, 962, 963, 5, 45, 0, 0, 963, 964, 5, 109, 0, 0, 964, 965, 5, 97, 0, 0, 965, 966, 5, 106, 0, 0, 966, 967, 5, 111, 0, 0, 967, 968, 5, 114, 0, 0, 968, 164, 1, 0, 0, 0, 969, 970, 5, 111, 0, 0, 970, 971, 5, 110, 0, 0, 971, 972, 5, 45, 0, 0, 972, 973, 5, 100, 0, 0, 973, 974, 5, 101, 0, 0, 974, 975, 5, 108, 0, 0, 975, 976, 5, 101, 0, 0, 976, 977, 5, 116, 0, 0, 977, 978, 5, 101, 0, 0, 978, 166, 1, 0, 0, 0, 979, 980, 5, 108, 0, 0, 980, 981, 5, 105, 0, 0, 981, 982, 5, 102, 0, 0, 982, 983, 5, 101, 0, 0, 983, 984, 5, 99, 0, 0, 984, 985, 5, 121, 0, 0, 985, 986, 5, 99, 0, 0, 986, 987, 5, 108, 0, 0, 987, 988, 5, 101, 0, 0, 988, 168, 1, 0, 0, 0, 989, 990, 5, 106, 0, 0, 990, 991, 5, 111, 0, 0, 991, 992, 5, 117, 0, 0, 992, 993, 5, 114, 0, 0, 993, 994, 5, 110, 0, 0, 994, 995, 5, 97, 0, 0, 995, 996, 5, 108, 0, 0, 996, 170, 1, 0, 0, 0, 997, 998, 5, 115, 0, 0, 998, 999, 5, 117, 0, 0, 999, 1000, 5, 98, 0, 0, 1000, 1001, 5, 115, 0, 0, 1001, 1002, 5, 99, 0, 0, 1002, 1003, 5, 114, 0, 0, 1003, 1004, 5, 105, 0, 0, 1004, 1005, 5, 112, 0, 0, 1005, 1006, 5, 116, 0, 0, 1006, 1007, 5, 105, 0, 0, 1007, 1008, 5, 111, 0, 0, 1008, 1009, 5, 110, 0, 0, 1009, 172, 1, 0, 0, 0, 1010, 1011, 5, 114, 0, 0, 1011, 1012, 5, 101, 0, 0, 1012, 1013, 5, 116, 0, 0, 1013, 1014, 5, 97, 0, 0, 1014, 1015, 5, 105, 0, 0, 1015, 1016, 5, 110, 0, 0, 1016, 1017, 5, 45, 0, 0, 1017, 1018, 5, 111, 0, 0, 1018, 1019, 5, 116, 0, 0, 1019, 1020, 5, 104, 0, 0, 1020, 1021, 5, 101, 0, 0, 1021, 1022, 5, 114, 0, 0, 1022, 174, 1, 0, 0, 0, 1023, 1024, 5, 107, 0, 0, 1024, 1025, 5, 101, 0, 0, 1025, 1026, 5, 121, 0, 0, 1026, 1027, 5, 101, 0, 0, 1027, 1028, 5, 100, 0, 0, 1028, 176, 1, 0, 0, 0, 1029, 1030, 5, 112, 0, 0, 1030, 1031, 5, 117, 0, 0, 1031, 1032, 5, 98, 0, 0, 1032, 1033, 5, 108, 0, 0, 1033, 1034, 5, 105, 0, 0, 1034, 1035, 5, 99, 0, 0, 1035, 1036, 5, 45, 0, 0, 1036, 1037, 5, 116, 0, 0, 1037, 1038, 5, 114, 0, 0, 1038, 1039, 5, 97, 0, 0, 1039, 1040, 5, 118, 0, 0, 1040, 1041, 5, 101, 0, 0, 1041, 1042, 5, 114, 0, 0, 1042, 1043, 5, 115, 0, 0, 1043, 1044, 5, 97, 0, 0, 1044, 1045, 5, 108, 0, 0, 1045, 178, 1, 0, 0, 0, 1046, 1047, 5, 105, 0, 0, 1047, 1048, 5, 100, 0, 0, 1048, 180, 1, 0, 0, 0, 1049, 1050, 5, 100, 0, 0, 1050, 1051, 5, 111, 0, 0, 1051, 1052, 5, 99, 0, 0, 1052, 182, 1, 0, 0, 0, 1053, 1054, 5, 109, 0, 0, 1054, 1055, 5, 111, 0, 0, 1055, 1056, 5, 100, 0, 0, 1056, 1057, 5, 101, 0, 0, 1057, 184, 1, 0, 0, 0, 1058, 1059, 5, 101, 0, 0, 1059, 1060, 5, 109, 0, 0, 1060, 1061, 5, 105, 0, 0, 1061, 1062, 5, 116, 0, 0, 1062, 1063, 5, 115, 0, 0, 1063, 186, 1, 0, 0, 0, 1064, 1065, 5, 114, 0, 0, 1065, 1066, 5, 101, 0, 0, 1066, 1067, 5, 99, 0, 0, 1067, 1068, 5, 101, 0, 0, 1068, 1069, 5, 105, 0, 0, 1069, 1070, 5, 118, 0, 0, 1070, 1071, 5, 101, 0, 0, 1071, 1072, 5, 114, 0, 0, 1072, 188, 1, 0, 0, 0, 1073, 1074, 5, 114, 0, 0, 1074, 1075, 5, 101, 0, 0, 1075, 1076, 5, 113, 0, 0, 1076, 1077, 5, 117, 0, 0, 1077, 1078, 5, 105, 0, 0, 1078, 1079, 5, 114, 0, 0, 1079, 1080, 5, 101, 0, 0, 1080, 1081, 5, 115, 0, 0, 1081, 190, 1, 0, 0, 0, 1082, 1083, 5, 97, 0, 0, 1083, 1084, 5, 110, 0, 0, 1084, 1085, 5, 121, 0, 0, 1085, 192, 1, 0, 0, 0, 1086, 1087, 5, 103, 0, 0, 1087, 1088, 5, 101, 0, 0, 1088, 1089, 5, 116, 0, 0, 1089, 194, 1, 0, 0, 0, 1090, 1091, 5, 115, 0, 0, 1091, 1092, 5, 101, 0, 0, 1092, 1093, 5, 116, 0, 0, 1093, 196, 1, 0, 0, 0, 1094, 1095, 5, 119, 0, 0, 1095, 1096, 5, 97, 0, 0, 1096, 1097, 5, 116, 0, 0, 1097, 1098, 5, 99, 0, 0, 1098, 1099, 5, 104, 0, 0, 1099, 198, 1, 0, 0, 0, 1100, 1101, 5, 115, 0, 0, 1101, 1102, 5, 116, 0, 0, 1102, 1103, 5, 97, 0, 0, 1103, 1104, 5, 114, 0, 0, 1104, 1105, 5, 116, 0, 0, 1105, 200, 1, 0, 0, 0, 1106, 1107, 5, 115, 0, 0, 1107, 1108, 5, 116, 0, 0, 1108, 1109, 5, 111, 0, 0, 1109, 1110, 5, 112, 0, 0, 1110, 202, 1, 0, 0, 0, 1111, 1112, 5, 114, 0, 0, 1112, 1113, 5, 101, 0, 0, 1113, 1114, 5, 97, 0, 0, 1114, 1115, 5, 100, 0, 0, 1115, 204, 1, 0, 0, 0, 1116, 1117, 5, 119, 0, 0, 1117, 1118, 5, 114, 0, 0, 1118, 1119, 5, 105, 0, 0, 1119, 1120, 5, 116, 0, 0, 1120, 1121, 5, 101, 0, 0, 1121, 206, 1, 0, 0, 0, 1122, 1123, 5, 114, 0, 0, 1123, 1124, 5, 101, 0, 0, 1124, 1125, 5, 115, 0, 0, 1125, 1126, 5, 111, 0, 0, 1126, 1127, 5, 108, 0, 0, 1127, 1128, 5, 118, 0, 0, 1128, 1129, 5, 101, 0, 0, 1129, 208, 1, 0, 0, 0, 1130, 1131, 5, 99, 0, 0, 1131, 1132, 5, 111, 0, 0, 1132, 1133, 5, 110, 0, 0, 1133, 1134, 5, 110, 0, 0, 1134, 1135, 5, 101, 0, 0, 1135, 1136, 5, 99, 0, 0, 1136, 1137, 5, 116, 0, 0, 1137, 210, 1, 0, 0, 0, 1138, 1139, 5, 100, 0, 0, 1139, 1140, 5, 105, 0, 0, 1140, 1141, 5, 115, 0, 0, 1141, 1142, 5, 99, 0, 0, 1142, 1143, 5, 111, 0, 0, 1143, 1144, 5, 110, 0, 0, 1144, 1145, 5, 110, 0, 0, 1145, 1146, 5, 101, 0, 0, 1146, 1147, 5, 99, 0, 0, 1147, 1148, 5, 116, 0, 0, 1148, 212, 1, 0, 0, 0, 1149, 1150, 5, 99, 0, 0, 1150, 1151, 5, 97, 0, 0, 1151, 1152, 5, 108, 0, 0, 1152, 1153, 5, 108, 0, 0, 1153, 214, 1, 0, 0, 0, 1154, 1155, 5, 119, 0, 0, 1155, 1156, 5, 97, 0, 0, 1156, 1157, 5, 116, 0, 0, 1157, 1158, 5, 99, 0, 0, 1158, 1159, 5, 104, 0, 0, 1159, 1160, 5, 45, 0, 0, 1160, 1161, 5, 115, 0, 0, 1161, 1162, 5, 116, 0, 0, 1162, 1163, 5, 97, 0, 0, 1163, 1164, 5, 114, 0, 0, 1164, 1165, 5, 116, 0, 0, 1165, 216, 1, 0, 0, 0, 1166, 1167, 5, 119, 0, 0, 1167, 1168, 5, 97, 0, 0, 1168, 1169, 5, 116, 0, 0, 1169, 1170, 5, 99, 0, 0, 1170, 1171, 5, 104, 0, 0, 1171, 1172, 5, 45, 0, 0, 1172, 1173, 5, 115, 0, 0, 1173, 1174, 5, 116, 0, 0, 1174, 1175, 5, 111, 0, 0, 1175, 1176, 5, 112, 0, 0, 1176, 218, 1, 0, 0, 0, 1177, 1178, 5, 115, 0, 0, 1178, 1179, 5, 117, 0, 0, 1179, 1180, 5, 98, 0, 0, 1180, 1181, 5, 115, 0, 0, 1181, 1182, 5, 99, 0, 0, 1182, 1183, 5, 114, 0, 0, 1183, 1184, 5, 105, 0, 0, 1184, 1185, 5, 98, 0, 0, 1185, 1186, 5, 101, 0, 0, 1186, 220, 1, 0, 0, 0, 1187, 1188, 5, 117, 0, 0, 1188, 1189, 5, 110, 0, 0, 1189, 1190, 5, 115, 0, 0, 1190, 1191, 5, 117, 0, 0, 1191, 1192, 5, 98, 0, 0, 1192, 1193, 5, 115, 0, 0, 1193, 1194, 5, 99, 0, 0, 1194, 1195, 5, 114, 0, 0, 1195, 1196, 5, 105, 0, 0, 1196, 1197, 5, 98, 0, 0, 1197, 1198, 5, 101, 0, 0, 1198, 222, 1, 0, 0, 0, 1199, 1200, 5, 114, 0, 0, 1200, 1201, 5, 101, 0, 0, 1201, 1202, 5, 103, 0, 0, 1202, 1203, 5, 105, 0, 0, 1203, 1204, 5, 115, 0, 0, 1204, 1205, 5, 116, 0, 0, 1205, 1206, 5, 101, 0, 0, 1206, 1207, 5, 114, 0, 0, 1207, 224, 1, 0, 0, 0, 1208, 1209, 5, 99, 0, 0, 1209, 1210, 5, 114, 0, 0, 1210, 1211, 5, 100, 0, 0, 1211, 1212, 5, 116, 0, 0, 1212, 226, 1, 0, 0, 0, 1213, 1214, 5, 111, 0, 0, 1214, 1215, 5, 112, 0, 0, 1215, 1216, 5, 116, 0, 0, 1216, 1217, 5, 105, 0, 0, 1217, 1218, 5, 111, 0, 0, 1218, 1219, 5, 110, 0, 0, 1219, 1220, 5, 97, 0, 0, 1220, 1221, 5, 108, 0, 0, 1221, 1222, 5, 45, 0, 0, 1222, 1223, 5, 111, 0, 0, 1223, 1224, 5, 110, 0, 0, 1224, 1225, 5, 101, 0, 0, 1225, 228, 1, 0, 0, 0, 1226, 1227, 5, 101, 0, 0, 1227, 1228, 5, 120, 0, 0, 1228, 1229, 5, 97, 0, 0, 1229, 1230, 5, 99, 0, 0, 1230, 1231, 5, 116, 0, 0, 1231, 1232, 5, 108, 0, 0, 1232, 1233, 5, 121, 0, 0, 1233, 1234, 5, 45, 0, 0, 1234, 1235, 5, 111, 0, 0, 1235, 1236, 5, 110, 0, 0, 1236, 1237, 5, 101, 0, 0, 1237, 230, 1, 0, 0, 0, 1238, 1239, 5, 109, 0, 0, 1239, 1240, 5, 97, 0, 0, 1240, 1241, 5, 110, 0, 0, 1241, 1242, 5, 121, 0, 0, 1242, 1243, 5, 45, 0, 0, 1243, 1244, 5, 117, 0, 0, 1244, 1245, 5, 110, 0, 0, 1245, 1246, 5, 105, 0, 0, 1246, 1247, 5, 113, 0, 0, 1247, 1248, 5, 117, 0, 0, 1248, 1249, 5, 101, 0, 0, 1249, 232, 1, 0, 0, 0, 1250, 1251, 5, 109, 0, 0, 1251, 1252, 5, 97, 0, 0, 1252, 1253, 5, 110, 0, 0, 1253, 1254, 5, 121, 0, 0, 1254, 234, 1, 0, 0, 0, 1255, 1256, 5, 111, 0, 0, 1256, 1257, 5, 114, 0, 0, 1257, 1258, 5, 100, 0, 0, 1258, 1259, 5, 101, 0, 0, 1259, 1260, 5, 114, 0, 0, 1260, 1261, 5, 101, 0, 0, 1261, 1262, 5, 100, 0, 0, 1262, 236, 1, 0, 0, 0, 1263, 1264, 5, 117, 0, 0, 1264, 1265, 5, 110, 0, 0, 1265, 1266, 5, 105, 0, 0, 1266, 1267, 5, 116, 0, 0, 1267, 238, 1, 0, 0, 0, 1268, 1269, 5, 119, 0, 0, 1269, 1270, 5, 97, 0, 0, 1270, 1271, 5, 116, 0, 0, 1271, 1272, 5, 99, 0, 0, 1272, 1273, 5, 104, 0, 0, 1273, 1274, 5, 45, 0, 0, 1274, 1275, 5, 104, 0, 0, 1275, 1276, 5, 97, 0, 0, 1276, 1277, 5, 110, 0, 0, 1277, 1278, 5, 100, 0, 0, 1278, 1279, 5, 108, 0, 0, 1279, 1280, 5, 101, 0, 0, 1280, 240, 1, 0, 0, 0, 1281, 1282, 5, 109, 0, 0, 1282, 1283, 5, 101, 0, 0, 1283, 1284, 5, 115, 0, 0, 1284, 1285, 5, 115, 0, 0, 1285, 1286, 5, 97, 0, 0, 1286, 1287, 5, 103, 0, 0, 1287, 1288, 5, 101, 0, 0, 1288, 242, 1, 0, 0, 0, 1289, 1290, 5, 97, 0, 0, 1290, 1291, 5, 116, 0, 0, 1291, 1292, 5, 111, 0, 0, 1292, 1293, 5, 109, 0, 0, 1293, 1294, 5, 45, 0, 0, 1294, 1295, 5, 114, 0, 0, 1295, 1296, 5, 101, 0, 0, 1296, 1297, 5, 102, 0, 0, 1297, 244, 1, 0, 0, 0, 1298, 1299, 5, 105, 0, 0, 1299, 1300, 5, 110, 0, 0, 1300, 1301, 5, 116, 0, 0, 1301, 1302, 5, 101, 0, 0, 1302, 1303, 5, 114, 0, 0, 1303, 1304, 5, 102, 0, 0, 1304, 1305, 5, 97, 0, 0, 1305, 1306, 5, 99, 0, 0, 1306, 1307, 5, 101, 0, 0, 1307, 1308, 5, 45, 0, 0, 1308, 1309, 5, 114, 0, 0, 1309, 1310, 5, 101, 0, 0, 1310, 1311, 5, 102, 0, 0, 1311, 246, 1, 0, 0, 0, 1312, 1313, 5, 111, 0, 0, 1313, 1314, 5, 112, 0, 0, 1314, 1315, 5, 116, 0, 0, 1315, 1316, 5, 105, 0, 0, 1316, 1317, 5, 111, 0, 0, 1317, 1318, 5, 110, 0, 0, 1318, 1319, 5, 97, 0, 0, 1319, 1320, 5, 108, 0, 0, 1320, 248, 1, 0, 0, 0, 1321, 1322, 5, 108, 0, 0, 1322, 1323, 5, 105, 0, 0, 1323, 1324, 5, 115, 0, 0, 1324, 1325, 5, 116, 0, 0, 1325, 250, 1, 0, 0, 0, 1326, 1327, 5, 114, 0, 0, 1327, 1328, 5, 101, 0, 0, 1328, 1329, 5, 99, 0, 0, 1329, 1330, 5, 111, 0, 0, 1330, 1331, 5, 114, 0, 0, 1331, 1332, 5, 100, 0, 0, 1332, 252, 1, 0, 0, 0, 1333, 1334, 5, 98, 0, 0, 1334, 1335, 5, 111, 0, 0, 1335, 1336, 5, 111, 0, 0, 1336, 1337, 5, 108, 0, 0, 1337, 254, 1, 0, 0, 0, 1338, 1339, 5, 98, 0, 0, 1339, 1340, 5, 121, 0, 0, 1340, 1341, 5, 116, 0, 0, 1341, 1342, 5, 101, 0, 0, 1342, 1343, 5, 115, 0, 0, 1343, 256, 1, 0, 0, 0, 1344, 1345, 5, 100, 0, 0, 1345, 1346, 5, 111, 0, 0, 1346, 1347, 5, 117, 0, 0, 1347, 1348, 5, 98, 0, 0, 1348, 1349, 5, 108, 0, 0, 1349, 1350, 5, 101, 0, 0, 1350, 258, 1, 0, 0, 0, 1351, 1352, 5, 105, 0, 0, 1352, 1353, 5, 110, 0, 0, 1353, 1354, 5, 116, 0, 0, 1354, 1355, 5, 51, 0, 0, 1355, 1356, 5, 50, 0, 0, 1356, 260, 1, 0, 0, 0, 1357, 1358, 5, 105, 0, 0, 1358, 1359, 5, 110, 0, 0, 1359, 1360, 5, 116, 0, 0, 1360, 1361, 5, 54, 0, 0, 1361, 1362, 5, 52, 0, 0, 1362, 262, 1, 0, 0, 0, 1363, 1364, 5, 115, 0, 0, 1364, 1365, 5, 116, 0, 0, 1365, 1366, 5, 114, 0, 0, 1366, 1367, 5, 105, 0, 0, 1367, 1368, 5, 110, 0, 0, 1368, 1369, 5, 103, 0, 0, 1369, 264, 1, 0, 0, 0, 1370, 1371, 5, 117, 0, 0, 1371, 1372, 5, 105, 0, 0, 1372, 1373, 5, 110, 0, 0, 1373, 1374, 5, 116, 0, 0, 1374, 1375, 5, 51, 0, 0, 1375, 1376, 5, 50, 0, 0, 1376, 266, 1, 0, 0, 0, 1377, 1378, 5, 117, 0, 0, 1378, 1379, 5, 105, 0, 0, 1379, 1380, 5, 110, 0, 0, 1380, 1381, 5, 116, 0, 0, 1381, 1382, 5, 54, 0, 0, 1382, 1383, 5, 52, 0, 0, 1383, 268, 1, 0, 0, 0, 1384, 1385, 5, 116, 0, 0, 1385, 1386, 5, 114, 0, 0, 1386, 1387, 5, 117, 0, 0, 1387, 1388, 5, 101, 0, 0, 1388, 270, 1, 0, 0, 0, 1389, 1390, 5, 102, 0, 0, 1390, 1391, 5, 97, 0, 0, 1391, 1392, 5, 108, 0, 0, 1392, 1393, 5, 115, 0, 0, 1393, 1394, 5, 101, 0, 0, 1394, 272, 1, 0, 0, 0, 1395, 1396, 5, 110, 0, 0, 1396, 1397, 5, 117, 0, 0, 1397, 1398, 5, 108, 0, 0, 1398, 1399, 5, 108, 0, 0, 1399, 274, 1, 0, 0, 0, 1400, 1401, 5, 45, 0, 0, 1401, 1402, 5, 62, 0, 0, 1402, 276, 1, 0, 0, 0, 1403, 1404, 5, 58, 0, 0, 1404, 278, 1, 0, 0, 0, 1405, 1406, 5, 59, 0, 0, 1406, 280, 1, 0, 0, 0, 1407, 1408, 5, 44, 0, 0, 1408, 282, 1, 0, 0, 0, 1409, 1410, 5, 46, 0, 0, 1410, 284, 1, 0, 0, 0, 1411, 1412, 5, 123, 0, 0, 1412, 286, 1, 0, 0, 0, 1413, 1414, 5, 125, 0, 0, 1414, 288, 1, 0, 0, 0, 1415, 1416, 5, 91, 0, 0, 1416, 290, 1, 0, 0, 0, 1417, 1418, 5, 93, 0, 0, 1418, 292, 1, 0, 0, 0, 1419, 1420, 5, 40, 0, 0, 1420, 294, 1, 0, 0, 0, 1421, 1422, 5, 41, 0, 0, 1422, 296, 1, 0, 0, 0, 1423, 1424, 5, 60, 0, 0, 1424, 298, 1, 0, 0, 0, 1425, 1426, 5, 62, 0, 0, 1426, 300, 1, 0, 0, 0, 1427, 1428, 5, 38, 0, 0, 1428, 302, 1, 0, 0, 0, 1429, 1430, 5, 61, 0, 0, 1430, 304, 1, 0, 0, 0, 1431, 1433, 5, 45, 0, 0, 1432, 1431, 1, 0, 0, 0, 1432, 1433, 1, 0, 0, 0, 1433, 1435, 1, 0, 0, 0, 1434, 1436, 7, 0, 0, 0, 1435, 1434, 1, 0, 0, 0, 1436, 1437, 1, 0, 0, 0, 1437, 1435, 1, 0, 0, 0, 1437, 1438, 1, 0, 0, 0, 1438, 306, 1, 0, 0, 0, 1439, 1441, 5, 45, 0, 0, 1440, 1439, 1, 0, 0, 0, 1440, 1441, 1, 0, 0, 0, 1441, 1450, 1, 0, 0, 0, 1442, 1451, 5, 48, 0, 0, 1443, 1447, 7, 1, 0, 0, 1444, 1446, 7, 0, 0, 0, 1445, 1444, 1, 0, 0, 0, 1446, 1449, 1, 0, 0, 0, 1447, 1445, 1, 0, 0, 0, 1447, 1448, 1, 0, 0, 0, 1448, 1451, 1, 0, 0, 0, 1449, 1447, 1, 0, 0, 0, 1450, 1442, 1, 0, 0, 0, 1450, 1443, 1, 0, 0, 0, 1451, 1458, 1, 0, 0, 0, 1452, 1454, 5, 46, 0, 0, 1453, 1455, 7, 0, 0, 0, 1454, 1453, 1, 0, 0, 0, 1455, 1456, 1, 0, 0, 0, 1456, 1454, 1, 0, 0, 0, 1456, 1457, 1, 0, 0, 0, 1457, 1459, 1, 0, 0, 0, 1458, 1452, 1, 0, 0, 0, 1458, 1459, 1, 0, 0, 0, 1459, 1469, 1, 0, 0, 0, 1460, 1462, 7, 2, 0, 0, 1461, 1463, 7, 3, 0, 0, 1462, 1461, 1, 0, 0, 0, 1462, 1463, 1, 0, 0, 0, 1463, 1465, 1, 0, 0, 0, 1464, 1466, 7, 0, 0, 0, 1465, 1464, 1, 0, 0, 0, 1466, 1467, 1, 0, 0, 0, 1467, 1465, 1, 0, 0, 0, 1467, 1468, 1, 0, 0, 0, 1468, 1470, 1, 0, 0, 0, 1469, 1460, 1, 0, 0, 0, 1469, 1470, 1, 0, 0, 0, 1470, 308, 1, 0, 0, 0, 1471, 1475, 7, 4, 0, 0, 1472, 1474, 7, 5, 0, 0, 1473, 1472, 1, 0, 0, 0, 1474, 1477, 1, 0, 0, 0, 1475, 1473, 1, 0, 0, 0, 1475, 1476, 1, 0, 0, 0, 1476, 310, 1, 0, 0, 0, 1477, 1475, 1, 0, 0, 0, 1478, 1483, 5, 34, 0, 0, 1479, 1482, 3, 313, 156, 0, 1480, 1482, 8, 6, 0, 0, 1481, 1479, 1, 0, 0, 0, 1481, 1480, 1, 0, 0, 0, 1482, 1485, 1, 0, 0, 0, 1483, 1481, 1, 0, 0, 0, 1483, 1484, 1, 0, 0, 0, 1484, 1486, 1, 0, 0, 0, 1485, 1483, 1, 0, 0, 0, 1486, 1487, 5, 34, 0, 0, 1487, 312, 1, 0, 0, 0, 1488, 1496, 5, 92, 0, 0, 1489, 1497, 7, 7, 0, 0, 1490, 1491, 5, 117, 0, 0, 1491, 1492, 3, 315, 157, 0, 1492, 1493, 3, 315, 157, 0, 1493, 1494, 3, 315, 157, 0, 1494, 1495, 3, 315, 157, 0, 1495, 1497, 1, 0, 0, 0, 1496, 1489, 1, 0, 0, 0, 1496, 1490, 1, 0, 0, 0, 1497, 314, 1, 0, 0, 0, 1498, 1499, 7, 8, 0, 0, 1499, 316, 1, 0, 0, 0, 1500, 1501, 5, 47, 0, 0, 1501, 1502, 5, 47, 0, 0, 1502, 1506, 1, 0, 0, 0, 1503, 1505, 8, 9, 0, 0, 1504, 1503, 1, 0, 0, 0, 1505, 1508, 1, 0, 0, 0, 1506, 1504, 1, 0, 0, 0, 1506, 1507, 1, 0, 0, 0, 1507, 1509, 1, 0, 0, 0, 1508, 1506, 1, 0, 0, 0, 1509, 1510, 6, 158, 0, 0, 1510, 318, 1, 0, 0, 0, 1511, 1512, 5, 47, 0, 0, 1512, 1513, 5, 42, 0, 0, 1513, 1517, 1, 0, 0, 0, 1514, 1516, 9, 0, 0, 0, 1515, 1514, 1, 0, 0, 0, 1516, 1519, 1, 0, 0, 0, 1517, 1518, 1, 0, 0, 0, 1517, 1515, 1, 0, 0, 0, 1518, 1520, 1, 0, 0, 0, 1519, 1517, 1, 0, 0, 0, 1520, 1521, 5, 42, 0, 0, 1521, 1522, 5, 47, 0, 0, 1522, 1523, 1, 0, 0, 0, 1523, 1524, 6, 159, 0, 0, 1524, 320, 1, 0, 0, 0, 1525, 1527, 7, 10, 0, 0, 1526, 1525, 1, 0, 0, 0, 1527, 1528, 1, 0, 0, 0, 1528, 1526, 1, 0, 0, 0, 1528, 1529, 1, 0, 0, 0, 1529, 1530, 1, 0, 0, 0, 1530, 1531, 6, 160, 0, 0, 1531, 322, 1, 0, 0, 0, 18, 0, 1432, 1437, 1440, 1447, 1450, 1456, 1458, 1462, 1467, 1469, 1475, 1481, 1483, 1496, 1506, 1517, 1528, 1, 0, 1, 0] \ No newline at end of file diff --git a/src/capability-language/generated/QuixosCapabilityLexer.tokens b/src/capability-language/generated/QuixosCapabilityLexer.tokens index c90ad75..ec69fb8 100644 --- a/src/capability-language/generated/QuixosCapabilityLexer.tokens +++ b/src/capability-language/generated/QuixosCapabilityLexer.tokens @@ -1,259 +1,311 @@ -WORKSPACE=1 -RESULT_BYTES=2 -RPC_CALLS=3 -DEADLINE_MS=4 -QUERY=5 -SPECIALIZE=6 -ROOT=7 -DOCUMENT=8 -FRAGMENTS=9 -VIEW=10 -MAX=11 -ALLOW=12 -POLL=13 -QUERYABLE=14 -RPC=15 -QUERY_REASON=16 -TYPE=17 -OBJECT=18 -STORABLE=19 -IMPLEMENTS=20 -REF=21 -FRAGMENT=22 -IMPORT=23 -EXTERNAL=24 -ATOM=25 -INTERFACE=26 -INTERFACES=27 -PACKAGE=28 -VALUE=29 -RELATION=30 -OPERATION=31 -FUNCTION=32 -CONSTRUCTOR=33 -CONSTRUCTS=34 -INPUT=35 -CONFORM=36 -AS=37 -BIND=38 -STATIC=39 -TO=40 -PRIVATE=41 -SHARED=42 -STATE=43 -EDGE=44 -PROJECTION=45 -WITH=46 -USING=47 -VIA=48 -MATERIALIZE=49 -IF=50 -ABSENT=51 -ON=52 -POLICY=53 -DEFAULT=54 -SOURCE=55 -REPOSITORY=56 -COMMIT=57 -REVISION=58 -SEMANTIC_MAJOR=59 -ON_DELETE=60 -RETAIN_OTHER=61 -KEYED=62 -PUBLIC_TRAVERSAL=63 -ID=64 -DOC=65 -MODE=66 -EMITS=67 -RECEIVER=68 -REQUIRES=69 -ANY=70 -GET=71 -SET=72 -WATCH=73 -START=74 -STOP=75 -READ=76 -WRITE=77 -RESOLVE=78 -CONNECT=79 -DISCONNECT=80 -CALL=81 -WATCH_START=82 -WATCH_STOP=83 -SUBSCRIBE=84 -UNSUBSCRIBE=85 -OPTIMISTIC_REGISTER=86 -CRDT=87 -OPTIONAL_ONE=88 -EXACTLY_ONE=89 -MANY_UNIQUE=90 -MANY=91 -ORDERED=92 -UNIT=93 -WATCH_HANDLE=94 -MESSAGE=95 -ATOM_REF=96 -INTERFACE_REF=97 -OPTIONAL=98 -LIST=99 -RECORD=100 -BOOL=101 -BYTES=102 -DOUBLE=103 -INT32=104 -INT64=105 -STRING=106 -UINT32=107 -UINT64=108 -TRUE=109 -FALSE=110 -NULL=111 -ARROW=112 -COLON=113 -SEMI=114 -COMMA=115 -DOT=116 -LBRACE=117 -RBRACE=118 -LBRACK=119 -RBRACK=120 -LPAREN=121 -RPAREN=122 -LT=123 -GT=124 -AMP=125 -EQUAL=126 -INTEGER=127 -JSON_NUMBER=128 -IDENTIFIER=129 -STRING_LITERAL=130 -LINE_COMMENT=131 -BLOCK_COMMENT=132 -WS=133 -'workspace'=1 -'result-bytes'=2 -'rpc-calls'=3 -'deadline-ms'=4 -'query'=5 -'specialize'=6 -'root'=7 -'document'=8 -'fragments'=9 -'view'=10 -'max'=11 -'allow'=12 -'poll'=13 -'queryable'=14 -'rpc'=15 -'query-reason'=16 -'type'=17 -'object'=18 -'storable'=19 -'implements'=20 -'ref'=21 -'fragment'=22 -'import'=23 -'external'=24 -'atom'=25 -'interface'=26 -'interfaces'=27 -'package'=28 -'value'=29 -'relation'=30 -'operation'=31 -'function'=32 -'constructor'=33 -'constructs'=34 -'input'=35 -'conform'=36 -'as'=37 -'bind'=38 -'static'=39 -'to'=40 -'private'=41 -'shared'=42 -'state'=43 -'edge'=44 -'projection'=45 -'with'=46 -'using'=47 -'via'=48 -'materialize'=49 -'if'=50 -'absent'=51 -'on'=52 -'policy'=53 -'default'=54 -'source'=55 -'repository'=56 -'commit'=57 -'revision'=58 -'semantic-major'=59 -'on-delete'=60 -'retain-other'=61 -'keyed'=62 -'public-traversal'=63 -'id'=64 -'doc'=65 -'mode'=66 -'emits'=67 -'receiver'=68 -'requires'=69 -'any'=70 -'get'=71 -'set'=72 -'watch'=73 -'start'=74 -'stop'=75 -'read'=76 -'write'=77 -'resolve'=78 -'connect'=79 -'disconnect'=80 -'call'=81 -'watch-start'=82 -'watch-stop'=83 -'subscribe'=84 -'unsubscribe'=85 -'optimistic-register'=86 -'crdt'=87 -'optional-one'=88 -'exactly-one'=89 -'many-unique'=90 -'many'=91 -'ordered'=92 -'unit'=93 -'watch-handle'=94 -'message'=95 -'atom-ref'=96 -'interface-ref'=97 -'optional'=98 -'list'=99 -'record'=100 -'bool'=101 -'bytes'=102 -'double'=103 -'int32'=104 -'int64'=105 -'string'=106 -'uint32'=107 -'uint64'=108 -'true'=109 -'false'=110 -'null'=111 -'->'=112 -':'=113 -';'=114 -','=115 -'.'=116 -'{'=117 -'}'=118 -'['=119 -']'=120 -'('=121 -')'=122 -'<'=123 -'>'=124 -'&'=125 -'='=126 +EDIT=1 +MODULE=2 +CONTRACT=3 +PRESENTATION=4 +SUBJECT=5 +FOR=6 +CREATION=7 +VARIANT=8 +PREPARES=9 +INITIALIZE=10 +WORKSPACE=11 +EXECUTION=12 +SNAPSHOT=13 +TRANSACTION=14 +REMOTE=15 +TARGETS=16 +PORTABLE=17 +REPLICA=18 +LOCAL=19 +FROM=20 +RESULT_BYTES=21 +RPC_CALLS=22 +DEADLINE_MS=23 +QUERY=24 +REFLECTION=25 +SPECIALIZE=26 +ROOT=27 +DOCUMENT=28 +FRAGMENTS=29 +VIEW=30 +MAX=31 +ALLOW=32 +POLL=33 +QUERYABLE=34 +RPC=35 +QUERY_REASON=36 +TYPE=37 +OBJECT=38 +STORABLE=39 +IMPLEMENTS=40 +REF=41 +FRAGMENT=42 +IMPORT=43 +EXTERNAL=44 +SYMBOL=45 +ATOM=46 +INTERFACE=47 +INTERFACES=48 +PACKAGE=49 +VALUE=50 +FIELD=51 +RELATION=52 +OPERATION=53 +FUNCTION=54 +CONSTRUCTOR=55 +CONSTRUCTS=56 +INPUT=57 +CONFORM=58 +AS=59 +BIND=60 +STATIC=61 +TO=62 +PRIVATE=63 +SHARED=64 +STATE=65 +EDGE=66 +PROJECTION=67 +WITH=68 +USING=69 +ACCEPT=70 +VIA=71 +MATERIALIZE=72 +IF=73 +ABSENT=74 +ON=75 +POLICY=76 +DEFAULT=77 +SOURCE=78 +REPOSITORY=79 +COMMIT=80 +REVISION=81 +SEMANTIC_MAJOR=82 +ON_DELETE=83 +LIFECYCLE=84 +JOURNAL=85 +SUBSCRIPTION=86 +RETAIN_OTHER=87 +KEYED=88 +PUBLIC_TRAVERSAL=89 +ID=90 +DOC=91 +MODE=92 +EMITS=93 +RECEIVER=94 +REQUIRES=95 +ANY=96 +GET=97 +SET=98 +WATCH=99 +START=100 +STOP=101 +READ=102 +WRITE=103 +RESOLVE=104 +CONNECT=105 +DISCONNECT=106 +CALL=107 +WATCH_START=108 +WATCH_STOP=109 +SUBSCRIBE=110 +UNSUBSCRIBE=111 +REGISTER=112 +CRDT=113 +OPTIONAL_ONE=114 +EXACTLY_ONE=115 +MANY_UNIQUE=116 +MANY=117 +ORDERED=118 +UNIT=119 +WATCH_HANDLE=120 +MESSAGE=121 +ATOM_REF=122 +INTERFACE_REF=123 +OPTIONAL=124 +LIST=125 +RECORD=126 +BOOL=127 +BYTES=128 +DOUBLE=129 +INT32=130 +INT64=131 +STRING=132 +UINT32=133 +UINT64=134 +TRUE=135 +FALSE=136 +NULL=137 +ARROW=138 +COLON=139 +SEMI=140 +COMMA=141 +DOT=142 +LBRACE=143 +RBRACE=144 +LBRACK=145 +RBRACK=146 +LPAREN=147 +RPAREN=148 +LT=149 +GT=150 +AMP=151 +EQUAL=152 +INTEGER=153 +JSON_NUMBER=154 +IDENTIFIER=155 +STRING_LITERAL=156 +LINE_COMMENT=157 +BLOCK_COMMENT=158 +WS=159 +'edit'=1 +'module'=2 +'contract'=3 +'presentation'=4 +'subject'=5 +'for'=6 +'creation'=7 +'variant'=8 +'prepares'=9 +'initialize'=10 +'workspace'=11 +'execution'=12 +'snapshot'=13 +'transaction'=14 +'remote'=15 +'targets'=16 +'portable'=17 +'replica'=18 +'local'=19 +'from'=20 +'result-bytes'=21 +'rpc-calls'=22 +'deadline-ms'=23 +'query'=24 +'reflection'=25 +'specialize'=26 +'root'=27 +'document'=28 +'fragments'=29 +'view'=30 +'max'=31 +'allow'=32 +'poll'=33 +'queryable'=34 +'rpc'=35 +'query-reason'=36 +'type'=37 +'object'=38 +'storable'=39 +'implements'=40 +'ref'=41 +'fragment'=42 +'import'=43 +'external'=44 +'symbol'=45 +'atom'=46 +'interface'=47 +'interfaces'=48 +'package'=49 +'value'=50 +'field'=51 +'relation'=52 +'operation'=53 +'function'=54 +'constructor'=55 +'constructs'=56 +'input'=57 +'conform'=58 +'as'=59 +'bind'=60 +'static'=61 +'to'=62 +'private'=63 +'shared'=64 +'state'=65 +'edge'=66 +'projection'=67 +'with'=68 +'using'=69 +'accept'=70 +'via'=71 +'materialize'=72 +'if'=73 +'absent'=74 +'on'=75 +'policy'=76 +'default'=77 +'source'=78 +'repository'=79 +'commit'=80 +'revision'=81 +'semantic-major'=82 +'on-delete'=83 +'lifecycle'=84 +'journal'=85 +'subscription'=86 +'retain-other'=87 +'keyed'=88 +'public-traversal'=89 +'id'=90 +'doc'=91 +'mode'=92 +'emits'=93 +'receiver'=94 +'requires'=95 +'any'=96 +'get'=97 +'set'=98 +'watch'=99 +'start'=100 +'stop'=101 +'read'=102 +'write'=103 +'resolve'=104 +'connect'=105 +'disconnect'=106 +'call'=107 +'watch-start'=108 +'watch-stop'=109 +'subscribe'=110 +'unsubscribe'=111 +'register'=112 +'crdt'=113 +'optional-one'=114 +'exactly-one'=115 +'many-unique'=116 +'many'=117 +'ordered'=118 +'unit'=119 +'watch-handle'=120 +'message'=121 +'atom-ref'=122 +'interface-ref'=123 +'optional'=124 +'list'=125 +'record'=126 +'bool'=127 +'bytes'=128 +'double'=129 +'int32'=130 +'int64'=131 +'string'=132 +'uint32'=133 +'uint64'=134 +'true'=135 +'false'=136 +'null'=137 +'->'=138 +':'=139 +';'=140 +','=141 +'.'=142 +'{'=143 +'}'=144 +'['=145 +']'=146 +'('=147 +')'=148 +'<'=149 +'>'=150 +'&'=151 +'='=152 diff --git a/src/capability-language/generated/QuixosCapabilityLexer.ts b/src/capability-language/generated/QuixosCapabilityLexer.ts index 5d20b6a..78ceca0 100644 --- a/src/capability-language/generated/QuixosCapabilityLexer.ts +++ b/src/capability-language/generated/QuixosCapabilityLexer.ts @@ -4,191 +4,226 @@ import { Token } from "antlr4ng"; export class QuixosCapabilityLexer extends antlr.Lexer { - public static readonly WORKSPACE = 1; - public static readonly RESULT_BYTES = 2; - public static readonly RPC_CALLS = 3; - public static readonly DEADLINE_MS = 4; - public static readonly QUERY = 5; - public static readonly SPECIALIZE = 6; - public static readonly ROOT = 7; - public static readonly DOCUMENT = 8; - public static readonly FRAGMENTS = 9; - public static readonly VIEW = 10; - public static readonly MAX = 11; - public static readonly ALLOW = 12; - public static readonly POLL = 13; - public static readonly QUERYABLE = 14; - public static readonly RPC = 15; - public static readonly QUERY_REASON = 16; - public static readonly TYPE = 17; - public static readonly OBJECT = 18; - public static readonly STORABLE = 19; - public static readonly IMPLEMENTS = 20; - public static readonly REF = 21; - public static readonly FRAGMENT = 22; - public static readonly IMPORT = 23; - public static readonly EXTERNAL = 24; - public static readonly ATOM = 25; - public static readonly INTERFACE = 26; - public static readonly INTERFACES = 27; - public static readonly PACKAGE = 28; - public static readonly VALUE = 29; - public static readonly RELATION = 30; - public static readonly OPERATION = 31; - public static readonly FUNCTION = 32; - public static readonly CONSTRUCTOR = 33; - public static readonly CONSTRUCTS = 34; - public static readonly INPUT = 35; - public static readonly CONFORM = 36; - public static readonly AS = 37; - public static readonly BIND = 38; - public static readonly STATIC = 39; - public static readonly TO = 40; - public static readonly PRIVATE = 41; - public static readonly SHARED = 42; - public static readonly STATE = 43; - public static readonly EDGE = 44; - public static readonly PROJECTION = 45; - public static readonly WITH = 46; - public static readonly USING = 47; - public static readonly VIA = 48; - public static readonly MATERIALIZE = 49; - public static readonly IF = 50; - public static readonly ABSENT = 51; - public static readonly ON = 52; - public static readonly POLICY = 53; - public static readonly DEFAULT = 54; - public static readonly SOURCE = 55; - public static readonly REPOSITORY = 56; - public static readonly COMMIT = 57; - public static readonly REVISION = 58; - public static readonly SEMANTIC_MAJOR = 59; - public static readonly ON_DELETE = 60; - public static readonly RETAIN_OTHER = 61; - public static readonly KEYED = 62; - public static readonly PUBLIC_TRAVERSAL = 63; - public static readonly ID = 64; - public static readonly DOC = 65; - public static readonly MODE = 66; - public static readonly EMITS = 67; - public static readonly RECEIVER = 68; - public static readonly REQUIRES = 69; - public static readonly ANY = 70; - public static readonly GET = 71; - public static readonly SET = 72; - public static readonly WATCH = 73; - public static readonly START = 74; - public static readonly STOP = 75; - public static readonly READ = 76; - public static readonly WRITE = 77; - public static readonly RESOLVE = 78; - public static readonly CONNECT = 79; - public static readonly DISCONNECT = 80; - public static readonly CALL = 81; - public static readonly WATCH_START = 82; - public static readonly WATCH_STOP = 83; - public static readonly SUBSCRIBE = 84; - public static readonly UNSUBSCRIBE = 85; - public static readonly OPTIMISTIC_REGISTER = 86; - public static readonly CRDT = 87; - public static readonly OPTIONAL_ONE = 88; - public static readonly EXACTLY_ONE = 89; - public static readonly MANY_UNIQUE = 90; - public static readonly MANY = 91; - public static readonly ORDERED = 92; - public static readonly UNIT = 93; - public static readonly WATCH_HANDLE = 94; - public static readonly MESSAGE = 95; - public static readonly ATOM_REF = 96; - public static readonly INTERFACE_REF = 97; - public static readonly OPTIONAL = 98; - public static readonly LIST = 99; - public static readonly RECORD = 100; - public static readonly BOOL = 101; - public static readonly BYTES = 102; - public static readonly DOUBLE = 103; - public static readonly INT32 = 104; - public static readonly INT64 = 105; - public static readonly STRING = 106; - public static readonly UINT32 = 107; - public static readonly UINT64 = 108; - public static readonly TRUE = 109; - public static readonly FALSE = 110; - public static readonly NULL = 111; - public static readonly ARROW = 112; - public static readonly COLON = 113; - public static readonly SEMI = 114; - public static readonly COMMA = 115; - public static readonly DOT = 116; - public static readonly LBRACE = 117; - public static readonly RBRACE = 118; - public static readonly LBRACK = 119; - public static readonly RBRACK = 120; - public static readonly LPAREN = 121; - public static readonly RPAREN = 122; - public static readonly LT = 123; - public static readonly GT = 124; - public static readonly AMP = 125; - public static readonly EQUAL = 126; - public static readonly INTEGER = 127; - public static readonly JSON_NUMBER = 128; - public static readonly IDENTIFIER = 129; - public static readonly STRING_LITERAL = 130; - public static readonly LINE_COMMENT = 131; - public static readonly BLOCK_COMMENT = 132; - public static readonly WS = 133; + public static readonly EDIT = 1; + public static readonly MODULE = 2; + public static readonly CONTRACT = 3; + public static readonly PRESENTATION = 4; + public static readonly SUBJECT = 5; + public static readonly FOR = 6; + public static readonly CREATION = 7; + public static readonly VARIANT = 8; + public static readonly PREPARES = 9; + public static readonly INITIALIZE = 10; + public static readonly WORKSPACE = 11; + public static readonly EXECUTION = 12; + public static readonly SNAPSHOT = 13; + public static readonly TRANSACTION = 14; + public static readonly REMOTE = 15; + public static readonly TARGETS = 16; + public static readonly PORTABLE = 17; + public static readonly REPLICA = 18; + public static readonly LOCAL = 19; + public static readonly FROM = 20; + public static readonly RESULT_BYTES = 21; + public static readonly RPC_CALLS = 22; + public static readonly DEADLINE_MS = 23; + public static readonly QUERY = 24; + public static readonly REFLECTION = 25; + public static readonly SPECIALIZE = 26; + public static readonly ROOT = 27; + public static readonly DOCUMENT = 28; + public static readonly FRAGMENTS = 29; + public static readonly VIEW = 30; + public static readonly MAX = 31; + public static readonly ALLOW = 32; + public static readonly POLL = 33; + public static readonly QUERYABLE = 34; + public static readonly RPC = 35; + public static readonly QUERY_REASON = 36; + public static readonly TYPE = 37; + public static readonly OBJECT = 38; + public static readonly STORABLE = 39; + public static readonly IMPLEMENTS = 40; + public static readonly REF = 41; + public static readonly FRAGMENT = 42; + public static readonly IMPORT = 43; + public static readonly EXTERNAL = 44; + public static readonly SYMBOL = 45; + public static readonly ATOM = 46; + public static readonly INTERFACE = 47; + public static readonly INTERFACES = 48; + public static readonly PACKAGE = 49; + public static readonly VALUE = 50; + public static readonly FIELD = 51; + public static readonly RELATION = 52; + public static readonly OPERATION = 53; + public static readonly FUNCTION = 54; + public static readonly CONSTRUCTOR = 55; + public static readonly CONSTRUCTS = 56; + public static readonly INPUT = 57; + public static readonly CONFORM = 58; + public static readonly AS = 59; + public static readonly BIND = 60; + public static readonly STATIC = 61; + public static readonly TO = 62; + public static readonly PRIVATE = 63; + public static readonly SHARED = 64; + public static readonly STATE = 65; + public static readonly EDGE = 66; + public static readonly PROJECTION = 67; + public static readonly WITH = 68; + public static readonly USING = 69; + public static readonly ACCEPT = 70; + public static readonly VIA = 71; + public static readonly MATERIALIZE = 72; + public static readonly IF = 73; + public static readonly ABSENT = 74; + public static readonly ON = 75; + public static readonly POLICY = 76; + public static readonly DEFAULT = 77; + public static readonly SOURCE = 78; + public static readonly REPOSITORY = 79; + public static readonly COMMIT = 80; + public static readonly REVISION = 81; + public static readonly SEMANTIC_MAJOR = 82; + public static readonly ON_DELETE = 83; + public static readonly LIFECYCLE = 84; + public static readonly JOURNAL = 85; + public static readonly SUBSCRIPTION = 86; + public static readonly RETAIN_OTHER = 87; + public static readonly KEYED = 88; + public static readonly PUBLIC_TRAVERSAL = 89; + public static readonly ID = 90; + public static readonly DOC = 91; + public static readonly MODE = 92; + public static readonly EMITS = 93; + public static readonly RECEIVER = 94; + public static readonly REQUIRES = 95; + public static readonly ANY = 96; + public static readonly GET = 97; + public static readonly SET = 98; + public static readonly WATCH = 99; + public static readonly START = 100; + public static readonly STOP = 101; + public static readonly READ = 102; + public static readonly WRITE = 103; + public static readonly RESOLVE = 104; + public static readonly CONNECT = 105; + public static readonly DISCONNECT = 106; + public static readonly CALL = 107; + public static readonly WATCH_START = 108; + public static readonly WATCH_STOP = 109; + public static readonly SUBSCRIBE = 110; + public static readonly UNSUBSCRIBE = 111; + public static readonly REGISTER = 112; + public static readonly CRDT = 113; + public static readonly OPTIONAL_ONE = 114; + public static readonly EXACTLY_ONE = 115; + public static readonly MANY_UNIQUE = 116; + public static readonly MANY = 117; + public static readonly ORDERED = 118; + public static readonly UNIT = 119; + public static readonly WATCH_HANDLE = 120; + public static readonly MESSAGE = 121; + public static readonly ATOM_REF = 122; + public static readonly INTERFACE_REF = 123; + public static readonly OPTIONAL = 124; + public static readonly LIST = 125; + public static readonly RECORD = 126; + public static readonly BOOL = 127; + public static readonly BYTES = 128; + public static readonly DOUBLE = 129; + public static readonly INT32 = 130; + public static readonly INT64 = 131; + public static readonly STRING = 132; + public static readonly UINT32 = 133; + public static readonly UINT64 = 134; + public static readonly TRUE = 135; + public static readonly FALSE = 136; + public static readonly NULL = 137; + public static readonly ARROW = 138; + public static readonly COLON = 139; + public static readonly SEMI = 140; + public static readonly COMMA = 141; + public static readonly DOT = 142; + public static readonly LBRACE = 143; + public static readonly RBRACE = 144; + public static readonly LBRACK = 145; + public static readonly RBRACK = 146; + public static readonly LPAREN = 147; + public static readonly RPAREN = 148; + public static readonly LT = 149; + public static readonly GT = 150; + public static readonly AMP = 151; + public static readonly EQUAL = 152; + public static readonly INTEGER = 153; + public static readonly JSON_NUMBER = 154; + public static readonly IDENTIFIER = 155; + public static readonly STRING_LITERAL = 156; + public static readonly LINE_COMMENT = 157; + public static readonly BLOCK_COMMENT = 158; + public static readonly WS = 159; public static readonly channelNames = [ "DEFAULT_TOKEN_CHANNEL", "HIDDEN" ]; public static readonly literalNames = [ - null, "'workspace'", "'result-bytes'", "'rpc-calls'", "'deadline-ms'", - "'query'", "'specialize'", "'root'", "'document'", "'fragments'", - "'view'", "'max'", "'allow'", "'poll'", "'queryable'", "'rpc'", - "'query-reason'", "'type'", "'object'", "'storable'", "'implements'", - "'ref'", "'fragment'", "'import'", "'external'", "'atom'", "'interface'", - "'interfaces'", "'package'", "'value'", "'relation'", "'operation'", + null, "'edit'", "'module'", "'contract'", "'presentation'", "'subject'", + "'for'", "'creation'", "'variant'", "'prepares'", "'initialize'", + "'workspace'", "'execution'", "'snapshot'", "'transaction'", "'remote'", + "'targets'", "'portable'", "'replica'", "'local'", "'from'", "'result-bytes'", + "'rpc-calls'", "'deadline-ms'", "'query'", "'reflection'", "'specialize'", + "'root'", "'document'", "'fragments'", "'view'", "'max'", "'allow'", + "'poll'", "'queryable'", "'rpc'", "'query-reason'", "'type'", "'object'", + "'storable'", "'implements'", "'ref'", "'fragment'", "'import'", + "'external'", "'symbol'", "'atom'", "'interface'", "'interfaces'", + "'package'", "'value'", "'field'", "'relation'", "'operation'", "'function'", "'constructor'", "'constructs'", "'input'", "'conform'", "'as'", "'bind'", "'static'", "'to'", "'private'", "'shared'", "'state'", - "'edge'", "'projection'", "'with'", "'using'", "'via'", "'materialize'", - "'if'", "'absent'", "'on'", "'policy'", "'default'", "'source'", - "'repository'", "'commit'", "'revision'", "'semantic-major'", "'on-delete'", - "'retain-other'", "'keyed'", "'public-traversal'", "'id'", "'doc'", - "'mode'", "'emits'", "'receiver'", "'requires'", "'any'", "'get'", - "'set'", "'watch'", "'start'", "'stop'", "'read'", "'write'", "'resolve'", - "'connect'", "'disconnect'", "'call'", "'watch-start'", "'watch-stop'", - "'subscribe'", "'unsubscribe'", "'optimistic-register'", "'crdt'", - "'optional-one'", "'exactly-one'", "'many-unique'", "'many'", "'ordered'", - "'unit'", "'watch-handle'", "'message'", "'atom-ref'", "'interface-ref'", - "'optional'", "'list'", "'record'", "'bool'", "'bytes'", "'double'", - "'int32'", "'int64'", "'string'", "'uint32'", "'uint64'", "'true'", - "'false'", "'null'", "'->'", "':'", "';'", "','", "'.'", "'{'", - "'}'", "'['", "']'", "'('", "')'", "'<'", "'>'", "'&'", "'='" + "'edge'", "'projection'", "'with'", "'using'", "'accept'", "'via'", + "'materialize'", "'if'", "'absent'", "'on'", "'policy'", "'default'", + "'source'", "'repository'", "'commit'", "'revision'", "'semantic-major'", + "'on-delete'", "'lifecycle'", "'journal'", "'subscription'", "'retain-other'", + "'keyed'", "'public-traversal'", "'id'", "'doc'", "'mode'", "'emits'", + "'receiver'", "'requires'", "'any'", "'get'", "'set'", "'watch'", + "'start'", "'stop'", "'read'", "'write'", "'resolve'", "'connect'", + "'disconnect'", "'call'", "'watch-start'", "'watch-stop'", "'subscribe'", + "'unsubscribe'", "'register'", "'crdt'", "'optional-one'", "'exactly-one'", + "'many-unique'", "'many'", "'ordered'", "'unit'", "'watch-handle'", + "'message'", "'atom-ref'", "'interface-ref'", "'optional'", "'list'", + "'record'", "'bool'", "'bytes'", "'double'", "'int32'", "'int64'", + "'string'", "'uint32'", "'uint64'", "'true'", "'false'", "'null'", + "'->'", "':'", "';'", "','", "'.'", "'{'", "'}'", "'['", "']'", + "'('", "')'", "'<'", "'>'", "'&'", "'='" ]; public static readonly symbolicNames = [ - null, "WORKSPACE", "RESULT_BYTES", "RPC_CALLS", "DEADLINE_MS", "QUERY", - "SPECIALIZE", "ROOT", "DOCUMENT", "FRAGMENTS", "VIEW", "MAX", "ALLOW", - "POLL", "QUERYABLE", "RPC", "QUERY_REASON", "TYPE", "OBJECT", "STORABLE", - "IMPLEMENTS", "REF", "FRAGMENT", "IMPORT", "EXTERNAL", "ATOM", "INTERFACE", - "INTERFACES", "PACKAGE", "VALUE", "RELATION", "OPERATION", "FUNCTION", - "CONSTRUCTOR", "CONSTRUCTS", "INPUT", "CONFORM", "AS", "BIND", "STATIC", - "TO", "PRIVATE", "SHARED", "STATE", "EDGE", "PROJECTION", "WITH", - "USING", "VIA", "MATERIALIZE", "IF", "ABSENT", "ON", "POLICY", "DEFAULT", - "SOURCE", "REPOSITORY", "COMMIT", "REVISION", "SEMANTIC_MAJOR", - "ON_DELETE", "RETAIN_OTHER", "KEYED", "PUBLIC_TRAVERSAL", "ID", - "DOC", "MODE", "EMITS", "RECEIVER", "REQUIRES", "ANY", "GET", "SET", - "WATCH", "START", "STOP", "READ", "WRITE", "RESOLVE", "CONNECT", - "DISCONNECT", "CALL", "WATCH_START", "WATCH_STOP", "SUBSCRIBE", - "UNSUBSCRIBE", "OPTIMISTIC_REGISTER", "CRDT", "OPTIONAL_ONE", "EXACTLY_ONE", - "MANY_UNIQUE", "MANY", "ORDERED", "UNIT", "WATCH_HANDLE", "MESSAGE", - "ATOM_REF", "INTERFACE_REF", "OPTIONAL", "LIST", "RECORD", "BOOL", - "BYTES", "DOUBLE", "INT32", "INT64", "STRING", "UINT32", "UINT64", - "TRUE", "FALSE", "NULL", "ARROW", "COLON", "SEMI", "COMMA", "DOT", - "LBRACE", "RBRACE", "LBRACK", "RBRACK", "LPAREN", "RPAREN", "LT", - "GT", "AMP", "EQUAL", "INTEGER", "JSON_NUMBER", "IDENTIFIER", "STRING_LITERAL", - "LINE_COMMENT", "BLOCK_COMMENT", "WS" + null, "EDIT", "MODULE", "CONTRACT", "PRESENTATION", "SUBJECT", "FOR", + "CREATION", "VARIANT", "PREPARES", "INITIALIZE", "WORKSPACE", "EXECUTION", + "SNAPSHOT", "TRANSACTION", "REMOTE", "TARGETS", "PORTABLE", "REPLICA", + "LOCAL", "FROM", "RESULT_BYTES", "RPC_CALLS", "DEADLINE_MS", "QUERY", + "REFLECTION", "SPECIALIZE", "ROOT", "DOCUMENT", "FRAGMENTS", "VIEW", + "MAX", "ALLOW", "POLL", "QUERYABLE", "RPC", "QUERY_REASON", "TYPE", + "OBJECT", "STORABLE", "IMPLEMENTS", "REF", "FRAGMENT", "IMPORT", + "EXTERNAL", "SYMBOL", "ATOM", "INTERFACE", "INTERFACES", "PACKAGE", + "VALUE", "FIELD", "RELATION", "OPERATION", "FUNCTION", "CONSTRUCTOR", + "CONSTRUCTS", "INPUT", "CONFORM", "AS", "BIND", "STATIC", "TO", + "PRIVATE", "SHARED", "STATE", "EDGE", "PROJECTION", "WITH", "USING", + "ACCEPT", "VIA", "MATERIALIZE", "IF", "ABSENT", "ON", "POLICY", + "DEFAULT", "SOURCE", "REPOSITORY", "COMMIT", "REVISION", "SEMANTIC_MAJOR", + "ON_DELETE", "LIFECYCLE", "JOURNAL", "SUBSCRIPTION", "RETAIN_OTHER", + "KEYED", "PUBLIC_TRAVERSAL", "ID", "DOC", "MODE", "EMITS", "RECEIVER", + "REQUIRES", "ANY", "GET", "SET", "WATCH", "START", "STOP", "READ", + "WRITE", "RESOLVE", "CONNECT", "DISCONNECT", "CALL", "WATCH_START", + "WATCH_STOP", "SUBSCRIBE", "UNSUBSCRIBE", "REGISTER", "CRDT", "OPTIONAL_ONE", + "EXACTLY_ONE", "MANY_UNIQUE", "MANY", "ORDERED", "UNIT", "WATCH_HANDLE", + "MESSAGE", "ATOM_REF", "INTERFACE_REF", "OPTIONAL", "LIST", "RECORD", + "BOOL", "BYTES", "DOUBLE", "INT32", "INT64", "STRING", "UINT32", + "UINT64", "TRUE", "FALSE", "NULL", "ARROW", "COLON", "SEMI", "COMMA", + "DOT", "LBRACE", "RBRACE", "LBRACK", "RBRACK", "LPAREN", "RPAREN", + "LT", "GT", "AMP", "EQUAL", "INTEGER", "JSON_NUMBER", "IDENTIFIER", + "STRING_LITERAL", "LINE_COMMENT", "BLOCK_COMMENT", "WS" ]; public static readonly modeNames = [ @@ -196,27 +231,32 @@ export class QuixosCapabilityLexer extends antlr.Lexer { ]; public static readonly ruleNames = [ - "WORKSPACE", "RESULT_BYTES", "RPC_CALLS", "DEADLINE_MS", "QUERY", - "SPECIALIZE", "ROOT", "DOCUMENT", "FRAGMENTS", "VIEW", "MAX", "ALLOW", - "POLL", "QUERYABLE", "RPC", "QUERY_REASON", "TYPE", "OBJECT", "STORABLE", - "IMPLEMENTS", "REF", "FRAGMENT", "IMPORT", "EXTERNAL", "ATOM", "INTERFACE", - "INTERFACES", "PACKAGE", "VALUE", "RELATION", "OPERATION", "FUNCTION", - "CONSTRUCTOR", "CONSTRUCTS", "INPUT", "CONFORM", "AS", "BIND", "STATIC", - "TO", "PRIVATE", "SHARED", "STATE", "EDGE", "PROJECTION", "WITH", - "USING", "VIA", "MATERIALIZE", "IF", "ABSENT", "ON", "POLICY", "DEFAULT", - "SOURCE", "REPOSITORY", "COMMIT", "REVISION", "SEMANTIC_MAJOR", - "ON_DELETE", "RETAIN_OTHER", "KEYED", "PUBLIC_TRAVERSAL", "ID", - "DOC", "MODE", "EMITS", "RECEIVER", "REQUIRES", "ANY", "GET", "SET", - "WATCH", "START", "STOP", "READ", "WRITE", "RESOLVE", "CONNECT", - "DISCONNECT", "CALL", "WATCH_START", "WATCH_STOP", "SUBSCRIBE", - "UNSUBSCRIBE", "OPTIMISTIC_REGISTER", "CRDT", "OPTIONAL_ONE", "EXACTLY_ONE", - "MANY_UNIQUE", "MANY", "ORDERED", "UNIT", "WATCH_HANDLE", "MESSAGE", - "ATOM_REF", "INTERFACE_REF", "OPTIONAL", "LIST", "RECORD", "BOOL", - "BYTES", "DOUBLE", "INT32", "INT64", "STRING", "UINT32", "UINT64", - "TRUE", "FALSE", "NULL", "ARROW", "COLON", "SEMI", "COMMA", "DOT", - "LBRACE", "RBRACE", "LBRACK", "RBRACK", "LPAREN", "RPAREN", "LT", - "GT", "AMP", "EQUAL", "INTEGER", "JSON_NUMBER", "IDENTIFIER", "STRING_LITERAL", - "ESC", "HEX", "LINE_COMMENT", "BLOCK_COMMENT", "WS", + "EDIT", "MODULE", "CONTRACT", "PRESENTATION", "SUBJECT", "FOR", + "CREATION", "VARIANT", "PREPARES", "INITIALIZE", "WORKSPACE", "EXECUTION", + "SNAPSHOT", "TRANSACTION", "REMOTE", "TARGETS", "PORTABLE", "REPLICA", + "LOCAL", "FROM", "RESULT_BYTES", "RPC_CALLS", "DEADLINE_MS", "QUERY", + "REFLECTION", "SPECIALIZE", "ROOT", "DOCUMENT", "FRAGMENTS", "VIEW", + "MAX", "ALLOW", "POLL", "QUERYABLE", "RPC", "QUERY_REASON", "TYPE", + "OBJECT", "STORABLE", "IMPLEMENTS", "REF", "FRAGMENT", "IMPORT", + "EXTERNAL", "SYMBOL", "ATOM", "INTERFACE", "INTERFACES", "PACKAGE", + "VALUE", "FIELD", "RELATION", "OPERATION", "FUNCTION", "CONSTRUCTOR", + "CONSTRUCTS", "INPUT", "CONFORM", "AS", "BIND", "STATIC", "TO", + "PRIVATE", "SHARED", "STATE", "EDGE", "PROJECTION", "WITH", "USING", + "ACCEPT", "VIA", "MATERIALIZE", "IF", "ABSENT", "ON", "POLICY", + "DEFAULT", "SOURCE", "REPOSITORY", "COMMIT", "REVISION", "SEMANTIC_MAJOR", + "ON_DELETE", "LIFECYCLE", "JOURNAL", "SUBSCRIPTION", "RETAIN_OTHER", + "KEYED", "PUBLIC_TRAVERSAL", "ID", "DOC", "MODE", "EMITS", "RECEIVER", + "REQUIRES", "ANY", "GET", "SET", "WATCH", "START", "STOP", "READ", + "WRITE", "RESOLVE", "CONNECT", "DISCONNECT", "CALL", "WATCH_START", + "WATCH_STOP", "SUBSCRIBE", "UNSUBSCRIBE", "REGISTER", "CRDT", "OPTIONAL_ONE", + "EXACTLY_ONE", "MANY_UNIQUE", "MANY", "ORDERED", "UNIT", "WATCH_HANDLE", + "MESSAGE", "ATOM_REF", "INTERFACE_REF", "OPTIONAL", "LIST", "RECORD", + "BOOL", "BYTES", "DOUBLE", "INT32", "INT64", "STRING", "UINT32", + "UINT64", "TRUE", "FALSE", "NULL", "ARROW", "COLON", "SEMI", "COMMA", + "DOT", "LBRACE", "RBRACE", "LBRACK", "RBRACK", "LPAREN", "RPAREN", + "LT", "GT", "AMP", "EQUAL", "INTEGER", "JSON_NUMBER", "IDENTIFIER", + "STRING_LITERAL", "ESC", "HEX", "LINE_COMMENT", "BLOCK_COMMENT", + "WS", ]; @@ -238,7 +278,7 @@ export class QuixosCapabilityLexer extends antlr.Lexer { public get modeNames(): string[] { return QuixosCapabilityLexer.modeNames; } public static readonly _serializedATN: number[] = [ - 4,0,133,1272,6,-1,2,0,7,0,2,1,7,1,2,2,7,2,2,3,7,3,2,4,7,4,2,5,7, + 4,0,159,1532,6,-1,2,0,7,0,2,1,7,1,2,2,7,2,2,3,7,3,2,4,7,4,2,5,7, 5,2,6,7,6,2,7,7,7,2,8,7,8,2,9,7,9,2,10,7,10,2,11,7,11,2,12,7,12, 2,13,7,13,2,14,7,14,2,15,7,15,2,16,7,16,2,17,7,17,2,18,7,18,2,19, 7,19,2,20,7,20,2,21,7,21,2,22,7,22,2,23,7,23,2,24,7,24,2,25,7,25, @@ -259,88 +299,112 @@ export class QuixosCapabilityLexer extends antlr.Lexer { 7,114,2,115,7,115,2,116,7,116,2,117,7,117,2,118,7,118,2,119,7,119, 2,120,7,120,2,121,7,121,2,122,7,122,2,123,7,123,2,124,7,124,2,125, 7,125,2,126,7,126,2,127,7,127,2,128,7,128,2,129,7,129,2,130,7,130, - 2,131,7,131,2,132,7,132,2,133,7,133,2,134,7,134,1,0,1,0,1,0,1,0, - 1,0,1,0,1,0,1,0,1,0,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, - 1,1,1,1,1,1,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,3,1,3,1,3, - 1,3,1,3,1,3,1,3,1,3,1,3,1,3,1,3,1,3,1,4,1,4,1,4,1,4,1,4,1,4,1,5, - 1,5,1,5,1,5,1,5,1,5,1,5,1,5,1,5,1,5,1,5,1,6,1,6,1,6,1,6,1,6,1,7, - 1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,8,1,8,1,8,1,8,1,8,1,8,1,8,1,8, - 1,8,1,8,1,9,1,9,1,9,1,9,1,9,1,10,1,10,1,10,1,10,1,11,1,11,1,11,1, - 11,1,11,1,11,1,12,1,12,1,12,1,12,1,12,1,13,1,13,1,13,1,13,1,13,1, - 13,1,13,1,13,1,13,1,13,1,14,1,14,1,14,1,14,1,15,1,15,1,15,1,15,1, - 15,1,15,1,15,1,15,1,15,1,15,1,15,1,15,1,15,1,16,1,16,1,16,1,16,1, - 16,1,17,1,17,1,17,1,17,1,17,1,17,1,17,1,18,1,18,1,18,1,18,1,18,1, - 18,1,18,1,18,1,18,1,19,1,19,1,19,1,19,1,19,1,19,1,19,1,19,1,19,1, - 19,1,19,1,20,1,20,1,20,1,20,1,21,1,21,1,21,1,21,1,21,1,21,1,21,1, - 21,1,21,1,22,1,22,1,22,1,22,1,22,1,22,1,22,1,23,1,23,1,23,1,23,1, - 23,1,23,1,23,1,23,1,23,1,24,1,24,1,24,1,24,1,24,1,25,1,25,1,25,1, - 25,1,25,1,25,1,25,1,25,1,25,1,25,1,26,1,26,1,26,1,26,1,26,1,26,1, + 2,131,7,131,2,132,7,132,2,133,7,133,2,134,7,134,2,135,7,135,2,136, + 7,136,2,137,7,137,2,138,7,138,2,139,7,139,2,140,7,140,2,141,7,141, + 2,142,7,142,2,143,7,143,2,144,7,144,2,145,7,145,2,146,7,146,2,147, + 7,147,2,148,7,148,2,149,7,149,2,150,7,150,2,151,7,151,2,152,7,152, + 2,153,7,153,2,154,7,154,2,155,7,155,2,156,7,156,2,157,7,157,2,158, + 7,158,2,159,7,159,2,160,7,160,1,0,1,0,1,0,1,0,1,0,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,3,1,3,1,3,1, + 3,1,3,1,3,1,3,1,3,1,3,1,3,1,3,1,3,1,3,1,4,1,4,1,4,1,4,1,4,1,4,1, + 4,1,4,1,5,1,5,1,5,1,5,1,6,1,6,1,6,1,6,1,6,1,6,1,6,1,6,1,6,1,7,1, + 7,1,7,1,7,1,7,1,7,1,7,1,7,1,8,1,8,1,8,1,8,1,8,1,8,1,8,1,8,1,8,1, + 9,1,9,1,9,1,9,1,9,1,9,1,9,1,9,1,9,1,9,1,9,1,10,1,10,1,10,1,10,1, + 10,1,10,1,10,1,10,1,10,1,10,1,11,1,11,1,11,1,11,1,11,1,11,1,11,1, + 11,1,11,1,11,1,12,1,12,1,12,1,12,1,12,1,12,1,12,1,12,1,12,1,13,1, + 13,1,13,1,13,1,13,1,13,1,13,1,13,1,13,1,13,1,13,1,13,1,14,1,14,1, + 14,1,14,1,14,1,14,1,14,1,15,1,15,1,15,1,15,1,15,1,15,1,15,1,15,1, + 16,1,16,1,16,1,16,1,16,1,16,1,16,1,16,1,16,1,17,1,17,1,17,1,17,1, + 17,1,17,1,17,1,17,1,18,1,18,1,18,1,18,1,18,1,18,1,19,1,19,1,19,1, + 19,1,19,1,20,1,20,1,20,1,20,1,20,1,20,1,20,1,20,1,20,1,20,1,20,1, + 20,1,20,1,21,1,21,1,21,1,21,1,21,1,21,1,21,1,21,1,21,1,21,1,22,1, + 22,1,22,1,22,1,22,1,22,1,22,1,22,1,22,1,22,1,22,1,22,1,23,1,23,1, + 23,1,23,1,23,1,23,1,24,1,24,1,24,1,24,1,24,1,24,1,24,1,24,1,24,1, + 24,1,24,1,25,1,25,1,25,1,25,1,25,1,25,1,25,1,25,1,25,1,25,1,25,1, 26,1,26,1,26,1,26,1,26,1,27,1,27,1,27,1,27,1,27,1,27,1,27,1,27,1, - 28,1,28,1,28,1,28,1,28,1,28,1,29,1,29,1,29,1,29,1,29,1,29,1,29,1, - 29,1,29,1,30,1,30,1,30,1,30,1,30,1,30,1,30,1,30,1,30,1,30,1,31,1, - 31,1,31,1,31,1,31,1,31,1,31,1,31,1,31,1,32,1,32,1,32,1,32,1,32,1, - 32,1,32,1,32,1,32,1,32,1,32,1,32,1,33,1,33,1,33,1,33,1,33,1,33,1, - 33,1,33,1,33,1,33,1,33,1,34,1,34,1,34,1,34,1,34,1,34,1,35,1,35,1, - 35,1,35,1,35,1,35,1,35,1,35,1,36,1,36,1,36,1,37,1,37,1,37,1,37,1, - 37,1,38,1,38,1,38,1,38,1,38,1,38,1,38,1,39,1,39,1,39,1,40,1,40,1, - 40,1,40,1,40,1,40,1,40,1,40,1,41,1,41,1,41,1,41,1,41,1,41,1,41,1, - 42,1,42,1,42,1,42,1,42,1,42,1,43,1,43,1,43,1,43,1,43,1,44,1,44,1, - 44,1,44,1,44,1,44,1,44,1,44,1,44,1,44,1,44,1,45,1,45,1,45,1,45,1, - 45,1,46,1,46,1,46,1,46,1,46,1,46,1,47,1,47,1,47,1,47,1,48,1,48,1, - 48,1,48,1,48,1,48,1,48,1,48,1,48,1,48,1,48,1,48,1,49,1,49,1,49,1, - 50,1,50,1,50,1,50,1,50,1,50,1,50,1,51,1,51,1,51,1,52,1,52,1,52,1, - 52,1,52,1,52,1,52,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,54,1, - 54,1,54,1,54,1,54,1,54,1,54,1,55,1,55,1,55,1,55,1,55,1,55,1,55,1, - 55,1,55,1,55,1,55,1,56,1,56,1,56,1,56,1,56,1,56,1,56,1,57,1,57,1, - 57,1,57,1,57,1,57,1,57,1,57,1,57,1,58,1,58,1,58,1,58,1,58,1,58,1, - 58,1,58,1,58,1,58,1,58,1,58,1,58,1,58,1,58,1,59,1,59,1,59,1,59,1, - 59,1,59,1,59,1,59,1,59,1,59,1,60,1,60,1,60,1,60,1,60,1,60,1,60,1, - 60,1,60,1,60,1,60,1,60,1,60,1,61,1,61,1,61,1,61,1,61,1,61,1,62,1, - 62,1,62,1,62,1,62,1,62,1,62,1,62,1,62,1,62,1,62,1,62,1,62,1,62,1, - 62,1,62,1,62,1,63,1,63,1,63,1,64,1,64,1,64,1,64,1,65,1,65,1,65,1, - 65,1,65,1,66,1,66,1,66,1,66,1,66,1,66,1,67,1,67,1,67,1,67,1,67,1, - 67,1,67,1,67,1,67,1,68,1,68,1,68,1,68,1,68,1,68,1,68,1,68,1,68,1, - 69,1,69,1,69,1,69,1,70,1,70,1,70,1,70,1,71,1,71,1,71,1,71,1,72,1, - 72,1,72,1,72,1,72,1,72,1,73,1,73,1,73,1,73,1,73,1,73,1,74,1,74,1, - 74,1,74,1,74,1,75,1,75,1,75,1,75,1,75,1,76,1,76,1,76,1,76,1,76,1, - 76,1,77,1,77,1,77,1,77,1,77,1,77,1,77,1,77,1,78,1,78,1,78,1,78,1, - 78,1,78,1,78,1,78,1,79,1,79,1,79,1,79,1,79,1,79,1,79,1,79,1,79,1, - 79,1,79,1,80,1,80,1,80,1,80,1,80,1,81,1,81,1,81,1,81,1,81,1,81,1, - 81,1,81,1,81,1,81,1,81,1,81,1,82,1,82,1,82,1,82,1,82,1,82,1,82,1, - 82,1,82,1,82,1,82,1,83,1,83,1,83,1,83,1,83,1,83,1,83,1,83,1,83,1, - 83,1,84,1,84,1,84,1,84,1,84,1,84,1,84,1,84,1,84,1,84,1,84,1,84,1, - 85,1,85,1,85,1,85,1,85,1,85,1,85,1,85,1,85,1,85,1,85,1,85,1,85,1, - 85,1,85,1,85,1,85,1,85,1,85,1,85,1,86,1,86,1,86,1,86,1,86,1,87,1, - 87,1,87,1,87,1,87,1,87,1,87,1,87,1,87,1,87,1,87,1,87,1,87,1,88,1, - 88,1,88,1,88,1,88,1,88,1,88,1,88,1,88,1,88,1,88,1,88,1,89,1,89,1, - 89,1,89,1,89,1,89,1,89,1,89,1,89,1,89,1,89,1,89,1,90,1,90,1,90,1, - 90,1,90,1,91,1,91,1,91,1,91,1,91,1,91,1,91,1,91,1,92,1,92,1,92,1, - 92,1,92,1,93,1,93,1,93,1,93,1,93,1,93,1,93,1,93,1,93,1,93,1,93,1, - 93,1,93,1,94,1,94,1,94,1,94,1,94,1,94,1,94,1,94,1,95,1,95,1,95,1, - 95,1,95,1,95,1,95,1,95,1,95,1,96,1,96,1,96,1,96,1,96,1,96,1,96,1, - 96,1,96,1,96,1,96,1,96,1,96,1,96,1,97,1,97,1,97,1,97,1,97,1,97,1, - 97,1,97,1,97,1,98,1,98,1,98,1,98,1,98,1,99,1,99,1,99,1,99,1,99,1, - 99,1,99,1,100,1,100,1,100,1,100,1,100,1,101,1,101,1,101,1,101,1, - 101,1,101,1,102,1,102,1,102,1,102,1,102,1,102,1,102,1,103,1,103, - 1,103,1,103,1,103,1,103,1,104,1,104,1,104,1,104,1,104,1,104,1,105, - 1,105,1,105,1,105,1,105,1,105,1,105,1,106,1,106,1,106,1,106,1,106, - 1,106,1,106,1,107,1,107,1,107,1,107,1,107,1,107,1,107,1,108,1,108, - 1,108,1,108,1,108,1,109,1,109,1,109,1,109,1,109,1,109,1,110,1,110, - 1,110,1,110,1,110,1,111,1,111,1,111,1,112,1,112,1,113,1,113,1,114, - 1,114,1,115,1,115,1,116,1,116,1,117,1,117,1,118,1,118,1,119,1,119, - 1,120,1,120,1,121,1,121,1,122,1,122,1,123,1,123,1,124,1,124,1,125, - 1,125,1,126,3,126,1173,8,126,1,126,4,126,1176,8,126,11,126,12,126, - 1177,1,127,3,127,1181,8,127,1,127,1,127,1,127,5,127,1186,8,127,10, - 127,12,127,1189,9,127,3,127,1191,8,127,1,127,1,127,4,127,1195,8, - 127,11,127,12,127,1196,3,127,1199,8,127,1,127,1,127,3,127,1203,8, - 127,1,127,4,127,1206,8,127,11,127,12,127,1207,3,127,1210,8,127,1, - 128,1,128,5,128,1214,8,128,10,128,12,128,1217,9,128,1,129,1,129, - 1,129,5,129,1222,8,129,10,129,12,129,1225,9,129,1,129,1,129,1,130, - 1,130,1,130,1,130,1,130,1,130,1,130,1,130,3,130,1237,8,130,1,131, - 1,131,1,132,1,132,1,132,1,132,5,132,1245,8,132,10,132,12,132,1248, - 9,132,1,132,1,132,1,133,1,133,1,133,1,133,5,133,1256,8,133,10,133, - 12,133,1259,9,133,1,133,1,133,1,133,1,133,1,133,1,134,4,134,1267, - 8,134,11,134,12,134,1268,1,134,1,134,1,1257,0,135,1,1,3,2,5,3,7, + 27,1,28,1,28,1,28,1,28,1,28,1,28,1,28,1,28,1,28,1,28,1,29,1,29,1, + 29,1,29,1,29,1,30,1,30,1,30,1,30,1,31,1,31,1,31,1,31,1,31,1,31,1, + 32,1,32,1,32,1,32,1,32,1,33,1,33,1,33,1,33,1,33,1,33,1,33,1,33,1, + 33,1,33,1,34,1,34,1,34,1,34,1,35,1,35,1,35,1,35,1,35,1,35,1,35,1, + 35,1,35,1,35,1,35,1,35,1,35,1,36,1,36,1,36,1,36,1,36,1,37,1,37,1, + 37,1,37,1,37,1,37,1,37,1,38,1,38,1,38,1,38,1,38,1,38,1,38,1,38,1, + 38,1,39,1,39,1,39,1,39,1,39,1,39,1,39,1,39,1,39,1,39,1,39,1,40,1, + 40,1,40,1,40,1,41,1,41,1,41,1,41,1,41,1,41,1,41,1,41,1,41,1,42,1, + 42,1,42,1,42,1,42,1,42,1,42,1,43,1,43,1,43,1,43,1,43,1,43,1,43,1, + 43,1,43,1,44,1,44,1,44,1,44,1,44,1,44,1,44,1,45,1,45,1,45,1,45,1, + 45,1,46,1,46,1,46,1,46,1,46,1,46,1,46,1,46,1,46,1,46,1,47,1,47,1, + 47,1,47,1,47,1,47,1,47,1,47,1,47,1,47,1,47,1,48,1,48,1,48,1,48,1, + 48,1,48,1,48,1,48,1,49,1,49,1,49,1,49,1,49,1,49,1,50,1,50,1,50,1, + 50,1,50,1,50,1,51,1,51,1,51,1,51,1,51,1,51,1,51,1,51,1,51,1,52,1, + 52,1,52,1,52,1,52,1,52,1,52,1,52,1,52,1,52,1,53,1,53,1,53,1,53,1, + 53,1,53,1,53,1,53,1,53,1,54,1,54,1,54,1,54,1,54,1,54,1,54,1,54,1, + 54,1,54,1,54,1,54,1,55,1,55,1,55,1,55,1,55,1,55,1,55,1,55,1,55,1, + 55,1,55,1,56,1,56,1,56,1,56,1,56,1,56,1,57,1,57,1,57,1,57,1,57,1, + 57,1,57,1,57,1,58,1,58,1,58,1,59,1,59,1,59,1,59,1,59,1,60,1,60,1, + 60,1,60,1,60,1,60,1,60,1,61,1,61,1,61,1,62,1,62,1,62,1,62,1,62,1, + 62,1,62,1,62,1,63,1,63,1,63,1,63,1,63,1,63,1,63,1,64,1,64,1,64,1, + 64,1,64,1,64,1,65,1,65,1,65,1,65,1,65,1,66,1,66,1,66,1,66,1,66,1, + 66,1,66,1,66,1,66,1,66,1,66,1,67,1,67,1,67,1,67,1,67,1,68,1,68,1, + 68,1,68,1,68,1,68,1,69,1,69,1,69,1,69,1,69,1,69,1,69,1,70,1,70,1, + 70,1,70,1,71,1,71,1,71,1,71,1,71,1,71,1,71,1,71,1,71,1,71,1,71,1, + 71,1,72,1,72,1,72,1,73,1,73,1,73,1,73,1,73,1,73,1,73,1,74,1,74,1, + 74,1,75,1,75,1,75,1,75,1,75,1,75,1,75,1,76,1,76,1,76,1,76,1,76,1, + 76,1,76,1,76,1,77,1,77,1,77,1,77,1,77,1,77,1,77,1,78,1,78,1,78,1, + 78,1,78,1,78,1,78,1,78,1,78,1,78,1,78,1,79,1,79,1,79,1,79,1,79,1, + 79,1,79,1,80,1,80,1,80,1,80,1,80,1,80,1,80,1,80,1,80,1,81,1,81,1, + 81,1,81,1,81,1,81,1,81,1,81,1,81,1,81,1,81,1,81,1,81,1,81,1,81,1, + 82,1,82,1,82,1,82,1,82,1,82,1,82,1,82,1,82,1,82,1,83,1,83,1,83,1, + 83,1,83,1,83,1,83,1,83,1,83,1,83,1,84,1,84,1,84,1,84,1,84,1,84,1, + 84,1,84,1,85,1,85,1,85,1,85,1,85,1,85,1,85,1,85,1,85,1,85,1,85,1, + 85,1,85,1,86,1,86,1,86,1,86,1,86,1,86,1,86,1,86,1,86,1,86,1,86,1, + 86,1,86,1,87,1,87,1,87,1,87,1,87,1,87,1,88,1,88,1,88,1,88,1,88,1, + 88,1,88,1,88,1,88,1,88,1,88,1,88,1,88,1,88,1,88,1,88,1,88,1,89,1, + 89,1,89,1,90,1,90,1,90,1,90,1,91,1,91,1,91,1,91,1,91,1,92,1,92,1, + 92,1,92,1,92,1,92,1,93,1,93,1,93,1,93,1,93,1,93,1,93,1,93,1,93,1, + 94,1,94,1,94,1,94,1,94,1,94,1,94,1,94,1,94,1,95,1,95,1,95,1,95,1, + 96,1,96,1,96,1,96,1,97,1,97,1,97,1,97,1,98,1,98,1,98,1,98,1,98,1, + 98,1,99,1,99,1,99,1,99,1,99,1,99,1,100,1,100,1,100,1,100,1,100,1, + 101,1,101,1,101,1,101,1,101,1,102,1,102,1,102,1,102,1,102,1,102, + 1,103,1,103,1,103,1,103,1,103,1,103,1,103,1,103,1,104,1,104,1,104, + 1,104,1,104,1,104,1,104,1,104,1,105,1,105,1,105,1,105,1,105,1,105, + 1,105,1,105,1,105,1,105,1,105,1,106,1,106,1,106,1,106,1,106,1,107, + 1,107,1,107,1,107,1,107,1,107,1,107,1,107,1,107,1,107,1,107,1,107, + 1,108,1,108,1,108,1,108,1,108,1,108,1,108,1,108,1,108,1,108,1,108, + 1,109,1,109,1,109,1,109,1,109,1,109,1,109,1,109,1,109,1,109,1,110, + 1,110,1,110,1,110,1,110,1,110,1,110,1,110,1,110,1,110,1,110,1,110, + 1,111,1,111,1,111,1,111,1,111,1,111,1,111,1,111,1,111,1,112,1,112, + 1,112,1,112,1,112,1,113,1,113,1,113,1,113,1,113,1,113,1,113,1,113, + 1,113,1,113,1,113,1,113,1,113,1,114,1,114,1,114,1,114,1,114,1,114, + 1,114,1,114,1,114,1,114,1,114,1,114,1,115,1,115,1,115,1,115,1,115, + 1,115,1,115,1,115,1,115,1,115,1,115,1,115,1,116,1,116,1,116,1,116, + 1,116,1,117,1,117,1,117,1,117,1,117,1,117,1,117,1,117,1,118,1,118, + 1,118,1,118,1,118,1,119,1,119,1,119,1,119,1,119,1,119,1,119,1,119, + 1,119,1,119,1,119,1,119,1,119,1,120,1,120,1,120,1,120,1,120,1,120, + 1,120,1,120,1,121,1,121,1,121,1,121,1,121,1,121,1,121,1,121,1,121, + 1,122,1,122,1,122,1,122,1,122,1,122,1,122,1,122,1,122,1,122,1,122, + 1,122,1,122,1,122,1,123,1,123,1,123,1,123,1,123,1,123,1,123,1,123, + 1,123,1,124,1,124,1,124,1,124,1,124,1,125,1,125,1,125,1,125,1,125, + 1,125,1,125,1,126,1,126,1,126,1,126,1,126,1,127,1,127,1,127,1,127, + 1,127,1,127,1,128,1,128,1,128,1,128,1,128,1,128,1,128,1,129,1,129, + 1,129,1,129,1,129,1,129,1,130,1,130,1,130,1,130,1,130,1,130,1,131, + 1,131,1,131,1,131,1,131,1,131,1,131,1,132,1,132,1,132,1,132,1,132, + 1,132,1,132,1,133,1,133,1,133,1,133,1,133,1,133,1,133,1,134,1,134, + 1,134,1,134,1,134,1,135,1,135,1,135,1,135,1,135,1,135,1,136,1,136, + 1,136,1,136,1,136,1,137,1,137,1,137,1,138,1,138,1,139,1,139,1,140, + 1,140,1,141,1,141,1,142,1,142,1,143,1,143,1,144,1,144,1,145,1,145, + 1,146,1,146,1,147,1,147,1,148,1,148,1,149,1,149,1,150,1,150,1,151, + 1,151,1,152,3,152,1433,8,152,1,152,4,152,1436,8,152,11,152,12,152, + 1437,1,153,3,153,1441,8,153,1,153,1,153,1,153,5,153,1446,8,153,10, + 153,12,153,1449,9,153,3,153,1451,8,153,1,153,1,153,4,153,1455,8, + 153,11,153,12,153,1456,3,153,1459,8,153,1,153,1,153,3,153,1463,8, + 153,1,153,4,153,1466,8,153,11,153,12,153,1467,3,153,1470,8,153,1, + 154,1,154,5,154,1474,8,154,10,154,12,154,1477,9,154,1,155,1,155, + 1,155,5,155,1482,8,155,10,155,12,155,1485,9,155,1,155,1,155,1,156, + 1,156,1,156,1,156,1,156,1,156,1,156,1,156,3,156,1497,8,156,1,157, + 1,157,1,158,1,158,1,158,1,158,5,158,1505,8,158,10,158,12,158,1508, + 9,158,1,158,1,158,1,159,1,159,1,159,1,159,5,159,1516,8,159,10,159, + 12,159,1519,9,159,1,159,1,159,1,159,1,159,1,159,1,160,4,160,1527, + 8,160,11,160,12,160,1528,1,160,1,160,1,1517,0,161,1,1,3,2,5,3,7, 4,9,5,11,6,13,7,15,8,17,9,19,10,21,11,23,12,25,13,27,14,29,15,31, 16,33,17,35,18,37,19,39,20,41,21,43,22,45,23,47,24,49,25,51,26,53, 27,55,28,57,29,59,30,61,31,63,32,65,33,67,34,69,35,71,36,73,37,75, @@ -354,353 +418,432 @@ export class QuixosCapabilityLexer extends antlr.Lexer { 105,211,106,213,107,215,108,217,109,219,110,221,111,223,112,225, 113,227,114,229,115,231,116,233,117,235,118,237,119,239,120,241, 121,243,122,245,123,247,124,249,125,251,126,253,127,255,128,257, - 129,259,130,261,0,263,0,265,131,267,132,269,133,1,0,11,1,0,48,57, - 1,0,49,57,2,0,69,69,101,101,2,0,43,43,45,45,3,0,65,90,95,95,97,122, - 4,0,48,57,65,90,95,95,97,122,4,0,10,10,13,13,34,34,92,92,8,0,34, - 34,47,47,92,92,98,98,102,102,110,110,114,114,116,116,3,0,48,57,65, - 70,97,102,2,0,10,10,13,13,3,0,9,10,13,13,32,32,1286,0,1,1,0,0,0, - 0,3,1,0,0,0,0,5,1,0,0,0,0,7,1,0,0,0,0,9,1,0,0,0,0,11,1,0,0,0,0,13, - 1,0,0,0,0,15,1,0,0,0,0,17,1,0,0,0,0,19,1,0,0,0,0,21,1,0,0,0,0,23, - 1,0,0,0,0,25,1,0,0,0,0,27,1,0,0,0,0,29,1,0,0,0,0,31,1,0,0,0,0,33, - 1,0,0,0,0,35,1,0,0,0,0,37,1,0,0,0,0,39,1,0,0,0,0,41,1,0,0,0,0,43, - 1,0,0,0,0,45,1,0,0,0,0,47,1,0,0,0,0,49,1,0,0,0,0,51,1,0,0,0,0,53, - 1,0,0,0,0,55,1,0,0,0,0,57,1,0,0,0,0,59,1,0,0,0,0,61,1,0,0,0,0,63, - 1,0,0,0,0,65,1,0,0,0,0,67,1,0,0,0,0,69,1,0,0,0,0,71,1,0,0,0,0,73, - 1,0,0,0,0,75,1,0,0,0,0,77,1,0,0,0,0,79,1,0,0,0,0,81,1,0,0,0,0,83, - 1,0,0,0,0,85,1,0,0,0,0,87,1,0,0,0,0,89,1,0,0,0,0,91,1,0,0,0,0,93, - 1,0,0,0,0,95,1,0,0,0,0,97,1,0,0,0,0,99,1,0,0,0,0,101,1,0,0,0,0,103, - 1,0,0,0,0,105,1,0,0,0,0,107,1,0,0,0,0,109,1,0,0,0,0,111,1,0,0,0, - 0,113,1,0,0,0,0,115,1,0,0,0,0,117,1,0,0,0,0,119,1,0,0,0,0,121,1, - 0,0,0,0,123,1,0,0,0,0,125,1,0,0,0,0,127,1,0,0,0,0,129,1,0,0,0,0, - 131,1,0,0,0,0,133,1,0,0,0,0,135,1,0,0,0,0,137,1,0,0,0,0,139,1,0, - 0,0,0,141,1,0,0,0,0,143,1,0,0,0,0,145,1,0,0,0,0,147,1,0,0,0,0,149, - 1,0,0,0,0,151,1,0,0,0,0,153,1,0,0,0,0,155,1,0,0,0,0,157,1,0,0,0, - 0,159,1,0,0,0,0,161,1,0,0,0,0,163,1,0,0,0,0,165,1,0,0,0,0,167,1, - 0,0,0,0,169,1,0,0,0,0,171,1,0,0,0,0,173,1,0,0,0,0,175,1,0,0,0,0, - 177,1,0,0,0,0,179,1,0,0,0,0,181,1,0,0,0,0,183,1,0,0,0,0,185,1,0, - 0,0,0,187,1,0,0,0,0,189,1,0,0,0,0,191,1,0,0,0,0,193,1,0,0,0,0,195, - 1,0,0,0,0,197,1,0,0,0,0,199,1,0,0,0,0,201,1,0,0,0,0,203,1,0,0,0, - 0,205,1,0,0,0,0,207,1,0,0,0,0,209,1,0,0,0,0,211,1,0,0,0,0,213,1, - 0,0,0,0,215,1,0,0,0,0,217,1,0,0,0,0,219,1,0,0,0,0,221,1,0,0,0,0, - 223,1,0,0,0,0,225,1,0,0,0,0,227,1,0,0,0,0,229,1,0,0,0,0,231,1,0, - 0,0,0,233,1,0,0,0,0,235,1,0,0,0,0,237,1,0,0,0,0,239,1,0,0,0,0,241, - 1,0,0,0,0,243,1,0,0,0,0,245,1,0,0,0,0,247,1,0,0,0,0,249,1,0,0,0, - 0,251,1,0,0,0,0,253,1,0,0,0,0,255,1,0,0,0,0,257,1,0,0,0,0,259,1, - 0,0,0,0,265,1,0,0,0,0,267,1,0,0,0,0,269,1,0,0,0,1,271,1,0,0,0,3, - 281,1,0,0,0,5,294,1,0,0,0,7,304,1,0,0,0,9,316,1,0,0,0,11,322,1,0, - 0,0,13,333,1,0,0,0,15,338,1,0,0,0,17,347,1,0,0,0,19,357,1,0,0,0, - 21,362,1,0,0,0,23,366,1,0,0,0,25,372,1,0,0,0,27,377,1,0,0,0,29,387, - 1,0,0,0,31,391,1,0,0,0,33,404,1,0,0,0,35,409,1,0,0,0,37,416,1,0, - 0,0,39,425,1,0,0,0,41,436,1,0,0,0,43,440,1,0,0,0,45,449,1,0,0,0, - 47,456,1,0,0,0,49,465,1,0,0,0,51,470,1,0,0,0,53,480,1,0,0,0,55,491, - 1,0,0,0,57,499,1,0,0,0,59,505,1,0,0,0,61,514,1,0,0,0,63,524,1,0, - 0,0,65,533,1,0,0,0,67,545,1,0,0,0,69,556,1,0,0,0,71,562,1,0,0,0, - 73,570,1,0,0,0,75,573,1,0,0,0,77,578,1,0,0,0,79,585,1,0,0,0,81,588, - 1,0,0,0,83,596,1,0,0,0,85,603,1,0,0,0,87,609,1,0,0,0,89,614,1,0, - 0,0,91,625,1,0,0,0,93,630,1,0,0,0,95,636,1,0,0,0,97,640,1,0,0,0, - 99,652,1,0,0,0,101,655,1,0,0,0,103,662,1,0,0,0,105,665,1,0,0,0,107, - 672,1,0,0,0,109,680,1,0,0,0,111,687,1,0,0,0,113,698,1,0,0,0,115, - 705,1,0,0,0,117,714,1,0,0,0,119,729,1,0,0,0,121,739,1,0,0,0,123, - 752,1,0,0,0,125,758,1,0,0,0,127,775,1,0,0,0,129,778,1,0,0,0,131, - 782,1,0,0,0,133,787,1,0,0,0,135,793,1,0,0,0,137,802,1,0,0,0,139, - 811,1,0,0,0,141,815,1,0,0,0,143,819,1,0,0,0,145,823,1,0,0,0,147, - 829,1,0,0,0,149,835,1,0,0,0,151,840,1,0,0,0,153,845,1,0,0,0,155, - 851,1,0,0,0,157,859,1,0,0,0,159,867,1,0,0,0,161,878,1,0,0,0,163, - 883,1,0,0,0,165,895,1,0,0,0,167,906,1,0,0,0,169,916,1,0,0,0,171, - 928,1,0,0,0,173,948,1,0,0,0,175,953,1,0,0,0,177,966,1,0,0,0,179, - 978,1,0,0,0,181,990,1,0,0,0,183,995,1,0,0,0,185,1003,1,0,0,0,187, - 1008,1,0,0,0,189,1021,1,0,0,0,191,1029,1,0,0,0,193,1038,1,0,0,0, - 195,1052,1,0,0,0,197,1061,1,0,0,0,199,1066,1,0,0,0,201,1073,1,0, - 0,0,203,1078,1,0,0,0,205,1084,1,0,0,0,207,1091,1,0,0,0,209,1097, - 1,0,0,0,211,1103,1,0,0,0,213,1110,1,0,0,0,215,1117,1,0,0,0,217,1124, - 1,0,0,0,219,1129,1,0,0,0,221,1135,1,0,0,0,223,1140,1,0,0,0,225,1143, - 1,0,0,0,227,1145,1,0,0,0,229,1147,1,0,0,0,231,1149,1,0,0,0,233,1151, - 1,0,0,0,235,1153,1,0,0,0,237,1155,1,0,0,0,239,1157,1,0,0,0,241,1159, - 1,0,0,0,243,1161,1,0,0,0,245,1163,1,0,0,0,247,1165,1,0,0,0,249,1167, - 1,0,0,0,251,1169,1,0,0,0,253,1172,1,0,0,0,255,1180,1,0,0,0,257,1211, - 1,0,0,0,259,1218,1,0,0,0,261,1228,1,0,0,0,263,1238,1,0,0,0,265,1240, - 1,0,0,0,267,1251,1,0,0,0,269,1266,1,0,0,0,271,272,5,119,0,0,272, - 273,5,111,0,0,273,274,5,114,0,0,274,275,5,107,0,0,275,276,5,115, - 0,0,276,277,5,112,0,0,277,278,5,97,0,0,278,279,5,99,0,0,279,280, - 5,101,0,0,280,2,1,0,0,0,281,282,5,114,0,0,282,283,5,101,0,0,283, - 284,5,115,0,0,284,285,5,117,0,0,285,286,5,108,0,0,286,287,5,116, - 0,0,287,288,5,45,0,0,288,289,5,98,0,0,289,290,5,121,0,0,290,291, - 5,116,0,0,291,292,5,101,0,0,292,293,5,115,0,0,293,4,1,0,0,0,294, - 295,5,114,0,0,295,296,5,112,0,0,296,297,5,99,0,0,297,298,5,45,0, - 0,298,299,5,99,0,0,299,300,5,97,0,0,300,301,5,108,0,0,301,302,5, - 108,0,0,302,303,5,115,0,0,303,6,1,0,0,0,304,305,5,100,0,0,305,306, - 5,101,0,0,306,307,5,97,0,0,307,308,5,100,0,0,308,309,5,108,0,0,309, - 310,5,105,0,0,310,311,5,110,0,0,311,312,5,101,0,0,312,313,5,45,0, - 0,313,314,5,109,0,0,314,315,5,115,0,0,315,8,1,0,0,0,316,317,5,113, - 0,0,317,318,5,117,0,0,318,319,5,101,0,0,319,320,5,114,0,0,320,321, - 5,121,0,0,321,10,1,0,0,0,322,323,5,115,0,0,323,324,5,112,0,0,324, - 325,5,101,0,0,325,326,5,99,0,0,326,327,5,105,0,0,327,328,5,97,0, - 0,328,329,5,108,0,0,329,330,5,105,0,0,330,331,5,122,0,0,331,332, - 5,101,0,0,332,12,1,0,0,0,333,334,5,114,0,0,334,335,5,111,0,0,335, - 336,5,111,0,0,336,337,5,116,0,0,337,14,1,0,0,0,338,339,5,100,0,0, - 339,340,5,111,0,0,340,341,5,99,0,0,341,342,5,117,0,0,342,343,5,109, - 0,0,343,344,5,101,0,0,344,345,5,110,0,0,345,346,5,116,0,0,346,16, - 1,0,0,0,347,348,5,102,0,0,348,349,5,114,0,0,349,350,5,97,0,0,350, - 351,5,103,0,0,351,352,5,109,0,0,352,353,5,101,0,0,353,354,5,110, - 0,0,354,355,5,116,0,0,355,356,5,115,0,0,356,18,1,0,0,0,357,358,5, - 118,0,0,358,359,5,105,0,0,359,360,5,101,0,0,360,361,5,119,0,0,361, - 20,1,0,0,0,362,363,5,109,0,0,363,364,5,97,0,0,364,365,5,120,0,0, - 365,22,1,0,0,0,366,367,5,97,0,0,367,368,5,108,0,0,368,369,5,108, - 0,0,369,370,5,111,0,0,370,371,5,119,0,0,371,24,1,0,0,0,372,373,5, - 112,0,0,373,374,5,111,0,0,374,375,5,108,0,0,375,376,5,108,0,0,376, - 26,1,0,0,0,377,378,5,113,0,0,378,379,5,117,0,0,379,380,5,101,0,0, - 380,381,5,114,0,0,381,382,5,121,0,0,382,383,5,97,0,0,383,384,5,98, - 0,0,384,385,5,108,0,0,385,386,5,101,0,0,386,28,1,0,0,0,387,388,5, - 114,0,0,388,389,5,112,0,0,389,390,5,99,0,0,390,30,1,0,0,0,391,392, - 5,113,0,0,392,393,5,117,0,0,393,394,5,101,0,0,394,395,5,114,0,0, - 395,396,5,121,0,0,396,397,5,45,0,0,397,398,5,114,0,0,398,399,5,101, - 0,0,399,400,5,97,0,0,400,401,5,115,0,0,401,402,5,111,0,0,402,403, - 5,110,0,0,403,32,1,0,0,0,404,405,5,116,0,0,405,406,5,121,0,0,406, - 407,5,112,0,0,407,408,5,101,0,0,408,34,1,0,0,0,409,410,5,111,0,0, - 410,411,5,98,0,0,411,412,5,106,0,0,412,413,5,101,0,0,413,414,5,99, - 0,0,414,415,5,116,0,0,415,36,1,0,0,0,416,417,5,115,0,0,417,418,5, - 116,0,0,418,419,5,111,0,0,419,420,5,114,0,0,420,421,5,97,0,0,421, - 422,5,98,0,0,422,423,5,108,0,0,423,424,5,101,0,0,424,38,1,0,0,0, - 425,426,5,105,0,0,426,427,5,109,0,0,427,428,5,112,0,0,428,429,5, - 108,0,0,429,430,5,101,0,0,430,431,5,109,0,0,431,432,5,101,0,0,432, - 433,5,110,0,0,433,434,5,116,0,0,434,435,5,115,0,0,435,40,1,0,0,0, - 436,437,5,114,0,0,437,438,5,101,0,0,438,439,5,102,0,0,439,42,1,0, - 0,0,440,441,5,102,0,0,441,442,5,114,0,0,442,443,5,97,0,0,443,444, - 5,103,0,0,444,445,5,109,0,0,445,446,5,101,0,0,446,447,5,110,0,0, - 447,448,5,116,0,0,448,44,1,0,0,0,449,450,5,105,0,0,450,451,5,109, - 0,0,451,452,5,112,0,0,452,453,5,111,0,0,453,454,5,114,0,0,454,455, - 5,116,0,0,455,46,1,0,0,0,456,457,5,101,0,0,457,458,5,120,0,0,458, - 459,5,116,0,0,459,460,5,101,0,0,460,461,5,114,0,0,461,462,5,110, - 0,0,462,463,5,97,0,0,463,464,5,108,0,0,464,48,1,0,0,0,465,466,5, - 97,0,0,466,467,5,116,0,0,467,468,5,111,0,0,468,469,5,109,0,0,469, - 50,1,0,0,0,470,471,5,105,0,0,471,472,5,110,0,0,472,473,5,116,0,0, - 473,474,5,101,0,0,474,475,5,114,0,0,475,476,5,102,0,0,476,477,5, - 97,0,0,477,478,5,99,0,0,478,479,5,101,0,0,479,52,1,0,0,0,480,481, - 5,105,0,0,481,482,5,110,0,0,482,483,5,116,0,0,483,484,5,101,0,0, - 484,485,5,114,0,0,485,486,5,102,0,0,486,487,5,97,0,0,487,488,5,99, - 0,0,488,489,5,101,0,0,489,490,5,115,0,0,490,54,1,0,0,0,491,492,5, - 112,0,0,492,493,5,97,0,0,493,494,5,99,0,0,494,495,5,107,0,0,495, - 496,5,97,0,0,496,497,5,103,0,0,497,498,5,101,0,0,498,56,1,0,0,0, - 499,500,5,118,0,0,500,501,5,97,0,0,501,502,5,108,0,0,502,503,5,117, - 0,0,503,504,5,101,0,0,504,58,1,0,0,0,505,506,5,114,0,0,506,507,5, - 101,0,0,507,508,5,108,0,0,508,509,5,97,0,0,509,510,5,116,0,0,510, - 511,5,105,0,0,511,512,5,111,0,0,512,513,5,110,0,0,513,60,1,0,0,0, - 514,515,5,111,0,0,515,516,5,112,0,0,516,517,5,101,0,0,517,518,5, - 114,0,0,518,519,5,97,0,0,519,520,5,116,0,0,520,521,5,105,0,0,521, - 522,5,111,0,0,522,523,5,110,0,0,523,62,1,0,0,0,524,525,5,102,0,0, - 525,526,5,117,0,0,526,527,5,110,0,0,527,528,5,99,0,0,528,529,5,116, - 0,0,529,530,5,105,0,0,530,531,5,111,0,0,531,532,5,110,0,0,532,64, - 1,0,0,0,533,534,5,99,0,0,534,535,5,111,0,0,535,536,5,110,0,0,536, - 537,5,115,0,0,537,538,5,116,0,0,538,539,5,114,0,0,539,540,5,117, - 0,0,540,541,5,99,0,0,541,542,5,116,0,0,542,543,5,111,0,0,543,544, - 5,114,0,0,544,66,1,0,0,0,545,546,5,99,0,0,546,547,5,111,0,0,547, - 548,5,110,0,0,548,549,5,115,0,0,549,550,5,116,0,0,550,551,5,114, - 0,0,551,552,5,117,0,0,552,553,5,99,0,0,553,554,5,116,0,0,554,555, - 5,115,0,0,555,68,1,0,0,0,556,557,5,105,0,0,557,558,5,110,0,0,558, - 559,5,112,0,0,559,560,5,117,0,0,560,561,5,116,0,0,561,70,1,0,0,0, - 562,563,5,99,0,0,563,564,5,111,0,0,564,565,5,110,0,0,565,566,5,102, - 0,0,566,567,5,111,0,0,567,568,5,114,0,0,568,569,5,109,0,0,569,72, - 1,0,0,0,570,571,5,97,0,0,571,572,5,115,0,0,572,74,1,0,0,0,573,574, - 5,98,0,0,574,575,5,105,0,0,575,576,5,110,0,0,576,577,5,100,0,0,577, - 76,1,0,0,0,578,579,5,115,0,0,579,580,5,116,0,0,580,581,5,97,0,0, - 581,582,5,116,0,0,582,583,5,105,0,0,583,584,5,99,0,0,584,78,1,0, - 0,0,585,586,5,116,0,0,586,587,5,111,0,0,587,80,1,0,0,0,588,589,5, - 112,0,0,589,590,5,114,0,0,590,591,5,105,0,0,591,592,5,118,0,0,592, - 593,5,97,0,0,593,594,5,116,0,0,594,595,5,101,0,0,595,82,1,0,0,0, - 596,597,5,115,0,0,597,598,5,104,0,0,598,599,5,97,0,0,599,600,5,114, - 0,0,600,601,5,101,0,0,601,602,5,100,0,0,602,84,1,0,0,0,603,604,5, - 115,0,0,604,605,5,116,0,0,605,606,5,97,0,0,606,607,5,116,0,0,607, - 608,5,101,0,0,608,86,1,0,0,0,609,610,5,101,0,0,610,611,5,100,0,0, - 611,612,5,103,0,0,612,613,5,101,0,0,613,88,1,0,0,0,614,615,5,112, - 0,0,615,616,5,114,0,0,616,617,5,111,0,0,617,618,5,106,0,0,618,619, - 5,101,0,0,619,620,5,99,0,0,620,621,5,116,0,0,621,622,5,105,0,0,622, - 623,5,111,0,0,623,624,5,110,0,0,624,90,1,0,0,0,625,626,5,119,0,0, - 626,627,5,105,0,0,627,628,5,116,0,0,628,629,5,104,0,0,629,92,1,0, - 0,0,630,631,5,117,0,0,631,632,5,115,0,0,632,633,5,105,0,0,633,634, - 5,110,0,0,634,635,5,103,0,0,635,94,1,0,0,0,636,637,5,118,0,0,637, - 638,5,105,0,0,638,639,5,97,0,0,639,96,1,0,0,0,640,641,5,109,0,0, - 641,642,5,97,0,0,642,643,5,116,0,0,643,644,5,101,0,0,644,645,5,114, - 0,0,645,646,5,105,0,0,646,647,5,97,0,0,647,648,5,108,0,0,648,649, - 5,105,0,0,649,650,5,122,0,0,650,651,5,101,0,0,651,98,1,0,0,0,652, - 653,5,105,0,0,653,654,5,102,0,0,654,100,1,0,0,0,655,656,5,97,0,0, - 656,657,5,98,0,0,657,658,5,115,0,0,658,659,5,101,0,0,659,660,5,110, - 0,0,660,661,5,116,0,0,661,102,1,0,0,0,662,663,5,111,0,0,663,664, - 5,110,0,0,664,104,1,0,0,0,665,666,5,112,0,0,666,667,5,111,0,0,667, - 668,5,108,0,0,668,669,5,105,0,0,669,670,5,99,0,0,670,671,5,121,0, - 0,671,106,1,0,0,0,672,673,5,100,0,0,673,674,5,101,0,0,674,675,5, - 102,0,0,675,676,5,97,0,0,676,677,5,117,0,0,677,678,5,108,0,0,678, - 679,5,116,0,0,679,108,1,0,0,0,680,681,5,115,0,0,681,682,5,111,0, - 0,682,683,5,117,0,0,683,684,5,114,0,0,684,685,5,99,0,0,685,686,5, - 101,0,0,686,110,1,0,0,0,687,688,5,114,0,0,688,689,5,101,0,0,689, - 690,5,112,0,0,690,691,5,111,0,0,691,692,5,115,0,0,692,693,5,105, - 0,0,693,694,5,116,0,0,694,695,5,111,0,0,695,696,5,114,0,0,696,697, - 5,121,0,0,697,112,1,0,0,0,698,699,5,99,0,0,699,700,5,111,0,0,700, - 701,5,109,0,0,701,702,5,109,0,0,702,703,5,105,0,0,703,704,5,116, - 0,0,704,114,1,0,0,0,705,706,5,114,0,0,706,707,5,101,0,0,707,708, - 5,118,0,0,708,709,5,105,0,0,709,710,5,115,0,0,710,711,5,105,0,0, - 711,712,5,111,0,0,712,713,5,110,0,0,713,116,1,0,0,0,714,715,5,115, - 0,0,715,716,5,101,0,0,716,717,5,109,0,0,717,718,5,97,0,0,718,719, - 5,110,0,0,719,720,5,116,0,0,720,721,5,105,0,0,721,722,5,99,0,0,722, - 723,5,45,0,0,723,724,5,109,0,0,724,725,5,97,0,0,725,726,5,106,0, - 0,726,727,5,111,0,0,727,728,5,114,0,0,728,118,1,0,0,0,729,730,5, - 111,0,0,730,731,5,110,0,0,731,732,5,45,0,0,732,733,5,100,0,0,733, - 734,5,101,0,0,734,735,5,108,0,0,735,736,5,101,0,0,736,737,5,116, - 0,0,737,738,5,101,0,0,738,120,1,0,0,0,739,740,5,114,0,0,740,741, - 5,101,0,0,741,742,5,116,0,0,742,743,5,97,0,0,743,744,5,105,0,0,744, - 745,5,110,0,0,745,746,5,45,0,0,746,747,5,111,0,0,747,748,5,116,0, - 0,748,749,5,104,0,0,749,750,5,101,0,0,750,751,5,114,0,0,751,122, - 1,0,0,0,752,753,5,107,0,0,753,754,5,101,0,0,754,755,5,121,0,0,755, - 756,5,101,0,0,756,757,5,100,0,0,757,124,1,0,0,0,758,759,5,112,0, - 0,759,760,5,117,0,0,760,761,5,98,0,0,761,762,5,108,0,0,762,763,5, - 105,0,0,763,764,5,99,0,0,764,765,5,45,0,0,765,766,5,116,0,0,766, - 767,5,114,0,0,767,768,5,97,0,0,768,769,5,118,0,0,769,770,5,101,0, - 0,770,771,5,114,0,0,771,772,5,115,0,0,772,773,5,97,0,0,773,774,5, - 108,0,0,774,126,1,0,0,0,775,776,5,105,0,0,776,777,5,100,0,0,777, - 128,1,0,0,0,778,779,5,100,0,0,779,780,5,111,0,0,780,781,5,99,0,0, - 781,130,1,0,0,0,782,783,5,109,0,0,783,784,5,111,0,0,784,785,5,100, - 0,0,785,786,5,101,0,0,786,132,1,0,0,0,787,788,5,101,0,0,788,789, - 5,109,0,0,789,790,5,105,0,0,790,791,5,116,0,0,791,792,5,115,0,0, - 792,134,1,0,0,0,793,794,5,114,0,0,794,795,5,101,0,0,795,796,5,99, - 0,0,796,797,5,101,0,0,797,798,5,105,0,0,798,799,5,118,0,0,799,800, - 5,101,0,0,800,801,5,114,0,0,801,136,1,0,0,0,802,803,5,114,0,0,803, - 804,5,101,0,0,804,805,5,113,0,0,805,806,5,117,0,0,806,807,5,105, - 0,0,807,808,5,114,0,0,808,809,5,101,0,0,809,810,5,115,0,0,810,138, - 1,0,0,0,811,812,5,97,0,0,812,813,5,110,0,0,813,814,5,121,0,0,814, - 140,1,0,0,0,815,816,5,103,0,0,816,817,5,101,0,0,817,818,5,116,0, - 0,818,142,1,0,0,0,819,820,5,115,0,0,820,821,5,101,0,0,821,822,5, - 116,0,0,822,144,1,0,0,0,823,824,5,119,0,0,824,825,5,97,0,0,825,826, - 5,116,0,0,826,827,5,99,0,0,827,828,5,104,0,0,828,146,1,0,0,0,829, - 830,5,115,0,0,830,831,5,116,0,0,831,832,5,97,0,0,832,833,5,114,0, - 0,833,834,5,116,0,0,834,148,1,0,0,0,835,836,5,115,0,0,836,837,5, - 116,0,0,837,838,5,111,0,0,838,839,5,112,0,0,839,150,1,0,0,0,840, - 841,5,114,0,0,841,842,5,101,0,0,842,843,5,97,0,0,843,844,5,100,0, - 0,844,152,1,0,0,0,845,846,5,119,0,0,846,847,5,114,0,0,847,848,5, - 105,0,0,848,849,5,116,0,0,849,850,5,101,0,0,850,154,1,0,0,0,851, - 852,5,114,0,0,852,853,5,101,0,0,853,854,5,115,0,0,854,855,5,111, - 0,0,855,856,5,108,0,0,856,857,5,118,0,0,857,858,5,101,0,0,858,156, - 1,0,0,0,859,860,5,99,0,0,860,861,5,111,0,0,861,862,5,110,0,0,862, - 863,5,110,0,0,863,864,5,101,0,0,864,865,5,99,0,0,865,866,5,116,0, - 0,866,158,1,0,0,0,867,868,5,100,0,0,868,869,5,105,0,0,869,870,5, - 115,0,0,870,871,5,99,0,0,871,872,5,111,0,0,872,873,5,110,0,0,873, - 874,5,110,0,0,874,875,5,101,0,0,875,876,5,99,0,0,876,877,5,116,0, - 0,877,160,1,0,0,0,878,879,5,99,0,0,879,880,5,97,0,0,880,881,5,108, - 0,0,881,882,5,108,0,0,882,162,1,0,0,0,883,884,5,119,0,0,884,885, - 5,97,0,0,885,886,5,116,0,0,886,887,5,99,0,0,887,888,5,104,0,0,888, - 889,5,45,0,0,889,890,5,115,0,0,890,891,5,116,0,0,891,892,5,97,0, - 0,892,893,5,114,0,0,893,894,5,116,0,0,894,164,1,0,0,0,895,896,5, - 119,0,0,896,897,5,97,0,0,897,898,5,116,0,0,898,899,5,99,0,0,899, - 900,5,104,0,0,900,901,5,45,0,0,901,902,5,115,0,0,902,903,5,116,0, - 0,903,904,5,111,0,0,904,905,5,112,0,0,905,166,1,0,0,0,906,907,5, - 115,0,0,907,908,5,117,0,0,908,909,5,98,0,0,909,910,5,115,0,0,910, - 911,5,99,0,0,911,912,5,114,0,0,912,913,5,105,0,0,913,914,5,98,0, - 0,914,915,5,101,0,0,915,168,1,0,0,0,916,917,5,117,0,0,917,918,5, - 110,0,0,918,919,5,115,0,0,919,920,5,117,0,0,920,921,5,98,0,0,921, - 922,5,115,0,0,922,923,5,99,0,0,923,924,5,114,0,0,924,925,5,105,0, - 0,925,926,5,98,0,0,926,927,5,101,0,0,927,170,1,0,0,0,928,929,5,111, - 0,0,929,930,5,112,0,0,930,931,5,116,0,0,931,932,5,105,0,0,932,933, - 5,109,0,0,933,934,5,105,0,0,934,935,5,115,0,0,935,936,5,116,0,0, - 936,937,5,105,0,0,937,938,5,99,0,0,938,939,5,45,0,0,939,940,5,114, - 0,0,940,941,5,101,0,0,941,942,5,103,0,0,942,943,5,105,0,0,943,944, - 5,115,0,0,944,945,5,116,0,0,945,946,5,101,0,0,946,947,5,114,0,0, - 947,172,1,0,0,0,948,949,5,99,0,0,949,950,5,114,0,0,950,951,5,100, - 0,0,951,952,5,116,0,0,952,174,1,0,0,0,953,954,5,111,0,0,954,955, - 5,112,0,0,955,956,5,116,0,0,956,957,5,105,0,0,957,958,5,111,0,0, - 958,959,5,110,0,0,959,960,5,97,0,0,960,961,5,108,0,0,961,962,5,45, - 0,0,962,963,5,111,0,0,963,964,5,110,0,0,964,965,5,101,0,0,965,176, - 1,0,0,0,966,967,5,101,0,0,967,968,5,120,0,0,968,969,5,97,0,0,969, - 970,5,99,0,0,970,971,5,116,0,0,971,972,5,108,0,0,972,973,5,121,0, - 0,973,974,5,45,0,0,974,975,5,111,0,0,975,976,5,110,0,0,976,977,5, - 101,0,0,977,178,1,0,0,0,978,979,5,109,0,0,979,980,5,97,0,0,980,981, - 5,110,0,0,981,982,5,121,0,0,982,983,5,45,0,0,983,984,5,117,0,0,984, - 985,5,110,0,0,985,986,5,105,0,0,986,987,5,113,0,0,987,988,5,117, - 0,0,988,989,5,101,0,0,989,180,1,0,0,0,990,991,5,109,0,0,991,992, - 5,97,0,0,992,993,5,110,0,0,993,994,5,121,0,0,994,182,1,0,0,0,995, - 996,5,111,0,0,996,997,5,114,0,0,997,998,5,100,0,0,998,999,5,101, - 0,0,999,1000,5,114,0,0,1000,1001,5,101,0,0,1001,1002,5,100,0,0,1002, - 184,1,0,0,0,1003,1004,5,117,0,0,1004,1005,5,110,0,0,1005,1006,5, - 105,0,0,1006,1007,5,116,0,0,1007,186,1,0,0,0,1008,1009,5,119,0,0, - 1009,1010,5,97,0,0,1010,1011,5,116,0,0,1011,1012,5,99,0,0,1012,1013, - 5,104,0,0,1013,1014,5,45,0,0,1014,1015,5,104,0,0,1015,1016,5,97, - 0,0,1016,1017,5,110,0,0,1017,1018,5,100,0,0,1018,1019,5,108,0,0, - 1019,1020,5,101,0,0,1020,188,1,0,0,0,1021,1022,5,109,0,0,1022,1023, - 5,101,0,0,1023,1024,5,115,0,0,1024,1025,5,115,0,0,1025,1026,5,97, - 0,0,1026,1027,5,103,0,0,1027,1028,5,101,0,0,1028,190,1,0,0,0,1029, - 1030,5,97,0,0,1030,1031,5,116,0,0,1031,1032,5,111,0,0,1032,1033, - 5,109,0,0,1033,1034,5,45,0,0,1034,1035,5,114,0,0,1035,1036,5,101, - 0,0,1036,1037,5,102,0,0,1037,192,1,0,0,0,1038,1039,5,105,0,0,1039, - 1040,5,110,0,0,1040,1041,5,116,0,0,1041,1042,5,101,0,0,1042,1043, - 5,114,0,0,1043,1044,5,102,0,0,1044,1045,5,97,0,0,1045,1046,5,99, - 0,0,1046,1047,5,101,0,0,1047,1048,5,45,0,0,1048,1049,5,114,0,0,1049, - 1050,5,101,0,0,1050,1051,5,102,0,0,1051,194,1,0,0,0,1052,1053,5, - 111,0,0,1053,1054,5,112,0,0,1054,1055,5,116,0,0,1055,1056,5,105, - 0,0,1056,1057,5,111,0,0,1057,1058,5,110,0,0,1058,1059,5,97,0,0,1059, - 1060,5,108,0,0,1060,196,1,0,0,0,1061,1062,5,108,0,0,1062,1063,5, - 105,0,0,1063,1064,5,115,0,0,1064,1065,5,116,0,0,1065,198,1,0,0,0, - 1066,1067,5,114,0,0,1067,1068,5,101,0,0,1068,1069,5,99,0,0,1069, - 1070,5,111,0,0,1070,1071,5,114,0,0,1071,1072,5,100,0,0,1072,200, - 1,0,0,0,1073,1074,5,98,0,0,1074,1075,5,111,0,0,1075,1076,5,111,0, - 0,1076,1077,5,108,0,0,1077,202,1,0,0,0,1078,1079,5,98,0,0,1079,1080, - 5,121,0,0,1080,1081,5,116,0,0,1081,1082,5,101,0,0,1082,1083,5,115, - 0,0,1083,204,1,0,0,0,1084,1085,5,100,0,0,1085,1086,5,111,0,0,1086, - 1087,5,117,0,0,1087,1088,5,98,0,0,1088,1089,5,108,0,0,1089,1090, - 5,101,0,0,1090,206,1,0,0,0,1091,1092,5,105,0,0,1092,1093,5,110,0, - 0,1093,1094,5,116,0,0,1094,1095,5,51,0,0,1095,1096,5,50,0,0,1096, - 208,1,0,0,0,1097,1098,5,105,0,0,1098,1099,5,110,0,0,1099,1100,5, - 116,0,0,1100,1101,5,54,0,0,1101,1102,5,52,0,0,1102,210,1,0,0,0,1103, - 1104,5,115,0,0,1104,1105,5,116,0,0,1105,1106,5,114,0,0,1106,1107, - 5,105,0,0,1107,1108,5,110,0,0,1108,1109,5,103,0,0,1109,212,1,0,0, - 0,1110,1111,5,117,0,0,1111,1112,5,105,0,0,1112,1113,5,110,0,0,1113, - 1114,5,116,0,0,1114,1115,5,51,0,0,1115,1116,5,50,0,0,1116,214,1, - 0,0,0,1117,1118,5,117,0,0,1118,1119,5,105,0,0,1119,1120,5,110,0, - 0,1120,1121,5,116,0,0,1121,1122,5,54,0,0,1122,1123,5,52,0,0,1123, - 216,1,0,0,0,1124,1125,5,116,0,0,1125,1126,5,114,0,0,1126,1127,5, - 117,0,0,1127,1128,5,101,0,0,1128,218,1,0,0,0,1129,1130,5,102,0,0, - 1130,1131,5,97,0,0,1131,1132,5,108,0,0,1132,1133,5,115,0,0,1133, - 1134,5,101,0,0,1134,220,1,0,0,0,1135,1136,5,110,0,0,1136,1137,5, - 117,0,0,1137,1138,5,108,0,0,1138,1139,5,108,0,0,1139,222,1,0,0,0, - 1140,1141,5,45,0,0,1141,1142,5,62,0,0,1142,224,1,0,0,0,1143,1144, - 5,58,0,0,1144,226,1,0,0,0,1145,1146,5,59,0,0,1146,228,1,0,0,0,1147, - 1148,5,44,0,0,1148,230,1,0,0,0,1149,1150,5,46,0,0,1150,232,1,0,0, - 0,1151,1152,5,123,0,0,1152,234,1,0,0,0,1153,1154,5,125,0,0,1154, - 236,1,0,0,0,1155,1156,5,91,0,0,1156,238,1,0,0,0,1157,1158,5,93,0, - 0,1158,240,1,0,0,0,1159,1160,5,40,0,0,1160,242,1,0,0,0,1161,1162, - 5,41,0,0,1162,244,1,0,0,0,1163,1164,5,60,0,0,1164,246,1,0,0,0,1165, - 1166,5,62,0,0,1166,248,1,0,0,0,1167,1168,5,38,0,0,1168,250,1,0,0, - 0,1169,1170,5,61,0,0,1170,252,1,0,0,0,1171,1173,5,45,0,0,1172,1171, - 1,0,0,0,1172,1173,1,0,0,0,1173,1175,1,0,0,0,1174,1176,7,0,0,0,1175, - 1174,1,0,0,0,1176,1177,1,0,0,0,1177,1175,1,0,0,0,1177,1178,1,0,0, - 0,1178,254,1,0,0,0,1179,1181,5,45,0,0,1180,1179,1,0,0,0,1180,1181, - 1,0,0,0,1181,1190,1,0,0,0,1182,1191,5,48,0,0,1183,1187,7,1,0,0,1184, - 1186,7,0,0,0,1185,1184,1,0,0,0,1186,1189,1,0,0,0,1187,1185,1,0,0, - 0,1187,1188,1,0,0,0,1188,1191,1,0,0,0,1189,1187,1,0,0,0,1190,1182, - 1,0,0,0,1190,1183,1,0,0,0,1191,1198,1,0,0,0,1192,1194,5,46,0,0,1193, - 1195,7,0,0,0,1194,1193,1,0,0,0,1195,1196,1,0,0,0,1196,1194,1,0,0, - 0,1196,1197,1,0,0,0,1197,1199,1,0,0,0,1198,1192,1,0,0,0,1198,1199, - 1,0,0,0,1199,1209,1,0,0,0,1200,1202,7,2,0,0,1201,1203,7,3,0,0,1202, - 1201,1,0,0,0,1202,1203,1,0,0,0,1203,1205,1,0,0,0,1204,1206,7,0,0, - 0,1205,1204,1,0,0,0,1206,1207,1,0,0,0,1207,1205,1,0,0,0,1207,1208, - 1,0,0,0,1208,1210,1,0,0,0,1209,1200,1,0,0,0,1209,1210,1,0,0,0,1210, - 256,1,0,0,0,1211,1215,7,4,0,0,1212,1214,7,5,0,0,1213,1212,1,0,0, - 0,1214,1217,1,0,0,0,1215,1213,1,0,0,0,1215,1216,1,0,0,0,1216,258, - 1,0,0,0,1217,1215,1,0,0,0,1218,1223,5,34,0,0,1219,1222,3,261,130, - 0,1220,1222,8,6,0,0,1221,1219,1,0,0,0,1221,1220,1,0,0,0,1222,1225, - 1,0,0,0,1223,1221,1,0,0,0,1223,1224,1,0,0,0,1224,1226,1,0,0,0,1225, - 1223,1,0,0,0,1226,1227,5,34,0,0,1227,260,1,0,0,0,1228,1236,5,92, - 0,0,1229,1237,7,7,0,0,1230,1231,5,117,0,0,1231,1232,3,263,131,0, - 1232,1233,3,263,131,0,1233,1234,3,263,131,0,1234,1235,3,263,131, - 0,1235,1237,1,0,0,0,1236,1229,1,0,0,0,1236,1230,1,0,0,0,1237,262, - 1,0,0,0,1238,1239,7,8,0,0,1239,264,1,0,0,0,1240,1241,5,47,0,0,1241, - 1242,5,47,0,0,1242,1246,1,0,0,0,1243,1245,8,9,0,0,1244,1243,1,0, - 0,0,1245,1248,1,0,0,0,1246,1244,1,0,0,0,1246,1247,1,0,0,0,1247,1249, - 1,0,0,0,1248,1246,1,0,0,0,1249,1250,6,132,0,0,1250,266,1,0,0,0,1251, - 1252,5,47,0,0,1252,1253,5,42,0,0,1253,1257,1,0,0,0,1254,1256,9,0, - 0,0,1255,1254,1,0,0,0,1256,1259,1,0,0,0,1257,1258,1,0,0,0,1257,1255, - 1,0,0,0,1258,1260,1,0,0,0,1259,1257,1,0,0,0,1260,1261,5,42,0,0,1261, - 1262,5,47,0,0,1262,1263,1,0,0,0,1263,1264,6,133,0,0,1264,268,1,0, - 0,0,1265,1267,7,10,0,0,1266,1265,1,0,0,0,1267,1268,1,0,0,0,1268, - 1266,1,0,0,0,1268,1269,1,0,0,0,1269,1270,1,0,0,0,1270,1271,6,134, - 0,0,1271,270,1,0,0,0,18,0,1172,1177,1180,1187,1190,1196,1198,1202, - 1207,1209,1215,1221,1223,1236,1246,1257,1268,1,0,1,0 + 129,259,130,261,131,263,132,265,133,267,134,269,135,271,136,273, + 137,275,138,277,139,279,140,281,141,283,142,285,143,287,144,289, + 145,291,146,293,147,295,148,297,149,299,150,301,151,303,152,305, + 153,307,154,309,155,311,156,313,0,315,0,317,157,319,158,321,159, + 1,0,11,1,0,48,57,1,0,49,57,2,0,69,69,101,101,2,0,43,43,45,45,3,0, + 65,90,95,95,97,122,4,0,48,57,65,90,95,95,97,122,4,0,10,10,13,13, + 34,34,92,92,8,0,34,34,47,47,92,92,98,98,102,102,110,110,114,114, + 116,116,3,0,48,57,65,70,97,102,2,0,10,10,13,13,3,0,9,10,13,13,32, + 32,1546,0,1,1,0,0,0,0,3,1,0,0,0,0,5,1,0,0,0,0,7,1,0,0,0,0,9,1,0, + 0,0,0,11,1,0,0,0,0,13,1,0,0,0,0,15,1,0,0,0,0,17,1,0,0,0,0,19,1,0, + 0,0,0,21,1,0,0,0,0,23,1,0,0,0,0,25,1,0,0,0,0,27,1,0,0,0,0,29,1,0, + 0,0,0,31,1,0,0,0,0,33,1,0,0,0,0,35,1,0,0,0,0,37,1,0,0,0,0,39,1,0, + 0,0,0,41,1,0,0,0,0,43,1,0,0,0,0,45,1,0,0,0,0,47,1,0,0,0,0,49,1,0, + 0,0,0,51,1,0,0,0,0,53,1,0,0,0,0,55,1,0,0,0,0,57,1,0,0,0,0,59,1,0, + 0,0,0,61,1,0,0,0,0,63,1,0,0,0,0,65,1,0,0,0,0,67,1,0,0,0,0,69,1,0, + 0,0,0,71,1,0,0,0,0,73,1,0,0,0,0,75,1,0,0,0,0,77,1,0,0,0,0,79,1,0, + 0,0,0,81,1,0,0,0,0,83,1,0,0,0,0,85,1,0,0,0,0,87,1,0,0,0,0,89,1,0, + 0,0,0,91,1,0,0,0,0,93,1,0,0,0,0,95,1,0,0,0,0,97,1,0,0,0,0,99,1,0, + 0,0,0,101,1,0,0,0,0,103,1,0,0,0,0,105,1,0,0,0,0,107,1,0,0,0,0,109, + 1,0,0,0,0,111,1,0,0,0,0,113,1,0,0,0,0,115,1,0,0,0,0,117,1,0,0,0, + 0,119,1,0,0,0,0,121,1,0,0,0,0,123,1,0,0,0,0,125,1,0,0,0,0,127,1, + 0,0,0,0,129,1,0,0,0,0,131,1,0,0,0,0,133,1,0,0,0,0,135,1,0,0,0,0, + 137,1,0,0,0,0,139,1,0,0,0,0,141,1,0,0,0,0,143,1,0,0,0,0,145,1,0, + 0,0,0,147,1,0,0,0,0,149,1,0,0,0,0,151,1,0,0,0,0,153,1,0,0,0,0,155, + 1,0,0,0,0,157,1,0,0,0,0,159,1,0,0,0,0,161,1,0,0,0,0,163,1,0,0,0, + 0,165,1,0,0,0,0,167,1,0,0,0,0,169,1,0,0,0,0,171,1,0,0,0,0,173,1, + 0,0,0,0,175,1,0,0,0,0,177,1,0,0,0,0,179,1,0,0,0,0,181,1,0,0,0,0, + 183,1,0,0,0,0,185,1,0,0,0,0,187,1,0,0,0,0,189,1,0,0,0,0,191,1,0, + 0,0,0,193,1,0,0,0,0,195,1,0,0,0,0,197,1,0,0,0,0,199,1,0,0,0,0,201, + 1,0,0,0,0,203,1,0,0,0,0,205,1,0,0,0,0,207,1,0,0,0,0,209,1,0,0,0, + 0,211,1,0,0,0,0,213,1,0,0,0,0,215,1,0,0,0,0,217,1,0,0,0,0,219,1, + 0,0,0,0,221,1,0,0,0,0,223,1,0,0,0,0,225,1,0,0,0,0,227,1,0,0,0,0, + 229,1,0,0,0,0,231,1,0,0,0,0,233,1,0,0,0,0,235,1,0,0,0,0,237,1,0, + 0,0,0,239,1,0,0,0,0,241,1,0,0,0,0,243,1,0,0,0,0,245,1,0,0,0,0,247, + 1,0,0,0,0,249,1,0,0,0,0,251,1,0,0,0,0,253,1,0,0,0,0,255,1,0,0,0, + 0,257,1,0,0,0,0,259,1,0,0,0,0,261,1,0,0,0,0,263,1,0,0,0,0,265,1, + 0,0,0,0,267,1,0,0,0,0,269,1,0,0,0,0,271,1,0,0,0,0,273,1,0,0,0,0, + 275,1,0,0,0,0,277,1,0,0,0,0,279,1,0,0,0,0,281,1,0,0,0,0,283,1,0, + 0,0,0,285,1,0,0,0,0,287,1,0,0,0,0,289,1,0,0,0,0,291,1,0,0,0,0,293, + 1,0,0,0,0,295,1,0,0,0,0,297,1,0,0,0,0,299,1,0,0,0,0,301,1,0,0,0, + 0,303,1,0,0,0,0,305,1,0,0,0,0,307,1,0,0,0,0,309,1,0,0,0,0,311,1, + 0,0,0,0,317,1,0,0,0,0,319,1,0,0,0,0,321,1,0,0,0,1,323,1,0,0,0,3, + 328,1,0,0,0,5,335,1,0,0,0,7,344,1,0,0,0,9,357,1,0,0,0,11,365,1,0, + 0,0,13,369,1,0,0,0,15,378,1,0,0,0,17,386,1,0,0,0,19,395,1,0,0,0, + 21,406,1,0,0,0,23,416,1,0,0,0,25,426,1,0,0,0,27,435,1,0,0,0,29,447, + 1,0,0,0,31,454,1,0,0,0,33,462,1,0,0,0,35,471,1,0,0,0,37,479,1,0, + 0,0,39,485,1,0,0,0,41,490,1,0,0,0,43,503,1,0,0,0,45,513,1,0,0,0, + 47,525,1,0,0,0,49,531,1,0,0,0,51,542,1,0,0,0,53,553,1,0,0,0,55,558, + 1,0,0,0,57,567,1,0,0,0,59,577,1,0,0,0,61,582,1,0,0,0,63,586,1,0, + 0,0,65,592,1,0,0,0,67,597,1,0,0,0,69,607,1,0,0,0,71,611,1,0,0,0, + 73,624,1,0,0,0,75,629,1,0,0,0,77,636,1,0,0,0,79,645,1,0,0,0,81,656, + 1,0,0,0,83,660,1,0,0,0,85,669,1,0,0,0,87,676,1,0,0,0,89,685,1,0, + 0,0,91,692,1,0,0,0,93,697,1,0,0,0,95,707,1,0,0,0,97,718,1,0,0,0, + 99,726,1,0,0,0,101,732,1,0,0,0,103,738,1,0,0,0,105,747,1,0,0,0,107, + 757,1,0,0,0,109,766,1,0,0,0,111,778,1,0,0,0,113,789,1,0,0,0,115, + 795,1,0,0,0,117,803,1,0,0,0,119,806,1,0,0,0,121,811,1,0,0,0,123, + 818,1,0,0,0,125,821,1,0,0,0,127,829,1,0,0,0,129,836,1,0,0,0,131, + 842,1,0,0,0,133,847,1,0,0,0,135,858,1,0,0,0,137,863,1,0,0,0,139, + 869,1,0,0,0,141,876,1,0,0,0,143,880,1,0,0,0,145,892,1,0,0,0,147, + 895,1,0,0,0,149,902,1,0,0,0,151,905,1,0,0,0,153,912,1,0,0,0,155, + 920,1,0,0,0,157,927,1,0,0,0,159,938,1,0,0,0,161,945,1,0,0,0,163, + 954,1,0,0,0,165,969,1,0,0,0,167,979,1,0,0,0,169,989,1,0,0,0,171, + 997,1,0,0,0,173,1010,1,0,0,0,175,1023,1,0,0,0,177,1029,1,0,0,0,179, + 1046,1,0,0,0,181,1049,1,0,0,0,183,1053,1,0,0,0,185,1058,1,0,0,0, + 187,1064,1,0,0,0,189,1073,1,0,0,0,191,1082,1,0,0,0,193,1086,1,0, + 0,0,195,1090,1,0,0,0,197,1094,1,0,0,0,199,1100,1,0,0,0,201,1106, + 1,0,0,0,203,1111,1,0,0,0,205,1116,1,0,0,0,207,1122,1,0,0,0,209,1130, + 1,0,0,0,211,1138,1,0,0,0,213,1149,1,0,0,0,215,1154,1,0,0,0,217,1166, + 1,0,0,0,219,1177,1,0,0,0,221,1187,1,0,0,0,223,1199,1,0,0,0,225,1208, + 1,0,0,0,227,1213,1,0,0,0,229,1226,1,0,0,0,231,1238,1,0,0,0,233,1250, + 1,0,0,0,235,1255,1,0,0,0,237,1263,1,0,0,0,239,1268,1,0,0,0,241,1281, + 1,0,0,0,243,1289,1,0,0,0,245,1298,1,0,0,0,247,1312,1,0,0,0,249,1321, + 1,0,0,0,251,1326,1,0,0,0,253,1333,1,0,0,0,255,1338,1,0,0,0,257,1344, + 1,0,0,0,259,1351,1,0,0,0,261,1357,1,0,0,0,263,1363,1,0,0,0,265,1370, + 1,0,0,0,267,1377,1,0,0,0,269,1384,1,0,0,0,271,1389,1,0,0,0,273,1395, + 1,0,0,0,275,1400,1,0,0,0,277,1403,1,0,0,0,279,1405,1,0,0,0,281,1407, + 1,0,0,0,283,1409,1,0,0,0,285,1411,1,0,0,0,287,1413,1,0,0,0,289,1415, + 1,0,0,0,291,1417,1,0,0,0,293,1419,1,0,0,0,295,1421,1,0,0,0,297,1423, + 1,0,0,0,299,1425,1,0,0,0,301,1427,1,0,0,0,303,1429,1,0,0,0,305,1432, + 1,0,0,0,307,1440,1,0,0,0,309,1471,1,0,0,0,311,1478,1,0,0,0,313,1488, + 1,0,0,0,315,1498,1,0,0,0,317,1500,1,0,0,0,319,1511,1,0,0,0,321,1526, + 1,0,0,0,323,324,5,101,0,0,324,325,5,100,0,0,325,326,5,105,0,0,326, + 327,5,116,0,0,327,2,1,0,0,0,328,329,5,109,0,0,329,330,5,111,0,0, + 330,331,5,100,0,0,331,332,5,117,0,0,332,333,5,108,0,0,333,334,5, + 101,0,0,334,4,1,0,0,0,335,336,5,99,0,0,336,337,5,111,0,0,337,338, + 5,110,0,0,338,339,5,116,0,0,339,340,5,114,0,0,340,341,5,97,0,0,341, + 342,5,99,0,0,342,343,5,116,0,0,343,6,1,0,0,0,344,345,5,112,0,0,345, + 346,5,114,0,0,346,347,5,101,0,0,347,348,5,115,0,0,348,349,5,101, + 0,0,349,350,5,110,0,0,350,351,5,116,0,0,351,352,5,97,0,0,352,353, + 5,116,0,0,353,354,5,105,0,0,354,355,5,111,0,0,355,356,5,110,0,0, + 356,8,1,0,0,0,357,358,5,115,0,0,358,359,5,117,0,0,359,360,5,98,0, + 0,360,361,5,106,0,0,361,362,5,101,0,0,362,363,5,99,0,0,363,364,5, + 116,0,0,364,10,1,0,0,0,365,366,5,102,0,0,366,367,5,111,0,0,367,368, + 5,114,0,0,368,12,1,0,0,0,369,370,5,99,0,0,370,371,5,114,0,0,371, + 372,5,101,0,0,372,373,5,97,0,0,373,374,5,116,0,0,374,375,5,105,0, + 0,375,376,5,111,0,0,376,377,5,110,0,0,377,14,1,0,0,0,378,379,5,118, + 0,0,379,380,5,97,0,0,380,381,5,114,0,0,381,382,5,105,0,0,382,383, + 5,97,0,0,383,384,5,110,0,0,384,385,5,116,0,0,385,16,1,0,0,0,386, + 387,5,112,0,0,387,388,5,114,0,0,388,389,5,101,0,0,389,390,5,112, + 0,0,390,391,5,97,0,0,391,392,5,114,0,0,392,393,5,101,0,0,393,394, + 5,115,0,0,394,18,1,0,0,0,395,396,5,105,0,0,396,397,5,110,0,0,397, + 398,5,105,0,0,398,399,5,116,0,0,399,400,5,105,0,0,400,401,5,97,0, + 0,401,402,5,108,0,0,402,403,5,105,0,0,403,404,5,122,0,0,404,405, + 5,101,0,0,405,20,1,0,0,0,406,407,5,119,0,0,407,408,5,111,0,0,408, + 409,5,114,0,0,409,410,5,107,0,0,410,411,5,115,0,0,411,412,5,112, + 0,0,412,413,5,97,0,0,413,414,5,99,0,0,414,415,5,101,0,0,415,22,1, + 0,0,0,416,417,5,101,0,0,417,418,5,120,0,0,418,419,5,101,0,0,419, + 420,5,99,0,0,420,421,5,117,0,0,421,422,5,116,0,0,422,423,5,105,0, + 0,423,424,5,111,0,0,424,425,5,110,0,0,425,24,1,0,0,0,426,427,5,115, + 0,0,427,428,5,110,0,0,428,429,5,97,0,0,429,430,5,112,0,0,430,431, + 5,115,0,0,431,432,5,104,0,0,432,433,5,111,0,0,433,434,5,116,0,0, + 434,26,1,0,0,0,435,436,5,116,0,0,436,437,5,114,0,0,437,438,5,97, + 0,0,438,439,5,110,0,0,439,440,5,115,0,0,440,441,5,97,0,0,441,442, + 5,99,0,0,442,443,5,116,0,0,443,444,5,105,0,0,444,445,5,111,0,0,445, + 446,5,110,0,0,446,28,1,0,0,0,447,448,5,114,0,0,448,449,5,101,0,0, + 449,450,5,109,0,0,450,451,5,111,0,0,451,452,5,116,0,0,452,453,5, + 101,0,0,453,30,1,0,0,0,454,455,5,116,0,0,455,456,5,97,0,0,456,457, + 5,114,0,0,457,458,5,103,0,0,458,459,5,101,0,0,459,460,5,116,0,0, + 460,461,5,115,0,0,461,32,1,0,0,0,462,463,5,112,0,0,463,464,5,111, + 0,0,464,465,5,114,0,0,465,466,5,116,0,0,466,467,5,97,0,0,467,468, + 5,98,0,0,468,469,5,108,0,0,469,470,5,101,0,0,470,34,1,0,0,0,471, + 472,5,114,0,0,472,473,5,101,0,0,473,474,5,112,0,0,474,475,5,108, + 0,0,475,476,5,105,0,0,476,477,5,99,0,0,477,478,5,97,0,0,478,36,1, + 0,0,0,479,480,5,108,0,0,480,481,5,111,0,0,481,482,5,99,0,0,482,483, + 5,97,0,0,483,484,5,108,0,0,484,38,1,0,0,0,485,486,5,102,0,0,486, + 487,5,114,0,0,487,488,5,111,0,0,488,489,5,109,0,0,489,40,1,0,0,0, + 490,491,5,114,0,0,491,492,5,101,0,0,492,493,5,115,0,0,493,494,5, + 117,0,0,494,495,5,108,0,0,495,496,5,116,0,0,496,497,5,45,0,0,497, + 498,5,98,0,0,498,499,5,121,0,0,499,500,5,116,0,0,500,501,5,101,0, + 0,501,502,5,115,0,0,502,42,1,0,0,0,503,504,5,114,0,0,504,505,5,112, + 0,0,505,506,5,99,0,0,506,507,5,45,0,0,507,508,5,99,0,0,508,509,5, + 97,0,0,509,510,5,108,0,0,510,511,5,108,0,0,511,512,5,115,0,0,512, + 44,1,0,0,0,513,514,5,100,0,0,514,515,5,101,0,0,515,516,5,97,0,0, + 516,517,5,100,0,0,517,518,5,108,0,0,518,519,5,105,0,0,519,520,5, + 110,0,0,520,521,5,101,0,0,521,522,5,45,0,0,522,523,5,109,0,0,523, + 524,5,115,0,0,524,46,1,0,0,0,525,526,5,113,0,0,526,527,5,117,0,0, + 527,528,5,101,0,0,528,529,5,114,0,0,529,530,5,121,0,0,530,48,1,0, + 0,0,531,532,5,114,0,0,532,533,5,101,0,0,533,534,5,102,0,0,534,535, + 5,108,0,0,535,536,5,101,0,0,536,537,5,99,0,0,537,538,5,116,0,0,538, + 539,5,105,0,0,539,540,5,111,0,0,540,541,5,110,0,0,541,50,1,0,0,0, + 542,543,5,115,0,0,543,544,5,112,0,0,544,545,5,101,0,0,545,546,5, + 99,0,0,546,547,5,105,0,0,547,548,5,97,0,0,548,549,5,108,0,0,549, + 550,5,105,0,0,550,551,5,122,0,0,551,552,5,101,0,0,552,52,1,0,0,0, + 553,554,5,114,0,0,554,555,5,111,0,0,555,556,5,111,0,0,556,557,5, + 116,0,0,557,54,1,0,0,0,558,559,5,100,0,0,559,560,5,111,0,0,560,561, + 5,99,0,0,561,562,5,117,0,0,562,563,5,109,0,0,563,564,5,101,0,0,564, + 565,5,110,0,0,565,566,5,116,0,0,566,56,1,0,0,0,567,568,5,102,0,0, + 568,569,5,114,0,0,569,570,5,97,0,0,570,571,5,103,0,0,571,572,5,109, + 0,0,572,573,5,101,0,0,573,574,5,110,0,0,574,575,5,116,0,0,575,576, + 5,115,0,0,576,58,1,0,0,0,577,578,5,118,0,0,578,579,5,105,0,0,579, + 580,5,101,0,0,580,581,5,119,0,0,581,60,1,0,0,0,582,583,5,109,0,0, + 583,584,5,97,0,0,584,585,5,120,0,0,585,62,1,0,0,0,586,587,5,97,0, + 0,587,588,5,108,0,0,588,589,5,108,0,0,589,590,5,111,0,0,590,591, + 5,119,0,0,591,64,1,0,0,0,592,593,5,112,0,0,593,594,5,111,0,0,594, + 595,5,108,0,0,595,596,5,108,0,0,596,66,1,0,0,0,597,598,5,113,0,0, + 598,599,5,117,0,0,599,600,5,101,0,0,600,601,5,114,0,0,601,602,5, + 121,0,0,602,603,5,97,0,0,603,604,5,98,0,0,604,605,5,108,0,0,605, + 606,5,101,0,0,606,68,1,0,0,0,607,608,5,114,0,0,608,609,5,112,0,0, + 609,610,5,99,0,0,610,70,1,0,0,0,611,612,5,113,0,0,612,613,5,117, + 0,0,613,614,5,101,0,0,614,615,5,114,0,0,615,616,5,121,0,0,616,617, + 5,45,0,0,617,618,5,114,0,0,618,619,5,101,0,0,619,620,5,97,0,0,620, + 621,5,115,0,0,621,622,5,111,0,0,622,623,5,110,0,0,623,72,1,0,0,0, + 624,625,5,116,0,0,625,626,5,121,0,0,626,627,5,112,0,0,627,628,5, + 101,0,0,628,74,1,0,0,0,629,630,5,111,0,0,630,631,5,98,0,0,631,632, + 5,106,0,0,632,633,5,101,0,0,633,634,5,99,0,0,634,635,5,116,0,0,635, + 76,1,0,0,0,636,637,5,115,0,0,637,638,5,116,0,0,638,639,5,111,0,0, + 639,640,5,114,0,0,640,641,5,97,0,0,641,642,5,98,0,0,642,643,5,108, + 0,0,643,644,5,101,0,0,644,78,1,0,0,0,645,646,5,105,0,0,646,647,5, + 109,0,0,647,648,5,112,0,0,648,649,5,108,0,0,649,650,5,101,0,0,650, + 651,5,109,0,0,651,652,5,101,0,0,652,653,5,110,0,0,653,654,5,116, + 0,0,654,655,5,115,0,0,655,80,1,0,0,0,656,657,5,114,0,0,657,658,5, + 101,0,0,658,659,5,102,0,0,659,82,1,0,0,0,660,661,5,102,0,0,661,662, + 5,114,0,0,662,663,5,97,0,0,663,664,5,103,0,0,664,665,5,109,0,0,665, + 666,5,101,0,0,666,667,5,110,0,0,667,668,5,116,0,0,668,84,1,0,0,0, + 669,670,5,105,0,0,670,671,5,109,0,0,671,672,5,112,0,0,672,673,5, + 111,0,0,673,674,5,114,0,0,674,675,5,116,0,0,675,86,1,0,0,0,676,677, + 5,101,0,0,677,678,5,120,0,0,678,679,5,116,0,0,679,680,5,101,0,0, + 680,681,5,114,0,0,681,682,5,110,0,0,682,683,5,97,0,0,683,684,5,108, + 0,0,684,88,1,0,0,0,685,686,5,115,0,0,686,687,5,121,0,0,687,688,5, + 109,0,0,688,689,5,98,0,0,689,690,5,111,0,0,690,691,5,108,0,0,691, + 90,1,0,0,0,692,693,5,97,0,0,693,694,5,116,0,0,694,695,5,111,0,0, + 695,696,5,109,0,0,696,92,1,0,0,0,697,698,5,105,0,0,698,699,5,110, + 0,0,699,700,5,116,0,0,700,701,5,101,0,0,701,702,5,114,0,0,702,703, + 5,102,0,0,703,704,5,97,0,0,704,705,5,99,0,0,705,706,5,101,0,0,706, + 94,1,0,0,0,707,708,5,105,0,0,708,709,5,110,0,0,709,710,5,116,0,0, + 710,711,5,101,0,0,711,712,5,114,0,0,712,713,5,102,0,0,713,714,5, + 97,0,0,714,715,5,99,0,0,715,716,5,101,0,0,716,717,5,115,0,0,717, + 96,1,0,0,0,718,719,5,112,0,0,719,720,5,97,0,0,720,721,5,99,0,0,721, + 722,5,107,0,0,722,723,5,97,0,0,723,724,5,103,0,0,724,725,5,101,0, + 0,725,98,1,0,0,0,726,727,5,118,0,0,727,728,5,97,0,0,728,729,5,108, + 0,0,729,730,5,117,0,0,730,731,5,101,0,0,731,100,1,0,0,0,732,733, + 5,102,0,0,733,734,5,105,0,0,734,735,5,101,0,0,735,736,5,108,0,0, + 736,737,5,100,0,0,737,102,1,0,0,0,738,739,5,114,0,0,739,740,5,101, + 0,0,740,741,5,108,0,0,741,742,5,97,0,0,742,743,5,116,0,0,743,744, + 5,105,0,0,744,745,5,111,0,0,745,746,5,110,0,0,746,104,1,0,0,0,747, + 748,5,111,0,0,748,749,5,112,0,0,749,750,5,101,0,0,750,751,5,114, + 0,0,751,752,5,97,0,0,752,753,5,116,0,0,753,754,5,105,0,0,754,755, + 5,111,0,0,755,756,5,110,0,0,756,106,1,0,0,0,757,758,5,102,0,0,758, + 759,5,117,0,0,759,760,5,110,0,0,760,761,5,99,0,0,761,762,5,116,0, + 0,762,763,5,105,0,0,763,764,5,111,0,0,764,765,5,110,0,0,765,108, + 1,0,0,0,766,767,5,99,0,0,767,768,5,111,0,0,768,769,5,110,0,0,769, + 770,5,115,0,0,770,771,5,116,0,0,771,772,5,114,0,0,772,773,5,117, + 0,0,773,774,5,99,0,0,774,775,5,116,0,0,775,776,5,111,0,0,776,777, + 5,114,0,0,777,110,1,0,0,0,778,779,5,99,0,0,779,780,5,111,0,0,780, + 781,5,110,0,0,781,782,5,115,0,0,782,783,5,116,0,0,783,784,5,114, + 0,0,784,785,5,117,0,0,785,786,5,99,0,0,786,787,5,116,0,0,787,788, + 5,115,0,0,788,112,1,0,0,0,789,790,5,105,0,0,790,791,5,110,0,0,791, + 792,5,112,0,0,792,793,5,117,0,0,793,794,5,116,0,0,794,114,1,0,0, + 0,795,796,5,99,0,0,796,797,5,111,0,0,797,798,5,110,0,0,798,799,5, + 102,0,0,799,800,5,111,0,0,800,801,5,114,0,0,801,802,5,109,0,0,802, + 116,1,0,0,0,803,804,5,97,0,0,804,805,5,115,0,0,805,118,1,0,0,0,806, + 807,5,98,0,0,807,808,5,105,0,0,808,809,5,110,0,0,809,810,5,100,0, + 0,810,120,1,0,0,0,811,812,5,115,0,0,812,813,5,116,0,0,813,814,5, + 97,0,0,814,815,5,116,0,0,815,816,5,105,0,0,816,817,5,99,0,0,817, + 122,1,0,0,0,818,819,5,116,0,0,819,820,5,111,0,0,820,124,1,0,0,0, + 821,822,5,112,0,0,822,823,5,114,0,0,823,824,5,105,0,0,824,825,5, + 118,0,0,825,826,5,97,0,0,826,827,5,116,0,0,827,828,5,101,0,0,828, + 126,1,0,0,0,829,830,5,115,0,0,830,831,5,104,0,0,831,832,5,97,0,0, + 832,833,5,114,0,0,833,834,5,101,0,0,834,835,5,100,0,0,835,128,1, + 0,0,0,836,837,5,115,0,0,837,838,5,116,0,0,838,839,5,97,0,0,839,840, + 5,116,0,0,840,841,5,101,0,0,841,130,1,0,0,0,842,843,5,101,0,0,843, + 844,5,100,0,0,844,845,5,103,0,0,845,846,5,101,0,0,846,132,1,0,0, + 0,847,848,5,112,0,0,848,849,5,114,0,0,849,850,5,111,0,0,850,851, + 5,106,0,0,851,852,5,101,0,0,852,853,5,99,0,0,853,854,5,116,0,0,854, + 855,5,105,0,0,855,856,5,111,0,0,856,857,5,110,0,0,857,134,1,0,0, + 0,858,859,5,119,0,0,859,860,5,105,0,0,860,861,5,116,0,0,861,862, + 5,104,0,0,862,136,1,0,0,0,863,864,5,117,0,0,864,865,5,115,0,0,865, + 866,5,105,0,0,866,867,5,110,0,0,867,868,5,103,0,0,868,138,1,0,0, + 0,869,870,5,97,0,0,870,871,5,99,0,0,871,872,5,99,0,0,872,873,5,101, + 0,0,873,874,5,112,0,0,874,875,5,116,0,0,875,140,1,0,0,0,876,877, + 5,118,0,0,877,878,5,105,0,0,878,879,5,97,0,0,879,142,1,0,0,0,880, + 881,5,109,0,0,881,882,5,97,0,0,882,883,5,116,0,0,883,884,5,101,0, + 0,884,885,5,114,0,0,885,886,5,105,0,0,886,887,5,97,0,0,887,888,5, + 108,0,0,888,889,5,105,0,0,889,890,5,122,0,0,890,891,5,101,0,0,891, + 144,1,0,0,0,892,893,5,105,0,0,893,894,5,102,0,0,894,146,1,0,0,0, + 895,896,5,97,0,0,896,897,5,98,0,0,897,898,5,115,0,0,898,899,5,101, + 0,0,899,900,5,110,0,0,900,901,5,116,0,0,901,148,1,0,0,0,902,903, + 5,111,0,0,903,904,5,110,0,0,904,150,1,0,0,0,905,906,5,112,0,0,906, + 907,5,111,0,0,907,908,5,108,0,0,908,909,5,105,0,0,909,910,5,99,0, + 0,910,911,5,121,0,0,911,152,1,0,0,0,912,913,5,100,0,0,913,914,5, + 101,0,0,914,915,5,102,0,0,915,916,5,97,0,0,916,917,5,117,0,0,917, + 918,5,108,0,0,918,919,5,116,0,0,919,154,1,0,0,0,920,921,5,115,0, + 0,921,922,5,111,0,0,922,923,5,117,0,0,923,924,5,114,0,0,924,925, + 5,99,0,0,925,926,5,101,0,0,926,156,1,0,0,0,927,928,5,114,0,0,928, + 929,5,101,0,0,929,930,5,112,0,0,930,931,5,111,0,0,931,932,5,115, + 0,0,932,933,5,105,0,0,933,934,5,116,0,0,934,935,5,111,0,0,935,936, + 5,114,0,0,936,937,5,121,0,0,937,158,1,0,0,0,938,939,5,99,0,0,939, + 940,5,111,0,0,940,941,5,109,0,0,941,942,5,109,0,0,942,943,5,105, + 0,0,943,944,5,116,0,0,944,160,1,0,0,0,945,946,5,114,0,0,946,947, + 5,101,0,0,947,948,5,118,0,0,948,949,5,105,0,0,949,950,5,115,0,0, + 950,951,5,105,0,0,951,952,5,111,0,0,952,953,5,110,0,0,953,162,1, + 0,0,0,954,955,5,115,0,0,955,956,5,101,0,0,956,957,5,109,0,0,957, + 958,5,97,0,0,958,959,5,110,0,0,959,960,5,116,0,0,960,961,5,105,0, + 0,961,962,5,99,0,0,962,963,5,45,0,0,963,964,5,109,0,0,964,965,5, + 97,0,0,965,966,5,106,0,0,966,967,5,111,0,0,967,968,5,114,0,0,968, + 164,1,0,0,0,969,970,5,111,0,0,970,971,5,110,0,0,971,972,5,45,0,0, + 972,973,5,100,0,0,973,974,5,101,0,0,974,975,5,108,0,0,975,976,5, + 101,0,0,976,977,5,116,0,0,977,978,5,101,0,0,978,166,1,0,0,0,979, + 980,5,108,0,0,980,981,5,105,0,0,981,982,5,102,0,0,982,983,5,101, + 0,0,983,984,5,99,0,0,984,985,5,121,0,0,985,986,5,99,0,0,986,987, + 5,108,0,0,987,988,5,101,0,0,988,168,1,0,0,0,989,990,5,106,0,0,990, + 991,5,111,0,0,991,992,5,117,0,0,992,993,5,114,0,0,993,994,5,110, + 0,0,994,995,5,97,0,0,995,996,5,108,0,0,996,170,1,0,0,0,997,998,5, + 115,0,0,998,999,5,117,0,0,999,1000,5,98,0,0,1000,1001,5,115,0,0, + 1001,1002,5,99,0,0,1002,1003,5,114,0,0,1003,1004,5,105,0,0,1004, + 1005,5,112,0,0,1005,1006,5,116,0,0,1006,1007,5,105,0,0,1007,1008, + 5,111,0,0,1008,1009,5,110,0,0,1009,172,1,0,0,0,1010,1011,5,114,0, + 0,1011,1012,5,101,0,0,1012,1013,5,116,0,0,1013,1014,5,97,0,0,1014, + 1015,5,105,0,0,1015,1016,5,110,0,0,1016,1017,5,45,0,0,1017,1018, + 5,111,0,0,1018,1019,5,116,0,0,1019,1020,5,104,0,0,1020,1021,5,101, + 0,0,1021,1022,5,114,0,0,1022,174,1,0,0,0,1023,1024,5,107,0,0,1024, + 1025,5,101,0,0,1025,1026,5,121,0,0,1026,1027,5,101,0,0,1027,1028, + 5,100,0,0,1028,176,1,0,0,0,1029,1030,5,112,0,0,1030,1031,5,117,0, + 0,1031,1032,5,98,0,0,1032,1033,5,108,0,0,1033,1034,5,105,0,0,1034, + 1035,5,99,0,0,1035,1036,5,45,0,0,1036,1037,5,116,0,0,1037,1038,5, + 114,0,0,1038,1039,5,97,0,0,1039,1040,5,118,0,0,1040,1041,5,101,0, + 0,1041,1042,5,114,0,0,1042,1043,5,115,0,0,1043,1044,5,97,0,0,1044, + 1045,5,108,0,0,1045,178,1,0,0,0,1046,1047,5,105,0,0,1047,1048,5, + 100,0,0,1048,180,1,0,0,0,1049,1050,5,100,0,0,1050,1051,5,111,0,0, + 1051,1052,5,99,0,0,1052,182,1,0,0,0,1053,1054,5,109,0,0,1054,1055, + 5,111,0,0,1055,1056,5,100,0,0,1056,1057,5,101,0,0,1057,184,1,0,0, + 0,1058,1059,5,101,0,0,1059,1060,5,109,0,0,1060,1061,5,105,0,0,1061, + 1062,5,116,0,0,1062,1063,5,115,0,0,1063,186,1,0,0,0,1064,1065,5, + 114,0,0,1065,1066,5,101,0,0,1066,1067,5,99,0,0,1067,1068,5,101,0, + 0,1068,1069,5,105,0,0,1069,1070,5,118,0,0,1070,1071,5,101,0,0,1071, + 1072,5,114,0,0,1072,188,1,0,0,0,1073,1074,5,114,0,0,1074,1075,5, + 101,0,0,1075,1076,5,113,0,0,1076,1077,5,117,0,0,1077,1078,5,105, + 0,0,1078,1079,5,114,0,0,1079,1080,5,101,0,0,1080,1081,5,115,0,0, + 1081,190,1,0,0,0,1082,1083,5,97,0,0,1083,1084,5,110,0,0,1084,1085, + 5,121,0,0,1085,192,1,0,0,0,1086,1087,5,103,0,0,1087,1088,5,101,0, + 0,1088,1089,5,116,0,0,1089,194,1,0,0,0,1090,1091,5,115,0,0,1091, + 1092,5,101,0,0,1092,1093,5,116,0,0,1093,196,1,0,0,0,1094,1095,5, + 119,0,0,1095,1096,5,97,0,0,1096,1097,5,116,0,0,1097,1098,5,99,0, + 0,1098,1099,5,104,0,0,1099,198,1,0,0,0,1100,1101,5,115,0,0,1101, + 1102,5,116,0,0,1102,1103,5,97,0,0,1103,1104,5,114,0,0,1104,1105, + 5,116,0,0,1105,200,1,0,0,0,1106,1107,5,115,0,0,1107,1108,5,116,0, + 0,1108,1109,5,111,0,0,1109,1110,5,112,0,0,1110,202,1,0,0,0,1111, + 1112,5,114,0,0,1112,1113,5,101,0,0,1113,1114,5,97,0,0,1114,1115, + 5,100,0,0,1115,204,1,0,0,0,1116,1117,5,119,0,0,1117,1118,5,114,0, + 0,1118,1119,5,105,0,0,1119,1120,5,116,0,0,1120,1121,5,101,0,0,1121, + 206,1,0,0,0,1122,1123,5,114,0,0,1123,1124,5,101,0,0,1124,1125,5, + 115,0,0,1125,1126,5,111,0,0,1126,1127,5,108,0,0,1127,1128,5,118, + 0,0,1128,1129,5,101,0,0,1129,208,1,0,0,0,1130,1131,5,99,0,0,1131, + 1132,5,111,0,0,1132,1133,5,110,0,0,1133,1134,5,110,0,0,1134,1135, + 5,101,0,0,1135,1136,5,99,0,0,1136,1137,5,116,0,0,1137,210,1,0,0, + 0,1138,1139,5,100,0,0,1139,1140,5,105,0,0,1140,1141,5,115,0,0,1141, + 1142,5,99,0,0,1142,1143,5,111,0,0,1143,1144,5,110,0,0,1144,1145, + 5,110,0,0,1145,1146,5,101,0,0,1146,1147,5,99,0,0,1147,1148,5,116, + 0,0,1148,212,1,0,0,0,1149,1150,5,99,0,0,1150,1151,5,97,0,0,1151, + 1152,5,108,0,0,1152,1153,5,108,0,0,1153,214,1,0,0,0,1154,1155,5, + 119,0,0,1155,1156,5,97,0,0,1156,1157,5,116,0,0,1157,1158,5,99,0, + 0,1158,1159,5,104,0,0,1159,1160,5,45,0,0,1160,1161,5,115,0,0,1161, + 1162,5,116,0,0,1162,1163,5,97,0,0,1163,1164,5,114,0,0,1164,1165, + 5,116,0,0,1165,216,1,0,0,0,1166,1167,5,119,0,0,1167,1168,5,97,0, + 0,1168,1169,5,116,0,0,1169,1170,5,99,0,0,1170,1171,5,104,0,0,1171, + 1172,5,45,0,0,1172,1173,5,115,0,0,1173,1174,5,116,0,0,1174,1175, + 5,111,0,0,1175,1176,5,112,0,0,1176,218,1,0,0,0,1177,1178,5,115,0, + 0,1178,1179,5,117,0,0,1179,1180,5,98,0,0,1180,1181,5,115,0,0,1181, + 1182,5,99,0,0,1182,1183,5,114,0,0,1183,1184,5,105,0,0,1184,1185, + 5,98,0,0,1185,1186,5,101,0,0,1186,220,1,0,0,0,1187,1188,5,117,0, + 0,1188,1189,5,110,0,0,1189,1190,5,115,0,0,1190,1191,5,117,0,0,1191, + 1192,5,98,0,0,1192,1193,5,115,0,0,1193,1194,5,99,0,0,1194,1195,5, + 114,0,0,1195,1196,5,105,0,0,1196,1197,5,98,0,0,1197,1198,5,101,0, + 0,1198,222,1,0,0,0,1199,1200,5,114,0,0,1200,1201,5,101,0,0,1201, + 1202,5,103,0,0,1202,1203,5,105,0,0,1203,1204,5,115,0,0,1204,1205, + 5,116,0,0,1205,1206,5,101,0,0,1206,1207,5,114,0,0,1207,224,1,0,0, + 0,1208,1209,5,99,0,0,1209,1210,5,114,0,0,1210,1211,5,100,0,0,1211, + 1212,5,116,0,0,1212,226,1,0,0,0,1213,1214,5,111,0,0,1214,1215,5, + 112,0,0,1215,1216,5,116,0,0,1216,1217,5,105,0,0,1217,1218,5,111, + 0,0,1218,1219,5,110,0,0,1219,1220,5,97,0,0,1220,1221,5,108,0,0,1221, + 1222,5,45,0,0,1222,1223,5,111,0,0,1223,1224,5,110,0,0,1224,1225, + 5,101,0,0,1225,228,1,0,0,0,1226,1227,5,101,0,0,1227,1228,5,120,0, + 0,1228,1229,5,97,0,0,1229,1230,5,99,0,0,1230,1231,5,116,0,0,1231, + 1232,5,108,0,0,1232,1233,5,121,0,0,1233,1234,5,45,0,0,1234,1235, + 5,111,0,0,1235,1236,5,110,0,0,1236,1237,5,101,0,0,1237,230,1,0,0, + 0,1238,1239,5,109,0,0,1239,1240,5,97,0,0,1240,1241,5,110,0,0,1241, + 1242,5,121,0,0,1242,1243,5,45,0,0,1243,1244,5,117,0,0,1244,1245, + 5,110,0,0,1245,1246,5,105,0,0,1246,1247,5,113,0,0,1247,1248,5,117, + 0,0,1248,1249,5,101,0,0,1249,232,1,0,0,0,1250,1251,5,109,0,0,1251, + 1252,5,97,0,0,1252,1253,5,110,0,0,1253,1254,5,121,0,0,1254,234,1, + 0,0,0,1255,1256,5,111,0,0,1256,1257,5,114,0,0,1257,1258,5,100,0, + 0,1258,1259,5,101,0,0,1259,1260,5,114,0,0,1260,1261,5,101,0,0,1261, + 1262,5,100,0,0,1262,236,1,0,0,0,1263,1264,5,117,0,0,1264,1265,5, + 110,0,0,1265,1266,5,105,0,0,1266,1267,5,116,0,0,1267,238,1,0,0,0, + 1268,1269,5,119,0,0,1269,1270,5,97,0,0,1270,1271,5,116,0,0,1271, + 1272,5,99,0,0,1272,1273,5,104,0,0,1273,1274,5,45,0,0,1274,1275,5, + 104,0,0,1275,1276,5,97,0,0,1276,1277,5,110,0,0,1277,1278,5,100,0, + 0,1278,1279,5,108,0,0,1279,1280,5,101,0,0,1280,240,1,0,0,0,1281, + 1282,5,109,0,0,1282,1283,5,101,0,0,1283,1284,5,115,0,0,1284,1285, + 5,115,0,0,1285,1286,5,97,0,0,1286,1287,5,103,0,0,1287,1288,5,101, + 0,0,1288,242,1,0,0,0,1289,1290,5,97,0,0,1290,1291,5,116,0,0,1291, + 1292,5,111,0,0,1292,1293,5,109,0,0,1293,1294,5,45,0,0,1294,1295, + 5,114,0,0,1295,1296,5,101,0,0,1296,1297,5,102,0,0,1297,244,1,0,0, + 0,1298,1299,5,105,0,0,1299,1300,5,110,0,0,1300,1301,5,116,0,0,1301, + 1302,5,101,0,0,1302,1303,5,114,0,0,1303,1304,5,102,0,0,1304,1305, + 5,97,0,0,1305,1306,5,99,0,0,1306,1307,5,101,0,0,1307,1308,5,45,0, + 0,1308,1309,5,114,0,0,1309,1310,5,101,0,0,1310,1311,5,102,0,0,1311, + 246,1,0,0,0,1312,1313,5,111,0,0,1313,1314,5,112,0,0,1314,1315,5, + 116,0,0,1315,1316,5,105,0,0,1316,1317,5,111,0,0,1317,1318,5,110, + 0,0,1318,1319,5,97,0,0,1319,1320,5,108,0,0,1320,248,1,0,0,0,1321, + 1322,5,108,0,0,1322,1323,5,105,0,0,1323,1324,5,115,0,0,1324,1325, + 5,116,0,0,1325,250,1,0,0,0,1326,1327,5,114,0,0,1327,1328,5,101,0, + 0,1328,1329,5,99,0,0,1329,1330,5,111,0,0,1330,1331,5,114,0,0,1331, + 1332,5,100,0,0,1332,252,1,0,0,0,1333,1334,5,98,0,0,1334,1335,5,111, + 0,0,1335,1336,5,111,0,0,1336,1337,5,108,0,0,1337,254,1,0,0,0,1338, + 1339,5,98,0,0,1339,1340,5,121,0,0,1340,1341,5,116,0,0,1341,1342, + 5,101,0,0,1342,1343,5,115,0,0,1343,256,1,0,0,0,1344,1345,5,100,0, + 0,1345,1346,5,111,0,0,1346,1347,5,117,0,0,1347,1348,5,98,0,0,1348, + 1349,5,108,0,0,1349,1350,5,101,0,0,1350,258,1,0,0,0,1351,1352,5, + 105,0,0,1352,1353,5,110,0,0,1353,1354,5,116,0,0,1354,1355,5,51,0, + 0,1355,1356,5,50,0,0,1356,260,1,0,0,0,1357,1358,5,105,0,0,1358,1359, + 5,110,0,0,1359,1360,5,116,0,0,1360,1361,5,54,0,0,1361,1362,5,52, + 0,0,1362,262,1,0,0,0,1363,1364,5,115,0,0,1364,1365,5,116,0,0,1365, + 1366,5,114,0,0,1366,1367,5,105,0,0,1367,1368,5,110,0,0,1368,1369, + 5,103,0,0,1369,264,1,0,0,0,1370,1371,5,117,0,0,1371,1372,5,105,0, + 0,1372,1373,5,110,0,0,1373,1374,5,116,0,0,1374,1375,5,51,0,0,1375, + 1376,5,50,0,0,1376,266,1,0,0,0,1377,1378,5,117,0,0,1378,1379,5,105, + 0,0,1379,1380,5,110,0,0,1380,1381,5,116,0,0,1381,1382,5,54,0,0,1382, + 1383,5,52,0,0,1383,268,1,0,0,0,1384,1385,5,116,0,0,1385,1386,5,114, + 0,0,1386,1387,5,117,0,0,1387,1388,5,101,0,0,1388,270,1,0,0,0,1389, + 1390,5,102,0,0,1390,1391,5,97,0,0,1391,1392,5,108,0,0,1392,1393, + 5,115,0,0,1393,1394,5,101,0,0,1394,272,1,0,0,0,1395,1396,5,110,0, + 0,1396,1397,5,117,0,0,1397,1398,5,108,0,0,1398,1399,5,108,0,0,1399, + 274,1,0,0,0,1400,1401,5,45,0,0,1401,1402,5,62,0,0,1402,276,1,0,0, + 0,1403,1404,5,58,0,0,1404,278,1,0,0,0,1405,1406,5,59,0,0,1406,280, + 1,0,0,0,1407,1408,5,44,0,0,1408,282,1,0,0,0,1409,1410,5,46,0,0,1410, + 284,1,0,0,0,1411,1412,5,123,0,0,1412,286,1,0,0,0,1413,1414,5,125, + 0,0,1414,288,1,0,0,0,1415,1416,5,91,0,0,1416,290,1,0,0,0,1417,1418, + 5,93,0,0,1418,292,1,0,0,0,1419,1420,5,40,0,0,1420,294,1,0,0,0,1421, + 1422,5,41,0,0,1422,296,1,0,0,0,1423,1424,5,60,0,0,1424,298,1,0,0, + 0,1425,1426,5,62,0,0,1426,300,1,0,0,0,1427,1428,5,38,0,0,1428,302, + 1,0,0,0,1429,1430,5,61,0,0,1430,304,1,0,0,0,1431,1433,5,45,0,0,1432, + 1431,1,0,0,0,1432,1433,1,0,0,0,1433,1435,1,0,0,0,1434,1436,7,0,0, + 0,1435,1434,1,0,0,0,1436,1437,1,0,0,0,1437,1435,1,0,0,0,1437,1438, + 1,0,0,0,1438,306,1,0,0,0,1439,1441,5,45,0,0,1440,1439,1,0,0,0,1440, + 1441,1,0,0,0,1441,1450,1,0,0,0,1442,1451,5,48,0,0,1443,1447,7,1, + 0,0,1444,1446,7,0,0,0,1445,1444,1,0,0,0,1446,1449,1,0,0,0,1447,1445, + 1,0,0,0,1447,1448,1,0,0,0,1448,1451,1,0,0,0,1449,1447,1,0,0,0,1450, + 1442,1,0,0,0,1450,1443,1,0,0,0,1451,1458,1,0,0,0,1452,1454,5,46, + 0,0,1453,1455,7,0,0,0,1454,1453,1,0,0,0,1455,1456,1,0,0,0,1456,1454, + 1,0,0,0,1456,1457,1,0,0,0,1457,1459,1,0,0,0,1458,1452,1,0,0,0,1458, + 1459,1,0,0,0,1459,1469,1,0,0,0,1460,1462,7,2,0,0,1461,1463,7,3,0, + 0,1462,1461,1,0,0,0,1462,1463,1,0,0,0,1463,1465,1,0,0,0,1464,1466, + 7,0,0,0,1465,1464,1,0,0,0,1466,1467,1,0,0,0,1467,1465,1,0,0,0,1467, + 1468,1,0,0,0,1468,1470,1,0,0,0,1469,1460,1,0,0,0,1469,1470,1,0,0, + 0,1470,308,1,0,0,0,1471,1475,7,4,0,0,1472,1474,7,5,0,0,1473,1472, + 1,0,0,0,1474,1477,1,0,0,0,1475,1473,1,0,0,0,1475,1476,1,0,0,0,1476, + 310,1,0,0,0,1477,1475,1,0,0,0,1478,1483,5,34,0,0,1479,1482,3,313, + 156,0,1480,1482,8,6,0,0,1481,1479,1,0,0,0,1481,1480,1,0,0,0,1482, + 1485,1,0,0,0,1483,1481,1,0,0,0,1483,1484,1,0,0,0,1484,1486,1,0,0, + 0,1485,1483,1,0,0,0,1486,1487,5,34,0,0,1487,312,1,0,0,0,1488,1496, + 5,92,0,0,1489,1497,7,7,0,0,1490,1491,5,117,0,0,1491,1492,3,315,157, + 0,1492,1493,3,315,157,0,1493,1494,3,315,157,0,1494,1495,3,315,157, + 0,1495,1497,1,0,0,0,1496,1489,1,0,0,0,1496,1490,1,0,0,0,1497,314, + 1,0,0,0,1498,1499,7,8,0,0,1499,316,1,0,0,0,1500,1501,5,47,0,0,1501, + 1502,5,47,0,0,1502,1506,1,0,0,0,1503,1505,8,9,0,0,1504,1503,1,0, + 0,0,1505,1508,1,0,0,0,1506,1504,1,0,0,0,1506,1507,1,0,0,0,1507,1509, + 1,0,0,0,1508,1506,1,0,0,0,1509,1510,6,158,0,0,1510,318,1,0,0,0,1511, + 1512,5,47,0,0,1512,1513,5,42,0,0,1513,1517,1,0,0,0,1514,1516,9,0, + 0,0,1515,1514,1,0,0,0,1516,1519,1,0,0,0,1517,1518,1,0,0,0,1517,1515, + 1,0,0,0,1518,1520,1,0,0,0,1519,1517,1,0,0,0,1520,1521,5,42,0,0,1521, + 1522,5,47,0,0,1522,1523,1,0,0,0,1523,1524,6,159,0,0,1524,320,1,0, + 0,0,1525,1527,7,10,0,0,1526,1525,1,0,0,0,1527,1528,1,0,0,0,1528, + 1526,1,0,0,0,1528,1529,1,0,0,0,1529,1530,1,0,0,0,1530,1531,6,160, + 0,0,1531,322,1,0,0,0,18,0,1432,1437,1440,1447,1450,1456,1458,1462, + 1467,1469,1475,1481,1483,1496,1506,1517,1528,1,0,1,0 ]; private static __ATN: antlr.ATN; diff --git a/src/capability-language/generated/QuixosCapabilityParser.ts b/src/capability-language/generated/QuixosCapabilityParser.ts index 3bd3db2..04d09e9 100644 --- a/src/capability-language/generated/QuixosCapabilityParser.ts +++ b/src/capability-language/generated/QuixosCapabilityParser.ts @@ -10,139 +10,165 @@ type int = number; export class QuixosCapabilityParser extends antlr.Parser { - public static readonly WORKSPACE = 1; - public static readonly RESULT_BYTES = 2; - public static readonly RPC_CALLS = 3; - public static readonly DEADLINE_MS = 4; - public static readonly QUERY = 5; - public static readonly SPECIALIZE = 6; - public static readonly ROOT = 7; - public static readonly DOCUMENT = 8; - public static readonly FRAGMENTS = 9; - public static readonly VIEW = 10; - public static readonly MAX = 11; - public static readonly ALLOW = 12; - public static readonly POLL = 13; - public static readonly QUERYABLE = 14; - public static readonly RPC = 15; - public static readonly QUERY_REASON = 16; - public static readonly TYPE = 17; - public static readonly OBJECT = 18; - public static readonly STORABLE = 19; - public static readonly IMPLEMENTS = 20; - public static readonly REF = 21; - public static readonly FRAGMENT = 22; - public static readonly IMPORT = 23; - public static readonly EXTERNAL = 24; - public static readonly ATOM = 25; - public static readonly INTERFACE = 26; - public static readonly INTERFACES = 27; - public static readonly PACKAGE = 28; - public static readonly VALUE = 29; - public static readonly RELATION = 30; - public static readonly OPERATION = 31; - public static readonly FUNCTION = 32; - public static readonly CONSTRUCTOR = 33; - public static readonly CONSTRUCTS = 34; - public static readonly INPUT = 35; - public static readonly CONFORM = 36; - public static readonly AS = 37; - public static readonly BIND = 38; - public static readonly STATIC = 39; - public static readonly TO = 40; - public static readonly PRIVATE = 41; - public static readonly SHARED = 42; - public static readonly STATE = 43; - public static readonly EDGE = 44; - public static readonly PROJECTION = 45; - public static readonly WITH = 46; - public static readonly USING = 47; - public static readonly VIA = 48; - public static readonly MATERIALIZE = 49; - public static readonly IF = 50; - public static readonly ABSENT = 51; - public static readonly ON = 52; - public static readonly POLICY = 53; - public static readonly DEFAULT = 54; - public static readonly SOURCE = 55; - public static readonly REPOSITORY = 56; - public static readonly COMMIT = 57; - public static readonly REVISION = 58; - public static readonly SEMANTIC_MAJOR = 59; - public static readonly ON_DELETE = 60; - public static readonly RETAIN_OTHER = 61; - public static readonly KEYED = 62; - public static readonly PUBLIC_TRAVERSAL = 63; - public static readonly ID = 64; - public static readonly DOC = 65; - public static readonly MODE = 66; - public static readonly EMITS = 67; - public static readonly RECEIVER = 68; - public static readonly REQUIRES = 69; - public static readonly ANY = 70; - public static readonly GET = 71; - public static readonly SET = 72; - public static readonly WATCH = 73; - public static readonly START = 74; - public static readonly STOP = 75; - public static readonly READ = 76; - public static readonly WRITE = 77; - public static readonly RESOLVE = 78; - public static readonly CONNECT = 79; - public static readonly DISCONNECT = 80; - public static readonly CALL = 81; - public static readonly WATCH_START = 82; - public static readonly WATCH_STOP = 83; - public static readonly SUBSCRIBE = 84; - public static readonly UNSUBSCRIBE = 85; - public static readonly OPTIMISTIC_REGISTER = 86; - public static readonly CRDT = 87; - public static readonly OPTIONAL_ONE = 88; - public static readonly EXACTLY_ONE = 89; - public static readonly MANY_UNIQUE = 90; - public static readonly MANY = 91; - public static readonly ORDERED = 92; - public static readonly UNIT = 93; - public static readonly WATCH_HANDLE = 94; - public static readonly MESSAGE = 95; - public static readonly ATOM_REF = 96; - public static readonly INTERFACE_REF = 97; - public static readonly OPTIONAL = 98; - public static readonly LIST = 99; - public static readonly RECORD = 100; - public static readonly BOOL = 101; - public static readonly BYTES = 102; - public static readonly DOUBLE = 103; - public static readonly INT32 = 104; - public static readonly INT64 = 105; - public static readonly STRING = 106; - public static readonly UINT32 = 107; - public static readonly UINT64 = 108; - public static readonly TRUE = 109; - public static readonly FALSE = 110; - public static readonly NULL = 111; - public static readonly ARROW = 112; - public static readonly COLON = 113; - public static readonly SEMI = 114; - public static readonly COMMA = 115; - public static readonly DOT = 116; - public static readonly LBRACE = 117; - public static readonly RBRACE = 118; - public static readonly LBRACK = 119; - public static readonly RBRACK = 120; - public static readonly LPAREN = 121; - public static readonly RPAREN = 122; - public static readonly LT = 123; - public static readonly GT = 124; - public static readonly AMP = 125; - public static readonly EQUAL = 126; - public static readonly INTEGER = 127; - public static readonly JSON_NUMBER = 128; - public static readonly IDENTIFIER = 129; - public static readonly STRING_LITERAL = 130; - public static readonly LINE_COMMENT = 131; - public static readonly BLOCK_COMMENT = 132; - public static readonly WS = 133; + public static readonly EDIT = 1; + public static readonly MODULE = 2; + public static readonly CONTRACT = 3; + public static readonly PRESENTATION = 4; + public static readonly SUBJECT = 5; + public static readonly FOR = 6; + public static readonly CREATION = 7; + public static readonly VARIANT = 8; + public static readonly PREPARES = 9; + public static readonly INITIALIZE = 10; + public static readonly WORKSPACE = 11; + public static readonly EXECUTION = 12; + public static readonly SNAPSHOT = 13; + public static readonly TRANSACTION = 14; + public static readonly REMOTE = 15; + public static readonly TARGETS = 16; + public static readonly PORTABLE = 17; + public static readonly REPLICA = 18; + public static readonly LOCAL = 19; + public static readonly FROM = 20; + public static readonly RESULT_BYTES = 21; + public static readonly RPC_CALLS = 22; + public static readonly DEADLINE_MS = 23; + public static readonly QUERY = 24; + public static readonly REFLECTION = 25; + public static readonly SPECIALIZE = 26; + public static readonly ROOT = 27; + public static readonly DOCUMENT = 28; + public static readonly FRAGMENTS = 29; + public static readonly VIEW = 30; + public static readonly MAX = 31; + public static readonly ALLOW = 32; + public static readonly POLL = 33; + public static readonly QUERYABLE = 34; + public static readonly RPC = 35; + public static readonly QUERY_REASON = 36; + public static readonly TYPE = 37; + public static readonly OBJECT = 38; + public static readonly STORABLE = 39; + public static readonly IMPLEMENTS = 40; + public static readonly REF = 41; + public static readonly FRAGMENT = 42; + public static readonly IMPORT = 43; + public static readonly EXTERNAL = 44; + public static readonly SYMBOL = 45; + public static readonly ATOM = 46; + public static readonly INTERFACE = 47; + public static readonly INTERFACES = 48; + public static readonly PACKAGE = 49; + public static readonly VALUE = 50; + public static readonly FIELD = 51; + public static readonly RELATION = 52; + public static readonly OPERATION = 53; + public static readonly FUNCTION = 54; + public static readonly CONSTRUCTOR = 55; + public static readonly CONSTRUCTS = 56; + public static readonly INPUT = 57; + public static readonly CONFORM = 58; + public static readonly AS = 59; + public static readonly BIND = 60; + public static readonly STATIC = 61; + public static readonly TO = 62; + public static readonly PRIVATE = 63; + public static readonly SHARED = 64; + public static readonly STATE = 65; + public static readonly EDGE = 66; + public static readonly PROJECTION = 67; + public static readonly WITH = 68; + public static readonly USING = 69; + public static readonly ACCEPT = 70; + public static readonly VIA = 71; + public static readonly MATERIALIZE = 72; + public static readonly IF = 73; + public static readonly ABSENT = 74; + public static readonly ON = 75; + public static readonly POLICY = 76; + public static readonly DEFAULT = 77; + public static readonly SOURCE = 78; + public static readonly REPOSITORY = 79; + public static readonly COMMIT = 80; + public static readonly REVISION = 81; + public static readonly SEMANTIC_MAJOR = 82; + public static readonly ON_DELETE = 83; + public static readonly LIFECYCLE = 84; + public static readonly JOURNAL = 85; + public static readonly SUBSCRIPTION = 86; + public static readonly RETAIN_OTHER = 87; + public static readonly KEYED = 88; + public static readonly PUBLIC_TRAVERSAL = 89; + public static readonly ID = 90; + public static readonly DOC = 91; + public static readonly MODE = 92; + public static readonly EMITS = 93; + public static readonly RECEIVER = 94; + public static readonly REQUIRES = 95; + public static readonly ANY = 96; + public static readonly GET = 97; + public static readonly SET = 98; + public static readonly WATCH = 99; + public static readonly START = 100; + public static readonly STOP = 101; + public static readonly READ = 102; + public static readonly WRITE = 103; + public static readonly RESOLVE = 104; + public static readonly CONNECT = 105; + public static readonly DISCONNECT = 106; + public static readonly CALL = 107; + public static readonly WATCH_START = 108; + public static readonly WATCH_STOP = 109; + public static readonly SUBSCRIBE = 110; + public static readonly UNSUBSCRIBE = 111; + public static readonly REGISTER = 112; + public static readonly CRDT = 113; + public static readonly OPTIONAL_ONE = 114; + public static readonly EXACTLY_ONE = 115; + public static readonly MANY_UNIQUE = 116; + public static readonly MANY = 117; + public static readonly ORDERED = 118; + public static readonly UNIT = 119; + public static readonly WATCH_HANDLE = 120; + public static readonly MESSAGE = 121; + public static readonly ATOM_REF = 122; + public static readonly INTERFACE_REF = 123; + public static readonly OPTIONAL = 124; + public static readonly LIST = 125; + public static readonly RECORD = 126; + public static readonly BOOL = 127; + public static readonly BYTES = 128; + public static readonly DOUBLE = 129; + public static readonly INT32 = 130; + public static readonly INT64 = 131; + public static readonly STRING = 132; + public static readonly UINT32 = 133; + public static readonly UINT64 = 134; + public static readonly TRUE = 135; + public static readonly FALSE = 136; + public static readonly NULL = 137; + public static readonly ARROW = 138; + public static readonly COLON = 139; + public static readonly SEMI = 140; + public static readonly COMMA = 141; + public static readonly DOT = 142; + public static readonly LBRACE = 143; + public static readonly RBRACE = 144; + public static readonly LBRACK = 145; + public static readonly RBRACK = 146; + public static readonly LPAREN = 147; + public static readonly RPAREN = 148; + public static readonly LT = 149; + public static readonly GT = 150; + public static readonly AMP = 151; + public static readonly EQUAL = 152; + public static readonly INTEGER = 153; + public static readonly JSON_NUMBER = 154; + public static readonly IDENTIFIER = 155; + public static readonly STRING_LITERAL = 156; + public static readonly LINE_COMMENT = 157; + public static readonly BLOCK_COMMENT = 158; + public static readonly WS = 159; public static readonly RULE_document = 0; public static readonly RULE_fragmentDecl = 1; public static readonly RULE_sourceImportDecl = 2; @@ -151,137 +177,165 @@ export class QuixosCapabilityParser extends antlr.Parser { public static readonly RULE_resourceImportDecl = 5; public static readonly RULE_externalAtomDecl = 6; public static readonly RULE_externalInterfaceDecl = 7; - public static readonly RULE_resourcePreamble = 8; - public static readonly RULE_atomDecl = 9; - public static readonly RULE_interfaceResourceDecl = 10; - public static readonly RULE_typeParameters = 11; - public static readonly RULE_typeParameter = 12; - public static readonly RULE_interfaceType = 13; - public static readonly RULE_typeArguments = 14; - public static readonly RULE_typeArgument = 15; - public static readonly RULE_typeAliasDecl = 16; - public static readonly RULE_interfaceMember = 17; - public static readonly RULE_operationMember = 18; - public static readonly RULE_valueMember = 19; - public static readonly RULE_valueMemberOperation = 20; - public static readonly RULE_relationshipMember = 21; - public static readonly RULE_queryReadContract = 22; - public static readonly RULE_relationshipOperation = 23; - public static readonly RULE_targetConstraint = 24; - public static readonly RULE_packageResourceDecl = 25; - public static readonly RULE_packageExport = 26; - public static readonly RULE_packageQuery = 27; - public static readonly RULE_packageQuerySpecialization = 28; - public static readonly RULE_queryClause = 29; - public static readonly RULE_packageOperationExport = 30; - public static readonly RULE_packageFunctionExport = 31; - public static readonly RULE_packageConstructorExport = 32; - public static readonly RULE_eventClause = 33; - public static readonly RULE_operationMode = 34; - public static readonly RULE_receiverRequirement = 35; - public static readonly RULE_identifierList = 36; - public static readonly RULE_dependencyBlock = 37; - public static readonly RULE_dependencyPort = 38; - public static readonly RULE_primitiveList = 39; - public static readonly RULE_primitive = 40; - public static readonly RULE_sharedAttachmentDecl = 41; - public static readonly RULE_attachmentDecl = 42; - public static readonly RULE_stateDecl = 43; - public static readonly RULE_storagePolicy = 44; - public static readonly RULE_edgeDecl = 45; - public static readonly RULE_edgeEndpoint = 46; - public static readonly RULE_conformanceDecl = 47; - public static readonly RULE_conformanceItem = 48; - public static readonly RULE_stateFieldBindingDecl = 49; - public static readonly RULE_relationshipMaterializationDecl = 50; - public static readonly RULE_operationBindingDecl = 51; - public static readonly RULE_memberOperationRef = 52; - public static readonly RULE_operationName = 53; - public static readonly RULE_operationProvider = 54; - public static readonly RULE_statePrimitive = 55; - public static readonly RULE_edgePrimitive = 56; - public static readonly RULE_dependencyBindingBlock = 57; - public static readonly RULE_dependencyBinding = 58; - public static readonly RULE_constructorBindingDecl = 59; - public static readonly RULE_valueType = 60; - public static readonly RULE_recordField = 61; - public static readonly RULE_scalarType = 62; - public static readonly RULE_cardinality = 63; - public static readonly RULE_jsonLiteral = 64; - public static readonly RULE_jsonObject = 65; - public static readonly RULE_jsonMember = 66; - public static readonly RULE_jsonArray = 67; - public static readonly RULE_identifier = 68; - public static readonly RULE_stringLiteral = 69; + public static readonly RULE_symbolDecl = 8; + public static readonly RULE_resourcePreamble = 9; + public static readonly RULE_atomDecl = 10; + public static readonly RULE_interfaceResourceDecl = 11; + public static readonly RULE_typeParameters = 12; + public static readonly RULE_typeParameter = 13; + public static readonly RULE_interfaceType = 14; + public static readonly RULE_typeArguments = 15; + public static readonly RULE_typeArgument = 16; + public static readonly RULE_typeAliasDecl = 17; + public static readonly RULE_interfaceMember = 18; + public static readonly RULE_moduleMember = 19; + public static readonly RULE_operationMember = 20; + public static readonly RULE_valueMember = 21; + public static readonly RULE_valueMemberOperation = 22; + public static readonly RULE_relationshipMember = 23; + public static readonly RULE_queryReadContract = 24; + public static readonly RULE_relationshipOperation = 25; + public static readonly RULE_targetConstraint = 26; + public static readonly RULE_packageResourceDecl = 27; + public static readonly RULE_packageExport = 28; + public static readonly RULE_moduleExport = 29; + public static readonly RULE_presentationDecl = 30; + public static readonly RULE_packageQuery = 31; + public static readonly RULE_packageQuerySpecialization = 32; + public static readonly RULE_queryClause = 33; + public static readonly RULE_fragmentVariable = 34; + public static readonly RULE_packageOperationExport = 35; + public static readonly RULE_packageFunctionExport = 36; + public static readonly RULE_acceptanceClause = 37; + public static readonly RULE_preparationClause = 38; + public static readonly RULE_executionClause = 39; + public static readonly RULE_selectedOperations = 40; + public static readonly RULE_packageConstructorExport = 41; + public static readonly RULE_eventClause = 42; + public static readonly RULE_operationMode = 43; + public static readonly RULE_receiverRequirement = 44; + public static readonly RULE_identifierList = 45; + public static readonly RULE_dependencyBlock = 46; + public static readonly RULE_dependencyPort = 47; + public static readonly RULE_primitiveList = 48; + public static readonly RULE_primitive = 49; + public static readonly RULE_sharedAttachmentDecl = 50; + public static readonly RULE_attachmentDecl = 51; + public static readonly RULE_stateDecl = 52; + public static readonly RULE_storagePolicy = 53; + public static readonly RULE_edgeDecl = 54; + public static readonly RULE_edgeEndpoint = 55; + public static readonly RULE_conformanceDecl = 56; + public static readonly RULE_conformanceSourceArguments = 57; + public static readonly RULE_conformanceItem = 58; + public static readonly RULE_moduleBindingDecl = 59; + public static readonly RULE_initializationDecl = 60; + public static readonly RULE_initializationField = 61; + public static readonly RULE_creationOwnerMapping = 62; + public static readonly RULE_stateFieldBindingDecl = 63; + public static readonly RULE_relationshipMaterializationDecl = 64; + public static readonly RULE_operationBindingDecl = 65; + public static readonly RULE_memberOperationRef = 66; + public static readonly RULE_operationName = 67; + public static readonly RULE_operationProvider = 68; + public static readonly RULE_acceptanceBinding = 69; + public static readonly RULE_statePrimitive = 70; + public static readonly RULE_edgePrimitive = 71; + public static readonly RULE_dependencyBindingBlock = 72; + public static readonly RULE_dependencyBinding = 73; + public static readonly RULE_constructorBindingDecl = 74; + public static readonly RULE_valueType = 75; + public static readonly RULE_recordField = 76; + public static readonly RULE_variantCase = 77; + public static readonly RULE_scalarType = 78; + public static readonly RULE_cardinality = 79; + public static readonly RULE_jsonLiteral = 80; + public static readonly RULE_jsonObject = 81; + public static readonly RULE_jsonMember = 82; + public static readonly RULE_jsonArray = 83; + public static readonly RULE_identifier = 84; + public static readonly RULE_stringLiteral = 85; public static readonly literalNames = [ - null, "'workspace'", "'result-bytes'", "'rpc-calls'", "'deadline-ms'", - "'query'", "'specialize'", "'root'", "'document'", "'fragments'", - "'view'", "'max'", "'allow'", "'poll'", "'queryable'", "'rpc'", - "'query-reason'", "'type'", "'object'", "'storable'", "'implements'", - "'ref'", "'fragment'", "'import'", "'external'", "'atom'", "'interface'", - "'interfaces'", "'package'", "'value'", "'relation'", "'operation'", + null, "'edit'", "'module'", "'contract'", "'presentation'", "'subject'", + "'for'", "'creation'", "'variant'", "'prepares'", "'initialize'", + "'workspace'", "'execution'", "'snapshot'", "'transaction'", "'remote'", + "'targets'", "'portable'", "'replica'", "'local'", "'from'", "'result-bytes'", + "'rpc-calls'", "'deadline-ms'", "'query'", "'reflection'", "'specialize'", + "'root'", "'document'", "'fragments'", "'view'", "'max'", "'allow'", + "'poll'", "'queryable'", "'rpc'", "'query-reason'", "'type'", "'object'", + "'storable'", "'implements'", "'ref'", "'fragment'", "'import'", + "'external'", "'symbol'", "'atom'", "'interface'", "'interfaces'", + "'package'", "'value'", "'field'", "'relation'", "'operation'", "'function'", "'constructor'", "'constructs'", "'input'", "'conform'", "'as'", "'bind'", "'static'", "'to'", "'private'", "'shared'", "'state'", - "'edge'", "'projection'", "'with'", "'using'", "'via'", "'materialize'", - "'if'", "'absent'", "'on'", "'policy'", "'default'", "'source'", - "'repository'", "'commit'", "'revision'", "'semantic-major'", "'on-delete'", - "'retain-other'", "'keyed'", "'public-traversal'", "'id'", "'doc'", - "'mode'", "'emits'", "'receiver'", "'requires'", "'any'", "'get'", - "'set'", "'watch'", "'start'", "'stop'", "'read'", "'write'", "'resolve'", - "'connect'", "'disconnect'", "'call'", "'watch-start'", "'watch-stop'", - "'subscribe'", "'unsubscribe'", "'optimistic-register'", "'crdt'", - "'optional-one'", "'exactly-one'", "'many-unique'", "'many'", "'ordered'", - "'unit'", "'watch-handle'", "'message'", "'atom-ref'", "'interface-ref'", - "'optional'", "'list'", "'record'", "'bool'", "'bytes'", "'double'", - "'int32'", "'int64'", "'string'", "'uint32'", "'uint64'", "'true'", - "'false'", "'null'", "'->'", "':'", "';'", "','", "'.'", "'{'", - "'}'", "'['", "']'", "'('", "')'", "'<'", "'>'", "'&'", "'='" + "'edge'", "'projection'", "'with'", "'using'", "'accept'", "'via'", + "'materialize'", "'if'", "'absent'", "'on'", "'policy'", "'default'", + "'source'", "'repository'", "'commit'", "'revision'", "'semantic-major'", + "'on-delete'", "'lifecycle'", "'journal'", "'subscription'", "'retain-other'", + "'keyed'", "'public-traversal'", "'id'", "'doc'", "'mode'", "'emits'", + "'receiver'", "'requires'", "'any'", "'get'", "'set'", "'watch'", + "'start'", "'stop'", "'read'", "'write'", "'resolve'", "'connect'", + "'disconnect'", "'call'", "'watch-start'", "'watch-stop'", "'subscribe'", + "'unsubscribe'", "'register'", "'crdt'", "'optional-one'", "'exactly-one'", + "'many-unique'", "'many'", "'ordered'", "'unit'", "'watch-handle'", + "'message'", "'atom-ref'", "'interface-ref'", "'optional'", "'list'", + "'record'", "'bool'", "'bytes'", "'double'", "'int32'", "'int64'", + "'string'", "'uint32'", "'uint64'", "'true'", "'false'", "'null'", + "'->'", "':'", "';'", "','", "'.'", "'{'", "'}'", "'['", "']'", + "'('", "')'", "'<'", "'>'", "'&'", "'='" ]; public static readonly symbolicNames = [ - null, "WORKSPACE", "RESULT_BYTES", "RPC_CALLS", "DEADLINE_MS", "QUERY", - "SPECIALIZE", "ROOT", "DOCUMENT", "FRAGMENTS", "VIEW", "MAX", "ALLOW", - "POLL", "QUERYABLE", "RPC", "QUERY_REASON", "TYPE", "OBJECT", "STORABLE", - "IMPLEMENTS", "REF", "FRAGMENT", "IMPORT", "EXTERNAL", "ATOM", "INTERFACE", - "INTERFACES", "PACKAGE", "VALUE", "RELATION", "OPERATION", "FUNCTION", - "CONSTRUCTOR", "CONSTRUCTS", "INPUT", "CONFORM", "AS", "BIND", "STATIC", - "TO", "PRIVATE", "SHARED", "STATE", "EDGE", "PROJECTION", "WITH", - "USING", "VIA", "MATERIALIZE", "IF", "ABSENT", "ON", "POLICY", "DEFAULT", - "SOURCE", "REPOSITORY", "COMMIT", "REVISION", "SEMANTIC_MAJOR", - "ON_DELETE", "RETAIN_OTHER", "KEYED", "PUBLIC_TRAVERSAL", "ID", - "DOC", "MODE", "EMITS", "RECEIVER", "REQUIRES", "ANY", "GET", "SET", - "WATCH", "START", "STOP", "READ", "WRITE", "RESOLVE", "CONNECT", - "DISCONNECT", "CALL", "WATCH_START", "WATCH_STOP", "SUBSCRIBE", - "UNSUBSCRIBE", "OPTIMISTIC_REGISTER", "CRDT", "OPTIONAL_ONE", "EXACTLY_ONE", - "MANY_UNIQUE", "MANY", "ORDERED", "UNIT", "WATCH_HANDLE", "MESSAGE", - "ATOM_REF", "INTERFACE_REF", "OPTIONAL", "LIST", "RECORD", "BOOL", - "BYTES", "DOUBLE", "INT32", "INT64", "STRING", "UINT32", "UINT64", - "TRUE", "FALSE", "NULL", "ARROW", "COLON", "SEMI", "COMMA", "DOT", - "LBRACE", "RBRACE", "LBRACK", "RBRACK", "LPAREN", "RPAREN", "LT", - "GT", "AMP", "EQUAL", "INTEGER", "JSON_NUMBER", "IDENTIFIER", "STRING_LITERAL", - "LINE_COMMENT", "BLOCK_COMMENT", "WS" + null, "EDIT", "MODULE", "CONTRACT", "PRESENTATION", "SUBJECT", "FOR", + "CREATION", "VARIANT", "PREPARES", "INITIALIZE", "WORKSPACE", "EXECUTION", + "SNAPSHOT", "TRANSACTION", "REMOTE", "TARGETS", "PORTABLE", "REPLICA", + "LOCAL", "FROM", "RESULT_BYTES", "RPC_CALLS", "DEADLINE_MS", "QUERY", + "REFLECTION", "SPECIALIZE", "ROOT", "DOCUMENT", "FRAGMENTS", "VIEW", + "MAX", "ALLOW", "POLL", "QUERYABLE", "RPC", "QUERY_REASON", "TYPE", + "OBJECT", "STORABLE", "IMPLEMENTS", "REF", "FRAGMENT", "IMPORT", + "EXTERNAL", "SYMBOL", "ATOM", "INTERFACE", "INTERFACES", "PACKAGE", + "VALUE", "FIELD", "RELATION", "OPERATION", "FUNCTION", "CONSTRUCTOR", + "CONSTRUCTS", "INPUT", "CONFORM", "AS", "BIND", "STATIC", "TO", + "PRIVATE", "SHARED", "STATE", "EDGE", "PROJECTION", "WITH", "USING", + "ACCEPT", "VIA", "MATERIALIZE", "IF", "ABSENT", "ON", "POLICY", + "DEFAULT", "SOURCE", "REPOSITORY", "COMMIT", "REVISION", "SEMANTIC_MAJOR", + "ON_DELETE", "LIFECYCLE", "JOURNAL", "SUBSCRIPTION", "RETAIN_OTHER", + "KEYED", "PUBLIC_TRAVERSAL", "ID", "DOC", "MODE", "EMITS", "RECEIVER", + "REQUIRES", "ANY", "GET", "SET", "WATCH", "START", "STOP", "READ", + "WRITE", "RESOLVE", "CONNECT", "DISCONNECT", "CALL", "WATCH_START", + "WATCH_STOP", "SUBSCRIBE", "UNSUBSCRIBE", "REGISTER", "CRDT", "OPTIONAL_ONE", + "EXACTLY_ONE", "MANY_UNIQUE", "MANY", "ORDERED", "UNIT", "WATCH_HANDLE", + "MESSAGE", "ATOM_REF", "INTERFACE_REF", "OPTIONAL", "LIST", "RECORD", + "BOOL", "BYTES", "DOUBLE", "INT32", "INT64", "STRING", "UINT32", + "UINT64", "TRUE", "FALSE", "NULL", "ARROW", "COLON", "SEMI", "COMMA", + "DOT", "LBRACE", "RBRACE", "LBRACK", "RBRACK", "LPAREN", "RPAREN", + "LT", "GT", "AMP", "EQUAL", "INTEGER", "JSON_NUMBER", "IDENTIFIER", + "STRING_LITERAL", "LINE_COMMENT", "BLOCK_COMMENT", "WS" ]; public static readonly ruleNames = [ "document", "fragmentDecl", "sourceImportDecl", "workspaceDecl", "workspaceItem", "resourceImportDecl", "externalAtomDecl", "externalInterfaceDecl", - "resourcePreamble", "atomDecl", "interfaceResourceDecl", "typeParameters", - "typeParameter", "interfaceType", "typeArguments", "typeArgument", - "typeAliasDecl", "interfaceMember", "operationMember", "valueMember", - "valueMemberOperation", "relationshipMember", "queryReadContract", - "relationshipOperation", "targetConstraint", "packageResourceDecl", - "packageExport", "packageQuery", "packageQuerySpecialization", "queryClause", - "packageOperationExport", "packageFunctionExport", "packageConstructorExport", + "symbolDecl", "resourcePreamble", "atomDecl", "interfaceResourceDecl", + "typeParameters", "typeParameter", "interfaceType", "typeArguments", + "typeArgument", "typeAliasDecl", "interfaceMember", "moduleMember", + "operationMember", "valueMember", "valueMemberOperation", "relationshipMember", + "queryReadContract", "relationshipOperation", "targetConstraint", + "packageResourceDecl", "packageExport", "moduleExport", "presentationDecl", + "packageQuery", "packageQuerySpecialization", "queryClause", "fragmentVariable", + "packageOperationExport", "packageFunctionExport", "acceptanceClause", + "preparationClause", "executionClause", "selectedOperations", "packageConstructorExport", "eventClause", "operationMode", "receiverRequirement", "identifierList", "dependencyBlock", "dependencyPort", "primitiveList", "primitive", "sharedAttachmentDecl", "attachmentDecl", "stateDecl", "storagePolicy", - "edgeDecl", "edgeEndpoint", "conformanceDecl", "conformanceItem", - "stateFieldBindingDecl", "relationshipMaterializationDecl", "operationBindingDecl", - "memberOperationRef", "operationName", "operationProvider", "statePrimitive", - "edgePrimitive", "dependencyBindingBlock", "dependencyBinding", - "constructorBindingDecl", "valueType", "recordField", "scalarType", - "cardinality", "jsonLiteral", "jsonObject", "jsonMember", "jsonArray", - "identifier", "stringLiteral", + "edgeDecl", "edgeEndpoint", "conformanceDecl", "conformanceSourceArguments", + "conformanceItem", "moduleBindingDecl", "initializationDecl", "initializationField", + "creationOwnerMapping", "stateFieldBindingDecl", "relationshipMaterializationDecl", + "operationBindingDecl", "memberOperationRef", "operationName", "operationProvider", + "acceptanceBinding", "statePrimitive", "edgePrimitive", "dependencyBindingBlock", + "dependencyBinding", "constructorBindingDecl", "valueType", "recordField", + "variantCase", "scalarType", "cardinality", "jsonLiteral", "jsonObject", + "jsonMember", "jsonArray", "identifier", "stringLiteral", ]; public get grammarFileName(): string { return "QuixosCapability.g4"; } @@ -302,42 +356,42 @@ export class QuixosCapabilityParser extends antlr.Parser { let localContext = new DocumentContext(this.context, this.state); this.enterRule(localContext, 0, QuixosCapabilityParser.RULE_document); try { - this.state = 152; + this.state = 184; this.errorHandler.sync(this); switch (this.interpreter.adaptivePredict(this.tokenStream, 0, this.context) ) { case 1: this.enterOuterAlt(localContext, 1); { - this.state = 140; + this.state = 172; this.workspaceDecl(); - this.state = 141; + this.state = 173; this.match(QuixosCapabilityParser.EOF); } break; case 2: this.enterOuterAlt(localContext, 2); { - this.state = 143; + this.state = 175; this.interfaceResourceDecl(); - this.state = 144; + this.state = 176; this.match(QuixosCapabilityParser.EOF); } break; case 3: this.enterOuterAlt(localContext, 3); { - this.state = 146; + this.state = 178; this.packageResourceDecl(); - this.state = 147; + this.state = 179; this.match(QuixosCapabilityParser.EOF); } break; case 4: this.enterOuterAlt(localContext, 4); { - this.state = 149; + this.state = 181; this.fragmentDecl(); - this.state = 150; + this.state = 182; this.match(QuixosCapabilityParser.EOF); } break; @@ -363,25 +417,25 @@ export class QuixosCapabilityParser extends antlr.Parser { try { this.enterOuterAlt(localContext, 1); { - this.state = 154; + this.state = 186; this.match(QuixosCapabilityParser.FRAGMENT); - this.state = 155; + this.state = 187; this.match(QuixosCapabilityParser.LBRACE); - this.state = 159; + this.state = 191; this.errorHandler.sync(this); _la = this.tokenStream.LA(1); - while (((((_la - 17)) & ~0x1F) === 0 && ((1 << (_la - 17)) & 34144577) !== 0)) { + while (_la === 4 || ((((_la - 37)) & ~0x1F) === 0 && ((1 << (_la - 37)) & 136577601) !== 0)) { { { - this.state = 156; + this.state = 188; this.workspaceItem(); } } - this.state = 161; + this.state = 193; this.errorHandler.sync(this); _la = this.tokenStream.LA(1); } - this.state = 162; + this.state = 194; this.match(QuixosCapabilityParser.RBRACE); } } @@ -404,11 +458,11 @@ export class QuixosCapabilityParser extends antlr.Parser { try { this.enterOuterAlt(localContext, 1); { - this.state = 164; + this.state = 196; this.match(QuixosCapabilityParser.IMPORT); - this.state = 165; + this.state = 197; this.stringLiteral(); - this.state = 166; + this.state = 198; this.match(QuixosCapabilityParser.SEMI); } } @@ -432,39 +486,39 @@ export class QuixosCapabilityParser extends antlr.Parser { try { this.enterOuterAlt(localContext, 1); { - this.state = 168; + this.state = 200; this.match(QuixosCapabilityParser.WORKSPACE); - this.state = 169; + this.state = 201; this.identifier(); - this.state = 170; + this.state = 202; this.match(QuixosCapabilityParser.ID); - this.state = 171; + this.state = 203; this.stringLiteral(); - this.state = 172; + this.state = 204; this.match(QuixosCapabilityParser.REVISION); - this.state = 173; + this.state = 205; this.stringLiteral(); - this.state = 174; + this.state = 206; this.match(QuixosCapabilityParser.COMMIT); - this.state = 175; + this.state = 207; this.stringLiteral(); - this.state = 176; + this.state = 208; this.match(QuixosCapabilityParser.LBRACE); - this.state = 180; + this.state = 212; this.errorHandler.sync(this); _la = this.tokenStream.LA(1); - while (((((_la - 17)) & ~0x1F) === 0 && ((1 << (_la - 17)) & 34144577) !== 0)) { + while (_la === 4 || ((((_la - 37)) & ~0x1F) === 0 && ((1 << (_la - 37)) & 136577601) !== 0)) { { { - this.state = 177; + this.state = 209; this.workspaceItem(); } } - this.state = 182; + this.state = 214; this.errorHandler.sync(this); _la = this.tokenStream.LA(1); } - this.state = 183; + this.state = 215; this.match(QuixosCapabilityParser.RBRACE); } } @@ -485,55 +539,62 @@ export class QuixosCapabilityParser extends antlr.Parser { let localContext = new WorkspaceItemContext(this.context, this.state); this.enterRule(localContext, 8, QuixosCapabilityParser.RULE_workspaceItem); try { - this.state = 192; + this.state = 225; this.errorHandler.sync(this); switch (this.interpreter.adaptivePredict(this.tokenStream, 3, this.context) ) { case 1: this.enterOuterAlt(localContext, 1); { - this.state = 185; + this.state = 217; this.sourceImportDecl(); } break; case 2: this.enterOuterAlt(localContext, 2); { - this.state = 186; + this.state = 218; this.atomDecl(); } break; case 3: this.enterOuterAlt(localContext, 3); { - this.state = 187; + this.state = 219; this.resourceImportDecl(); } break; case 4: this.enterOuterAlt(localContext, 4); { - this.state = 188; + this.state = 220; this.sharedAttachmentDecl(); } break; case 5: this.enterOuterAlt(localContext, 5); { - this.state = 189; + this.state = 221; this.conformanceDecl(); } break; case 6: this.enterOuterAlt(localContext, 6); { - this.state = 190; + this.state = 222; this.constructorBindingDecl(); } break; case 7: this.enterOuterAlt(localContext, 7); { - this.state = 191; + this.state = 223; + this.presentationDecl(); + } + break; + case 8: + this.enterOuterAlt(localContext, 8); + { + this.state = 224; this.typeAliasDecl(); } break; @@ -556,32 +617,32 @@ export class QuixosCapabilityParser extends antlr.Parser { let localContext = new ResourceImportDeclContext(this.context, this.state); this.enterRule(localContext, 10, QuixosCapabilityParser.RULE_resourceImportDecl); try { - this.state = 204; + this.state = 237; this.errorHandler.sync(this); switch (this.interpreter.adaptivePredict(this.tokenStream, 4, this.context) ) { case 1: this.enterOuterAlt(localContext, 1); { - this.state = 194; + this.state = 227; this.match(QuixosCapabilityParser.IMPORT); - this.state = 195; + this.state = 228; this.match(QuixosCapabilityParser.INTERFACE); - this.state = 196; + this.state = 229; this.identifier(); - this.state = 197; + this.state = 230; this.match(QuixosCapabilityParser.SEMI); } break; case 2: this.enterOuterAlt(localContext, 2); { - this.state = 199; + this.state = 232; this.match(QuixosCapabilityParser.IMPORT); - this.state = 200; + this.state = 233; this.match(QuixosCapabilityParser.PACKAGE); - this.state = 201; + this.state = 234; this.identifier(); - this.state = 202; + this.state = 235; this.match(QuixosCapabilityParser.SEMI); } break; @@ -606,17 +667,17 @@ export class QuixosCapabilityParser extends antlr.Parser { try { this.enterOuterAlt(localContext, 1); { - this.state = 206; + this.state = 239; this.match(QuixosCapabilityParser.EXTERNAL); - this.state = 207; + this.state = 240; this.match(QuixosCapabilityParser.ATOM); - this.state = 208; + this.state = 241; this.identifier(); - this.state = 209; + this.state = 242; this.match(QuixosCapabilityParser.ID); - this.state = 210; + this.state = 243; this.stringLiteral(); - this.state = 211; + this.state = 244; this.match(QuixosCapabilityParser.SEMI); } } @@ -639,17 +700,48 @@ export class QuixosCapabilityParser extends antlr.Parser { try { this.enterOuterAlt(localContext, 1); { - this.state = 213; + this.state = 246; this.match(QuixosCapabilityParser.EXTERNAL); - this.state = 214; + this.state = 247; this.match(QuixosCapabilityParser.INTERFACE); - this.state = 215; + this.state = 248; this.identifier(); - this.state = 216; + this.state = 249; this.match(QuixosCapabilityParser.REVISION); - this.state = 217; + this.state = 250; this.stringLiteral(); - this.state = 218; + this.state = 251; + this.match(QuixosCapabilityParser.SEMI); + } + } + catch (re) { + if (re instanceof antlr.RecognitionException) { + this.errorHandler.reportError(this, re); + this.errorHandler.recover(this, re); + } else { + throw re; + } + } + finally { + this.exitRule(); + } + return localContext; + } + public symbolDecl(): SymbolDeclContext { + let localContext = new SymbolDeclContext(this.context, this.state); + this.enterRule(localContext, 16, QuixosCapabilityParser.RULE_symbolDecl); + try { + this.enterOuterAlt(localContext, 1); + { + this.state = 253; + this.match(QuixosCapabilityParser.SYMBOL); + this.state = 254; + this.identifier(); + this.state = 255; + this.match(QuixosCapabilityParser.ID); + this.state = 256; + this.stringLiteral(); + this.state = 257; this.match(QuixosCapabilityParser.SEMI); } } @@ -668,36 +760,43 @@ export class QuixosCapabilityParser extends antlr.Parser { } public resourcePreamble(): ResourcePreambleContext { let localContext = new ResourcePreambleContext(this.context, this.state); - this.enterRule(localContext, 16, QuixosCapabilityParser.RULE_resourcePreamble); + this.enterRule(localContext, 18, QuixosCapabilityParser.RULE_resourcePreamble); try { - this.state = 224; + this.state = 264; this.errorHandler.sync(this); switch (this.interpreter.adaptivePredict(this.tokenStream, 5, this.context) ) { case 1: this.enterOuterAlt(localContext, 1); { - this.state = 220; + this.state = 259; this.resourceImportDecl(); } break; case 2: this.enterOuterAlt(localContext, 2); { - this.state = 221; + this.state = 260; this.externalAtomDecl(); } break; case 3: this.enterOuterAlt(localContext, 3); { - this.state = 222; + this.state = 261; this.externalInterfaceDecl(); } break; case 4: this.enterOuterAlt(localContext, 4); { - this.state = 223; + this.state = 262; + this.symbolDecl(); + } + break; + case 5: + this.enterOuterAlt(localContext, 5); + { + this.state = 263; this.typeAliasDecl(); } break; @@ -718,32 +817,32 @@ export class QuixosCapabilityParser extends antlr.Parser { } public atomDecl(): AtomDeclContext { let localContext = new AtomDeclContext(this.context, this.state); - this.enterRule(localContext, 18, QuixosCapabilityParser.RULE_atomDecl); + this.enterRule(localContext, 20, QuixosCapabilityParser.RULE_atomDecl); let _la: number; try { this.enterOuterAlt(localContext, 1); { - this.state = 226; + this.state = 266; this.match(QuixosCapabilityParser.ATOM); - this.state = 227; + this.state = 267; this.identifier(); - this.state = 228; + this.state = 268; this.match(QuixosCapabilityParser.ID); - this.state = 229; + this.state = 269; this.stringLiteral(); - this.state = 232; + this.state = 272; this.errorHandler.sync(this); _la = this.tokenStream.LA(1); - if (_la === 65) { + if (_la === 91) { { - this.state = 230; + this.state = 270; this.match(QuixosCapabilityParser.DOC); - this.state = 231; + this.state = 271; this.stringLiteral(); } } - this.state = 234; + this.state = 274; this.match(QuixosCapabilityParser.SEMI); } } @@ -762,92 +861,92 @@ export class QuixosCapabilityParser extends antlr.Parser { } public interfaceResourceDecl(): InterfaceResourceDeclContext { let localContext = new InterfaceResourceDeclContext(this.context, this.state); - this.enterRule(localContext, 20, QuixosCapabilityParser.RULE_interfaceResourceDecl); + this.enterRule(localContext, 22, QuixosCapabilityParser.RULE_interfaceResourceDecl); let _la: number; try { this.enterOuterAlt(localContext, 1); { - this.state = 239; + this.state = 279; this.errorHandler.sync(this); _la = this.tokenStream.LA(1); - while ((((_la) & ~0x1F) === 0 && ((1 << _la) & 25296896) !== 0)) { + while (((((_la - 37)) & ~0x1F) === 0 && ((1 << (_la - 37)) & 449) !== 0)) { { { - this.state = 236; + this.state = 276; this.resourcePreamble(); } } - this.state = 241; + this.state = 281; this.errorHandler.sync(this); _la = this.tokenStream.LA(1); } - this.state = 242; + this.state = 282; this.match(QuixosCapabilityParser.INTERFACE); - this.state = 243; + this.state = 283; this.identifier(); - this.state = 245; + this.state = 285; this.errorHandler.sync(this); _la = this.tokenStream.LA(1); - if (_la === 123) { + if (_la === 149) { { - this.state = 244; + this.state = 284; this.typeParameters(); } } - this.state = 247; + this.state = 287; this.match(QuixosCapabilityParser.ID); - this.state = 248; + this.state = 288; this.stringLiteral(); - this.state = 249; + this.state = 289; this.match(QuixosCapabilityParser.REVISION); - this.state = 250; + this.state = 290; this.stringLiteral(); - this.state = 260; + this.state = 300; this.errorHandler.sync(this); _la = this.tokenStream.LA(1); - if (_la === 69) { + if (_la === 95) { { - this.state = 251; + this.state = 291; this.match(QuixosCapabilityParser.REQUIRES); - this.state = 252; + this.state = 292; this.interfaceType(); - this.state = 257; + this.state = 297; this.errorHandler.sync(this); _la = this.tokenStream.LA(1); - while (_la === 115) { + while (_la === 141) { { { - this.state = 253; + this.state = 293; this.match(QuixosCapabilityParser.COMMA); - this.state = 254; + this.state = 294; this.interfaceType(); } } - this.state = 259; + this.state = 299; this.errorHandler.sync(this); _la = this.tokenStream.LA(1); } } } - this.state = 262; + this.state = 302; this.match(QuixosCapabilityParser.LBRACE); - this.state = 266; + this.state = 306; this.errorHandler.sync(this); _la = this.tokenStream.LA(1); - while (((((_la - 14)) & ~0x1F) === 0 && ((1 << (_la - 14)) & 33783809) !== 0)) { + while (_la === 2 || ((((_la - 34)) & ~0x1F) === 0 && ((1 << (_la - 34)) & 135200769) !== 0)) { { { - this.state = 263; + this.state = 303; this.interfaceMember(); } } - this.state = 268; + this.state = 308; this.errorHandler.sync(this); _la = this.tokenStream.LA(1); } - this.state = 269; + this.state = 309; this.match(QuixosCapabilityParser.RBRACE); } } @@ -866,32 +965,32 @@ export class QuixosCapabilityParser extends antlr.Parser { } public typeParameters(): TypeParametersContext { let localContext = new TypeParametersContext(this.context, this.state); - this.enterRule(localContext, 22, QuixosCapabilityParser.RULE_typeParameters); + this.enterRule(localContext, 24, QuixosCapabilityParser.RULE_typeParameters); let _la: number; try { this.enterOuterAlt(localContext, 1); { - this.state = 271; + this.state = 311; this.match(QuixosCapabilityParser.LT); - this.state = 272; + this.state = 312; this.typeParameter(); - this.state = 277; + this.state = 317; this.errorHandler.sync(this); _la = this.tokenStream.LA(1); - while (_la === 115) { + while (_la === 141) { { { - this.state = 273; + this.state = 313; this.match(QuixosCapabilityParser.COMMA); - this.state = 274; + this.state = 314; this.typeParameter(); } } - this.state = 279; + this.state = 319; this.errorHandler.sync(this); _la = this.tokenStream.LA(1); } - this.state = 280; + this.state = 320; this.match(QuixosCapabilityParser.GT); } } @@ -910,27 +1009,27 @@ export class QuixosCapabilityParser extends antlr.Parser { } public typeParameter(): TypeParameterContext { let localContext = new TypeParameterContext(this.context, this.state); - this.enterRule(localContext, 24, QuixosCapabilityParser.RULE_typeParameter); + this.enterRule(localContext, 26, QuixosCapabilityParser.RULE_typeParameter); let _la: number; try { - this.state = 301; + this.state = 341; this.errorHandler.sync(this); switch (this.tokenStream.LA(1)) { case QuixosCapabilityParser.VALUE: this.enterOuterAlt(localContext, 1); { - this.state = 282; + this.state = 322; this.match(QuixosCapabilityParser.VALUE); - this.state = 283; + this.state = 323; this.identifier(); - this.state = 286; + this.state = 326; this.errorHandler.sync(this); _la = this.tokenStream.LA(1); - if (_la === 113) { + if (_la === 139) { { - this.state = 284; + this.state = 324; this.match(QuixosCapabilityParser.COLON); - this.state = 285; + this.state = 325; this.match(QuixosCapabilityParser.STORABLE); } } @@ -940,32 +1039,32 @@ export class QuixosCapabilityParser extends antlr.Parser { case QuixosCapabilityParser.OBJECT: this.enterOuterAlt(localContext, 2); { - this.state = 288; + this.state = 328; this.match(QuixosCapabilityParser.OBJECT); - this.state = 289; + this.state = 329; this.identifier(); - this.state = 299; + this.state = 339; this.errorHandler.sync(this); _la = this.tokenStream.LA(1); - if (_la === 20) { + if (_la === 40) { { - this.state = 290; + this.state = 330; this.match(QuixosCapabilityParser.IMPLEMENTS); - this.state = 291; + this.state = 331; this.interfaceType(); - this.state = 296; + this.state = 336; this.errorHandler.sync(this); _la = this.tokenStream.LA(1); - while (_la === 125) { + while (_la === 151) { { { - this.state = 292; + this.state = 332; this.match(QuixosCapabilityParser.AMP); - this.state = 293; + this.state = 333; this.interfaceType(); } } - this.state = 298; + this.state = 338; this.errorHandler.sync(this); _la = this.tokenStream.LA(1); } @@ -993,19 +1092,19 @@ export class QuixosCapabilityParser extends antlr.Parser { } public interfaceType(): InterfaceTypeContext { let localContext = new InterfaceTypeContext(this.context, this.state); - this.enterRule(localContext, 26, QuixosCapabilityParser.RULE_interfaceType); + this.enterRule(localContext, 28, QuixosCapabilityParser.RULE_interfaceType); let _la: number; try { this.enterOuterAlt(localContext, 1); { - this.state = 303; + this.state = 343; this.identifier(); - this.state = 305; + this.state = 345; this.errorHandler.sync(this); _la = this.tokenStream.LA(1); - if (_la === 123) { + if (_la === 149) { { - this.state = 304; + this.state = 344; this.typeArguments(); } } @@ -1027,32 +1126,32 @@ export class QuixosCapabilityParser extends antlr.Parser { } public typeArguments(): TypeArgumentsContext { let localContext = new TypeArgumentsContext(this.context, this.state); - this.enterRule(localContext, 28, QuixosCapabilityParser.RULE_typeArguments); + this.enterRule(localContext, 30, QuixosCapabilityParser.RULE_typeArguments); let _la: number; try { this.enterOuterAlt(localContext, 1); { - this.state = 307; + this.state = 347; this.match(QuixosCapabilityParser.LT); - this.state = 308; + this.state = 348; this.typeArgument(); - this.state = 313; + this.state = 353; this.errorHandler.sync(this); _la = this.tokenStream.LA(1); - while (_la === 115) { + while (_la === 141) { { { - this.state = 309; + this.state = 349; this.match(QuixosCapabilityParser.COMMA); - this.state = 310; + this.state = 350; this.typeArgument(); } } - this.state = 315; + this.state = 355; this.errorHandler.sync(this); _la = this.tokenStream.LA(1); } - this.state = 316; + this.state = 356; this.match(QuixosCapabilityParser.GT); } } @@ -1071,42 +1170,59 @@ export class QuixosCapabilityParser extends antlr.Parser { } public typeArgument(): TypeArgumentContext { let localContext = new TypeArgumentContext(this.context, this.state); - this.enterRule(localContext, 30, QuixosCapabilityParser.RULE_typeArgument); + this.enterRule(localContext, 32, QuixosCapabilityParser.RULE_typeArgument); try { - this.state = 325; + this.state = 365; this.errorHandler.sync(this); switch (this.tokenStream.LA(1)) { case QuixosCapabilityParser.ATOM: this.enterOuterAlt(localContext, 1); { - this.state = 318; + this.state = 358; this.match(QuixosCapabilityParser.ATOM); - this.state = 319; + this.state = 359; this.identifier(); } break; case QuixosCapabilityParser.INTERFACE: this.enterOuterAlt(localContext, 2); { - this.state = 320; + this.state = 360; this.match(QuixosCapabilityParser.INTERFACE); - this.state = 321; + this.state = 361; this.interfaceType(); } break; case QuixosCapabilityParser.OBJECT: this.enterOuterAlt(localContext, 3); { - this.state = 322; + this.state = 362; this.match(QuixosCapabilityParser.OBJECT); - this.state = 323; + this.state = 363; this.identifier(); } break; + case QuixosCapabilityParser.EDIT: + case QuixosCapabilityParser.PRESENTATION: + case QuixosCapabilityParser.SUBJECT: + case QuixosCapabilityParser.FOR: + case QuixosCapabilityParser.CREATION: + case QuixosCapabilityParser.VARIANT: + case QuixosCapabilityParser.INITIALIZE: + case QuixosCapabilityParser.EXECUTION: + case QuixosCapabilityParser.SNAPSHOT: + case QuixosCapabilityParser.TRANSACTION: + case QuixosCapabilityParser.REMOTE: + case QuixosCapabilityParser.TARGETS: + case QuixosCapabilityParser.PORTABLE: + case QuixosCapabilityParser.REPLICA: + case QuixosCapabilityParser.LOCAL: + case QuixosCapabilityParser.FROM: case QuixosCapabilityParser.RESULT_BYTES: case QuixosCapabilityParser.RPC_CALLS: case QuixosCapabilityParser.DEADLINE_MS: case QuixosCapabilityParser.QUERY: + case QuixosCapabilityParser.REFLECTION: case QuixosCapabilityParser.SPECIALIZE: case QuixosCapabilityParser.ROOT: case QuixosCapabilityParser.DOCUMENT: @@ -1118,7 +1234,15 @@ export class QuixosCapabilityParser extends antlr.Parser { case QuixosCapabilityParser.QUERYABLE: case QuixosCapabilityParser.RPC: case QuixosCapabilityParser.REF: + case QuixosCapabilityParser.FRAGMENT: + case QuixosCapabilityParser.INTERFACES: case QuixosCapabilityParser.SOURCE: + case QuixosCapabilityParser.JOURNAL: + case QuixosCapabilityParser.SUBSCRIPTION: + case QuixosCapabilityParser.START: + case QuixosCapabilityParser.STOP: + case QuixosCapabilityParser.CONNECT: + case QuixosCapabilityParser.REGISTER: case QuixosCapabilityParser.UNIT: case QuixosCapabilityParser.WATCH_HANDLE: case QuixosCapabilityParser.MESSAGE: @@ -1138,7 +1262,7 @@ export class QuixosCapabilityParser extends antlr.Parser { case QuixosCapabilityParser.IDENTIFIER: this.enterOuterAlt(localContext, 4); { - this.state = 324; + this.state = 364; this.valueType(); } break; @@ -1161,30 +1285,30 @@ export class QuixosCapabilityParser extends antlr.Parser { } public typeAliasDecl(): TypeAliasDeclContext { let localContext = new TypeAliasDeclContext(this.context, this.state); - this.enterRule(localContext, 32, QuixosCapabilityParser.RULE_typeAliasDecl); + this.enterRule(localContext, 34, QuixosCapabilityParser.RULE_typeAliasDecl); let _la: number; try { this.enterOuterAlt(localContext, 1); { - this.state = 327; + this.state = 367; this.match(QuixosCapabilityParser.TYPE); - this.state = 328; + this.state = 368; this.identifier(); - this.state = 330; + this.state = 370; this.errorHandler.sync(this); _la = this.tokenStream.LA(1); - if (_la === 123) { + if (_la === 149) { { - this.state = 329; + this.state = 369; this.typeParameters(); } } - this.state = 332; + this.state = 372; this.match(QuixosCapabilityParser.EQUAL); - this.state = 333; + this.state = 373; this.valueType(); - this.state = 334; + this.state = 374; this.match(QuixosCapabilityParser.SEMI); } } @@ -1203,32 +1327,39 @@ export class QuixosCapabilityParser extends antlr.Parser { } public interfaceMember(): InterfaceMemberContext { let localContext = new InterfaceMemberContext(this.context, this.state); - this.enterRule(localContext, 34, QuixosCapabilityParser.RULE_interfaceMember); + this.enterRule(localContext, 36, QuixosCapabilityParser.RULE_interfaceMember); try { - this.state = 339; + this.state = 380; this.errorHandler.sync(this); switch (this.interpreter.adaptivePredict(this.tokenStream, 21, this.context) ) { case 1: this.enterOuterAlt(localContext, 1); { - this.state = 336; + this.state = 376; this.valueMember(); } break; case 2: this.enterOuterAlt(localContext, 2); { - this.state = 337; + this.state = 377; this.relationshipMember(); } break; case 3: this.enterOuterAlt(localContext, 3); { - this.state = 338; + this.state = 378; this.operationMember(); } break; + case 4: + this.enterOuterAlt(localContext, 4); + { + this.state = 379; + this.moduleMember(); + } + break; } } catch (re) { @@ -1244,50 +1375,108 @@ export class QuixosCapabilityParser extends antlr.Parser { } return localContext; } - public operationMember(): OperationMemberContext { - let localContext = new OperationMemberContext(this.context, this.state); - this.enterRule(localContext, 36, QuixosCapabilityParser.RULE_operationMember); + public moduleMember(): ModuleMemberContext { + let localContext = new ModuleMemberContext(this.context, this.state); + this.enterRule(localContext, 38, QuixosCapabilityParser.RULE_moduleMember); let _la: number; try { this.enterOuterAlt(localContext, 1); { - this.state = 342; + this.state = 382; + this.match(QuixosCapabilityParser.MODULE); + this.state = 383; + this.identifier(); + this.state = 384; + this.match(QuixosCapabilityParser.ID); + this.state = 385; + this.stringLiteral(); + this.state = 386; + this.match(QuixosCapabilityParser.CONTRACT); + this.state = 387; + this.stringLiteral(); + this.state = 390; this.errorHandler.sync(this); _la = this.tokenStream.LA(1); - if (_la === 39) { + if (_la === 5) { { - this.state = 341; + this.state = 388; + this.match(QuixosCapabilityParser.SUBJECT); + this.state = 389; + this.targetConstraint(); + } + } + + this.state = 392; + this.match(QuixosCapabilityParser.SEMI); + } + } + catch (re) { + if (re instanceof antlr.RecognitionException) { + this.errorHandler.reportError(this, re); + this.errorHandler.recover(this, re); + } else { + throw re; + } + } + finally { + this.exitRule(); + } + return localContext; + } + public operationMember(): OperationMemberContext { + let localContext = new OperationMemberContext(this.context, this.state); + this.enterRule(localContext, 40, QuixosCapabilityParser.RULE_operationMember); + let _la: number; + try { + this.enterOuterAlt(localContext, 1); + { + this.state = 395; + this.errorHandler.sync(this); + _la = this.tokenStream.LA(1); + if (_la === 61) { + { + this.state = 394; this.match(QuixosCapabilityParser.STATIC); } } - this.state = 344; + this.state = 397; this.match(QuixosCapabilityParser.OPERATION); - this.state = 345; + this.state = 398; this.identifier(); - this.state = 346; + this.state = 399; this.match(QuixosCapabilityParser.ID); - this.state = 347; + this.state = 400; this.stringLiteral(); - this.state = 348; + this.state = 401; this.match(QuixosCapabilityParser.COLON); - this.state = 349; + this.state = 402; this.valueType(); - this.state = 350; + this.state = 403; this.match(QuixosCapabilityParser.ARROW); - this.state = 351; + this.state = 404; this.valueType(); - this.state = 352; + this.state = 405; this.match(QuixosCapabilityParser.LBRACE); - this.state = 353; + this.state = 406; this.match(QuixosCapabilityParser.CALL); - this.state = 354; + this.state = 407; this.match(QuixosCapabilityParser.ID); - this.state = 355; + this.state = 408; this.stringLiteral(); - this.state = 356; + this.state = 410; + this.errorHandler.sync(this); + _la = this.tokenStream.LA(1); + if (_la === 12) { + { + this.state = 409; + this.executionClause(); + } + } + + this.state = 412; this.match(QuixosCapabilityParser.SEMI); - this.state = 357; + this.state = 413; this.match(QuixosCapabilityParser.RBRACE); } } @@ -1306,50 +1495,57 @@ export class QuixosCapabilityParser extends antlr.Parser { } public valueMember(): ValueMemberContext { let localContext = new ValueMemberContext(this.context, this.state); - this.enterRule(localContext, 38, QuixosCapabilityParser.RULE_valueMember); + this.enterRule(localContext, 42, QuixosCapabilityParser.RULE_valueMember); let _la: number; try { this.enterOuterAlt(localContext, 1); { - this.state = 360; + this.state = 416; this.errorHandler.sync(this); _la = this.tokenStream.LA(1); - if (_la === 14) { + if (_la === 34) { { - this.state = 359; + this.state = 415; this.queryReadContract(); } } - this.state = 362; - this.match(QuixosCapabilityParser.VALUE); - this.state = 363; + this.state = 418; + _la = this.tokenStream.LA(1); + if(!(_la === 50 || _la === 51)) { + this.errorHandler.recoverInline(this); + } + else { + this.errorHandler.reportMatch(this); + this.consume(); + } + this.state = 419; this.identifier(); - this.state = 364; + this.state = 420; this.match(QuixosCapabilityParser.ID); - this.state = 365; + this.state = 421; this.stringLiteral(); - this.state = 366; + this.state = 422; this.match(QuixosCapabilityParser.COLON); - this.state = 367; + this.state = 423; this.valueType(); - this.state = 368; + this.state = 424; this.match(QuixosCapabilityParser.LBRACE); - this.state = 372; + this.state = 428; this.errorHandler.sync(this); _la = this.tokenStream.LA(1); - while (((((_la - 71)) & ~0x1F) === 0 && ((1 << (_la - 71)) & 7) !== 0)) { + while (_la === 1 || ((((_la - 97)) & ~0x1F) === 0 && ((1 << (_la - 97)) & 7) !== 0)) { { { - this.state = 369; + this.state = 425; this.valueMemberOperation(); } } - this.state = 374; + this.state = 430; this.errorHandler.sync(this); _la = this.tokenStream.LA(1); } - this.state = 375; + this.state = 431; this.match(QuixosCapabilityParser.RBRACE); } } @@ -1368,55 +1564,99 @@ export class QuixosCapabilityParser extends antlr.Parser { } public valueMemberOperation(): ValueMemberOperationContext { let localContext = new ValueMemberOperationContext(this.context, this.state); - this.enterRule(localContext, 40, QuixosCapabilityParser.RULE_valueMemberOperation); + this.enterRule(localContext, 44, QuixosCapabilityParser.RULE_valueMemberOperation); + let _la: number; try { - this.state = 396; + this.state = 466; this.errorHandler.sync(this); switch (this.tokenStream.LA(1)) { case QuixosCapabilityParser.GET: this.enterOuterAlt(localContext, 1); { - this.state = 377; + this.state = 433; this.match(QuixosCapabilityParser.GET); - this.state = 378; + this.state = 434; this.match(QuixosCapabilityParser.ID); - this.state = 379; + this.state = 435; this.stringLiteral(); - this.state = 380; + this.state = 437; + this.errorHandler.sync(this); + _la = this.tokenStream.LA(1); + if (_la === 12) { + { + this.state = 436; + this.executionClause(); + } + } + + this.state = 439; this.match(QuixosCapabilityParser.SEMI); } break; case QuixosCapabilityParser.SET: this.enterOuterAlt(localContext, 2); { - this.state = 382; + this.state = 441; this.match(QuixosCapabilityParser.SET); - this.state = 383; + this.state = 442; this.match(QuixosCapabilityParser.ID); - this.state = 384; + this.state = 443; this.stringLiteral(); - this.state = 385; + this.state = 445; + this.errorHandler.sync(this); + _la = this.tokenStream.LA(1); + if (_la === 12) { + { + this.state = 444; + this.executionClause(); + } + } + + this.state = 447; + this.match(QuixosCapabilityParser.SEMI); + } + break; + case QuixosCapabilityParser.EDIT: + this.enterOuterAlt(localContext, 3); + { + this.state = 449; + this.match(QuixosCapabilityParser.EDIT); + this.state = 450; + this.match(QuixosCapabilityParser.ID); + this.state = 451; + this.stringLiteral(); + this.state = 453; + this.errorHandler.sync(this); + _la = this.tokenStream.LA(1); + if (_la === 12) { + { + this.state = 452; + this.executionClause(); + } + } + + this.state = 455; this.match(QuixosCapabilityParser.SEMI); } break; case QuixosCapabilityParser.WATCH: - this.enterOuterAlt(localContext, 3); + this.enterOuterAlt(localContext, 4); { - this.state = 387; + this.state = 457; this.match(QuixosCapabilityParser.WATCH); - this.state = 388; + this.state = 458; this.match(QuixosCapabilityParser.START); - this.state = 389; + this.state = 459; this.match(QuixosCapabilityParser.ID); - this.state = 390; + this.state = 460; this.stringLiteral(); - this.state = 391; + this.state = 461; this.match(QuixosCapabilityParser.STOP); - this.state = 392; + this.state = 462; this.match(QuixosCapabilityParser.ID); - this.state = 393; + this.state = 463; this.stringLiteral(); - this.state = 394; + this.state = 464; this.match(QuixosCapabilityParser.SEMI); } break; @@ -1439,74 +1679,74 @@ export class QuixosCapabilityParser extends antlr.Parser { } public relationshipMember(): RelationshipMemberContext { let localContext = new RelationshipMemberContext(this.context, this.state); - this.enterRule(localContext, 42, QuixosCapabilityParser.RULE_relationshipMember); + this.enterRule(localContext, 46, QuixosCapabilityParser.RULE_relationshipMember); let _la: number; try { this.enterOuterAlt(localContext, 1); { - this.state = 399; + this.state = 469; this.errorHandler.sync(this); _la = this.tokenStream.LA(1); - if (_la === 14) { + if (_la === 34) { { - this.state = 398; + this.state = 468; this.queryReadContract(); } } - this.state = 401; + this.state = 471; this.match(QuixosCapabilityParser.RELATION); - this.state = 402; + this.state = 472; this.identifier(); - this.state = 403; + this.state = 473; this.match(QuixosCapabilityParser.ID); - this.state = 404; + this.state = 474; this.stringLiteral(); - this.state = 405; + this.state = 475; this.match(QuixosCapabilityParser.COLON); - this.state = 406; + this.state = 476; this.cardinality(); - this.state = 407; + this.state = 477; this.targetConstraint(); - this.state = 409; + this.state = 479; this.errorHandler.sync(this); _la = this.tokenStream.LA(1); - if (_la === 92) { + if (_la === 118) { { - this.state = 408; + this.state = 478; this.match(QuixosCapabilityParser.ORDERED); } } - this.state = 413; + this.state = 483; this.errorHandler.sync(this); _la = this.tokenStream.LA(1); - if (_la === 62) { + if (_la === 88) { { - this.state = 411; + this.state = 481; this.match(QuixosCapabilityParser.KEYED); - this.state = 412; + this.state = 482; this.stringLiteral(); } } - this.state = 415; + this.state = 485; this.match(QuixosCapabilityParser.LBRACE); - this.state = 419; + this.state = 489; this.errorHandler.sync(this); _la = this.tokenStream.LA(1); - while (((((_la - 73)) & ~0x1F) === 0 && ((1 << (_la - 73)) & 225) !== 0)) { + while (((((_la - 99)) & ~0x1F) === 0 && ((1 << (_la - 99)) & 225) !== 0)) { { { - this.state = 416; + this.state = 486; this.relationshipOperation(); } } - this.state = 421; + this.state = 491; this.errorHandler.sync(this); _la = this.tokenStream.LA(1); } - this.state = 422; + this.state = 492; this.match(QuixosCapabilityParser.RBRACE); } } @@ -1525,20 +1765,27 @@ export class QuixosCapabilityParser extends antlr.Parser { } public queryReadContract(): QueryReadContractContext { let localContext = new QueryReadContractContext(this.context, this.state); - this.enterRule(localContext, 44, QuixosCapabilityParser.RULE_queryReadContract); + this.enterRule(localContext, 48, QuixosCapabilityParser.RULE_queryReadContract); let _la: number; try { this.enterOuterAlt(localContext, 1); { - this.state = 424; + this.state = 494; this.match(QuixosCapabilityParser.QUERYABLE); - this.state = 426; + this.state = 496; this.errorHandler.sync(this); _la = this.tokenStream.LA(1); - if (_la === 15) { + if (_la === 17 || _la === 35) { { - this.state = 425; - this.match(QuixosCapabilityParser.RPC); + this.state = 495; + _la = this.tokenStream.LA(1); + if(!(_la === 17 || _la === 35)) { + this.errorHandler.recoverInline(this); + } + else { + this.errorHandler.reportMatch(this); + this.consume(); + } } } @@ -1559,68 +1806,99 @@ export class QuixosCapabilityParser extends antlr.Parser { } public relationshipOperation(): RelationshipOperationContext { let localContext = new RelationshipOperationContext(this.context, this.state); - this.enterRule(localContext, 46, QuixosCapabilityParser.RULE_relationshipOperation); + this.enterRule(localContext, 50, QuixosCapabilityParser.RULE_relationshipOperation); + let _la: number; try { - this.state = 452; + this.state = 531; this.errorHandler.sync(this); switch (this.tokenStream.LA(1)) { case QuixosCapabilityParser.RESOLVE: this.enterOuterAlt(localContext, 1); { - this.state = 428; + this.state = 498; this.match(QuixosCapabilityParser.RESOLVE); - this.state = 429; + this.state = 499; this.match(QuixosCapabilityParser.ID); - this.state = 430; + this.state = 500; this.stringLiteral(); - this.state = 431; + this.state = 502; + this.errorHandler.sync(this); + _la = this.tokenStream.LA(1); + if (_la === 12) { + { + this.state = 501; + this.executionClause(); + } + } + + this.state = 504; this.match(QuixosCapabilityParser.SEMI); } break; case QuixosCapabilityParser.CONNECT: this.enterOuterAlt(localContext, 2); { - this.state = 433; + this.state = 506; this.match(QuixosCapabilityParser.CONNECT); - this.state = 434; + this.state = 507; this.match(QuixosCapabilityParser.ID); - this.state = 435; + this.state = 508; this.stringLiteral(); - this.state = 436; + this.state = 510; + this.errorHandler.sync(this); + _la = this.tokenStream.LA(1); + if (_la === 12) { + { + this.state = 509; + this.executionClause(); + } + } + + this.state = 512; this.match(QuixosCapabilityParser.SEMI); } break; case QuixosCapabilityParser.DISCONNECT: this.enterOuterAlt(localContext, 3); { - this.state = 438; + this.state = 514; this.match(QuixosCapabilityParser.DISCONNECT); - this.state = 439; + this.state = 515; this.match(QuixosCapabilityParser.ID); - this.state = 440; + this.state = 516; this.stringLiteral(); - this.state = 441; + this.state = 518; + this.errorHandler.sync(this); + _la = this.tokenStream.LA(1); + if (_la === 12) { + { + this.state = 517; + this.executionClause(); + } + } + + this.state = 520; this.match(QuixosCapabilityParser.SEMI); } break; case QuixosCapabilityParser.WATCH: this.enterOuterAlt(localContext, 4); { - this.state = 443; + this.state = 522; this.match(QuixosCapabilityParser.WATCH); - this.state = 444; + this.state = 523; this.match(QuixosCapabilityParser.START); - this.state = 445; + this.state = 524; this.match(QuixosCapabilityParser.ID); - this.state = 446; + this.state = 525; this.stringLiteral(); - this.state = 447; + this.state = 526; this.match(QuixosCapabilityParser.STOP); - this.state = 448; + this.state = 527; this.match(QuixosCapabilityParser.ID); - this.state = 449; + this.state = 528; this.stringLiteral(); - this.state = 450; + this.state = 529; this.match(QuixosCapabilityParser.SEMI); } break; @@ -1643,34 +1921,34 @@ export class QuixosCapabilityParser extends antlr.Parser { } public targetConstraint(): TargetConstraintContext { let localContext = new TargetConstraintContext(this.context, this.state); - this.enterRule(localContext, 48, QuixosCapabilityParser.RULE_targetConstraint); + this.enterRule(localContext, 52, QuixosCapabilityParser.RULE_targetConstraint); let _la: number; try { - this.state = 463; + this.state = 542; this.errorHandler.sync(this); switch (this.tokenStream.LA(1)) { case QuixosCapabilityParser.ATOM: this.enterOuterAlt(localContext, 1); { - this.state = 454; + this.state = 533; this.match(QuixosCapabilityParser.ATOM); - this.state = 455; + this.state = 534; this.identifier(); } break; case QuixosCapabilityParser.INTERFACE: this.enterOuterAlt(localContext, 2); { - this.state = 456; + this.state = 535; this.match(QuixosCapabilityParser.INTERFACE); - this.state = 457; + this.state = 536; this.identifier(); - this.state = 459; + this.state = 538; this.errorHandler.sync(this); _la = this.tokenStream.LA(1); - if (_la === 123) { + if (_la === 149) { { - this.state = 458; + this.state = 537; this.typeArguments(); } } @@ -1680,9 +1958,9 @@ export class QuixosCapabilityParser extends antlr.Parser { case QuixosCapabilityParser.OBJECT: this.enterOuterAlt(localContext, 3); { - this.state = 461; + this.state = 540; this.match(QuixosCapabilityParser.OBJECT); - this.state = 462; + this.state = 541; this.identifier(); } break; @@ -1705,66 +1983,66 @@ export class QuixosCapabilityParser extends antlr.Parser { } public packageResourceDecl(): PackageResourceDeclContext { let localContext = new PackageResourceDeclContext(this.context, this.state); - this.enterRule(localContext, 50, QuixosCapabilityParser.RULE_packageResourceDecl); + this.enterRule(localContext, 54, QuixosCapabilityParser.RULE_packageResourceDecl); let _la: number; try { this.enterOuterAlt(localContext, 1); { - this.state = 468; + this.state = 547; this.errorHandler.sync(this); _la = this.tokenStream.LA(1); - while ((((_la) & ~0x1F) === 0 && ((1 << _la) & 25296896) !== 0)) { + while (((((_la - 37)) & ~0x1F) === 0 && ((1 << (_la - 37)) & 449) !== 0)) { { { - this.state = 465; + this.state = 544; this.resourcePreamble(); } } - this.state = 470; + this.state = 549; this.errorHandler.sync(this); _la = this.tokenStream.LA(1); } - this.state = 471; + this.state = 550; this.match(QuixosCapabilityParser.PACKAGE); - this.state = 472; + this.state = 551; this.identifier(); - this.state = 473; + this.state = 552; this.match(QuixosCapabilityParser.ID); - this.state = 474; + this.state = 553; this.stringLiteral(); - this.state = 475; + this.state = 554; this.match(QuixosCapabilityParser.REVISION); - this.state = 476; + this.state = 555; this.stringLiteral(); - this.state = 479; + this.state = 558; this.errorHandler.sync(this); _la = this.tokenStream.LA(1); - if (_la === 59) { + if (_la === 82) { { - this.state = 477; + this.state = 556; this.match(QuixosCapabilityParser.SEMANTIC_MAJOR); - this.state = 478; + this.state = 557; this.match(QuixosCapabilityParser.INTEGER); } } - this.state = 481; + this.state = 560; this.match(QuixosCapabilityParser.LBRACE); - this.state = 485; + this.state = 564; this.errorHandler.sync(this); _la = this.tokenStream.LA(1); - while (((((_la - 5)) & ~0x1F) === 0 && ((1 << (_la - 5)) & 469762049) !== 0)) { + while (_la === 2 || _la === 24 || ((((_la - 53)) & ~0x1F) === 0 && ((1 << (_la - 53)) & 7) !== 0)) { { { - this.state = 482; + this.state = 561; this.packageExport(); } } - this.state = 487; + this.state = 566; this.errorHandler.sync(this); _la = this.tokenStream.LA(1); } - this.state = 488; + this.state = 567; this.match(QuixosCapabilityParser.RBRACE); } } @@ -1783,46 +2061,135 @@ export class QuixosCapabilityParser extends antlr.Parser { } public packageExport(): PackageExportContext { let localContext = new PackageExportContext(this.context, this.state); - this.enterRule(localContext, 52, QuixosCapabilityParser.RULE_packageExport); + this.enterRule(localContext, 56, QuixosCapabilityParser.RULE_packageExport); try { - this.state = 495; + this.state = 575; this.errorHandler.sync(this); - switch (this.interpreter.adaptivePredict(this.tokenStream, 37, this.context) ) { + switch (this.interpreter.adaptivePredict(this.tokenStream, 45, this.context) ) { case 1: this.enterOuterAlt(localContext, 1); { - this.state = 490; + this.state = 569; this.packageOperationExport(); } break; case 2: this.enterOuterAlt(localContext, 2); { - this.state = 491; + this.state = 570; this.packageFunctionExport(); } break; case 3: this.enterOuterAlt(localContext, 3); { - this.state = 492; + this.state = 571; this.packageConstructorExport(); } break; case 4: this.enterOuterAlt(localContext, 4); { - this.state = 493; + this.state = 572; this.packageQuery(); } break; case 5: this.enterOuterAlt(localContext, 5); { - this.state = 494; + this.state = 573; this.packageQuerySpecialization(); } break; + case 6: + this.enterOuterAlt(localContext, 6); + { + this.state = 574; + this.moduleExport(); + } + break; + } + } + catch (re) { + if (re instanceof antlr.RecognitionException) { + this.errorHandler.reportError(this, re); + this.errorHandler.recover(this, re); + } else { + throw re; + } + } + finally { + this.exitRule(); + } + return localContext; + } + public moduleExport(): ModuleExportContext { + let localContext = new ModuleExportContext(this.context, this.state); + this.enterRule(localContext, 58, QuixosCapabilityParser.RULE_moduleExport); + try { + this.enterOuterAlt(localContext, 1); + { + this.state = 577; + this.match(QuixosCapabilityParser.MODULE); + this.state = 578; + this.identifier(); + this.state = 579; + this.match(QuixosCapabilityParser.ID); + this.state = 580; + this.stringLiteral(); + this.state = 581; + this.match(QuixosCapabilityParser.IMPLEMENTS); + this.state = 582; + this.interfaceType(); + this.state = 583; + this.match(QuixosCapabilityParser.DOT); + this.state = 584; + this.identifier(); + this.state = 585; + this.match(QuixosCapabilityParser.SEMI); + } + } + catch (re) { + if (re instanceof antlr.RecognitionException) { + this.errorHandler.reportError(this, re); + this.errorHandler.recover(this, re); + } else { + throw re; + } + } + finally { + this.exitRule(); + } + return localContext; + } + public presentationDecl(): PresentationDeclContext { + let localContext = new PresentationDeclContext(this.context, this.state); + this.enterRule(localContext, 60, QuixosCapabilityParser.RULE_presentationDecl); + try { + this.enterOuterAlt(localContext, 1); + { + this.state = 587; + this.match(QuixosCapabilityParser.PRESENTATION); + this.state = 588; + this.identifier(); + this.state = 589; + this.match(QuixosCapabilityParser.ID); + this.state = 590; + this.stringLiteral(); + this.state = 591; + this.match(QuixosCapabilityParser.FOR); + this.state = 592; + this.interfaceType(); + this.state = 593; + this.match(QuixosCapabilityParser.USING); + this.state = 594; + this.identifier(); + this.state = 595; + this.match(QuixosCapabilityParser.DOT); + this.state = 596; + this.identifier(); + this.state = 597; + this.match(QuixosCapabilityParser.SEMI); } } catch (re) { @@ -1840,59 +2207,143 @@ export class QuixosCapabilityParser extends antlr.Parser { } public packageQuery(): PackageQueryContext { let localContext = new PackageQueryContext(this.context, this.state); - this.enterRule(localContext, 54, QuixosCapabilityParser.RULE_packageQuery); + this.enterRule(localContext, 62, QuixosCapabilityParser.RULE_packageQuery); let _la: number; try { this.enterOuterAlt(localContext, 1); { - this.state = 497; + this.state = 599; this.match(QuixosCapabilityParser.QUERY); - this.state = 498; + this.state = 601; + this.errorHandler.sync(this); + switch (this.interpreter.adaptivePredict(this.tokenStream, 46, this.context) ) { + case 1: + { + this.state = 600; + this.match(QuixosCapabilityParser.FRAGMENT); + } + break; + } + this.state = 603; this.identifier(); - this.state = 500; + this.state = 605; this.errorHandler.sync(this); _la = this.tokenStream.LA(1); - if (_la === 123) { + if (_la === 149) { { - this.state = 499; + this.state = 604; this.typeParameters(); } } - this.state = 502; + this.state = 607; this.match(QuixosCapabilityParser.ID); - this.state = 503; + this.state = 608; this.stringLiteral(); - this.state = 504; + this.state = 609; this.match(QuixosCapabilityParser.ROOT); - this.state = 505; - this.interfaceType(); - this.state = 506; - this.match(QuixosCapabilityParser.DOCUMENT); - this.state = 507; - this.stringLiteral(); - this.state = 508; - this.match(QuixosCapabilityParser.OPERATION); - this.state = 509; - this.stringLiteral(); - this.state = 510; - this.match(QuixosCapabilityParser.LBRACE); - this.state = 514; + this.state = 611; this.errorHandler.sync(this); _la = this.tokenStream.LA(1); - while ((((_la) & ~0x1F) === 0 && ((1 << _la) & 15872) !== 0) || _la === 73) { + if (_la === 11) { { + this.state = 610; + this.match(QuixosCapabilityParser.WORKSPACE); + } + } + + this.state = 613; + this.interfaceType(); + this.state = 614; + this.match(QuixosCapabilityParser.DOCUMENT); + this.state = 615; + this.stringLiteral(); + this.state = 644; + this.errorHandler.sync(this); + switch (this.tokenStream.LA(1)) { + case QuixosCapabilityParser.OPERATION: { - this.state = 511; - this.queryClause(); - } - } - this.state = 516; + this.state = 616; + this.match(QuixosCapabilityParser.OPERATION); + this.state = 617; + this.stringLiteral(); + this.state = 618; + this.match(QuixosCapabilityParser.LBRACE); + this.state = 622; this.errorHandler.sync(this); _la = this.tokenStream.LA(1); + while (((((_la - 18)) & ~0x1F) === 0 && ((1 << (_la - 18)) & 63491) !== 0) || _la === 99) { + { + { + this.state = 619; + this.queryClause(); + } + } + this.state = 624; + this.errorHandler.sync(this); + _la = this.tokenStream.LA(1); + } + this.state = 625; + this.match(QuixosCapabilityParser.RBRACE); + } + break; + case QuixosCapabilityParser.FRAGMENT: + { + this.state = 627; + this.match(QuixosCapabilityParser.FRAGMENT); + this.state = 628; + this.stringLiteral(); + this.state = 631; + this.errorHandler.sync(this); + _la = this.tokenStream.LA(1); + if (_la === 53) { + { + this.state = 629; + this.match(QuixosCapabilityParser.OPERATION); + this.state = 630; + this.stringLiteral(); + } + } + + this.state = 642; + this.errorHandler.sync(this); + switch (this.tokenStream.LA(1)) { + case QuixosCapabilityParser.LBRACE: + { + this.state = 633; + this.match(QuixosCapabilityParser.LBRACE); + this.state = 637; + this.errorHandler.sync(this); + _la = this.tokenStream.LA(1); + while (((((_la - 18)) & ~0x1F) === 0 && ((1 << (_la - 18)) & 63491) !== 0) || _la === 99) { + { + { + this.state = 634; + this.queryClause(); + } + } + this.state = 639; + this.errorHandler.sync(this); + _la = this.tokenStream.LA(1); + } + this.state = 640; + this.match(QuixosCapabilityParser.RBRACE); + } + break; + case QuixosCapabilityParser.SEMI: + { + this.state = 641; + this.match(QuixosCapabilityParser.SEMI); + } + break; + default: + throw new antlr.NoViableAltException(this); + } + } + break; + default: + throw new antlr.NoViableAltException(this); } - this.state = 517; - this.match(QuixosCapabilityParser.RBRACE); } } catch (re) { @@ -1910,25 +2361,25 @@ export class QuixosCapabilityParser extends antlr.Parser { } public packageQuerySpecialization(): PackageQuerySpecializationContext { let localContext = new PackageQuerySpecializationContext(this.context, this.state); - this.enterRule(localContext, 56, QuixosCapabilityParser.RULE_packageQuerySpecialization); + this.enterRule(localContext, 64, QuixosCapabilityParser.RULE_packageQuerySpecialization); try { this.enterOuterAlt(localContext, 1); { - this.state = 519; + this.state = 646; this.match(QuixosCapabilityParser.QUERY); - this.state = 520; + this.state = 647; this.identifier(); - this.state = 521; + this.state = 648; this.match(QuixosCapabilityParser.ID); - this.state = 522; + this.state = 649; this.stringLiteral(); - this.state = 523; + this.state = 650; this.match(QuixosCapabilityParser.SPECIALIZE); - this.state = 524; + this.state = 651; this.identifier(); - this.state = 525; + this.state = 652; this.typeArguments(); - this.state = 526; + this.state = 653; this.match(QuixosCapabilityParser.SEMI); } } @@ -1947,104 +2398,196 @@ export class QuixosCapabilityParser extends antlr.Parser { } public queryClause(): QueryClauseContext { let localContext = new QueryClauseContext(this.context, this.state); - this.enterRule(localContext, 58, QuixosCapabilityParser.RULE_queryClause); + this.enterRule(localContext, 66, QuixosCapabilityParser.RULE_queryClause); let _la: number; try { - this.state = 561; + this.state = 712; this.errorHandler.sync(this); - switch (this.tokenStream.LA(1)) { - case QuixosCapabilityParser.FRAGMENTS: + switch (this.interpreter.adaptivePredict(this.tokenStream, 57, this.context) ) { + case 1: this.enterOuterAlt(localContext, 1); { - this.state = 528; - this.match(QuixosCapabilityParser.FRAGMENTS); - this.state = 529; - this.stringLiteral(); - this.state = 530; + this.state = 655; + this.match(QuixosCapabilityParser.REPLICA); + this.state = 656; this.match(QuixosCapabilityParser.SEMI); } break; - case QuixosCapabilityParser.VIEW: + case 2: this.enterOuterAlt(localContext, 2); { - this.state = 532; - this.match(QuixosCapabilityParser.VIEW); - this.state = 534; + this.state = 657; + this.match(QuixosCapabilityParser.LOCAL); + this.state = 658; + this.match(QuixosCapabilityParser.FROM); + this.state = 659; + this.identifier(); + this.state = 660; + this.match(QuixosCapabilityParser.SEMI); + } + break; + case 3: + this.enterOuterAlt(localContext, 3); + { + this.state = 662; + this.match(QuixosCapabilityParser.FRAGMENTS); + this.state = 663; + this.stringLiteral(); + this.state = 664; + this.match(QuixosCapabilityParser.SEMI); + } + break; + case 4: + this.enterOuterAlt(localContext, 4); + { + this.state = 666; + this.match(QuixosCapabilityParser.FRAGMENTS); + this.state = 667; + this.identifier(); + this.state = 668; + this.match(QuixosCapabilityParser.DOT); + this.state = 669; + this.identifier(); + this.state = 679; this.errorHandler.sync(this); _la = this.tokenStream.LA(1); - if (_la === 18) { + if (_la === 68) { { - this.state = 533; + this.state = 670; + this.match(QuixosCapabilityParser.WITH); + this.state = 671; + this.match(QuixosCapabilityParser.LBRACE); + this.state = 675; + this.errorHandler.sync(this); + _la = this.tokenStream.LA(1); + while ((((_la) & ~0x1F) === 0 && ((1 << _la) & 4294964466) !== 0) || ((((_la - 32)) & ~0x1F) === 0 && ((1 << (_la - 32)) & 66575) !== 0) || ((((_la - 78)) & ~0x1F) === 0 && ((1 << (_la - 78)) & 146801025) !== 0) || _la === 112 || _la === 121 || _la === 155) { + { + { + this.state = 672; + this.fragmentVariable(); + } + } + this.state = 677; + this.errorHandler.sync(this); + _la = this.tokenStream.LA(1); + } + this.state = 678; + this.match(QuixosCapabilityParser.RBRACE); + } + } + + this.state = 681; + this.match(QuixosCapabilityParser.SEMI); + } + break; + case 5: + this.enterOuterAlt(localContext, 5); + { + this.state = 683; + this.match(QuixosCapabilityParser.VIEW); + this.state = 685; + this.errorHandler.sync(this); + _la = this.tokenStream.LA(1); + if (_la === 38) { + { + this.state = 684; this.match(QuixosCapabilityParser.OBJECT); } } - this.state = 536; + this.state = 687; this.identifier(); - this.state = 537; + this.state = 688; this.match(QuixosCapabilityParser.AS); - this.state = 538; + this.state = 689; this.interfaceType(); - this.state = 539; + this.state = 690; this.match(QuixosCapabilityParser.SEMI); } break; - case QuixosCapabilityParser.MAX: - this.enterOuterAlt(localContext, 3); - { - this.state = 541; - this.match(QuixosCapabilityParser.MAX); - this.state = 542; - this.identifier(); - this.state = 543; - this.match(QuixosCapabilityParser.INTEGER); - this.state = 544; - this.match(QuixosCapabilityParser.SEMI); - } - break; - case QuixosCapabilityParser.ALLOW: - this.enterOuterAlt(localContext, 4); - { - this.state = 546; - this.match(QuixosCapabilityParser.ALLOW); - this.state = 547; - this.interfaceType(); - this.state = 548; - this.match(QuixosCapabilityParser.DOT); - this.state = 549; - this.identifier(); - this.state = 550; - this.identifier(); - this.state = 551; - this.stringLiteral(); - this.state = 552; - this.match(QuixosCapabilityParser.SEMI); - } - break; - case QuixosCapabilityParser.WATCH: - this.enterOuterAlt(localContext, 5); - { - this.state = 554; - this.match(QuixosCapabilityParser.WATCH); - this.state = 555; - this.match(QuixosCapabilityParser.SEMI); - } - break; - case QuixosCapabilityParser.POLL: + case 6: this.enterOuterAlt(localContext, 6); { - this.state = 556; - this.match(QuixosCapabilityParser.POLL); - this.state = 557; + this.state = 692; + this.match(QuixosCapabilityParser.MAX); + this.state = 693; + this.identifier(); + this.state = 694; this.match(QuixosCapabilityParser.INTEGER); - this.state = 558; - this.stringLiteral(); - this.state = 559; + this.state = 695; this.match(QuixosCapabilityParser.SEMI); } break; - default: - throw new antlr.NoViableAltException(this); + case 7: + this.enterOuterAlt(localContext, 7); + { + this.state = 697; + this.match(QuixosCapabilityParser.ALLOW); + this.state = 698; + this.interfaceType(); + this.state = 699; + this.match(QuixosCapabilityParser.DOT); + this.state = 700; + this.identifier(); + this.state = 701; + this.identifier(); + this.state = 702; + this.stringLiteral(); + this.state = 703; + this.match(QuixosCapabilityParser.SEMI); + } + break; + case 8: + this.enterOuterAlt(localContext, 8); + { + this.state = 705; + this.match(QuixosCapabilityParser.WATCH); + this.state = 706; + this.match(QuixosCapabilityParser.SEMI); + } + break; + case 9: + this.enterOuterAlt(localContext, 9); + { + this.state = 707; + this.match(QuixosCapabilityParser.POLL); + this.state = 708; + this.match(QuixosCapabilityParser.INTEGER); + this.state = 709; + this.stringLiteral(); + this.state = 710; + this.match(QuixosCapabilityParser.SEMI); + } + break; + } + } + catch (re) { + if (re instanceof antlr.RecognitionException) { + this.errorHandler.reportError(this, re); + this.errorHandler.recover(this, re); + } else { + throw re; + } + } + finally { + this.exitRule(); + } + return localContext; + } + public fragmentVariable(): FragmentVariableContext { + let localContext = new FragmentVariableContext(this.context, this.state); + this.enterRule(localContext, 68, QuixosCapabilityParser.RULE_fragmentVariable); + try { + this.enterOuterAlt(localContext, 1); + { + this.state = 714; + this.identifier(); + this.state = 715; + this.match(QuixosCapabilityParser.TO); + this.state = 716; + this.identifier(); + this.state = 717; + this.match(QuixosCapabilityParser.SEMI); } } catch (re) { @@ -2062,66 +2605,96 @@ export class QuixosCapabilityParser extends antlr.Parser { } public packageOperationExport(): PackageOperationExportContext { let localContext = new PackageOperationExportContext(this.context, this.state); - this.enterRule(localContext, 60, QuixosCapabilityParser.RULE_packageOperationExport); + this.enterRule(localContext, 70, QuixosCapabilityParser.RULE_packageOperationExport); let _la: number; try { this.enterOuterAlt(localContext, 1); { - this.state = 563; + this.state = 719; this.match(QuixosCapabilityParser.OPERATION); - this.state = 564; + this.state = 720; this.identifier(); - this.state = 566; + this.state = 722; this.errorHandler.sync(this); _la = this.tokenStream.LA(1); - if (_la === 123) { + if (_la === 149) { { - this.state = 565; + this.state = 721; this.typeParameters(); } } - this.state = 568; + this.state = 724; this.match(QuixosCapabilityParser.ID); - this.state = 569; + this.state = 725; this.stringLiteral(); - this.state = 570; + this.state = 726; this.match(QuixosCapabilityParser.COLON); - this.state = 571; + this.state = 727; this.valueType(); - this.state = 572; + this.state = 728; this.match(QuixosCapabilityParser.ARROW); - this.state = 573; + this.state = 729; this.valueType(); - this.state = 574; + this.state = 730; this.match(QuixosCapabilityParser.MODE); - this.state = 575; + this.state = 731; this.operationMode(); - this.state = 577; + this.state = 733; this.errorHandler.sync(this); _la = this.tokenStream.LA(1); - if (_la === 67) { + if (_la === 12) { { - this.state = 576; + this.state = 732; + this.executionClause(); + } + } + + this.state = 736; + this.errorHandler.sync(this); + _la = this.tokenStream.LA(1); + if (_la === 93) { + { + this.state = 735; this.eventClause(); } } - this.state = 579; + this.state = 738; this.match(QuixosCapabilityParser.RECEIVER); - this.state = 580; + this.state = 739; this.receiverRequirement(); - this.state = 582; + this.state = 741; this.errorHandler.sync(this); _la = this.tokenStream.LA(1); - if (_la === 69) { + if (_la === 95) { { - this.state = 581; + this.state = 740; this.dependencyBlock(); } } - this.state = 584; + this.state = 744; + this.errorHandler.sync(this); + _la = this.tokenStream.LA(1); + if (_la === 9) { + { + this.state = 743; + this.preparationClause(); + } + } + + this.state = 747; + this.errorHandler.sync(this); + _la = this.tokenStream.LA(1); + if (_la === 70) { + { + this.state = 746; + this.acceptanceClause(); + } + } + + this.state = 749; this.match(QuixosCapabilityParser.SEMI); } } @@ -2140,48 +2713,78 @@ export class QuixosCapabilityParser extends antlr.Parser { } public packageFunctionExport(): PackageFunctionExportContext { let localContext = new PackageFunctionExportContext(this.context, this.state); - this.enterRule(localContext, 62, QuixosCapabilityParser.RULE_packageFunctionExport); + this.enterRule(localContext, 72, QuixosCapabilityParser.RULE_packageFunctionExport); let _la: number; try { this.enterOuterAlt(localContext, 1); { - this.state = 586; + this.state = 751; this.match(QuixosCapabilityParser.FUNCTION); - this.state = 587; + this.state = 752; this.identifier(); - this.state = 589; + this.state = 754; this.errorHandler.sync(this); _la = this.tokenStream.LA(1); - if (_la === 123) { + if (_la === 149) { { - this.state = 588; + this.state = 753; this.typeParameters(); } } - this.state = 591; + this.state = 756; this.match(QuixosCapabilityParser.ID); - this.state = 592; + this.state = 757; this.stringLiteral(); - this.state = 593; + this.state = 758; this.match(QuixosCapabilityParser.COLON); - this.state = 594; + this.state = 759; this.valueType(); - this.state = 595; + this.state = 760; this.match(QuixosCapabilityParser.ARROW); - this.state = 596; + this.state = 761; this.valueType(); - this.state = 598; + this.state = 763; this.errorHandler.sync(this); _la = this.tokenStream.LA(1); - if (_la === 69) { + if (_la === 12) { { - this.state = 597; + this.state = 762; + this.executionClause(); + } + } + + this.state = 766; + this.errorHandler.sync(this); + _la = this.tokenStream.LA(1); + if (_la === 95) { + { + this.state = 765; this.dependencyBlock(); } } - this.state = 600; + this.state = 769; + this.errorHandler.sync(this); + _la = this.tokenStream.LA(1); + if (_la === 9) { + { + this.state = 768; + this.preparationClause(); + } + } + + this.state = 772; + this.errorHandler.sync(this); + _la = this.tokenStream.LA(1); + if (_la === 70) { + { + this.state = 771; + this.acceptanceClause(); + } + } + + this.state = 774; this.match(QuixosCapabilityParser.SEMI); } } @@ -2198,40 +2801,208 @@ export class QuixosCapabilityParser extends antlr.Parser { } return localContext; } + public acceptanceClause(): AcceptanceClauseContext { + let localContext = new AcceptanceClauseContext(this.context, this.state); + this.enterRule(localContext, 74, QuixosCapabilityParser.RULE_acceptanceClause); + try { + this.enterOuterAlt(localContext, 1); + { + this.state = 776; + this.match(QuixosCapabilityParser.ACCEPT); + this.state = 777; + this.match(QuixosCapabilityParser.USING); + this.state = 778; + this.identifier(); + } + } + catch (re) { + if (re instanceof antlr.RecognitionException) { + this.errorHandler.reportError(this, re); + this.errorHandler.recover(this, re); + } else { + throw re; + } + } + finally { + this.exitRule(); + } + return localContext; + } + public preparationClause(): PreparationClauseContext { + let localContext = new PreparationClauseContext(this.context, this.state); + this.enterRule(localContext, 76, QuixosCapabilityParser.RULE_preparationClause); + try { + this.enterOuterAlt(localContext, 1); + { + this.state = 780; + this.match(QuixosCapabilityParser.PREPARES); + this.state = 781; + this.match(QuixosCapabilityParser.CREATION); + this.state = 782; + this.identifier(); + this.state = 783; + this.match(QuixosCapabilityParser.USING); + this.state = 784; + this.stringLiteral(); + } + } + catch (re) { + if (re instanceof antlr.RecognitionException) { + this.errorHandler.reportError(this, re); + this.errorHandler.recover(this, re); + } else { + throw re; + } + } + finally { + this.exitRule(); + } + return localContext; + } + public executionClause(): ExecutionClauseContext { + let localContext = new ExecutionClauseContext(this.context, this.state); + this.enterRule(localContext, 78, QuixosCapabilityParser.RULE_executionClause); + let _la: number; + try { + this.state = 797; + this.errorHandler.sync(this); + switch (this.interpreter.adaptivePredict(this.tokenStream, 70, this.context) ) { + case 1: + this.enterOuterAlt(localContext, 1); + { + this.state = 786; + this.match(QuixosCapabilityParser.EXECUTION); + this.state = 787; + this.match(QuixosCapabilityParser.REMOTE); + } + break; + case 2: + this.enterOuterAlt(localContext, 2); + { + this.state = 788; + this.match(QuixosCapabilityParser.EXECUTION); + this.state = 789; + _la = this.tokenStream.LA(1); + if(!(_la === 13 || _la === 14)) { + this.errorHandler.recoverInline(this); + } + else { + this.errorHandler.reportMatch(this); + this.consume(); + } + this.state = 795; + this.errorHandler.sync(this); + _la = this.tokenStream.LA(1); + if (_la === 16) { + { + this.state = 790; + this.match(QuixosCapabilityParser.TARGETS); + this.state = 791; + this.match(QuixosCapabilityParser.LBRACK); + this.state = 792; + this.identifierList(); + this.state = 793; + this.match(QuixosCapabilityParser.RBRACK); + } + } + + } + break; + } + } + catch (re) { + if (re instanceof antlr.RecognitionException) { + this.errorHandler.reportError(this, re); + this.errorHandler.recover(this, re); + } else { + throw re; + } + } + finally { + this.exitRule(); + } + return localContext; + } + public selectedOperations(): SelectedOperationsContext { + let localContext = new SelectedOperationsContext(this.context, this.state); + this.enterRule(localContext, 80, QuixosCapabilityParser.RULE_selectedOperations); + let _la: number; + try { + this.enterOuterAlt(localContext, 1); + { + this.state = 799; + this.match(QuixosCapabilityParser.USING); + this.state = 800; + this.match(QuixosCapabilityParser.LBRACK); + this.state = 801; + this.memberOperationRef(); + this.state = 806; + this.errorHandler.sync(this); + _la = this.tokenStream.LA(1); + while (_la === 141) { + { + { + this.state = 802; + this.match(QuixosCapabilityParser.COMMA); + this.state = 803; + this.memberOperationRef(); + } + } + this.state = 808; + this.errorHandler.sync(this); + _la = this.tokenStream.LA(1); + } + this.state = 809; + this.match(QuixosCapabilityParser.RBRACK); + } + } + catch (re) { + if (re instanceof antlr.RecognitionException) { + this.errorHandler.reportError(this, re); + this.errorHandler.recover(this, re); + } else { + throw re; + } + } + finally { + this.exitRule(); + } + return localContext; + } public packageConstructorExport(): PackageConstructorExportContext { let localContext = new PackageConstructorExportContext(this.context, this.state); - this.enterRule(localContext, 64, QuixosCapabilityParser.RULE_packageConstructorExport); + this.enterRule(localContext, 82, QuixosCapabilityParser.RULE_packageConstructorExport); let _la: number; try { this.enterOuterAlt(localContext, 1); { - this.state = 602; + this.state = 811; this.match(QuixosCapabilityParser.CONSTRUCTOR); - this.state = 603; + this.state = 812; this.identifier(); - this.state = 604; + this.state = 813; this.match(QuixosCapabilityParser.ID); - this.state = 605; + this.state = 814; this.stringLiteral(); - this.state = 606; + this.state = 815; this.match(QuixosCapabilityParser.CONSTRUCTS); - this.state = 607; + this.state = 816; this.identifier(); - this.state = 608; + this.state = 817; this.match(QuixosCapabilityParser.COLON); - this.state = 609; + this.state = 818; this.valueType(); - this.state = 611; + this.state = 820; this.errorHandler.sync(this); _la = this.tokenStream.LA(1); - if (_la === 69) { + if (_la === 95) { { - this.state = 610; + this.state = 819; this.dependencyBlock(); } } - this.state = 613; + this.state = 822; this.match(QuixosCapabilityParser.SEMI); } } @@ -2250,13 +3021,13 @@ export class QuixosCapabilityParser extends antlr.Parser { } public eventClause(): EventClauseContext { let localContext = new EventClauseContext(this.context, this.state); - this.enterRule(localContext, 66, QuixosCapabilityParser.RULE_eventClause); + this.enterRule(localContext, 84, QuixosCapabilityParser.RULE_eventClause); try { this.enterOuterAlt(localContext, 1); { - this.state = 615; + this.state = 824; this.match(QuixosCapabilityParser.EMITS); - this.state = 616; + this.state = 825; this.valueType(); } } @@ -2275,14 +3046,14 @@ export class QuixosCapabilityParser extends antlr.Parser { } public operationMode(): OperationModeContext { let localContext = new OperationModeContext(this.context, this.state); - this.enterRule(localContext, 68, QuixosCapabilityParser.RULE_operationMode); + this.enterRule(localContext, 86, QuixosCapabilityParser.RULE_operationMode); let _la: number; try { this.enterOuterAlt(localContext, 1); { - this.state = 618; + this.state = 827; _la = this.tokenStream.LA(1); - if(!(((((_la - 81)) & ~0x1F) === 0 && ((1 << (_la - 81)) & 31) !== 0))) { + if(!(((((_la - 107)) & ~0x1F) === 0 && ((1 << (_la - 107)) & 31) !== 0))) { this.errorHandler.recoverInline(this); } else { @@ -2306,71 +3077,71 @@ export class QuixosCapabilityParser extends antlr.Parser { } public receiverRequirement(): ReceiverRequirementContext { let localContext = new ReceiverRequirementContext(this.context, this.state); - this.enterRule(localContext, 70, QuixosCapabilityParser.RULE_receiverRequirement); + this.enterRule(localContext, 88, QuixosCapabilityParser.RULE_receiverRequirement); let _la: number; try { - this.state = 638; + this.state = 847; this.errorHandler.sync(this); switch (this.tokenStream.LA(1)) { case QuixosCapabilityParser.ANY: this.enterOuterAlt(localContext, 1); { - this.state = 620; + this.state = 829; this.match(QuixosCapabilityParser.ANY); } break; case QuixosCapabilityParser.ATOM: this.enterOuterAlt(localContext, 2); { - this.state = 621; + this.state = 830; this.match(QuixosCapabilityParser.ATOM); - this.state = 622; + this.state = 831; this.identifier(); } break; case QuixosCapabilityParser.OBJECT: this.enterOuterAlt(localContext, 3); { - this.state = 623; + this.state = 832; this.match(QuixosCapabilityParser.OBJECT); - this.state = 624; + this.state = 833; this.identifier(); } break; case QuixosCapabilityParser.INTERFACES: this.enterOuterAlt(localContext, 4); { - this.state = 625; + this.state = 834; this.match(QuixosCapabilityParser.INTERFACES); - this.state = 626; + this.state = 835; this.match(QuixosCapabilityParser.LBRACK); - this.state = 635; + this.state = 844; this.errorHandler.sync(this); _la = this.tokenStream.LA(1); - if ((((_la) & ~0x1F) === 0 && ((1 << _la) & 65532) !== 0) || _la === 55 || _la === 129) { + if ((((_la) & ~0x1F) === 0 && ((1 << _la) & 4294964466) !== 0) || ((((_la - 32)) & ~0x1F) === 0 && ((1 << (_la - 32)) & 66575) !== 0) || ((((_la - 78)) & ~0x1F) === 0 && ((1 << (_la - 78)) & 146801025) !== 0) || _la === 112 || _la === 121 || _la === 155) { { - this.state = 627; + this.state = 836; this.interfaceType(); - this.state = 632; + this.state = 841; this.errorHandler.sync(this); _la = this.tokenStream.LA(1); - while (_la === 115) { + while (_la === 141) { { { - this.state = 628; + this.state = 837; this.match(QuixosCapabilityParser.COMMA); - this.state = 629; + this.state = 838; this.interfaceType(); } } - this.state = 634; + this.state = 843; this.errorHandler.sync(this); _la = this.tokenStream.LA(1); } } } - this.state = 637; + this.state = 846; this.match(QuixosCapabilityParser.RBRACK); } break; @@ -2393,26 +3164,26 @@ export class QuixosCapabilityParser extends antlr.Parser { } public identifierList(): IdentifierListContext { let localContext = new IdentifierListContext(this.context, this.state); - this.enterRule(localContext, 72, QuixosCapabilityParser.RULE_identifierList); + this.enterRule(localContext, 90, QuixosCapabilityParser.RULE_identifierList); let _la: number; try { this.enterOuterAlt(localContext, 1); { - this.state = 640; + this.state = 849; this.identifier(); - this.state = 645; + this.state = 854; this.errorHandler.sync(this); _la = this.tokenStream.LA(1); - while (_la === 115) { + while (_la === 141) { { { - this.state = 641; + this.state = 850; this.match(QuixosCapabilityParser.COMMA); - this.state = 642; + this.state = 851; this.identifier(); } } - this.state = 647; + this.state = 856; this.errorHandler.sync(this); _la = this.tokenStream.LA(1); } @@ -2433,30 +3204,30 @@ export class QuixosCapabilityParser extends antlr.Parser { } public dependencyBlock(): DependencyBlockContext { let localContext = new DependencyBlockContext(this.context, this.state); - this.enterRule(localContext, 74, QuixosCapabilityParser.RULE_dependencyBlock); + this.enterRule(localContext, 92, QuixosCapabilityParser.RULE_dependencyBlock); let _la: number; try { this.enterOuterAlt(localContext, 1); { - this.state = 648; + this.state = 857; this.match(QuixosCapabilityParser.REQUIRES); - this.state = 649; + this.state = 858; this.match(QuixosCapabilityParser.LBRACE); - this.state = 653; + this.state = 862; this.errorHandler.sync(this); _la = this.tokenStream.LA(1); - while (_la === 5 || _la === 26 || ((((_la - 33)) & ~0x1F) === 0 && ((1 << (_la - 33)) & 3073) !== 0)) { + while ((((_la) & ~0x1F) === 0 && ((1 << _la) & 50331776) !== 0) || ((((_la - 47)) & ~0x1F) === 0 && ((1 << (_la - 47)) & 786689) !== 0)) { { { - this.state = 650; + this.state = 859; this.dependencyPort(); } } - this.state = 655; + this.state = 864; this.errorHandler.sync(this); _la = this.tokenStream.LA(1); } - this.state = 656; + this.state = 865; this.match(QuixosCapabilityParser.RBRACE); } } @@ -2475,136 +3246,214 @@ export class QuixosCapabilityParser extends antlr.Parser { } public dependencyPort(): DependencyPortContext { let localContext = new DependencyPortContext(this.context, this.state); - this.enterRule(localContext, 76, QuixosCapabilityParser.RULE_dependencyPort); + this.enterRule(localContext, 94, QuixosCapabilityParser.RULE_dependencyPort); let _la: number; try { - this.state = 710; + this.state = 949; this.errorHandler.sync(this); switch (this.tokenStream.LA(1)) { - case QuixosCapabilityParser.STATE: + case QuixosCapabilityParser.REFLECTION: this.enterOuterAlt(localContext, 1); { - this.state = 658; - this.match(QuixosCapabilityParser.STATE); - this.state = 659; + this.state = 867; + this.match(QuixosCapabilityParser.REFLECTION); + this.state = 868; this.identifier(); - this.state = 660; + this.state = 869; this.match(QuixosCapabilityParser.ID); - this.state = 661; + this.state = 870; this.stringLiteral(); - this.state = 662; + this.state = 871; + this.match(QuixosCapabilityParser.LBRACK); + this.state = 872; + this.identifierList(); + this.state = 873; + this.match(QuixosCapabilityParser.RBRACK); + this.state = 874; + this.match(QuixosCapabilityParser.SEMI); + } + break; + case QuixosCapabilityParser.STATE: + this.enterOuterAlt(localContext, 2); + { + this.state = 876; + this.match(QuixosCapabilityParser.STATE); + this.state = 877; + this.identifier(); + this.state = 878; + this.match(QuixosCapabilityParser.ID); + this.state = 879; + this.stringLiteral(); + this.state = 880; this.match(QuixosCapabilityParser.COLON); - this.state = 663; + this.state = 881; this.valueType(); - this.state = 664; + this.state = 882; this.primitiveList(); - this.state = 665; + this.state = 883; this.match(QuixosCapabilityParser.SEMI); } break; case QuixosCapabilityParser.EDGE: - this.enterOuterAlt(localContext, 2); + this.enterOuterAlt(localContext, 3); { - this.state = 667; + this.state = 885; this.match(QuixosCapabilityParser.EDGE); - this.state = 668; + this.state = 886; this.identifier(); - this.state = 669; + this.state = 887; this.match(QuixosCapabilityParser.ID); - this.state = 670; + this.state = 888; this.stringLiteral(); - this.state = 671; + this.state = 889; this.match(QuixosCapabilityParser.COLON); - this.state = 672; + this.state = 890; this.cardinality(); - this.state = 673; + this.state = 891; this.targetConstraint(); - this.state = 674; + this.state = 894; + this.errorHandler.sync(this); + _la = this.tokenStream.LA(1); + if (_la === 88) { + { + this.state = 892; + this.match(QuixosCapabilityParser.KEYED); + this.state = 893; + this.stringLiteral(); + } + } + + this.state = 896; this.primitiveList(); - this.state = 675; + this.state = 897; this.match(QuixosCapabilityParser.SEMI); } break; case QuixosCapabilityParser.INTERFACE: - this.enterOuterAlt(localContext, 3); + this.enterOuterAlt(localContext, 4); { - this.state = 677; + this.state = 899; this.match(QuixosCapabilityParser.INTERFACE); - this.state = 678; + this.state = 900; this.identifier(); - this.state = 679; + this.state = 901; this.match(QuixosCapabilityParser.ID); - this.state = 680; + this.state = 902; this.stringLiteral(); - this.state = 681; + this.state = 903; this.match(QuixosCapabilityParser.COLON); - this.state = 682; + this.state = 904; this.identifier(); - this.state = 684; + this.state = 906; this.errorHandler.sync(this); _la = this.tokenStream.LA(1); - if (_la === 123) { + if (_la === 149) { { - this.state = 683; + this.state = 905; this.typeArguments(); } } - this.state = 686; + this.state = 909; + this.errorHandler.sync(this); + _la = this.tokenStream.LA(1); + if (_la === 69) { + { + this.state = 908; + this.selectedOperations(); + } + } + + this.state = 913; + this.errorHandler.sync(this); + _la = this.tokenStream.LA(1); + if (_la === 20) { + { + this.state = 911; + this.match(QuixosCapabilityParser.FROM); + this.state = 912; + this.match(QuixosCapabilityParser.INPUT); + } + } + + this.state = 915; + this.match(QuixosCapabilityParser.SEMI); + } + break; + case QuixosCapabilityParser.CREATION: + this.enterOuterAlt(localContext, 5); + { + this.state = 917; + this.match(QuixosCapabilityParser.CREATION); + this.state = 918; + this.identifier(); + this.state = 919; + this.match(QuixosCapabilityParser.ID); + this.state = 920; + this.stringLiteral(); + this.state = 921; + this.match(QuixosCapabilityParser.COLON); + this.state = 922; + this.identifier(); + this.state = 923; + this.match(QuixosCapabilityParser.INPUT); + this.state = 924; + this.valueType(); + this.state = 925; this.match(QuixosCapabilityParser.SEMI); } break; case QuixosCapabilityParser.CONSTRUCTOR: - this.enterOuterAlt(localContext, 4); + this.enterOuterAlt(localContext, 6); { - this.state = 688; + this.state = 927; this.match(QuixosCapabilityParser.CONSTRUCTOR); - this.state = 689; + this.state = 928; this.identifier(); - this.state = 690; + this.state = 929; this.match(QuixosCapabilityParser.ID); - this.state = 691; + this.state = 930; this.stringLiteral(); - this.state = 692; + this.state = 931; this.match(QuixosCapabilityParser.COLON); - this.state = 693; + this.state = 932; this.identifier(); - this.state = 696; + this.state = 935; this.errorHandler.sync(this); _la = this.tokenStream.LA(1); - if (_la === 35) { + if (_la === 57) { { - this.state = 694; + this.state = 933; this.match(QuixosCapabilityParser.INPUT); - this.state = 695; + this.state = 934; this.valueType(); } } - this.state = 698; + this.state = 937; this.match(QuixosCapabilityParser.SEMI); } break; case QuixosCapabilityParser.QUERY: - this.enterOuterAlt(localContext, 5); + this.enterOuterAlt(localContext, 7); { - this.state = 700; + this.state = 939; this.match(QuixosCapabilityParser.QUERY); - this.state = 701; + this.state = 940; this.identifier(); - this.state = 702; + this.state = 941; this.match(QuixosCapabilityParser.ID); - this.state = 703; + this.state = 942; this.stringLiteral(); - this.state = 704; + this.state = 943; this.match(QuixosCapabilityParser.COLON); - this.state = 705; + this.state = 944; this.identifier(); - this.state = 706; + this.state = 945; this.match(QuixosCapabilityParser.DOT); - this.state = 707; + this.state = 946; this.identifier(); - this.state = 708; + this.state = 947; this.match(QuixosCapabilityParser.SEMI); } break; @@ -2627,32 +3476,32 @@ export class QuixosCapabilityParser extends antlr.Parser { } public primitiveList(): PrimitiveListContext { let localContext = new PrimitiveListContext(this.context, this.state); - this.enterRule(localContext, 78, QuixosCapabilityParser.RULE_primitiveList); + this.enterRule(localContext, 96, QuixosCapabilityParser.RULE_primitiveList); let _la: number; try { this.enterOuterAlt(localContext, 1); { - this.state = 712; + this.state = 951; this.match(QuixosCapabilityParser.LBRACK); - this.state = 713; + this.state = 952; this.primitive(); - this.state = 718; + this.state = 957; this.errorHandler.sync(this); _la = this.tokenStream.LA(1); - while (_la === 115) { + while (_la === 141) { { { - this.state = 714; + this.state = 953; this.match(QuixosCapabilityParser.COMMA); - this.state = 715; + this.state = 954; this.primitive(); } } - this.state = 720; + this.state = 959; this.errorHandler.sync(this); _la = this.tokenStream.LA(1); } - this.state = 721; + this.state = 960; this.match(QuixosCapabilityParser.RBRACK); } } @@ -2671,14 +3520,14 @@ export class QuixosCapabilityParser extends antlr.Parser { } public primitive(): PrimitiveContext { let localContext = new PrimitiveContext(this.context, this.state); - this.enterRule(localContext, 80, QuixosCapabilityParser.RULE_primitive); + this.enterRule(localContext, 98, QuixosCapabilityParser.RULE_primitive); let _la: number; try { this.enterOuterAlt(localContext, 1); { - this.state = 723; + this.state = 962; _la = this.tokenStream.LA(1); - if(!(((((_la - 76)) & ~0x1F) === 0 && ((1 << (_la - 76)) & 223) !== 0))) { + if(!(_la === 1 || ((((_la - 102)) & ~0x1F) === 0 && ((1 << (_la - 102)) & 223) !== 0))) { this.errorHandler.recoverInline(this); } else { @@ -2702,13 +3551,13 @@ export class QuixosCapabilityParser extends antlr.Parser { } public sharedAttachmentDecl(): SharedAttachmentDeclContext { let localContext = new SharedAttachmentDeclContext(this.context, this.state); - this.enterRule(localContext, 82, QuixosCapabilityParser.RULE_sharedAttachmentDecl); + this.enterRule(localContext, 100, QuixosCapabilityParser.RULE_sharedAttachmentDecl); try { this.enterOuterAlt(localContext, 1); { - this.state = 725; + this.state = 964; this.match(QuixosCapabilityParser.SHARED); - this.state = 726; + this.state = 965; this.attachmentDecl(); } } @@ -2727,22 +3576,22 @@ export class QuixosCapabilityParser extends antlr.Parser { } public attachmentDecl(): AttachmentDeclContext { let localContext = new AttachmentDeclContext(this.context, this.state); - this.enterRule(localContext, 84, QuixosCapabilityParser.RULE_attachmentDecl); + this.enterRule(localContext, 102, QuixosCapabilityParser.RULE_attachmentDecl); try { - this.state = 730; + this.state = 969; this.errorHandler.sync(this); switch (this.tokenStream.LA(1)) { case QuixosCapabilityParser.STATE: this.enterOuterAlt(localContext, 1); { - this.state = 728; + this.state = 967; this.stateDecl(); } break; case QuixosCapabilityParser.EDGE: this.enterOuterAlt(localContext, 2); { - this.state = 729; + this.state = 968; this.edgeDecl(); } break; @@ -2765,44 +3614,44 @@ export class QuixosCapabilityParser extends antlr.Parser { } public stateDecl(): StateDeclContext { let localContext = new StateDeclContext(this.context, this.state); - this.enterRule(localContext, 86, QuixosCapabilityParser.RULE_stateDecl); + this.enterRule(localContext, 104, QuixosCapabilityParser.RULE_stateDecl); let _la: number; try { this.enterOuterAlt(localContext, 1); { - this.state = 732; + this.state = 971; this.match(QuixosCapabilityParser.STATE); - this.state = 733; + this.state = 972; this.identifier(); - this.state = 734; + this.state = 973; this.match(QuixosCapabilityParser.ID); - this.state = 735; + this.state = 974; this.stringLiteral(); - this.state = 736; + this.state = 975; this.match(QuixosCapabilityParser.ON); - this.state = 737; + this.state = 976; this.identifier(); - this.state = 738; + this.state = 977; this.match(QuixosCapabilityParser.COLON); - this.state = 739; + this.state = 978; this.valueType(); - this.state = 740; + this.state = 979; this.match(QuixosCapabilityParser.POLICY); - this.state = 741; + this.state = 980; this.storagePolicy(); - this.state = 744; + this.state = 983; this.errorHandler.sync(this); _la = this.tokenStream.LA(1); - if (_la === 54) { + if (_la === 77) { { - this.state = 742; + this.state = 981; this.match(QuixosCapabilityParser.DEFAULT); - this.state = 743; + this.state = 982; this.jsonLiteral(); } } - this.state = 746; + this.state = 985; this.match(QuixosCapabilityParser.SEMI); } } @@ -2821,28 +3670,50 @@ export class QuixosCapabilityParser extends antlr.Parser { } public storagePolicy(): StoragePolicyContext { let localContext = new StoragePolicyContext(this.context, this.state); - this.enterRule(localContext, 88, QuixosCapabilityParser.RULE_storagePolicy); + this.enterRule(localContext, 106, QuixosCapabilityParser.RULE_storagePolicy); try { - this.state = 754; + this.state = 1000; this.errorHandler.sync(this); switch (this.tokenStream.LA(1)) { - case QuixosCapabilityParser.OPTIMISTIC_REGISTER: + case QuixosCapabilityParser.REGISTER: this.enterOuterAlt(localContext, 1); { - this.state = 748; - this.match(QuixosCapabilityParser.OPTIMISTIC_REGISTER); + this.state = 987; + this.match(QuixosCapabilityParser.REGISTER); } break; case QuixosCapabilityParser.CRDT: this.enterOuterAlt(localContext, 2); { - this.state = 749; + this.state = 988; this.match(QuixosCapabilityParser.CRDT); - this.state = 750; + this.state = 989; this.match(QuixosCapabilityParser.LPAREN); - this.state = 751; + this.state = 990; this.valueType(); - this.state = 752; + this.state = 991; + this.match(QuixosCapabilityParser.RPAREN); + } + break; + case QuixosCapabilityParser.JOURNAL: + this.enterOuterAlt(localContext, 3); + { + this.state = 993; + this.match(QuixosCapabilityParser.JOURNAL); + this.state = 994; + this.jsonObject(); + } + break; + case QuixosCapabilityParser.SUBSCRIPTION: + this.enterOuterAlt(localContext, 4); + { + this.state = 995; + this.match(QuixosCapabilityParser.SUBSCRIPTION); + this.state = 996; + this.match(QuixosCapabilityParser.LPAREN); + this.state = 997; + this.valueType(); + this.state = 998; this.match(QuixosCapabilityParser.RPAREN); } break; @@ -2865,25 +3736,25 @@ export class QuixosCapabilityParser extends antlr.Parser { } public edgeDecl(): EdgeDeclContext { let localContext = new EdgeDeclContext(this.context, this.state); - this.enterRule(localContext, 90, QuixosCapabilityParser.RULE_edgeDecl); + this.enterRule(localContext, 108, QuixosCapabilityParser.RULE_edgeDecl); try { this.enterOuterAlt(localContext, 1); { - this.state = 756; + this.state = 1002; this.match(QuixosCapabilityParser.EDGE); - this.state = 757; + this.state = 1003; this.identifier(); - this.state = 758; + this.state = 1004; this.match(QuixosCapabilityParser.ID); - this.state = 759; + this.state = 1005; this.stringLiteral(); - this.state = 760; + this.state = 1006; this.match(QuixosCapabilityParser.LBRACE); - this.state = 761; + this.state = 1007; this.edgeEndpoint(); - this.state = 762; + this.state = 1008; this.edgeEndpoint(); - this.state = 763; + this.state = 1009; this.match(QuixosCapabilityParser.RBRACE); } } @@ -2902,78 +3773,78 @@ export class QuixosCapabilityParser extends antlr.Parser { } public edgeEndpoint(): EdgeEndpointContext { let localContext = new EdgeEndpointContext(this.context, this.state); - this.enterRule(localContext, 92, QuixosCapabilityParser.RULE_edgeEndpoint); + this.enterRule(localContext, 110, QuixosCapabilityParser.RULE_edgeEndpoint); let _la: number; try { this.enterOuterAlt(localContext, 1); { - this.state = 765; + this.state = 1011; this.targetConstraint(); - this.state = 766; + this.state = 1012; this.match(QuixosCapabilityParser.PROJECTION); - this.state = 767; + this.state = 1013; this.identifier(); - this.state = 768; + this.state = 1014; this.match(QuixosCapabilityParser.ID); - this.state = 769; + this.state = 1015; this.stringLiteral(); - this.state = 770; + this.state = 1016; this.cardinality(); - this.state = 772; + this.state = 1018; this.errorHandler.sync(this); _la = this.tokenStream.LA(1); - if (_la === 92) { + if (_la === 118) { { - this.state = 771; + this.state = 1017; this.match(QuixosCapabilityParser.ORDERED); } } - this.state = 776; + this.state = 1022; this.errorHandler.sync(this); _la = this.tokenStream.LA(1); - if (_la === 60) { + if (_la === 83) { { - this.state = 774; + this.state = 1020; this.match(QuixosCapabilityParser.ON_DELETE); - this.state = 775; + this.state = 1021; this.stringLiteral(); } } - this.state = 779; + this.state = 1025; this.errorHandler.sync(this); _la = this.tokenStream.LA(1); - if (_la === 61) { + if (_la === 87) { { - this.state = 778; + this.state = 1024; this.match(QuixosCapabilityParser.RETAIN_OTHER); } } - this.state = 783; + this.state = 1029; this.errorHandler.sync(this); _la = this.tokenStream.LA(1); - if (_la === 62) { + if (_la === 88) { { - this.state = 781; + this.state = 1027; this.match(QuixosCapabilityParser.KEYED); - this.state = 782; + this.state = 1028; this.stringLiteral(); } } - this.state = 786; + this.state = 1032; this.errorHandler.sync(this); _la = this.tokenStream.LA(1); - if (_la === 63) { + if (_la === 89) { { - this.state = 785; + this.state = 1031; this.match(QuixosCapabilityParser.PUBLIC_TRAVERSAL); } } - this.state = 788; + this.state = 1034; this.match(QuixosCapabilityParser.SEMI); } } @@ -2992,70 +3863,90 @@ export class QuixosCapabilityParser extends antlr.Parser { } public conformanceDecl(): ConformanceDeclContext { let localContext = new ConformanceDeclContext(this.context, this.state); - this.enterRule(localContext, 94, QuixosCapabilityParser.RULE_conformanceDecl); + this.enterRule(localContext, 112, QuixosCapabilityParser.RULE_conformanceDecl); let _la: number; try { this.enterOuterAlt(localContext, 1); { - this.state = 790; + this.state = 1036; this.match(QuixosCapabilityParser.CONFORM); - this.state = 791; - this.identifier(); - this.state = 792; - this.match(QuixosCapabilityParser.AS); - this.state = 793; - this.identifier(); - this.state = 795; + this.state = 1038; this.errorHandler.sync(this); _la = this.tokenStream.LA(1); - if (_la === 123) { + if (_la === 149) { { - this.state = 794; + this.state = 1037; + this.typeParameters(); + } + } + + this.state = 1040; + this.identifier(); + this.state = 1042; + this.errorHandler.sync(this); + _la = this.tokenStream.LA(1); + if (_la === 149) { + { + this.state = 1041; + this.conformanceSourceArguments(); + } + } + + this.state = 1044; + this.match(QuixosCapabilityParser.AS); + this.state = 1045; + this.identifier(); + this.state = 1047; + this.errorHandler.sync(this); + _la = this.tokenStream.LA(1); + if (_la === 149) { + { + this.state = 1046; this.typeArguments(); } } - this.state = 799; + this.state = 1051; this.errorHandler.sync(this); _la = this.tokenStream.LA(1); - if (_la === 64) { + if (_la === 90) { { - this.state = 797; + this.state = 1049; this.match(QuixosCapabilityParser.ID); - this.state = 798; + this.state = 1050; this.stringLiteral(); } } - this.state = 803; + this.state = 1055; this.errorHandler.sync(this); _la = this.tokenStream.LA(1); - if (_la === 59) { + if (_la === 82) { { - this.state = 801; + this.state = 1053; this.match(QuixosCapabilityParser.SEMANTIC_MAJOR); - this.state = 802; + this.state = 1054; this.match(QuixosCapabilityParser.INTEGER); } } - this.state = 805; + this.state = 1057; this.match(QuixosCapabilityParser.LBRACE); - this.state = 809; + this.state = 1061; this.errorHandler.sync(this); _la = this.tokenStream.LA(1); - while (((((_la - 38)) & ~0x1F) === 0 && ((1 << (_la - 38)) & 2057) !== 0)) { + while (_la === 10 || ((((_la - 60)) & ~0x1F) === 0 && ((1 << (_la - 60)) & 4105) !== 0)) { { { - this.state = 806; + this.state = 1058; this.conformanceItem(); } } - this.state = 811; + this.state = 1063; this.errorHandler.sync(this); _la = this.tokenStream.LA(1); } - this.state = 812; + this.state = 1064; this.match(QuixosCapabilityParser.RBRACE); } } @@ -3072,43 +3963,80 @@ export class QuixosCapabilityParser extends antlr.Parser { } return localContext; } + public conformanceSourceArguments(): ConformanceSourceArgumentsContext { + let localContext = new ConformanceSourceArgumentsContext(this.context, this.state); + this.enterRule(localContext, 114, QuixosCapabilityParser.RULE_conformanceSourceArguments); + try { + this.enterOuterAlt(localContext, 1); + { + this.state = 1066; + this.typeArguments(); + } + } + catch (re) { + if (re instanceof antlr.RecognitionException) { + this.errorHandler.reportError(this, re); + this.errorHandler.recover(this, re); + } else { + throw re; + } + } + finally { + this.exitRule(); + } + return localContext; + } public conformanceItem(): ConformanceItemContext { let localContext = new ConformanceItemContext(this.context, this.state); - this.enterRule(localContext, 96, QuixosCapabilityParser.RULE_conformanceItem); + this.enterRule(localContext, 116, QuixosCapabilityParser.RULE_conformanceItem); try { - this.state = 819; + this.state = 1075; this.errorHandler.sync(this); - switch (this.interpreter.adaptivePredict(this.tokenStream, 69, this.context) ) { + switch (this.interpreter.adaptivePredict(this.tokenStream, 99, this.context) ) { case 1: this.enterOuterAlt(localContext, 1); { - this.state = 814; + this.state = 1068; this.match(QuixosCapabilityParser.PRIVATE); - this.state = 815; + this.state = 1069; this.attachmentDecl(); } break; case 2: this.enterOuterAlt(localContext, 2); { - this.state = 816; + this.state = 1070; this.operationBindingDecl(); } break; case 3: this.enterOuterAlt(localContext, 3); { - this.state = 817; + this.state = 1071; this.stateFieldBindingDecl(); } break; case 4: this.enterOuterAlt(localContext, 4); { - this.state = 818; + this.state = 1072; + this.initializationDecl(); + } + break; + case 5: + this.enterOuterAlt(localContext, 5); + { + this.state = 1073; this.relationshipMaterializationDecl(); } break; + case 6: + this.enterOuterAlt(localContext, 6); + { + this.state = 1074; + this.moduleBindingDecl(); + } + break; } } catch (re) { @@ -3124,23 +4052,204 @@ export class QuixosCapabilityParser extends antlr.Parser { } return localContext; } - public stateFieldBindingDecl(): StateFieldBindingDeclContext { - let localContext = new StateFieldBindingDeclContext(this.context, this.state); - this.enterRule(localContext, 98, QuixosCapabilityParser.RULE_stateFieldBindingDecl); + public moduleBindingDecl(): ModuleBindingDeclContext { + let localContext = new ModuleBindingDeclContext(this.context, this.state); + this.enterRule(localContext, 118, QuixosCapabilityParser.RULE_moduleBindingDecl); try { this.enterOuterAlt(localContext, 1); { - this.state = 821; + this.state = 1077; this.match(QuixosCapabilityParser.BIND); - this.state = 822; + this.state = 1078; this.identifier(); - this.state = 823; + this.state = 1079; this.match(QuixosCapabilityParser.TO); - this.state = 824; - this.match(QuixosCapabilityParser.STATE); - this.state = 825; + this.state = 1080; this.identifier(); - this.state = 826; + this.state = 1081; + this.match(QuixosCapabilityParser.DOT); + this.state = 1082; + this.identifier(); + this.state = 1083; + this.match(QuixosCapabilityParser.SEMI); + } + } + catch (re) { + if (re instanceof antlr.RecognitionException) { + this.errorHandler.reportError(this, re); + this.errorHandler.recover(this, re); + } else { + throw re; + } + } + finally { + this.exitRule(); + } + return localContext; + } + public initializationDecl(): InitializationDeclContext { + let localContext = new InitializationDeclContext(this.context, this.state); + this.enterRule(localContext, 120, QuixosCapabilityParser.RULE_initializationDecl); + let _la: number; + try { + this.enterOuterAlt(localContext, 1); + { + this.state = 1085; + this.match(QuixosCapabilityParser.INITIALIZE); + this.state = 1086; + this.match(QuixosCapabilityParser.LBRACE); + this.state = 1090; + this.errorHandler.sync(this); + _la = this.tokenStream.LA(1); + while ((((_la) & ~0x1F) === 0 && ((1 << _la) & 4294964466) !== 0) || ((((_la - 32)) & ~0x1F) === 0 && ((1 << (_la - 32)) & 66575) !== 0) || ((((_la - 78)) & ~0x1F) === 0 && ((1 << (_la - 78)) & 146801025) !== 0) || _la === 112 || _la === 121 || _la === 155) { + { + { + this.state = 1087; + this.initializationField(); + } + } + this.state = 1092; + this.errorHandler.sync(this); + _la = this.tokenStream.LA(1); + } + this.state = 1093; + this.match(QuixosCapabilityParser.RBRACE); + this.state = 1094; + this.match(QuixosCapabilityParser.SEMI); + } + } + catch (re) { + if (re instanceof antlr.RecognitionException) { + this.errorHandler.reportError(this, re); + this.errorHandler.recover(this, re); + } else { + throw re; + } + } + finally { + this.exitRule(); + } + return localContext; + } + public initializationField(): InitializationFieldContext { + let localContext = new InitializationFieldContext(this.context, this.state); + this.enterRule(localContext, 122, QuixosCapabilityParser.RULE_initializationField); + try { + this.state = 1110; + this.errorHandler.sync(this); + switch (this.interpreter.adaptivePredict(this.tokenStream, 101, this.context) ) { + case 1: + this.enterOuterAlt(localContext, 1); + { + this.state = 1096; + this.identifier(); + this.state = 1097; + this.match(QuixosCapabilityParser.TO); + this.state = 1098; + this.match(QuixosCapabilityParser.STATE); + this.state = 1099; + this.identifier(); + this.state = 1100; + this.match(QuixosCapabilityParser.SEMI); + } + break; + case 2: + this.enterOuterAlt(localContext, 2); + { + this.state = 1102; + this.identifier(); + this.state = 1103; + this.match(QuixosCapabilityParser.TO); + this.state = 1104; + this.match(QuixosCapabilityParser.EDGE); + this.state = 1105; + this.identifier(); + this.state = 1106; + this.match(QuixosCapabilityParser.DOT); + this.state = 1107; + this.identifier(); + this.state = 1108; + this.match(QuixosCapabilityParser.SEMI); + } + break; + } + } + catch (re) { + if (re instanceof antlr.RecognitionException) { + this.errorHandler.reportError(this, re); + this.errorHandler.recover(this, re); + } else { + throw re; + } + } + finally { + this.exitRule(); + } + return localContext; + } + public creationOwnerMapping(): CreationOwnerMappingContext { + let localContext = new CreationOwnerMappingContext(this.context, this.state); + this.enterRule(localContext, 124, QuixosCapabilityParser.RULE_creationOwnerMapping); + let _la: number; + try { + this.enterOuterAlt(localContext, 1); + { + this.state = 1112; + this.interfaceType(); + this.state = 1113; + this.match(QuixosCapabilityParser.FROM); + this.state = 1114; + this.match(QuixosCapabilityParser.INPUT); + this.state = 1119; + this.errorHandler.sync(this); + _la = this.tokenStream.LA(1); + while (_la === 142) { + { + { + this.state = 1115; + this.match(QuixosCapabilityParser.DOT); + this.state = 1116; + this.identifier(); + } + } + this.state = 1121; + this.errorHandler.sync(this); + _la = this.tokenStream.LA(1); + } + this.state = 1122; + this.match(QuixosCapabilityParser.SEMI); + } + } + catch (re) { + if (re instanceof antlr.RecognitionException) { + this.errorHandler.reportError(this, re); + this.errorHandler.recover(this, re); + } else { + throw re; + } + } + finally { + this.exitRule(); + } + return localContext; + } + public stateFieldBindingDecl(): StateFieldBindingDeclContext { + let localContext = new StateFieldBindingDeclContext(this.context, this.state); + this.enterRule(localContext, 126, QuixosCapabilityParser.RULE_stateFieldBindingDecl); + try { + this.enterOuterAlt(localContext, 1); + { + this.state = 1124; + this.match(QuixosCapabilityParser.BIND); + this.state = 1125; + this.identifier(); + this.state = 1126; + this.match(QuixosCapabilityParser.TO); + this.state = 1127; + this.match(QuixosCapabilityParser.STATE); + this.state = 1128; + this.identifier(); + this.state = 1129; this.match(QuixosCapabilityParser.SEMI); } } @@ -3159,35 +4268,35 @@ export class QuixosCapabilityParser extends antlr.Parser { } public relationshipMaterializationDecl(): RelationshipMaterializationDeclContext { let localContext = new RelationshipMaterializationDeclContext(this.context, this.state); - this.enterRule(localContext, 100, QuixosCapabilityParser.RULE_relationshipMaterializationDecl); + this.enterRule(localContext, 128, QuixosCapabilityParser.RULE_relationshipMaterializationDecl); try { this.enterOuterAlt(localContext, 1); { - this.state = 828; + this.state = 1131; this.match(QuixosCapabilityParser.MATERIALIZE); - this.state = 829; + this.state = 1132; this.identifier(); - this.state = 830; + this.state = 1133; this.match(QuixosCapabilityParser.IF); - this.state = 831; + this.state = 1134; this.match(QuixosCapabilityParser.ABSENT); - this.state = 832; + this.state = 1135; this.match(QuixosCapabilityParser.USING); - this.state = 833; + this.state = 1136; this.match(QuixosCapabilityParser.CONSTRUCTOR); - this.state = 834; + this.state = 1137; this.identifier(); - this.state = 835; + this.state = 1138; this.match(QuixosCapabilityParser.VIA); - this.state = 836; + this.state = 1139; this.match(QuixosCapabilityParser.EDGE); - this.state = 837; + this.state = 1140; this.identifier(); - this.state = 838; + this.state = 1141; this.match(QuixosCapabilityParser.DOT); - this.state = 839; + this.state = 1142; this.identifier(); - this.state = 840; + this.state = 1143; this.match(QuixosCapabilityParser.SEMI); } } @@ -3206,32 +4315,32 @@ export class QuixosCapabilityParser extends antlr.Parser { } public operationBindingDecl(): OperationBindingDeclContext { let localContext = new OperationBindingDeclContext(this.context, this.state); - this.enterRule(localContext, 102, QuixosCapabilityParser.RULE_operationBindingDecl); + this.enterRule(localContext, 130, QuixosCapabilityParser.RULE_operationBindingDecl); let _la: number; try { this.enterOuterAlt(localContext, 1); { - this.state = 842; + this.state = 1145; this.match(QuixosCapabilityParser.BIND); - this.state = 843; + this.state = 1146; this.memberOperationRef(); - this.state = 844; + this.state = 1147; this.match(QuixosCapabilityParser.TO); - this.state = 845; + this.state = 1148; this.operationProvider(); - this.state = 848; + this.state = 1151; this.errorHandler.sync(this); _la = this.tokenStream.LA(1); - if (_la === 16) { + if (_la === 36) { { - this.state = 846; + this.state = 1149; this.match(QuixosCapabilityParser.QUERY_REASON); - this.state = 847; + this.state = 1150; this.stringLiteral(); } } - this.state = 850; + this.state = 1153; this.match(QuixosCapabilityParser.SEMI); } } @@ -3250,15 +4359,15 @@ export class QuixosCapabilityParser extends antlr.Parser { } public memberOperationRef(): MemberOperationRefContext { let localContext = new MemberOperationRefContext(this.context, this.state); - this.enterRule(localContext, 104, QuixosCapabilityParser.RULE_memberOperationRef); + this.enterRule(localContext, 132, QuixosCapabilityParser.RULE_memberOperationRef); try { this.enterOuterAlt(localContext, 1); { - this.state = 852; + this.state = 1155; this.identifier(); - this.state = 853; + this.state = 1156; this.match(QuixosCapabilityParser.DOT); - this.state = 854; + this.state = 1157; this.operationName(); } } @@ -3277,105 +4386,95 @@ export class QuixosCapabilityParser extends antlr.Parser { } public operationName(): OperationNameContext { let localContext = new OperationNameContext(this.context, this.state); - this.enterRule(localContext, 106, QuixosCapabilityParser.RULE_operationName); + this.enterRule(localContext, 134, QuixosCapabilityParser.RULE_operationName); try { - this.state = 867; + this.state = 1171; this.errorHandler.sync(this); - switch (this.tokenStream.LA(1)) { - case QuixosCapabilityParser.RESULT_BYTES: - case QuixosCapabilityParser.RPC_CALLS: - case QuixosCapabilityParser.DEADLINE_MS: - case QuixosCapabilityParser.QUERY: - case QuixosCapabilityParser.SPECIALIZE: - case QuixosCapabilityParser.ROOT: - case QuixosCapabilityParser.DOCUMENT: - case QuixosCapabilityParser.FRAGMENTS: - case QuixosCapabilityParser.VIEW: - case QuixosCapabilityParser.MAX: - case QuixosCapabilityParser.ALLOW: - case QuixosCapabilityParser.POLL: - case QuixosCapabilityParser.QUERYABLE: - case QuixosCapabilityParser.RPC: - case QuixosCapabilityParser.SOURCE: - case QuixosCapabilityParser.IDENTIFIER: + switch (this.interpreter.adaptivePredict(this.tokenStream, 104, this.context) ) { + case 1: this.enterOuterAlt(localContext, 1); { - this.state = 856; + this.state = 1159; this.identifier(); } break; - case QuixosCapabilityParser.CALL: + case 2: this.enterOuterAlt(localContext, 2); { - this.state = 857; + this.state = 1160; this.match(QuixosCapabilityParser.CALL); } break; - case QuixosCapabilityParser.GET: + case 3: this.enterOuterAlt(localContext, 3); { - this.state = 858; + this.state = 1161; this.match(QuixosCapabilityParser.GET); } break; - case QuixosCapabilityParser.SET: + case 4: this.enterOuterAlt(localContext, 4); { - this.state = 859; + this.state = 1162; this.match(QuixosCapabilityParser.SET); } break; - case QuixosCapabilityParser.RESOLVE: + case 5: this.enterOuterAlt(localContext, 5); { - this.state = 860; + this.state = 1163; + this.match(QuixosCapabilityParser.EDIT); + } + break; + case 6: + this.enterOuterAlt(localContext, 6); + { + this.state = 1164; this.match(QuixosCapabilityParser.RESOLVE); } break; - case QuixosCapabilityParser.CONNECT: - this.enterOuterAlt(localContext, 6); + case 7: + this.enterOuterAlt(localContext, 7); { - this.state = 861; + this.state = 1165; this.match(QuixosCapabilityParser.CONNECT); } break; - case QuixosCapabilityParser.DISCONNECT: - this.enterOuterAlt(localContext, 7); + case 8: + this.enterOuterAlt(localContext, 8); { - this.state = 862; + this.state = 1166; this.match(QuixosCapabilityParser.DISCONNECT); } break; - case QuixosCapabilityParser.WATCH_START: - this.enterOuterAlt(localContext, 8); + case 9: + this.enterOuterAlt(localContext, 9); { - this.state = 863; + this.state = 1167; this.match(QuixosCapabilityParser.WATCH_START); } break; - case QuixosCapabilityParser.WATCH_STOP: - this.enterOuterAlt(localContext, 9); + case 10: + this.enterOuterAlt(localContext, 10); { - this.state = 864; + this.state = 1168; this.match(QuixosCapabilityParser.WATCH_STOP); } break; - case QuixosCapabilityParser.SUBSCRIBE: - this.enterOuterAlt(localContext, 10); + case 11: + this.enterOuterAlt(localContext, 11); { - this.state = 865; + this.state = 1169; this.match(QuixosCapabilityParser.SUBSCRIBE); } break; - case QuixosCapabilityParser.UNSUBSCRIBE: - this.enterOuterAlt(localContext, 11); + case 12: + this.enterOuterAlt(localContext, 12); { - this.state = 866; + this.state = 1170; this.match(QuixosCapabilityParser.UNSUBSCRIBE); } break; - default: - throw new antlr.NoViableAltException(this); } } catch (re) { @@ -3393,73 +4492,143 @@ export class QuixosCapabilityParser extends antlr.Parser { } public operationProvider(): OperationProviderContext { let localContext = new OperationProviderContext(this.context, this.state); - this.enterRule(localContext, 108, QuixosCapabilityParser.RULE_operationProvider); + this.enterRule(localContext, 136, QuixosCapabilityParser.RULE_operationProvider); let _la: number; try { - this.state = 891; + this.state = 1219; this.errorHandler.sync(this); switch (this.tokenStream.LA(1)) { - case QuixosCapabilityParser.STATE: + case QuixosCapabilityParser.LIFECYCLE: this.enterOuterAlt(localContext, 1); { - this.state = 869; - this.match(QuixosCapabilityParser.STATE); - this.state = 870; - this.identifier(); - this.state = 871; + this.state = 1173; + this.match(QuixosCapabilityParser.LIFECYCLE); + this.state = 1174; this.match(QuixosCapabilityParser.DOT); - this.state = 872; + this.state = 1175; + this.identifier(); + } + break; + case QuixosCapabilityParser.JOURNAL: + this.enterOuterAlt(localContext, 2); + { + this.state = 1176; + this.match(QuixosCapabilityParser.JOURNAL); + this.state = 1177; + this.identifier(); + this.state = 1178; + this.match(QuixosCapabilityParser.DOT); + this.state = 1179; + this.identifier(); + } + break; + case QuixosCapabilityParser.SUBSCRIPTION: + this.enterOuterAlt(localContext, 3); + { + this.state = 1181; + this.match(QuixosCapabilityParser.SUBSCRIPTION); + this.state = 1182; + this.identifier(); + this.state = 1183; + this.match(QuixosCapabilityParser.DOT); + this.state = 1184; + this.identifier(); + } + break; + case QuixosCapabilityParser.STATE: + this.enterOuterAlt(localContext, 4); + { + this.state = 1186; + this.match(QuixosCapabilityParser.STATE); + this.state = 1187; + this.identifier(); + this.state = 1188; + this.match(QuixosCapabilityParser.DOT); + this.state = 1189; this.statePrimitive(); } break; case QuixosCapabilityParser.EDGE: - this.enterOuterAlt(localContext, 2); + this.enterOuterAlt(localContext, 5); { - this.state = 874; + this.state = 1191; this.match(QuixosCapabilityParser.EDGE); - this.state = 875; + this.state = 1192; this.identifier(); - this.state = 876; + this.state = 1193; this.match(QuixosCapabilityParser.DOT); - this.state = 877; + this.state = 1194; this.identifier(); - this.state = 878; + this.state = 1195; this.match(QuixosCapabilityParser.DOT); - this.state = 879; + this.state = 1196; this.edgePrimitive(); } break; - case QuixosCapabilityParser.PACKAGE: - this.enterOuterAlt(localContext, 3); + case QuixosCapabilityParser.INTERFACE: + this.enterOuterAlt(localContext, 6); { - this.state = 881; - this.match(QuixosCapabilityParser.PACKAGE); - this.state = 882; + this.state = 1198; + this.match(QuixosCapabilityParser.INTERFACE); + this.state = 1199; this.identifier(); - this.state = 883; - this.match(QuixosCapabilityParser.DOT); - this.state = 884; - this.identifier(); - this.state = 886; + this.state = 1201; this.errorHandler.sync(this); _la = this.tokenStream.LA(1); - if (_la === 123) { + if (_la === 149) { { - this.state = 885; + this.state = 1200; this.typeArguments(); } } - this.state = 889; + this.state = 1203; + this.match(QuixosCapabilityParser.DOT); + this.state = 1204; + this.memberOperationRef(); + } + break; + case QuixosCapabilityParser.PACKAGE: + this.enterOuterAlt(localContext, 7); + { + this.state = 1206; + this.match(QuixosCapabilityParser.PACKAGE); + this.state = 1207; + this.identifier(); + this.state = 1208; + this.match(QuixosCapabilityParser.DOT); + this.state = 1209; + this.identifier(); + this.state = 1211; this.errorHandler.sync(this); _la = this.tokenStream.LA(1); - if (_la === 46) { + if (_la === 149) { { - this.state = 888; + this.state = 1210; + this.typeArguments(); + } + } + + this.state = 1214; + this.errorHandler.sync(this); + _la = this.tokenStream.LA(1); + if (_la === 68) { + { + this.state = 1213; this.dependencyBindingBlock(); } } + this.state = 1217; + this.errorHandler.sync(this); + _la = this.tokenStream.LA(1); + if (_la === 70) { + { + this.state = 1216; + this.acceptanceBinding(); + } + } + } break; default: @@ -3479,16 +4648,41 @@ export class QuixosCapabilityParser extends antlr.Parser { } return localContext; } + public acceptanceBinding(): AcceptanceBindingContext { + let localContext = new AcceptanceBindingContext(this.context, this.state); + this.enterRule(localContext, 138, QuixosCapabilityParser.RULE_acceptanceBinding); + try { + this.enterOuterAlt(localContext, 1); + { + this.state = 1221; + this.match(QuixosCapabilityParser.ACCEPT); + this.state = 1222; + this.dependencyBindingBlock(); + } + } + catch (re) { + if (re instanceof antlr.RecognitionException) { + this.errorHandler.reportError(this, re); + this.errorHandler.recover(this, re); + } else { + throw re; + } + } + finally { + this.exitRule(); + } + return localContext; + } public statePrimitive(): StatePrimitiveContext { let localContext = new StatePrimitiveContext(this.context, this.state); - this.enterRule(localContext, 110, QuixosCapabilityParser.RULE_statePrimitive); + this.enterRule(localContext, 140, QuixosCapabilityParser.RULE_statePrimitive); let _la: number; try { this.enterOuterAlt(localContext, 1); { - this.state = 893; + this.state = 1224; _la = this.tokenStream.LA(1); - if(!(((((_la - 76)) & ~0x1F) === 0 && ((1 << (_la - 76)) & 195) !== 0))) { + if(!(_la === 1 || ((((_la - 102)) & ~0x1F) === 0 && ((1 << (_la - 102)) & 195) !== 0))) { this.errorHandler.recoverInline(this); } else { @@ -3512,14 +4706,14 @@ export class QuixosCapabilityParser extends antlr.Parser { } public edgePrimitive(): EdgePrimitiveContext { let localContext = new EdgePrimitiveContext(this.context, this.state); - this.enterRule(localContext, 112, QuixosCapabilityParser.RULE_edgePrimitive); + this.enterRule(localContext, 142, QuixosCapabilityParser.RULE_edgePrimitive); let _la: number; try { this.enterOuterAlt(localContext, 1); { - this.state = 895; + this.state = 1226; _la = this.tokenStream.LA(1); - if(!(((((_la - 78)) & ~0x1F) === 0 && ((1 << (_la - 78)) & 55) !== 0))) { + if(!(((((_la - 104)) & ~0x1F) === 0 && ((1 << (_la - 104)) & 55) !== 0))) { this.errorHandler.recoverInline(this); } else { @@ -3543,30 +4737,30 @@ export class QuixosCapabilityParser extends antlr.Parser { } public dependencyBindingBlock(): DependencyBindingBlockContext { let localContext = new DependencyBindingBlockContext(this.context, this.state); - this.enterRule(localContext, 114, QuixosCapabilityParser.RULE_dependencyBindingBlock); + this.enterRule(localContext, 144, QuixosCapabilityParser.RULE_dependencyBindingBlock); let _la: number; try { this.enterOuterAlt(localContext, 1); { - this.state = 897; + this.state = 1228; this.match(QuixosCapabilityParser.WITH); - this.state = 898; + this.state = 1229; this.match(QuixosCapabilityParser.LBRACE); - this.state = 902; + this.state = 1233; this.errorHandler.sync(this); _la = this.tokenStream.LA(1); - while ((((_la) & ~0x1F) === 0 && ((1 << _la) & 65532) !== 0) || _la === 55 || _la === 129) { + while ((((_la) & ~0x1F) === 0 && ((1 << _la) & 4294964466) !== 0) || ((((_la - 32)) & ~0x1F) === 0 && ((1 << (_la - 32)) & 66575) !== 0) || ((((_la - 78)) & ~0x1F) === 0 && ((1 << (_la - 78)) & 146801025) !== 0) || _la === 112 || _la === 121 || _la === 155) { { { - this.state = 899; + this.state = 1230; this.dependencyBinding(); } } - this.state = 904; + this.state = 1235; this.errorHandler.sync(this); _la = this.tokenStream.LA(1); } - this.state = 905; + this.state = 1236; this.match(QuixosCapabilityParser.RBRACE); } } @@ -3585,174 +4779,256 @@ export class QuixosCapabilityParser extends antlr.Parser { } public dependencyBinding(): DependencyBindingContext { let localContext = new DependencyBindingContext(this.context, this.state); - this.enterRule(localContext, 116, QuixosCapabilityParser.RULE_dependencyBinding); + this.enterRule(localContext, 146, QuixosCapabilityParser.RULE_dependencyBinding); let _la: number; try { - this.state = 976; + this.state = 1336; this.errorHandler.sync(this); - switch (this.interpreter.adaptivePredict(this.tokenStream, 81, this.context) ) { + switch (this.interpreter.adaptivePredict(this.tokenStream, 118, this.context) ) { case 1: this.enterOuterAlt(localContext, 1); { - this.state = 907; + this.state = 1238; this.identifier(); - this.state = 908; + this.state = 1239; this.match(QuixosCapabilityParser.TO); - this.state = 909; - this.match(QuixosCapabilityParser.STATE); - this.state = 910; - this.identifier(); - this.state = 917; - this.errorHandler.sync(this); - _la = this.tokenStream.LA(1); - if (_la === 48) { - { - this.state = 911; - this.match(QuixosCapabilityParser.VIA); - this.state = 912; - this.match(QuixosCapabilityParser.EDGE); - this.state = 913; - this.identifier(); - this.state = 914; - this.match(QuixosCapabilityParser.DOT); - this.state = 915; - this.identifier(); - } - } - - this.state = 919; + this.state = 1240; + this.match(QuixosCapabilityParser.WORKSPACE); + this.state = 1241; + this.match(QuixosCapabilityParser.REFLECTION); + this.state = 1242; + this.match(QuixosCapabilityParser.LBRACK); + this.state = 1243; + this.identifierList(); + this.state = 1244; + this.match(QuixosCapabilityParser.RBRACK); + this.state = 1245; this.match(QuixosCapabilityParser.SEMI); } break; case 2: this.enterOuterAlt(localContext, 2); { - this.state = 921; + this.state = 1247; this.identifier(); - this.state = 922; + this.state = 1248; this.match(QuixosCapabilityParser.TO); - this.state = 923; - this.match(QuixosCapabilityParser.EDGE); - this.state = 924; + this.state = 1249; + this.match(QuixosCapabilityParser.STATE); + this.state = 1250; this.identifier(); - this.state = 925; - this.match(QuixosCapabilityParser.DOT); - this.state = 926; - this.identifier(); - this.state = 933; + this.state = 1257; this.errorHandler.sync(this); _la = this.tokenStream.LA(1); - if (_la === 48) { + if (_la === 71) { { - this.state = 927; + this.state = 1251; this.match(QuixosCapabilityParser.VIA); - this.state = 928; + this.state = 1252; this.match(QuixosCapabilityParser.EDGE); - this.state = 929; + this.state = 1253; this.identifier(); - this.state = 930; + this.state = 1254; this.match(QuixosCapabilityParser.DOT); - this.state = 931; + this.state = 1255; this.identifier(); } } - this.state = 935; + this.state = 1259; this.match(QuixosCapabilityParser.SEMI); } break; case 3: this.enterOuterAlt(localContext, 3); { - this.state = 937; + this.state = 1261; this.identifier(); - this.state = 938; + this.state = 1262; this.match(QuixosCapabilityParser.TO); - this.state = 939; - this.match(QuixosCapabilityParser.INTERFACE); - this.state = 940; + this.state = 1263; + this.match(QuixosCapabilityParser.EDGE); + this.state = 1264; this.identifier(); - this.state = 942; + this.state = 1265; + this.match(QuixosCapabilityParser.DOT); + this.state = 1266; + this.identifier(); + this.state = 1273; this.errorHandler.sync(this); _la = this.tokenStream.LA(1); - if (_la === 123) { + if (_la === 71) { { - this.state = 941; - this.typeArguments(); - } - } - - this.state = 950; - this.errorHandler.sync(this); - _la = this.tokenStream.LA(1); - if (_la === 48) { - { - this.state = 944; + this.state = 1267; this.match(QuixosCapabilityParser.VIA); - this.state = 945; + this.state = 1268; this.match(QuixosCapabilityParser.EDGE); - this.state = 946; + this.state = 1269; this.identifier(); - this.state = 947; + this.state = 1270; this.match(QuixosCapabilityParser.DOT); - this.state = 948; + this.state = 1271; this.identifier(); } } - this.state = 952; + this.state = 1275; this.match(QuixosCapabilityParser.SEMI); } break; case 4: this.enterOuterAlt(localContext, 4); { - this.state = 954; + this.state = 1277; this.identifier(); - this.state = 955; + this.state = 1278; this.match(QuixosCapabilityParser.TO); - this.state = 956; - this.match(QuixosCapabilityParser.CONSTRUCTOR); - this.state = 957; + this.state = 1279; + this.match(QuixosCapabilityParser.INTERFACE); + this.state = 1280; this.identifier(); - this.state = 958; + this.state = 1282; + this.errorHandler.sync(this); + _la = this.tokenStream.LA(1); + if (_la === 149) { + { + this.state = 1281; + this.typeArguments(); + } + } + + this.state = 1292; + this.errorHandler.sync(this); + switch (this.tokenStream.LA(1)) { + case QuixosCapabilityParser.VIA: + { + { + this.state = 1284; + this.match(QuixosCapabilityParser.VIA); + this.state = 1285; + this.match(QuixosCapabilityParser.EDGE); + this.state = 1286; + this.identifier(); + this.state = 1287; + this.match(QuixosCapabilityParser.DOT); + this.state = 1288; + this.identifier(); + } + } + break; + case QuixosCapabilityParser.FROM: + { + { + this.state = 1290; + this.match(QuixosCapabilityParser.FROM); + this.state = 1291; + this.match(QuixosCapabilityParser.INPUT); + } + } + break; + case QuixosCapabilityParser.SEMI: + break; + default: + break; + } + this.state = 1294; this.match(QuixosCapabilityParser.SEMI); } break; case 5: this.enterOuterAlt(localContext, 5); { - this.state = 960; + this.state = 1296; this.identifier(); - this.state = 961; + this.state = 1297; this.match(QuixosCapabilityParser.TO); - this.state = 962; - this.match(QuixosCapabilityParser.QUERY); - this.state = 963; + this.state = 1298; + this.match(QuixosCapabilityParser.CREATION); + this.state = 1299; this.identifier(); - this.state = 964; - this.match(QuixosCapabilityParser.DOT); - this.state = 965; - this.identifier(); - this.state = 972; + this.state = 1300; + this.match(QuixosCapabilityParser.WITH); + this.state = 1301; + this.match(QuixosCapabilityParser.LBRACE); + this.state = 1305; this.errorHandler.sync(this); _la = this.tokenStream.LA(1); - if (_la === 48) { + while ((((_la) & ~0x1F) === 0 && ((1 << _la) & 4294964466) !== 0) || ((((_la - 32)) & ~0x1F) === 0 && ((1 << (_la - 32)) & 66575) !== 0) || ((((_la - 78)) & ~0x1F) === 0 && ((1 << (_la - 78)) & 146801025) !== 0) || _la === 112 || _la === 121 || _la === 155) { { - this.state = 966; + { + this.state = 1302; + this.creationOwnerMapping(); + } + } + this.state = 1307; + this.errorHandler.sync(this); + _la = this.tokenStream.LA(1); + } + this.state = 1308; + this.match(QuixosCapabilityParser.RBRACE); + this.state = 1309; + this.match(QuixosCapabilityParser.SEMI); + } + break; + case 6: + this.enterOuterAlt(localContext, 6); + { + this.state = 1311; + this.identifier(); + this.state = 1312; + this.match(QuixosCapabilityParser.TO); + this.state = 1313; + this.match(QuixosCapabilityParser.CONSTRUCTOR); + this.state = 1314; + this.identifier(); + this.state = 1315; + this.match(QuixosCapabilityParser.SEMI); + } + break; + case 7: + this.enterOuterAlt(localContext, 7); + { + this.state = 1317; + this.identifier(); + this.state = 1318; + this.match(QuixosCapabilityParser.TO); + this.state = 1320; + this.errorHandler.sync(this); + _la = this.tokenStream.LA(1); + if (_la === 11) { + { + this.state = 1319; + this.match(QuixosCapabilityParser.WORKSPACE); + } + } + + this.state = 1322; + this.match(QuixosCapabilityParser.QUERY); + this.state = 1323; + this.identifier(); + this.state = 1324; + this.match(QuixosCapabilityParser.DOT); + this.state = 1325; + this.identifier(); + this.state = 1332; + this.errorHandler.sync(this); + _la = this.tokenStream.LA(1); + if (_la === 71) { + { + this.state = 1326; this.match(QuixosCapabilityParser.VIA); - this.state = 967; + this.state = 1327; this.match(QuixosCapabilityParser.EDGE); - this.state = 968; + this.state = 1328; this.identifier(); - this.state = 969; + this.state = 1329; this.match(QuixosCapabilityParser.DOT); - this.state = 970; + this.state = 1330; this.identifier(); } } - this.state = 974; + this.state = 1334; this.match(QuixosCapabilityParser.SEMI); } break; @@ -3773,34 +5049,34 @@ export class QuixosCapabilityParser extends antlr.Parser { } public constructorBindingDecl(): ConstructorBindingDeclContext { let localContext = new ConstructorBindingDeclContext(this.context, this.state); - this.enterRule(localContext, 118, QuixosCapabilityParser.RULE_constructorBindingDecl); + this.enterRule(localContext, 148, QuixosCapabilityParser.RULE_constructorBindingDecl); let _la: number; try { this.enterOuterAlt(localContext, 1); { - this.state = 978; + this.state = 1338; this.match(QuixosCapabilityParser.CONSTRUCTOR); - this.state = 979; + this.state = 1339; this.identifier(); - this.state = 980; + this.state = 1340; this.match(QuixosCapabilityParser.TO); - this.state = 981; + this.state = 1341; this.identifier(); - this.state = 982; + this.state = 1342; this.match(QuixosCapabilityParser.DOT); - this.state = 983; + this.state = 1343; this.identifier(); - this.state = 985; + this.state = 1345; this.errorHandler.sync(this); _la = this.tokenStream.LA(1); - if (_la === 46) { + if (_la === 68) { { - this.state = 984; + this.state = 1344; this.dependencyBindingBlock(); } } - this.state = 987; + this.state = 1347; this.match(QuixosCapabilityParser.SEMI); } } @@ -3819,183 +5095,188 @@ export class QuixosCapabilityParser extends antlr.Parser { } public valueType(): ValueTypeContext { let localContext = new ValueTypeContext(this.context, this.state); - this.enterRule(localContext, 120, QuixosCapabilityParser.RULE_valueType); + this.enterRule(localContext, 150, QuixosCapabilityParser.RULE_valueType); let _la: number; try { - this.state = 1035; + this.state = 1406; this.errorHandler.sync(this); - switch (this.tokenStream.LA(1)) { - case QuixosCapabilityParser.BOOL: - case QuixosCapabilityParser.BYTES: - case QuixosCapabilityParser.DOUBLE: - case QuixosCapabilityParser.INT32: - case QuixosCapabilityParser.INT64: - case QuixosCapabilityParser.STRING: - case QuixosCapabilityParser.UINT32: - case QuixosCapabilityParser.UINT64: + switch (this.interpreter.adaptivePredict(this.tokenStream, 124, this.context) ) { + case 1: this.enterOuterAlt(localContext, 1); { - this.state = 989; + this.state = 1349; this.scalarType(); } break; - case QuixosCapabilityParser.UNIT: + case 2: this.enterOuterAlt(localContext, 2); { - this.state = 990; + this.state = 1350; this.match(QuixosCapabilityParser.UNIT); } break; - case QuixosCapabilityParser.WATCH_HANDLE: + case 3: this.enterOuterAlt(localContext, 3); { - this.state = 991; + this.state = 1351; this.match(QuixosCapabilityParser.WATCH_HANDLE); } break; - case QuixosCapabilityParser.MESSAGE: + case 4: this.enterOuterAlt(localContext, 4); { - this.state = 992; + this.state = 1352; this.match(QuixosCapabilityParser.MESSAGE); - this.state = 993; + this.state = 1353; this.stringLiteral(); } break; - case QuixosCapabilityParser.ATOM_REF: + case 5: this.enterOuterAlt(localContext, 5); { - this.state = 994; + this.state = 1354; this.match(QuixosCapabilityParser.ATOM_REF); - this.state = 995; + this.state = 1355; this.match(QuixosCapabilityParser.LT); - this.state = 996; + this.state = 1356; this.identifier(); - this.state = 997; + this.state = 1357; this.match(QuixosCapabilityParser.GT); } break; - case QuixosCapabilityParser.INTERFACE_REF: + case 6: this.enterOuterAlt(localContext, 6); { - this.state = 999; + this.state = 1359; this.match(QuixosCapabilityParser.INTERFACE_REF); - this.state = 1000; + this.state = 1360; this.match(QuixosCapabilityParser.LT); - this.state = 1001; + this.state = 1361; this.identifier(); - this.state = 1003; + this.state = 1363; this.errorHandler.sync(this); _la = this.tokenStream.LA(1); - if (_la === 123) { + if (_la === 149) { { - this.state = 1002; + this.state = 1362; this.typeArguments(); } } - this.state = 1005; + this.state = 1365; this.match(QuixosCapabilityParser.GT); } break; - case QuixosCapabilityParser.REF: + case 7: this.enterOuterAlt(localContext, 7); { - this.state = 1007; + this.state = 1367; this.match(QuixosCapabilityParser.REF); - this.state = 1008; + this.state = 1368; this.match(QuixosCapabilityParser.LT); - this.state = 1009; + this.state = 1369; this.identifier(); - this.state = 1010; + this.state = 1370; this.match(QuixosCapabilityParser.GT); } break; - case QuixosCapabilityParser.OPTIONAL: + case 8: this.enterOuterAlt(localContext, 8); { - this.state = 1012; + this.state = 1372; this.match(QuixosCapabilityParser.OPTIONAL); - this.state = 1013; + this.state = 1373; this.match(QuixosCapabilityParser.LT); - this.state = 1014; + this.state = 1374; this.valueType(); - this.state = 1015; + this.state = 1375; this.match(QuixosCapabilityParser.GT); } break; - case QuixosCapabilityParser.LIST: + case 9: this.enterOuterAlt(localContext, 9); { - this.state = 1017; + this.state = 1377; this.match(QuixosCapabilityParser.LIST); - this.state = 1018; + this.state = 1378; this.match(QuixosCapabilityParser.LT); - this.state = 1019; + this.state = 1379; this.valueType(); - this.state = 1020; + this.state = 1380; this.match(QuixosCapabilityParser.GT); } break; - case QuixosCapabilityParser.RECORD: + case 10: this.enterOuterAlt(localContext, 10); { - this.state = 1022; + this.state = 1382; this.match(QuixosCapabilityParser.RECORD); - this.state = 1023; + this.state = 1383; this.match(QuixosCapabilityParser.LBRACE); - this.state = 1027; + this.state = 1387; this.errorHandler.sync(this); _la = this.tokenStream.LA(1); - while ((((_la) & ~0x1F) === 0 && ((1 << _la) & 65532) !== 0) || _la === 55 || _la === 129) { + while ((((_la) & ~0x1F) === 0 && ((1 << _la) & 4294964466) !== 0) || ((((_la - 32)) & ~0x1F) === 0 && ((1 << (_la - 32)) & 66575) !== 0) || ((((_la - 78)) & ~0x1F) === 0 && ((1 << (_la - 78)) & 146801025) !== 0) || _la === 112 || _la === 121 || _la === 155) { { { - this.state = 1024; + this.state = 1384; this.recordField(); } } - this.state = 1029; + this.state = 1389; this.errorHandler.sync(this); _la = this.tokenStream.LA(1); } - this.state = 1030; + this.state = 1390; this.match(QuixosCapabilityParser.RBRACE); } break; - case QuixosCapabilityParser.RESULT_BYTES: - case QuixosCapabilityParser.RPC_CALLS: - case QuixosCapabilityParser.DEADLINE_MS: - case QuixosCapabilityParser.QUERY: - case QuixosCapabilityParser.SPECIALIZE: - case QuixosCapabilityParser.ROOT: - case QuixosCapabilityParser.DOCUMENT: - case QuixosCapabilityParser.FRAGMENTS: - case QuixosCapabilityParser.VIEW: - case QuixosCapabilityParser.MAX: - case QuixosCapabilityParser.ALLOW: - case QuixosCapabilityParser.POLL: - case QuixosCapabilityParser.QUERYABLE: - case QuixosCapabilityParser.RPC: - case QuixosCapabilityParser.SOURCE: - case QuixosCapabilityParser.IDENTIFIER: + case 11: this.enterOuterAlt(localContext, 11); { - this.state = 1031; - this.identifier(); - this.state = 1033; + this.state = 1391; + this.match(QuixosCapabilityParser.VARIANT); + this.state = 1392; + this.match(QuixosCapabilityParser.ID); + this.state = 1393; + this.stringLiteral(); + this.state = 1394; + this.match(QuixosCapabilityParser.LBRACE); + this.state = 1396; this.errorHandler.sync(this); _la = this.tokenStream.LA(1); - if (_la === 123) { + do { { - this.state = 1032; + { + this.state = 1395; + this.variantCase(); + } + } + this.state = 1398; + this.errorHandler.sync(this); + _la = this.tokenStream.LA(1); + } while ((((_la) & ~0x1F) === 0 && ((1 << _la) & 4294964466) !== 0) || ((((_la - 32)) & ~0x1F) === 0 && ((1 << (_la - 32)) & 66575) !== 0) || ((((_la - 78)) & ~0x1F) === 0 && ((1 << (_la - 78)) & 146801025) !== 0) || _la === 112 || _la === 121 || _la === 155); + this.state = 1400; + this.match(QuixosCapabilityParser.RBRACE); + } + break; + case 12: + this.enterOuterAlt(localContext, 12); + { + this.state = 1402; + this.identifier(); + this.state = 1404; + this.errorHandler.sync(this); + _la = this.tokenStream.LA(1); + if (_la === 149) { + { + this.state = 1403; this.typeArguments(); } } } break; - default: - throw new antlr.NoViableAltException(this); } } catch (re) { @@ -4013,17 +5294,50 @@ export class QuixosCapabilityParser extends antlr.Parser { } public recordField(): RecordFieldContext { let localContext = new RecordFieldContext(this.context, this.state); - this.enterRule(localContext, 122, QuixosCapabilityParser.RULE_recordField); + this.enterRule(localContext, 152, QuixosCapabilityParser.RULE_recordField); try { this.enterOuterAlt(localContext, 1); { - this.state = 1037; + this.state = 1408; this.identifier(); - this.state = 1038; + this.state = 1409; this.match(QuixosCapabilityParser.COLON); - this.state = 1039; + this.state = 1410; this.valueType(); - this.state = 1040; + this.state = 1411; + this.match(QuixosCapabilityParser.SEMI); + } + } + catch (re) { + if (re instanceof antlr.RecognitionException) { + this.errorHandler.reportError(this, re); + this.errorHandler.recover(this, re); + } else { + throw re; + } + } + finally { + this.exitRule(); + } + return localContext; + } + public variantCase(): VariantCaseContext { + let localContext = new VariantCaseContext(this.context, this.state); + this.enterRule(localContext, 154, QuixosCapabilityParser.RULE_variantCase); + try { + this.enterOuterAlt(localContext, 1); + { + this.state = 1413; + this.identifier(); + this.state = 1414; + this.match(QuixosCapabilityParser.ID); + this.state = 1415; + this.stringLiteral(); + this.state = 1416; + this.match(QuixosCapabilityParser.COLON); + this.state = 1417; + this.valueType(); + this.state = 1418; this.match(QuixosCapabilityParser.SEMI); } } @@ -4042,14 +5356,14 @@ export class QuixosCapabilityParser extends antlr.Parser { } public scalarType(): ScalarTypeContext { let localContext = new ScalarTypeContext(this.context, this.state); - this.enterRule(localContext, 124, QuixosCapabilityParser.RULE_scalarType); + this.enterRule(localContext, 156, QuixosCapabilityParser.RULE_scalarType); let _la: number; try { this.enterOuterAlt(localContext, 1); { - this.state = 1042; + this.state = 1420; _la = this.tokenStream.LA(1); - if(!(((((_la - 101)) & ~0x1F) === 0 && ((1 << (_la - 101)) & 255) !== 0))) { + if(!(((((_la - 127)) & ~0x1F) === 0 && ((1 << (_la - 127)) & 255) !== 0))) { this.errorHandler.recoverInline(this); } else { @@ -4073,14 +5387,14 @@ export class QuixosCapabilityParser extends antlr.Parser { } public cardinality(): CardinalityContext { let localContext = new CardinalityContext(this.context, this.state); - this.enterRule(localContext, 126, QuixosCapabilityParser.RULE_cardinality); + this.enterRule(localContext, 158, QuixosCapabilityParser.RULE_cardinality); let _la: number; try { this.enterOuterAlt(localContext, 1); { - this.state = 1044; + this.state = 1422; _la = this.tokenStream.LA(1); - if(!(((((_la - 88)) & ~0x1F) === 0 && ((1 << (_la - 88)) & 15) !== 0))) { + if(!(((((_la - 114)) & ~0x1F) === 0 && ((1 << (_la - 114)) & 15) !== 0))) { this.errorHandler.recoverInline(this); } else { @@ -4104,64 +5418,64 @@ export class QuixosCapabilityParser extends antlr.Parser { } public jsonLiteral(): JsonLiteralContext { let localContext = new JsonLiteralContext(this.context, this.state); - this.enterRule(localContext, 128, QuixosCapabilityParser.RULE_jsonLiteral); + this.enterRule(localContext, 160, QuixosCapabilityParser.RULE_jsonLiteral); try { - this.state = 1054; + this.state = 1432; this.errorHandler.sync(this); switch (this.tokenStream.LA(1)) { case QuixosCapabilityParser.STRING_LITERAL: this.enterOuterAlt(localContext, 1); { - this.state = 1046; + this.state = 1424; this.stringLiteral(); } break; case QuixosCapabilityParser.INTEGER: this.enterOuterAlt(localContext, 2); { - this.state = 1047; + this.state = 1425; this.match(QuixosCapabilityParser.INTEGER); } break; case QuixosCapabilityParser.JSON_NUMBER: this.enterOuterAlt(localContext, 3); { - this.state = 1048; + this.state = 1426; this.match(QuixosCapabilityParser.JSON_NUMBER); } break; case QuixosCapabilityParser.TRUE: this.enterOuterAlt(localContext, 4); { - this.state = 1049; + this.state = 1427; this.match(QuixosCapabilityParser.TRUE); } break; case QuixosCapabilityParser.FALSE: this.enterOuterAlt(localContext, 5); { - this.state = 1050; + this.state = 1428; this.match(QuixosCapabilityParser.FALSE); } break; case QuixosCapabilityParser.NULL: this.enterOuterAlt(localContext, 6); { - this.state = 1051; + this.state = 1429; this.match(QuixosCapabilityParser.NULL); } break; case QuixosCapabilityParser.LBRACE: this.enterOuterAlt(localContext, 7); { - this.state = 1052; + this.state = 1430; this.jsonObject(); } break; case QuixosCapabilityParser.LBRACK: this.enterOuterAlt(localContext, 8); { - this.state = 1053; + this.state = 1431; this.jsonArray(); } break; @@ -4184,40 +5498,40 @@ export class QuixosCapabilityParser extends antlr.Parser { } public jsonObject(): JsonObjectContext { let localContext = new JsonObjectContext(this.context, this.state); - this.enterRule(localContext, 130, QuixosCapabilityParser.RULE_jsonObject); + this.enterRule(localContext, 162, QuixosCapabilityParser.RULE_jsonObject); let _la: number; try { this.enterOuterAlt(localContext, 1); { - this.state = 1056; + this.state = 1434; this.match(QuixosCapabilityParser.LBRACE); - this.state = 1065; + this.state = 1443; this.errorHandler.sync(this); _la = this.tokenStream.LA(1); - if (_la === 130) { + if (_la === 156) { { - this.state = 1057; + this.state = 1435; this.jsonMember(); - this.state = 1062; + this.state = 1440; this.errorHandler.sync(this); _la = this.tokenStream.LA(1); - while (_la === 115) { + while (_la === 141) { { { - this.state = 1058; + this.state = 1436; this.match(QuixosCapabilityParser.COMMA); - this.state = 1059; + this.state = 1437; this.jsonMember(); } } - this.state = 1064; + this.state = 1442; this.errorHandler.sync(this); _la = this.tokenStream.LA(1); } } } - this.state = 1067; + this.state = 1445; this.match(QuixosCapabilityParser.RBRACE); } } @@ -4236,15 +5550,15 @@ export class QuixosCapabilityParser extends antlr.Parser { } public jsonMember(): JsonMemberContext { let localContext = new JsonMemberContext(this.context, this.state); - this.enterRule(localContext, 132, QuixosCapabilityParser.RULE_jsonMember); + this.enterRule(localContext, 164, QuixosCapabilityParser.RULE_jsonMember); try { this.enterOuterAlt(localContext, 1); { - this.state = 1069; + this.state = 1447; this.stringLiteral(); - this.state = 1070; + this.state = 1448; this.match(QuixosCapabilityParser.COLON); - this.state = 1071; + this.state = 1449; this.jsonLiteral(); } } @@ -4263,40 +5577,40 @@ export class QuixosCapabilityParser extends antlr.Parser { } public jsonArray(): JsonArrayContext { let localContext = new JsonArrayContext(this.context, this.state); - this.enterRule(localContext, 134, QuixosCapabilityParser.RULE_jsonArray); + this.enterRule(localContext, 166, QuixosCapabilityParser.RULE_jsonArray); let _la: number; try { this.enterOuterAlt(localContext, 1); { - this.state = 1073; + this.state = 1451; this.match(QuixosCapabilityParser.LBRACK); - this.state = 1082; + this.state = 1460; this.errorHandler.sync(this); _la = this.tokenStream.LA(1); - if (((((_la - 109)) & ~0x1F) === 0 && ((1 << (_la - 109)) & 2884871) !== 0)) { + if (((((_la - 135)) & ~0x1F) === 0 && ((1 << (_la - 135)) & 2884871) !== 0)) { { - this.state = 1074; + this.state = 1452; this.jsonLiteral(); - this.state = 1079; + this.state = 1457; this.errorHandler.sync(this); _la = this.tokenStream.LA(1); - while (_la === 115) { + while (_la === 141) { { { - this.state = 1075; + this.state = 1453; this.match(QuixosCapabilityParser.COMMA); - this.state = 1076; + this.state = 1454; this.jsonLiteral(); } } - this.state = 1081; + this.state = 1459; this.errorHandler.sync(this); _la = this.tokenStream.LA(1); } } } - this.state = 1084; + this.state = 1462; this.match(QuixosCapabilityParser.RBRACK); } } @@ -4315,14 +5629,14 @@ export class QuixosCapabilityParser extends antlr.Parser { } public identifier(): IdentifierContext { let localContext = new IdentifierContext(this.context, this.state); - this.enterRule(localContext, 136, QuixosCapabilityParser.RULE_identifier); + this.enterRule(localContext, 168, QuixosCapabilityParser.RULE_identifier); let _la: number; try { this.enterOuterAlt(localContext, 1); { - this.state = 1086; + this.state = 1464; _la = this.tokenStream.LA(1); - if(!((((_la) & ~0x1F) === 0 && ((1 << _la) & 65532) !== 0) || _la === 55 || _la === 129)) { + if(!((((_la) & ~0x1F) === 0 && ((1 << _la) & 4294964466) !== 0) || ((((_la - 32)) & ~0x1F) === 0 && ((1 << (_la - 32)) & 66575) !== 0) || ((((_la - 78)) & ~0x1F) === 0 && ((1 << (_la - 78)) & 146801025) !== 0) || _la === 112 || _la === 121 || _la === 155)) { this.errorHandler.recoverInline(this); } else { @@ -4346,11 +5660,11 @@ export class QuixosCapabilityParser extends antlr.Parser { } public stringLiteral(): StringLiteralContext { let localContext = new StringLiteralContext(this.context, this.state); - this.enterRule(localContext, 138, QuixosCapabilityParser.RULE_stringLiteral); + this.enterRule(localContext, 170, QuixosCapabilityParser.RULE_stringLiteral); try { this.enterOuterAlt(localContext, 1); { - this.state = 1088; + this.state = 1466; this.match(QuixosCapabilityParser.STRING_LITERAL); } } @@ -4369,7 +5683,7 @@ export class QuixosCapabilityParser extends antlr.Parser { } public static readonly _serializedATN: number[] = [ - 4,1,133,1091,2,0,7,0,2,1,7,1,2,2,7,2,2,3,7,3,2,4,7,4,2,5,7,5,2,6, + 4,1,159,1469,2,0,7,0,2,1,7,1,2,2,7,2,2,3,7,3,2,4,7,4,2,5,7,5,2,6, 7,6,2,7,7,7,2,8,7,8,2,9,7,9,2,10,7,10,2,11,7,11,2,12,7,12,2,13,7, 13,2,14,7,14,2,15,7,15,2,16,7,16,2,17,7,17,2,18,7,18,2,19,7,19,2, 20,7,20,2,21,7,21,2,22,7,22,2,23,7,23,2,24,7,24,2,25,7,25,2,26,7, @@ -4379,406 +5693,568 @@ export class QuixosCapabilityParser extends antlr.Parser { 46,7,46,2,47,7,47,2,48,7,48,2,49,7,49,2,50,7,50,2,51,7,51,2,52,7, 52,2,53,7,53,2,54,7,54,2,55,7,55,2,56,7,56,2,57,7,57,2,58,7,58,2, 59,7,59,2,60,7,60,2,61,7,61,2,62,7,62,2,63,7,63,2,64,7,64,2,65,7, - 65,2,66,7,66,2,67,7,67,2,68,7,68,2,69,7,69,1,0,1,0,1,0,1,0,1,0,1, - 0,1,0,1,0,1,0,1,0,1,0,1,0,3,0,153,8,0,1,1,1,1,1,1,5,1,158,8,1,10, - 1,12,1,161,9,1,1,1,1,1,1,2,1,2,1,2,1,2,1,3,1,3,1,3,1,3,1,3,1,3,1, - 3,1,3,1,3,1,3,5,3,179,8,3,10,3,12,3,182,9,3,1,3,1,3,1,4,1,4,1,4, - 1,4,1,4,1,4,1,4,3,4,193,8,4,1,5,1,5,1,5,1,5,1,5,1,5,1,5,1,5,1,5, - 1,5,3,5,205,8,5,1,6,1,6,1,6,1,6,1,6,1,6,1,6,1,7,1,7,1,7,1,7,1,7, - 1,7,1,7,1,8,1,8,1,8,1,8,3,8,225,8,8,1,9,1,9,1,9,1,9,1,9,1,9,3,9, - 233,8,9,1,9,1,9,1,10,5,10,238,8,10,10,10,12,10,241,9,10,1,10,1,10, - 1,10,3,10,246,8,10,1,10,1,10,1,10,1,10,1,10,1,10,1,10,1,10,5,10, - 256,8,10,10,10,12,10,259,9,10,3,10,261,8,10,1,10,1,10,5,10,265,8, - 10,10,10,12,10,268,9,10,1,10,1,10,1,11,1,11,1,11,1,11,5,11,276,8, - 11,10,11,12,11,279,9,11,1,11,1,11,1,12,1,12,1,12,1,12,3,12,287,8, - 12,1,12,1,12,1,12,1,12,1,12,1,12,5,12,295,8,12,10,12,12,12,298,9, - 12,3,12,300,8,12,3,12,302,8,12,1,13,1,13,3,13,306,8,13,1,14,1,14, - 1,14,1,14,5,14,312,8,14,10,14,12,14,315,9,14,1,14,1,14,1,15,1,15, - 1,15,1,15,1,15,1,15,1,15,3,15,326,8,15,1,16,1,16,1,16,3,16,331,8, - 16,1,16,1,16,1,16,1,16,1,17,1,17,1,17,3,17,340,8,17,1,18,3,18,343, - 8,18,1,18,1,18,1,18,1,18,1,18,1,18,1,18,1,18,1,18,1,18,1,18,1,18, - 1,18,1,18,1,18,1,19,3,19,361,8,19,1,19,1,19,1,19,1,19,1,19,1,19, - 1,19,1,19,5,19,371,8,19,10,19,12,19,374,9,19,1,19,1,19,1,20,1,20, - 1,20,1,20,1,20,1,20,1,20,1,20,1,20,1,20,1,20,1,20,1,20,1,20,1,20, - 1,20,1,20,1,20,1,20,3,20,397,8,20,1,21,3,21,400,8,21,1,21,1,21,1, - 21,1,21,1,21,1,21,1,21,1,21,3,21,410,8,21,1,21,1,21,3,21,414,8,21, - 1,21,1,21,5,21,418,8,21,10,21,12,21,421,9,21,1,21,1,21,1,22,1,22, - 3,22,427,8,22,1,23,1,23,1,23,1,23,1,23,1,23,1,23,1,23,1,23,1,23, - 1,23,1,23,1,23,1,23,1,23,1,23,1,23,1,23,1,23,1,23,1,23,1,23,1,23, - 1,23,3,23,453,8,23,1,24,1,24,1,24,1,24,1,24,3,24,460,8,24,1,24,1, - 24,3,24,464,8,24,1,25,5,25,467,8,25,10,25,12,25,470,9,25,1,25,1, - 25,1,25,1,25,1,25,1,25,1,25,1,25,3,25,480,8,25,1,25,1,25,5,25,484, - 8,25,10,25,12,25,487,9,25,1,25,1,25,1,26,1,26,1,26,1,26,1,26,3,26, - 496,8,26,1,27,1,27,1,27,3,27,501,8,27,1,27,1,27,1,27,1,27,1,27,1, - 27,1,27,1,27,1,27,1,27,5,27,513,8,27,10,27,12,27,516,9,27,1,27,1, - 27,1,28,1,28,1,28,1,28,1,28,1,28,1,28,1,28,1,28,1,29,1,29,1,29,1, - 29,1,29,1,29,3,29,535,8,29,1,29,1,29,1,29,1,29,1,29,1,29,1,29,1, - 29,1,29,1,29,1,29,1,29,1,29,1,29,1,29,1,29,1,29,1,29,1,29,1,29,1, - 29,1,29,1,29,1,29,1,29,3,29,562,8,29,1,30,1,30,1,30,3,30,567,8,30, - 1,30,1,30,1,30,1,30,1,30,1,30,1,30,1,30,1,30,3,30,578,8,30,1,30, - 1,30,1,30,3,30,583,8,30,1,30,1,30,1,31,1,31,1,31,3,31,590,8,31,1, - 31,1,31,1,31,1,31,1,31,1,31,1,31,3,31,599,8,31,1,31,1,31,1,32,1, - 32,1,32,1,32,1,32,1,32,1,32,1,32,1,32,3,32,612,8,32,1,32,1,32,1, - 33,1,33,1,33,1,34,1,34,1,35,1,35,1,35,1,35,1,35,1,35,1,35,1,35,1, - 35,1,35,5,35,631,8,35,10,35,12,35,634,9,35,3,35,636,8,35,1,35,3, - 35,639,8,35,1,36,1,36,1,36,5,36,644,8,36,10,36,12,36,647,9,36,1, - 37,1,37,1,37,5,37,652,8,37,10,37,12,37,655,9,37,1,37,1,37,1,38,1, - 38,1,38,1,38,1,38,1,38,1,38,1,38,1,38,1,38,1,38,1,38,1,38,1,38,1, - 38,1,38,1,38,1,38,1,38,1,38,1,38,1,38,1,38,1,38,1,38,1,38,3,38,685, - 8,38,1,38,1,38,1,38,1,38,1,38,1,38,1,38,1,38,1,38,1,38,3,38,697, - 8,38,1,38,1,38,1,38,1,38,1,38,1,38,1,38,1,38,1,38,1,38,1,38,1,38, - 3,38,711,8,38,1,39,1,39,1,39,1,39,5,39,717,8,39,10,39,12,39,720, - 9,39,1,39,1,39,1,40,1,40,1,41,1,41,1,41,1,42,1,42,3,42,731,8,42, - 1,43,1,43,1,43,1,43,1,43,1,43,1,43,1,43,1,43,1,43,1,43,1,43,3,43, - 745,8,43,1,43,1,43,1,44,1,44,1,44,1,44,1,44,1,44,3,44,755,8,44,1, - 45,1,45,1,45,1,45,1,45,1,45,1,45,1,45,1,45,1,46,1,46,1,46,1,46,1, - 46,1,46,1,46,3,46,773,8,46,1,46,1,46,3,46,777,8,46,1,46,3,46,780, - 8,46,1,46,1,46,3,46,784,8,46,1,46,3,46,787,8,46,1,46,1,46,1,47,1, - 47,1,47,1,47,1,47,3,47,796,8,47,1,47,1,47,3,47,800,8,47,1,47,1,47, - 3,47,804,8,47,1,47,1,47,5,47,808,8,47,10,47,12,47,811,9,47,1,47, - 1,47,1,48,1,48,1,48,1,48,1,48,3,48,820,8,48,1,49,1,49,1,49,1,49, - 1,49,1,49,1,49,1,50,1,50,1,50,1,50,1,50,1,50,1,50,1,50,1,50,1,50, - 1,50,1,50,1,50,1,50,1,51,1,51,1,51,1,51,1,51,1,51,3,51,849,8,51, - 1,51,1,51,1,52,1,52,1,52,1,52,1,53,1,53,1,53,1,53,1,53,1,53,1,53, - 1,53,1,53,1,53,1,53,3,53,868,8,53,1,54,1,54,1,54,1,54,1,54,1,54, - 1,54,1,54,1,54,1,54,1,54,1,54,1,54,1,54,1,54,1,54,1,54,3,54,887, - 8,54,1,54,3,54,890,8,54,3,54,892,8,54,1,55,1,55,1,56,1,56,1,57,1, - 57,1,57,5,57,901,8,57,10,57,12,57,904,9,57,1,57,1,57,1,58,1,58,1, - 58,1,58,1,58,1,58,1,58,1,58,1,58,1,58,3,58,918,8,58,1,58,1,58,1, - 58,1,58,1,58,1,58,1,58,1,58,1,58,1,58,1,58,1,58,1,58,1,58,3,58,934, - 8,58,1,58,1,58,1,58,1,58,1,58,1,58,1,58,3,58,943,8,58,1,58,1,58, - 1,58,1,58,1,58,1,58,3,58,951,8,58,1,58,1,58,1,58,1,58,1,58,1,58, - 1,58,1,58,1,58,1,58,1,58,1,58,1,58,1,58,1,58,1,58,1,58,1,58,1,58, - 1,58,3,58,973,8,58,1,58,1,58,3,58,977,8,58,1,59,1,59,1,59,1,59,1, - 59,1,59,1,59,3,59,986,8,59,1,59,1,59,1,60,1,60,1,60,1,60,1,60,1, - 60,1,60,1,60,1,60,1,60,1,60,1,60,1,60,1,60,3,60,1004,8,60,1,60,1, - 60,1,60,1,60,1,60,1,60,1,60,1,60,1,60,1,60,1,60,1,60,1,60,1,60,1, - 60,1,60,1,60,1,60,1,60,1,60,5,60,1026,8,60,10,60,12,60,1029,9,60, - 1,60,1,60,1,60,3,60,1034,8,60,3,60,1036,8,60,1,61,1,61,1,61,1,61, - 1,61,1,62,1,62,1,63,1,63,1,64,1,64,1,64,1,64,1,64,1,64,1,64,1,64, - 3,64,1055,8,64,1,65,1,65,1,65,1,65,5,65,1061,8,65,10,65,12,65,1064, - 9,65,3,65,1066,8,65,1,65,1,65,1,66,1,66,1,66,1,66,1,67,1,67,1,67, - 1,67,5,67,1078,8,67,10,67,12,67,1081,9,67,3,67,1083,8,67,1,67,1, - 67,1,68,1,68,1,69,1,69,1,69,0,0,70,0,2,4,6,8,10,12,14,16,18,20,22, - 24,26,28,30,32,34,36,38,40,42,44,46,48,50,52,54,56,58,60,62,64,66, - 68,70,72,74,76,78,80,82,84,86,88,90,92,94,96,98,100,102,104,106, - 108,110,112,114,116,118,120,122,124,126,128,130,132,134,136,138, - 0,7,1,0,81,85,2,0,76,80,82,83,2,0,76,77,82,83,2,0,78,80,82,83,1, - 0,101,108,1,0,88,91,3,0,2,15,55,55,129,129,1170,0,152,1,0,0,0,2, - 154,1,0,0,0,4,164,1,0,0,0,6,168,1,0,0,0,8,192,1,0,0,0,10,204,1,0, - 0,0,12,206,1,0,0,0,14,213,1,0,0,0,16,224,1,0,0,0,18,226,1,0,0,0, - 20,239,1,0,0,0,22,271,1,0,0,0,24,301,1,0,0,0,26,303,1,0,0,0,28,307, - 1,0,0,0,30,325,1,0,0,0,32,327,1,0,0,0,34,339,1,0,0,0,36,342,1,0, - 0,0,38,360,1,0,0,0,40,396,1,0,0,0,42,399,1,0,0,0,44,424,1,0,0,0, - 46,452,1,0,0,0,48,463,1,0,0,0,50,468,1,0,0,0,52,495,1,0,0,0,54,497, - 1,0,0,0,56,519,1,0,0,0,58,561,1,0,0,0,60,563,1,0,0,0,62,586,1,0, - 0,0,64,602,1,0,0,0,66,615,1,0,0,0,68,618,1,0,0,0,70,638,1,0,0,0, - 72,640,1,0,0,0,74,648,1,0,0,0,76,710,1,0,0,0,78,712,1,0,0,0,80,723, - 1,0,0,0,82,725,1,0,0,0,84,730,1,0,0,0,86,732,1,0,0,0,88,754,1,0, - 0,0,90,756,1,0,0,0,92,765,1,0,0,0,94,790,1,0,0,0,96,819,1,0,0,0, - 98,821,1,0,0,0,100,828,1,0,0,0,102,842,1,0,0,0,104,852,1,0,0,0,106, - 867,1,0,0,0,108,891,1,0,0,0,110,893,1,0,0,0,112,895,1,0,0,0,114, - 897,1,0,0,0,116,976,1,0,0,0,118,978,1,0,0,0,120,1035,1,0,0,0,122, - 1037,1,0,0,0,124,1042,1,0,0,0,126,1044,1,0,0,0,128,1054,1,0,0,0, - 130,1056,1,0,0,0,132,1069,1,0,0,0,134,1073,1,0,0,0,136,1086,1,0, - 0,0,138,1088,1,0,0,0,140,141,3,6,3,0,141,142,5,0,0,1,142,153,1,0, - 0,0,143,144,3,20,10,0,144,145,5,0,0,1,145,153,1,0,0,0,146,147,3, - 50,25,0,147,148,5,0,0,1,148,153,1,0,0,0,149,150,3,2,1,0,150,151, - 5,0,0,1,151,153,1,0,0,0,152,140,1,0,0,0,152,143,1,0,0,0,152,146, - 1,0,0,0,152,149,1,0,0,0,153,1,1,0,0,0,154,155,5,22,0,0,155,159,5, - 117,0,0,156,158,3,8,4,0,157,156,1,0,0,0,158,161,1,0,0,0,159,157, - 1,0,0,0,159,160,1,0,0,0,160,162,1,0,0,0,161,159,1,0,0,0,162,163, - 5,118,0,0,163,3,1,0,0,0,164,165,5,23,0,0,165,166,3,138,69,0,166, - 167,5,114,0,0,167,5,1,0,0,0,168,169,5,1,0,0,169,170,3,136,68,0,170, - 171,5,64,0,0,171,172,3,138,69,0,172,173,5,58,0,0,173,174,3,138,69, - 0,174,175,5,57,0,0,175,176,3,138,69,0,176,180,5,117,0,0,177,179, - 3,8,4,0,178,177,1,0,0,0,179,182,1,0,0,0,180,178,1,0,0,0,180,181, - 1,0,0,0,181,183,1,0,0,0,182,180,1,0,0,0,183,184,5,118,0,0,184,7, - 1,0,0,0,185,193,3,4,2,0,186,193,3,18,9,0,187,193,3,10,5,0,188,193, - 3,82,41,0,189,193,3,94,47,0,190,193,3,118,59,0,191,193,3,32,16,0, - 192,185,1,0,0,0,192,186,1,0,0,0,192,187,1,0,0,0,192,188,1,0,0,0, - 192,189,1,0,0,0,192,190,1,0,0,0,192,191,1,0,0,0,193,9,1,0,0,0,194, - 195,5,23,0,0,195,196,5,26,0,0,196,197,3,136,68,0,197,198,5,114,0, - 0,198,205,1,0,0,0,199,200,5,23,0,0,200,201,5,28,0,0,201,202,3,136, - 68,0,202,203,5,114,0,0,203,205,1,0,0,0,204,194,1,0,0,0,204,199,1, - 0,0,0,205,11,1,0,0,0,206,207,5,24,0,0,207,208,5,25,0,0,208,209,3, - 136,68,0,209,210,5,64,0,0,210,211,3,138,69,0,211,212,5,114,0,0,212, - 13,1,0,0,0,213,214,5,24,0,0,214,215,5,26,0,0,215,216,3,136,68,0, - 216,217,5,58,0,0,217,218,3,138,69,0,218,219,5,114,0,0,219,15,1,0, - 0,0,220,225,3,10,5,0,221,225,3,12,6,0,222,225,3,14,7,0,223,225,3, - 32,16,0,224,220,1,0,0,0,224,221,1,0,0,0,224,222,1,0,0,0,224,223, - 1,0,0,0,225,17,1,0,0,0,226,227,5,25,0,0,227,228,3,136,68,0,228,229, - 5,64,0,0,229,232,3,138,69,0,230,231,5,65,0,0,231,233,3,138,69,0, - 232,230,1,0,0,0,232,233,1,0,0,0,233,234,1,0,0,0,234,235,5,114,0, - 0,235,19,1,0,0,0,236,238,3,16,8,0,237,236,1,0,0,0,238,241,1,0,0, - 0,239,237,1,0,0,0,239,240,1,0,0,0,240,242,1,0,0,0,241,239,1,0,0, - 0,242,243,5,26,0,0,243,245,3,136,68,0,244,246,3,22,11,0,245,244, - 1,0,0,0,245,246,1,0,0,0,246,247,1,0,0,0,247,248,5,64,0,0,248,249, - 3,138,69,0,249,250,5,58,0,0,250,260,3,138,69,0,251,252,5,69,0,0, - 252,257,3,26,13,0,253,254,5,115,0,0,254,256,3,26,13,0,255,253,1, - 0,0,0,256,259,1,0,0,0,257,255,1,0,0,0,257,258,1,0,0,0,258,261,1, - 0,0,0,259,257,1,0,0,0,260,251,1,0,0,0,260,261,1,0,0,0,261,262,1, - 0,0,0,262,266,5,117,0,0,263,265,3,34,17,0,264,263,1,0,0,0,265,268, - 1,0,0,0,266,264,1,0,0,0,266,267,1,0,0,0,267,269,1,0,0,0,268,266, - 1,0,0,0,269,270,5,118,0,0,270,21,1,0,0,0,271,272,5,123,0,0,272,277, - 3,24,12,0,273,274,5,115,0,0,274,276,3,24,12,0,275,273,1,0,0,0,276, - 279,1,0,0,0,277,275,1,0,0,0,277,278,1,0,0,0,278,280,1,0,0,0,279, - 277,1,0,0,0,280,281,5,124,0,0,281,23,1,0,0,0,282,283,5,29,0,0,283, - 286,3,136,68,0,284,285,5,113,0,0,285,287,5,19,0,0,286,284,1,0,0, - 0,286,287,1,0,0,0,287,302,1,0,0,0,288,289,5,18,0,0,289,299,3,136, - 68,0,290,291,5,20,0,0,291,296,3,26,13,0,292,293,5,125,0,0,293,295, - 3,26,13,0,294,292,1,0,0,0,295,298,1,0,0,0,296,294,1,0,0,0,296,297, - 1,0,0,0,297,300,1,0,0,0,298,296,1,0,0,0,299,290,1,0,0,0,299,300, - 1,0,0,0,300,302,1,0,0,0,301,282,1,0,0,0,301,288,1,0,0,0,302,25,1, - 0,0,0,303,305,3,136,68,0,304,306,3,28,14,0,305,304,1,0,0,0,305,306, - 1,0,0,0,306,27,1,0,0,0,307,308,5,123,0,0,308,313,3,30,15,0,309,310, - 5,115,0,0,310,312,3,30,15,0,311,309,1,0,0,0,312,315,1,0,0,0,313, - 311,1,0,0,0,313,314,1,0,0,0,314,316,1,0,0,0,315,313,1,0,0,0,316, - 317,5,124,0,0,317,29,1,0,0,0,318,319,5,25,0,0,319,326,3,136,68,0, - 320,321,5,26,0,0,321,326,3,26,13,0,322,323,5,18,0,0,323,326,3,136, - 68,0,324,326,3,120,60,0,325,318,1,0,0,0,325,320,1,0,0,0,325,322, - 1,0,0,0,325,324,1,0,0,0,326,31,1,0,0,0,327,328,5,17,0,0,328,330, - 3,136,68,0,329,331,3,22,11,0,330,329,1,0,0,0,330,331,1,0,0,0,331, - 332,1,0,0,0,332,333,5,126,0,0,333,334,3,120,60,0,334,335,5,114,0, - 0,335,33,1,0,0,0,336,340,3,38,19,0,337,340,3,42,21,0,338,340,3,36, - 18,0,339,336,1,0,0,0,339,337,1,0,0,0,339,338,1,0,0,0,340,35,1,0, - 0,0,341,343,5,39,0,0,342,341,1,0,0,0,342,343,1,0,0,0,343,344,1,0, - 0,0,344,345,5,31,0,0,345,346,3,136,68,0,346,347,5,64,0,0,347,348, - 3,138,69,0,348,349,5,113,0,0,349,350,3,120,60,0,350,351,5,112,0, - 0,351,352,3,120,60,0,352,353,5,117,0,0,353,354,5,81,0,0,354,355, - 5,64,0,0,355,356,3,138,69,0,356,357,5,114,0,0,357,358,5,118,0,0, - 358,37,1,0,0,0,359,361,3,44,22,0,360,359,1,0,0,0,360,361,1,0,0,0, - 361,362,1,0,0,0,362,363,5,29,0,0,363,364,3,136,68,0,364,365,5,64, - 0,0,365,366,3,138,69,0,366,367,5,113,0,0,367,368,3,120,60,0,368, - 372,5,117,0,0,369,371,3,40,20,0,370,369,1,0,0,0,371,374,1,0,0,0, - 372,370,1,0,0,0,372,373,1,0,0,0,373,375,1,0,0,0,374,372,1,0,0,0, - 375,376,5,118,0,0,376,39,1,0,0,0,377,378,5,71,0,0,378,379,5,64,0, - 0,379,380,3,138,69,0,380,381,5,114,0,0,381,397,1,0,0,0,382,383,5, - 72,0,0,383,384,5,64,0,0,384,385,3,138,69,0,385,386,5,114,0,0,386, - 397,1,0,0,0,387,388,5,73,0,0,388,389,5,74,0,0,389,390,5,64,0,0,390, - 391,3,138,69,0,391,392,5,75,0,0,392,393,5,64,0,0,393,394,3,138,69, - 0,394,395,5,114,0,0,395,397,1,0,0,0,396,377,1,0,0,0,396,382,1,0, - 0,0,396,387,1,0,0,0,397,41,1,0,0,0,398,400,3,44,22,0,399,398,1,0, - 0,0,399,400,1,0,0,0,400,401,1,0,0,0,401,402,5,30,0,0,402,403,3,136, - 68,0,403,404,5,64,0,0,404,405,3,138,69,0,405,406,5,113,0,0,406,407, - 3,126,63,0,407,409,3,48,24,0,408,410,5,92,0,0,409,408,1,0,0,0,409, - 410,1,0,0,0,410,413,1,0,0,0,411,412,5,62,0,0,412,414,3,138,69,0, - 413,411,1,0,0,0,413,414,1,0,0,0,414,415,1,0,0,0,415,419,5,117,0, - 0,416,418,3,46,23,0,417,416,1,0,0,0,418,421,1,0,0,0,419,417,1,0, - 0,0,419,420,1,0,0,0,420,422,1,0,0,0,421,419,1,0,0,0,422,423,5,118, - 0,0,423,43,1,0,0,0,424,426,5,14,0,0,425,427,5,15,0,0,426,425,1,0, - 0,0,426,427,1,0,0,0,427,45,1,0,0,0,428,429,5,78,0,0,429,430,5,64, - 0,0,430,431,3,138,69,0,431,432,5,114,0,0,432,453,1,0,0,0,433,434, - 5,79,0,0,434,435,5,64,0,0,435,436,3,138,69,0,436,437,5,114,0,0,437, - 453,1,0,0,0,438,439,5,80,0,0,439,440,5,64,0,0,440,441,3,138,69,0, - 441,442,5,114,0,0,442,453,1,0,0,0,443,444,5,73,0,0,444,445,5,74, - 0,0,445,446,5,64,0,0,446,447,3,138,69,0,447,448,5,75,0,0,448,449, - 5,64,0,0,449,450,3,138,69,0,450,451,5,114,0,0,451,453,1,0,0,0,452, - 428,1,0,0,0,452,433,1,0,0,0,452,438,1,0,0,0,452,443,1,0,0,0,453, - 47,1,0,0,0,454,455,5,25,0,0,455,464,3,136,68,0,456,457,5,26,0,0, - 457,459,3,136,68,0,458,460,3,28,14,0,459,458,1,0,0,0,459,460,1,0, - 0,0,460,464,1,0,0,0,461,462,5,18,0,0,462,464,3,136,68,0,463,454, - 1,0,0,0,463,456,1,0,0,0,463,461,1,0,0,0,464,49,1,0,0,0,465,467,3, - 16,8,0,466,465,1,0,0,0,467,470,1,0,0,0,468,466,1,0,0,0,468,469,1, - 0,0,0,469,471,1,0,0,0,470,468,1,0,0,0,471,472,5,28,0,0,472,473,3, - 136,68,0,473,474,5,64,0,0,474,475,3,138,69,0,475,476,5,58,0,0,476, - 479,3,138,69,0,477,478,5,59,0,0,478,480,5,127,0,0,479,477,1,0,0, - 0,479,480,1,0,0,0,480,481,1,0,0,0,481,485,5,117,0,0,482,484,3,52, - 26,0,483,482,1,0,0,0,484,487,1,0,0,0,485,483,1,0,0,0,485,486,1,0, - 0,0,486,488,1,0,0,0,487,485,1,0,0,0,488,489,5,118,0,0,489,51,1,0, - 0,0,490,496,3,60,30,0,491,496,3,62,31,0,492,496,3,64,32,0,493,496, - 3,54,27,0,494,496,3,56,28,0,495,490,1,0,0,0,495,491,1,0,0,0,495, - 492,1,0,0,0,495,493,1,0,0,0,495,494,1,0,0,0,496,53,1,0,0,0,497,498, - 5,5,0,0,498,500,3,136,68,0,499,501,3,22,11,0,500,499,1,0,0,0,500, - 501,1,0,0,0,501,502,1,0,0,0,502,503,5,64,0,0,503,504,3,138,69,0, - 504,505,5,7,0,0,505,506,3,26,13,0,506,507,5,8,0,0,507,508,3,138, - 69,0,508,509,5,31,0,0,509,510,3,138,69,0,510,514,5,117,0,0,511,513, - 3,58,29,0,512,511,1,0,0,0,513,516,1,0,0,0,514,512,1,0,0,0,514,515, - 1,0,0,0,515,517,1,0,0,0,516,514,1,0,0,0,517,518,5,118,0,0,518,55, - 1,0,0,0,519,520,5,5,0,0,520,521,3,136,68,0,521,522,5,64,0,0,522, - 523,3,138,69,0,523,524,5,6,0,0,524,525,3,136,68,0,525,526,3,28,14, - 0,526,527,5,114,0,0,527,57,1,0,0,0,528,529,5,9,0,0,529,530,3,138, - 69,0,530,531,5,114,0,0,531,562,1,0,0,0,532,534,5,10,0,0,533,535, - 5,18,0,0,534,533,1,0,0,0,534,535,1,0,0,0,535,536,1,0,0,0,536,537, - 3,136,68,0,537,538,5,37,0,0,538,539,3,26,13,0,539,540,5,114,0,0, - 540,562,1,0,0,0,541,542,5,11,0,0,542,543,3,136,68,0,543,544,5,127, - 0,0,544,545,5,114,0,0,545,562,1,0,0,0,546,547,5,12,0,0,547,548,3, - 26,13,0,548,549,5,116,0,0,549,550,3,136,68,0,550,551,3,136,68,0, - 551,552,3,138,69,0,552,553,5,114,0,0,553,562,1,0,0,0,554,555,5,73, - 0,0,555,562,5,114,0,0,556,557,5,13,0,0,557,558,5,127,0,0,558,559, - 3,138,69,0,559,560,5,114,0,0,560,562,1,0,0,0,561,528,1,0,0,0,561, - 532,1,0,0,0,561,541,1,0,0,0,561,546,1,0,0,0,561,554,1,0,0,0,561, - 556,1,0,0,0,562,59,1,0,0,0,563,564,5,31,0,0,564,566,3,136,68,0,565, - 567,3,22,11,0,566,565,1,0,0,0,566,567,1,0,0,0,567,568,1,0,0,0,568, - 569,5,64,0,0,569,570,3,138,69,0,570,571,5,113,0,0,571,572,3,120, - 60,0,572,573,5,112,0,0,573,574,3,120,60,0,574,575,5,66,0,0,575,577, - 3,68,34,0,576,578,3,66,33,0,577,576,1,0,0,0,577,578,1,0,0,0,578, - 579,1,0,0,0,579,580,5,68,0,0,580,582,3,70,35,0,581,583,3,74,37,0, - 582,581,1,0,0,0,582,583,1,0,0,0,583,584,1,0,0,0,584,585,5,114,0, - 0,585,61,1,0,0,0,586,587,5,32,0,0,587,589,3,136,68,0,588,590,3,22, - 11,0,589,588,1,0,0,0,589,590,1,0,0,0,590,591,1,0,0,0,591,592,5,64, - 0,0,592,593,3,138,69,0,593,594,5,113,0,0,594,595,3,120,60,0,595, - 596,5,112,0,0,596,598,3,120,60,0,597,599,3,74,37,0,598,597,1,0,0, - 0,598,599,1,0,0,0,599,600,1,0,0,0,600,601,5,114,0,0,601,63,1,0,0, - 0,602,603,5,33,0,0,603,604,3,136,68,0,604,605,5,64,0,0,605,606,3, - 138,69,0,606,607,5,34,0,0,607,608,3,136,68,0,608,609,5,113,0,0,609, - 611,3,120,60,0,610,612,3,74,37,0,611,610,1,0,0,0,611,612,1,0,0,0, - 612,613,1,0,0,0,613,614,5,114,0,0,614,65,1,0,0,0,615,616,5,67,0, - 0,616,617,3,120,60,0,617,67,1,0,0,0,618,619,7,0,0,0,619,69,1,0,0, - 0,620,639,5,70,0,0,621,622,5,25,0,0,622,639,3,136,68,0,623,624,5, - 18,0,0,624,639,3,136,68,0,625,626,5,27,0,0,626,635,5,119,0,0,627, - 632,3,26,13,0,628,629,5,115,0,0,629,631,3,26,13,0,630,628,1,0,0, - 0,631,634,1,0,0,0,632,630,1,0,0,0,632,633,1,0,0,0,633,636,1,0,0, - 0,634,632,1,0,0,0,635,627,1,0,0,0,635,636,1,0,0,0,636,637,1,0,0, - 0,637,639,5,120,0,0,638,620,1,0,0,0,638,621,1,0,0,0,638,623,1,0, - 0,0,638,625,1,0,0,0,639,71,1,0,0,0,640,645,3,136,68,0,641,642,5, - 115,0,0,642,644,3,136,68,0,643,641,1,0,0,0,644,647,1,0,0,0,645,643, - 1,0,0,0,645,646,1,0,0,0,646,73,1,0,0,0,647,645,1,0,0,0,648,649,5, - 69,0,0,649,653,5,117,0,0,650,652,3,76,38,0,651,650,1,0,0,0,652,655, - 1,0,0,0,653,651,1,0,0,0,653,654,1,0,0,0,654,656,1,0,0,0,655,653, - 1,0,0,0,656,657,5,118,0,0,657,75,1,0,0,0,658,659,5,43,0,0,659,660, - 3,136,68,0,660,661,5,64,0,0,661,662,3,138,69,0,662,663,5,113,0,0, - 663,664,3,120,60,0,664,665,3,78,39,0,665,666,5,114,0,0,666,711,1, - 0,0,0,667,668,5,44,0,0,668,669,3,136,68,0,669,670,5,64,0,0,670,671, - 3,138,69,0,671,672,5,113,0,0,672,673,3,126,63,0,673,674,3,48,24, - 0,674,675,3,78,39,0,675,676,5,114,0,0,676,711,1,0,0,0,677,678,5, - 26,0,0,678,679,3,136,68,0,679,680,5,64,0,0,680,681,3,138,69,0,681, - 682,5,113,0,0,682,684,3,136,68,0,683,685,3,28,14,0,684,683,1,0,0, - 0,684,685,1,0,0,0,685,686,1,0,0,0,686,687,5,114,0,0,687,711,1,0, - 0,0,688,689,5,33,0,0,689,690,3,136,68,0,690,691,5,64,0,0,691,692, - 3,138,69,0,692,693,5,113,0,0,693,696,3,136,68,0,694,695,5,35,0,0, - 695,697,3,120,60,0,696,694,1,0,0,0,696,697,1,0,0,0,697,698,1,0,0, - 0,698,699,5,114,0,0,699,711,1,0,0,0,700,701,5,5,0,0,701,702,3,136, - 68,0,702,703,5,64,0,0,703,704,3,138,69,0,704,705,5,113,0,0,705,706, - 3,136,68,0,706,707,5,116,0,0,707,708,3,136,68,0,708,709,5,114,0, - 0,709,711,1,0,0,0,710,658,1,0,0,0,710,667,1,0,0,0,710,677,1,0,0, - 0,710,688,1,0,0,0,710,700,1,0,0,0,711,77,1,0,0,0,712,713,5,119,0, - 0,713,718,3,80,40,0,714,715,5,115,0,0,715,717,3,80,40,0,716,714, - 1,0,0,0,717,720,1,0,0,0,718,716,1,0,0,0,718,719,1,0,0,0,719,721, - 1,0,0,0,720,718,1,0,0,0,721,722,5,120,0,0,722,79,1,0,0,0,723,724, - 7,1,0,0,724,81,1,0,0,0,725,726,5,42,0,0,726,727,3,84,42,0,727,83, - 1,0,0,0,728,731,3,86,43,0,729,731,3,90,45,0,730,728,1,0,0,0,730, - 729,1,0,0,0,731,85,1,0,0,0,732,733,5,43,0,0,733,734,3,136,68,0,734, - 735,5,64,0,0,735,736,3,138,69,0,736,737,5,52,0,0,737,738,3,136,68, - 0,738,739,5,113,0,0,739,740,3,120,60,0,740,741,5,53,0,0,741,744, - 3,88,44,0,742,743,5,54,0,0,743,745,3,128,64,0,744,742,1,0,0,0,744, - 745,1,0,0,0,745,746,1,0,0,0,746,747,5,114,0,0,747,87,1,0,0,0,748, - 755,5,86,0,0,749,750,5,87,0,0,750,751,5,121,0,0,751,752,3,120,60, - 0,752,753,5,122,0,0,753,755,1,0,0,0,754,748,1,0,0,0,754,749,1,0, - 0,0,755,89,1,0,0,0,756,757,5,44,0,0,757,758,3,136,68,0,758,759,5, - 64,0,0,759,760,3,138,69,0,760,761,5,117,0,0,761,762,3,92,46,0,762, - 763,3,92,46,0,763,764,5,118,0,0,764,91,1,0,0,0,765,766,3,48,24,0, - 766,767,5,45,0,0,767,768,3,136,68,0,768,769,5,64,0,0,769,770,3,138, - 69,0,770,772,3,126,63,0,771,773,5,92,0,0,772,771,1,0,0,0,772,773, - 1,0,0,0,773,776,1,0,0,0,774,775,5,60,0,0,775,777,3,138,69,0,776, - 774,1,0,0,0,776,777,1,0,0,0,777,779,1,0,0,0,778,780,5,61,0,0,779, - 778,1,0,0,0,779,780,1,0,0,0,780,783,1,0,0,0,781,782,5,62,0,0,782, - 784,3,138,69,0,783,781,1,0,0,0,783,784,1,0,0,0,784,786,1,0,0,0,785, - 787,5,63,0,0,786,785,1,0,0,0,786,787,1,0,0,0,787,788,1,0,0,0,788, - 789,5,114,0,0,789,93,1,0,0,0,790,791,5,36,0,0,791,792,3,136,68,0, - 792,793,5,37,0,0,793,795,3,136,68,0,794,796,3,28,14,0,795,794,1, - 0,0,0,795,796,1,0,0,0,796,799,1,0,0,0,797,798,5,64,0,0,798,800,3, - 138,69,0,799,797,1,0,0,0,799,800,1,0,0,0,800,803,1,0,0,0,801,802, - 5,59,0,0,802,804,5,127,0,0,803,801,1,0,0,0,803,804,1,0,0,0,804,805, - 1,0,0,0,805,809,5,117,0,0,806,808,3,96,48,0,807,806,1,0,0,0,808, - 811,1,0,0,0,809,807,1,0,0,0,809,810,1,0,0,0,810,812,1,0,0,0,811, - 809,1,0,0,0,812,813,5,118,0,0,813,95,1,0,0,0,814,815,5,41,0,0,815, - 820,3,84,42,0,816,820,3,102,51,0,817,820,3,98,49,0,818,820,3,100, - 50,0,819,814,1,0,0,0,819,816,1,0,0,0,819,817,1,0,0,0,819,818,1,0, - 0,0,820,97,1,0,0,0,821,822,5,38,0,0,822,823,3,136,68,0,823,824,5, - 40,0,0,824,825,5,43,0,0,825,826,3,136,68,0,826,827,5,114,0,0,827, - 99,1,0,0,0,828,829,5,49,0,0,829,830,3,136,68,0,830,831,5,50,0,0, - 831,832,5,51,0,0,832,833,5,47,0,0,833,834,5,33,0,0,834,835,3,136, - 68,0,835,836,5,48,0,0,836,837,5,44,0,0,837,838,3,136,68,0,838,839, - 5,116,0,0,839,840,3,136,68,0,840,841,5,114,0,0,841,101,1,0,0,0,842, - 843,5,38,0,0,843,844,3,104,52,0,844,845,5,40,0,0,845,848,3,108,54, - 0,846,847,5,16,0,0,847,849,3,138,69,0,848,846,1,0,0,0,848,849,1, - 0,0,0,849,850,1,0,0,0,850,851,5,114,0,0,851,103,1,0,0,0,852,853, - 3,136,68,0,853,854,5,116,0,0,854,855,3,106,53,0,855,105,1,0,0,0, - 856,868,3,136,68,0,857,868,5,81,0,0,858,868,5,71,0,0,859,868,5,72, - 0,0,860,868,5,78,0,0,861,868,5,79,0,0,862,868,5,80,0,0,863,868,5, - 82,0,0,864,868,5,83,0,0,865,868,5,84,0,0,866,868,5,85,0,0,867,856, - 1,0,0,0,867,857,1,0,0,0,867,858,1,0,0,0,867,859,1,0,0,0,867,860, - 1,0,0,0,867,861,1,0,0,0,867,862,1,0,0,0,867,863,1,0,0,0,867,864, - 1,0,0,0,867,865,1,0,0,0,867,866,1,0,0,0,868,107,1,0,0,0,869,870, - 5,43,0,0,870,871,3,136,68,0,871,872,5,116,0,0,872,873,3,110,55,0, - 873,892,1,0,0,0,874,875,5,44,0,0,875,876,3,136,68,0,876,877,5,116, - 0,0,877,878,3,136,68,0,878,879,5,116,0,0,879,880,3,112,56,0,880, - 892,1,0,0,0,881,882,5,28,0,0,882,883,3,136,68,0,883,884,5,116,0, - 0,884,886,3,136,68,0,885,887,3,28,14,0,886,885,1,0,0,0,886,887,1, - 0,0,0,887,889,1,0,0,0,888,890,3,114,57,0,889,888,1,0,0,0,889,890, - 1,0,0,0,890,892,1,0,0,0,891,869,1,0,0,0,891,874,1,0,0,0,891,881, - 1,0,0,0,892,109,1,0,0,0,893,894,7,2,0,0,894,111,1,0,0,0,895,896, - 7,3,0,0,896,113,1,0,0,0,897,898,5,46,0,0,898,902,5,117,0,0,899,901, - 3,116,58,0,900,899,1,0,0,0,901,904,1,0,0,0,902,900,1,0,0,0,902,903, - 1,0,0,0,903,905,1,0,0,0,904,902,1,0,0,0,905,906,5,118,0,0,906,115, - 1,0,0,0,907,908,3,136,68,0,908,909,5,40,0,0,909,910,5,43,0,0,910, - 917,3,136,68,0,911,912,5,48,0,0,912,913,5,44,0,0,913,914,3,136,68, - 0,914,915,5,116,0,0,915,916,3,136,68,0,916,918,1,0,0,0,917,911,1, - 0,0,0,917,918,1,0,0,0,918,919,1,0,0,0,919,920,5,114,0,0,920,977, - 1,0,0,0,921,922,3,136,68,0,922,923,5,40,0,0,923,924,5,44,0,0,924, - 925,3,136,68,0,925,926,5,116,0,0,926,933,3,136,68,0,927,928,5,48, - 0,0,928,929,5,44,0,0,929,930,3,136,68,0,930,931,5,116,0,0,931,932, - 3,136,68,0,932,934,1,0,0,0,933,927,1,0,0,0,933,934,1,0,0,0,934,935, - 1,0,0,0,935,936,5,114,0,0,936,977,1,0,0,0,937,938,3,136,68,0,938, - 939,5,40,0,0,939,940,5,26,0,0,940,942,3,136,68,0,941,943,3,28,14, - 0,942,941,1,0,0,0,942,943,1,0,0,0,943,950,1,0,0,0,944,945,5,48,0, - 0,945,946,5,44,0,0,946,947,3,136,68,0,947,948,5,116,0,0,948,949, - 3,136,68,0,949,951,1,0,0,0,950,944,1,0,0,0,950,951,1,0,0,0,951,952, - 1,0,0,0,952,953,5,114,0,0,953,977,1,0,0,0,954,955,3,136,68,0,955, - 956,5,40,0,0,956,957,5,33,0,0,957,958,3,136,68,0,958,959,5,114,0, - 0,959,977,1,0,0,0,960,961,3,136,68,0,961,962,5,40,0,0,962,963,5, - 5,0,0,963,964,3,136,68,0,964,965,5,116,0,0,965,972,3,136,68,0,966, - 967,5,48,0,0,967,968,5,44,0,0,968,969,3,136,68,0,969,970,5,116,0, - 0,970,971,3,136,68,0,971,973,1,0,0,0,972,966,1,0,0,0,972,973,1,0, - 0,0,973,974,1,0,0,0,974,975,5,114,0,0,975,977,1,0,0,0,976,907,1, - 0,0,0,976,921,1,0,0,0,976,937,1,0,0,0,976,954,1,0,0,0,976,960,1, - 0,0,0,977,117,1,0,0,0,978,979,5,33,0,0,979,980,3,136,68,0,980,981, - 5,40,0,0,981,982,3,136,68,0,982,983,5,116,0,0,983,985,3,136,68,0, - 984,986,3,114,57,0,985,984,1,0,0,0,985,986,1,0,0,0,986,987,1,0,0, - 0,987,988,5,114,0,0,988,119,1,0,0,0,989,1036,3,124,62,0,990,1036, - 5,93,0,0,991,1036,5,94,0,0,992,993,5,95,0,0,993,1036,3,138,69,0, - 994,995,5,96,0,0,995,996,5,123,0,0,996,997,3,136,68,0,997,998,5, - 124,0,0,998,1036,1,0,0,0,999,1000,5,97,0,0,1000,1001,5,123,0,0,1001, - 1003,3,136,68,0,1002,1004,3,28,14,0,1003,1002,1,0,0,0,1003,1004, - 1,0,0,0,1004,1005,1,0,0,0,1005,1006,5,124,0,0,1006,1036,1,0,0,0, - 1007,1008,5,21,0,0,1008,1009,5,123,0,0,1009,1010,3,136,68,0,1010, - 1011,5,124,0,0,1011,1036,1,0,0,0,1012,1013,5,98,0,0,1013,1014,5, - 123,0,0,1014,1015,3,120,60,0,1015,1016,5,124,0,0,1016,1036,1,0,0, - 0,1017,1018,5,99,0,0,1018,1019,5,123,0,0,1019,1020,3,120,60,0,1020, - 1021,5,124,0,0,1021,1036,1,0,0,0,1022,1023,5,100,0,0,1023,1027,5, - 117,0,0,1024,1026,3,122,61,0,1025,1024,1,0,0,0,1026,1029,1,0,0,0, - 1027,1025,1,0,0,0,1027,1028,1,0,0,0,1028,1030,1,0,0,0,1029,1027, - 1,0,0,0,1030,1036,5,118,0,0,1031,1033,3,136,68,0,1032,1034,3,28, - 14,0,1033,1032,1,0,0,0,1033,1034,1,0,0,0,1034,1036,1,0,0,0,1035, - 989,1,0,0,0,1035,990,1,0,0,0,1035,991,1,0,0,0,1035,992,1,0,0,0,1035, - 994,1,0,0,0,1035,999,1,0,0,0,1035,1007,1,0,0,0,1035,1012,1,0,0,0, - 1035,1017,1,0,0,0,1035,1022,1,0,0,0,1035,1031,1,0,0,0,1036,121,1, - 0,0,0,1037,1038,3,136,68,0,1038,1039,5,113,0,0,1039,1040,3,120,60, - 0,1040,1041,5,114,0,0,1041,123,1,0,0,0,1042,1043,7,4,0,0,1043,125, - 1,0,0,0,1044,1045,7,5,0,0,1045,127,1,0,0,0,1046,1055,3,138,69,0, - 1047,1055,5,127,0,0,1048,1055,5,128,0,0,1049,1055,5,109,0,0,1050, - 1055,5,110,0,0,1051,1055,5,111,0,0,1052,1055,3,130,65,0,1053,1055, - 3,134,67,0,1054,1046,1,0,0,0,1054,1047,1,0,0,0,1054,1048,1,0,0,0, - 1054,1049,1,0,0,0,1054,1050,1,0,0,0,1054,1051,1,0,0,0,1054,1052, - 1,0,0,0,1054,1053,1,0,0,0,1055,129,1,0,0,0,1056,1065,5,117,0,0,1057, - 1062,3,132,66,0,1058,1059,5,115,0,0,1059,1061,3,132,66,0,1060,1058, - 1,0,0,0,1061,1064,1,0,0,0,1062,1060,1,0,0,0,1062,1063,1,0,0,0,1063, - 1066,1,0,0,0,1064,1062,1,0,0,0,1065,1057,1,0,0,0,1065,1066,1,0,0, - 0,1066,1067,1,0,0,0,1067,1068,5,118,0,0,1068,131,1,0,0,0,1069,1070, - 3,138,69,0,1070,1071,5,113,0,0,1071,1072,3,128,64,0,1072,133,1,0, - 0,0,1073,1082,5,119,0,0,1074,1079,3,128,64,0,1075,1076,5,115,0,0, - 1076,1078,3,128,64,0,1077,1075,1,0,0,0,1078,1081,1,0,0,0,1079,1077, - 1,0,0,0,1079,1080,1,0,0,0,1080,1083,1,0,0,0,1081,1079,1,0,0,0,1082, - 1074,1,0,0,0,1082,1083,1,0,0,0,1083,1084,1,0,0,0,1084,1085,5,120, - 0,0,1085,135,1,0,0,0,1086,1087,7,6,0,0,1087,137,1,0,0,0,1088,1089, - 5,130,0,0,1089,139,1,0,0,0,92,152,159,180,192,204,224,232,239,245, - 257,260,266,277,286,296,299,301,305,313,325,330,339,342,360,372, - 396,399,409,413,419,426,452,459,463,468,479,485,495,500,514,534, - 561,566,577,582,589,598,611,632,635,638,645,653,684,696,710,718, - 730,744,754,772,776,779,783,786,795,799,803,809,819,848,867,886, - 889,891,902,917,933,942,950,972,976,985,1003,1027,1033,1035,1054, - 1062,1065,1079,1082 + 65,2,66,7,66,2,67,7,67,2,68,7,68,2,69,7,69,2,70,7,70,2,71,7,71,2, + 72,7,72,2,73,7,73,2,74,7,74,2,75,7,75,2,76,7,76,2,77,7,77,2,78,7, + 78,2,79,7,79,2,80,7,80,2,81,7,81,2,82,7,82,2,83,7,83,2,84,7,84,2, + 85,7,85,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,3,0,185, + 8,0,1,1,1,1,1,1,5,1,190,8,1,10,1,12,1,193,9,1,1,1,1,1,1,2,1,2,1, + 2,1,2,1,3,1,3,1,3,1,3,1,3,1,3,1,3,1,3,1,3,1,3,5,3,211,8,3,10,3,12, + 3,214,9,3,1,3,1,3,1,4,1,4,1,4,1,4,1,4,1,4,1,4,1,4,3,4,226,8,4,1, + 5,1,5,1,5,1,5,1,5,1,5,1,5,1,5,1,5,1,5,3,5,238,8,5,1,6,1,6,1,6,1, + 6,1,6,1,6,1,6,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,8,1,8,1,8,1,8,1,8,1, + 8,1,9,1,9,1,9,1,9,1,9,3,9,265,8,9,1,10,1,10,1,10,1,10,1,10,1,10, + 3,10,273,8,10,1,10,1,10,1,11,5,11,278,8,11,10,11,12,11,281,9,11, + 1,11,1,11,1,11,3,11,286,8,11,1,11,1,11,1,11,1,11,1,11,1,11,1,11, + 1,11,5,11,296,8,11,10,11,12,11,299,9,11,3,11,301,8,11,1,11,1,11, + 5,11,305,8,11,10,11,12,11,308,9,11,1,11,1,11,1,12,1,12,1,12,1,12, + 5,12,316,8,12,10,12,12,12,319,9,12,1,12,1,12,1,13,1,13,1,13,1,13, + 3,13,327,8,13,1,13,1,13,1,13,1,13,1,13,1,13,5,13,335,8,13,10,13, + 12,13,338,9,13,3,13,340,8,13,3,13,342,8,13,1,14,1,14,3,14,346,8, + 14,1,15,1,15,1,15,1,15,5,15,352,8,15,10,15,12,15,355,9,15,1,15,1, + 15,1,16,1,16,1,16,1,16,1,16,1,16,1,16,3,16,366,8,16,1,17,1,17,1, + 17,3,17,371,8,17,1,17,1,17,1,17,1,17,1,18,1,18,1,18,1,18,3,18,381, + 8,18,1,19,1,19,1,19,1,19,1,19,1,19,1,19,1,19,3,19,391,8,19,1,19, + 1,19,1,20,3,20,396,8,20,1,20,1,20,1,20,1,20,1,20,1,20,1,20,1,20, + 1,20,1,20,1,20,1,20,1,20,3,20,411,8,20,1,20,1,20,1,20,1,21,3,21, + 417,8,21,1,21,1,21,1,21,1,21,1,21,1,21,1,21,1,21,5,21,427,8,21,10, + 21,12,21,430,9,21,1,21,1,21,1,22,1,22,1,22,1,22,3,22,438,8,22,1, + 22,1,22,1,22,1,22,1,22,1,22,3,22,446,8,22,1,22,1,22,1,22,1,22,1, + 22,1,22,3,22,454,8,22,1,22,1,22,1,22,1,22,1,22,1,22,1,22,1,22,1, + 22,1,22,1,22,3,22,467,8,22,1,23,3,23,470,8,23,1,23,1,23,1,23,1,23, + 1,23,1,23,1,23,1,23,3,23,480,8,23,1,23,1,23,3,23,484,8,23,1,23,1, + 23,5,23,488,8,23,10,23,12,23,491,9,23,1,23,1,23,1,24,1,24,3,24,497, + 8,24,1,25,1,25,1,25,1,25,3,25,503,8,25,1,25,1,25,1,25,1,25,1,25, + 1,25,3,25,511,8,25,1,25,1,25,1,25,1,25,1,25,1,25,3,25,519,8,25,1, + 25,1,25,1,25,1,25,1,25,1,25,1,25,1,25,1,25,1,25,1,25,3,25,532,8, + 25,1,26,1,26,1,26,1,26,1,26,3,26,539,8,26,1,26,1,26,3,26,543,8,26, + 1,27,5,27,546,8,27,10,27,12,27,549,9,27,1,27,1,27,1,27,1,27,1,27, + 1,27,1,27,1,27,3,27,559,8,27,1,27,1,27,5,27,563,8,27,10,27,12,27, + 566,9,27,1,27,1,27,1,28,1,28,1,28,1,28,1,28,1,28,3,28,576,8,28,1, + 29,1,29,1,29,1,29,1,29,1,29,1,29,1,29,1,29,1,29,1,30,1,30,1,30,1, + 30,1,30,1,30,1,30,1,30,1,30,1,30,1,30,1,30,1,31,1,31,3,31,602,8, + 31,1,31,1,31,3,31,606,8,31,1,31,1,31,1,31,1,31,3,31,612,8,31,1,31, + 1,31,1,31,1,31,1,31,1,31,1,31,5,31,621,8,31,10,31,12,31,624,9,31, + 1,31,1,31,1,31,1,31,1,31,1,31,3,31,632,8,31,1,31,1,31,5,31,636,8, + 31,10,31,12,31,639,9,31,1,31,1,31,3,31,643,8,31,3,31,645,8,31,1, + 32,1,32,1,32,1,32,1,32,1,32,1,32,1,32,1,32,1,33,1,33,1,33,1,33,1, + 33,1,33,1,33,1,33,1,33,1,33,1,33,1,33,1,33,1,33,1,33,1,33,1,33,1, + 33,5,33,674,8,33,10,33,12,33,677,9,33,1,33,3,33,680,8,33,1,33,1, + 33,1,33,1,33,3,33,686,8,33,1,33,1,33,1,33,1,33,1,33,1,33,1,33,1, + 33,1,33,1,33,1,33,1,33,1,33,1,33,1,33,1,33,1,33,1,33,1,33,1,33,1, + 33,1,33,1,33,1,33,1,33,3,33,713,8,33,1,34,1,34,1,34,1,34,1,34,1, + 35,1,35,1,35,3,35,723,8,35,1,35,1,35,1,35,1,35,1,35,1,35,1,35,1, + 35,1,35,3,35,734,8,35,1,35,3,35,737,8,35,1,35,1,35,1,35,3,35,742, + 8,35,1,35,3,35,745,8,35,1,35,3,35,748,8,35,1,35,1,35,1,36,1,36,1, + 36,3,36,755,8,36,1,36,1,36,1,36,1,36,1,36,1,36,1,36,3,36,764,8,36, + 1,36,3,36,767,8,36,1,36,3,36,770,8,36,1,36,3,36,773,8,36,1,36,1, + 36,1,37,1,37,1,37,1,37,1,38,1,38,1,38,1,38,1,38,1,38,1,39,1,39,1, + 39,1,39,1,39,1,39,1,39,1,39,1,39,3,39,796,8,39,3,39,798,8,39,1,40, + 1,40,1,40,1,40,1,40,5,40,805,8,40,10,40,12,40,808,9,40,1,40,1,40, + 1,41,1,41,1,41,1,41,1,41,1,41,1,41,1,41,1,41,3,41,821,8,41,1,41, + 1,41,1,42,1,42,1,42,1,43,1,43,1,44,1,44,1,44,1,44,1,44,1,44,1,44, + 1,44,1,44,1,44,5,44,840,8,44,10,44,12,44,843,9,44,3,44,845,8,44, + 1,44,3,44,848,8,44,1,45,1,45,1,45,5,45,853,8,45,10,45,12,45,856, + 9,45,1,46,1,46,1,46,5,46,861,8,46,10,46,12,46,864,9,46,1,46,1,46, + 1,47,1,47,1,47,1,47,1,47,1,47,1,47,1,47,1,47,1,47,1,47,1,47,1,47, + 1,47,1,47,1,47,1,47,1,47,1,47,1,47,1,47,1,47,1,47,1,47,1,47,1,47, + 1,47,3,47,895,8,47,1,47,1,47,1,47,1,47,1,47,1,47,1,47,1,47,1,47, + 1,47,3,47,907,8,47,1,47,3,47,910,8,47,1,47,1,47,3,47,914,8,47,1, + 47,1,47,1,47,1,47,1,47,1,47,1,47,1,47,1,47,1,47,1,47,1,47,1,47,1, + 47,1,47,1,47,1,47,1,47,1,47,1,47,3,47,936,8,47,1,47,1,47,1,47,1, + 47,1,47,1,47,1,47,1,47,1,47,1,47,1,47,1,47,3,47,950,8,47,1,48,1, + 48,1,48,1,48,5,48,956,8,48,10,48,12,48,959,9,48,1,48,1,48,1,49,1, + 49,1,50,1,50,1,50,1,51,1,51,3,51,970,8,51,1,52,1,52,1,52,1,52,1, + 52,1,52,1,52,1,52,1,52,1,52,1,52,1,52,3,52,984,8,52,1,52,1,52,1, + 53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,3, + 53,1001,8,53,1,54,1,54,1,54,1,54,1,54,1,54,1,54,1,54,1,54,1,55,1, + 55,1,55,1,55,1,55,1,55,1,55,3,55,1019,8,55,1,55,1,55,3,55,1023,8, + 55,1,55,3,55,1026,8,55,1,55,1,55,3,55,1030,8,55,1,55,3,55,1033,8, + 55,1,55,1,55,1,56,1,56,3,56,1039,8,56,1,56,1,56,3,56,1043,8,56,1, + 56,1,56,1,56,3,56,1048,8,56,1,56,1,56,3,56,1052,8,56,1,56,1,56,3, + 56,1056,8,56,1,56,1,56,5,56,1060,8,56,10,56,12,56,1063,9,56,1,56, + 1,56,1,57,1,57,1,58,1,58,1,58,1,58,1,58,1,58,1,58,3,58,1076,8,58, + 1,59,1,59,1,59,1,59,1,59,1,59,1,59,1,59,1,60,1,60,1,60,5,60,1089, + 8,60,10,60,12,60,1092,9,60,1,60,1,60,1,60,1,61,1,61,1,61,1,61,1, + 61,1,61,1,61,1,61,1,61,1,61,1,61,1,61,1,61,1,61,3,61,1111,8,61,1, + 62,1,62,1,62,1,62,1,62,5,62,1118,8,62,10,62,12,62,1121,9,62,1,62, + 1,62,1,63,1,63,1,63,1,63,1,63,1,63,1,63,1,64,1,64,1,64,1,64,1,64, + 1,64,1,64,1,64,1,64,1,64,1,64,1,64,1,64,1,64,1,65,1,65,1,65,1,65, + 1,65,1,65,3,65,1152,8,65,1,65,1,65,1,66,1,66,1,66,1,66,1,67,1,67, + 1,67,1,67,1,67,1,67,1,67,1,67,1,67,1,67,1,67,1,67,3,67,1172,8,67, + 1,68,1,68,1,68,1,68,1,68,1,68,1,68,1,68,1,68,1,68,1,68,1,68,1,68, + 1,68,1,68,1,68,1,68,1,68,1,68,1,68,1,68,1,68,1,68,1,68,1,68,1,68, + 1,68,1,68,3,68,1202,8,68,1,68,1,68,1,68,1,68,1,68,1,68,1,68,1,68, + 3,68,1212,8,68,1,68,3,68,1215,8,68,1,68,3,68,1218,8,68,3,68,1220, + 8,68,1,69,1,69,1,69,1,70,1,70,1,71,1,71,1,72,1,72,1,72,5,72,1232, + 8,72,10,72,12,72,1235,9,72,1,72,1,72,1,73,1,73,1,73,1,73,1,73,1, + 73,1,73,1,73,1,73,1,73,1,73,1,73,1,73,1,73,1,73,1,73,1,73,1,73,1, + 73,3,73,1258,8,73,1,73,1,73,1,73,1,73,1,73,1,73,1,73,1,73,1,73,1, + 73,1,73,1,73,1,73,1,73,3,73,1274,8,73,1,73,1,73,1,73,1,73,1,73,1, + 73,1,73,3,73,1283,8,73,1,73,1,73,1,73,1,73,1,73,1,73,1,73,1,73,3, + 73,1293,8,73,1,73,1,73,1,73,1,73,1,73,1,73,1,73,1,73,1,73,5,73,1304, + 8,73,10,73,12,73,1307,9,73,1,73,1,73,1,73,1,73,1,73,1,73,1,73,1, + 73,1,73,1,73,1,73,1,73,3,73,1321,8,73,1,73,1,73,1,73,1,73,1,73,1, + 73,1,73,1,73,1,73,1,73,3,73,1333,8,73,1,73,1,73,3,73,1337,8,73,1, + 74,1,74,1,74,1,74,1,74,1,74,1,74,3,74,1346,8,74,1,74,1,74,1,75,1, + 75,1,75,1,75,1,75,1,75,1,75,1,75,1,75,1,75,1,75,1,75,1,75,1,75,3, + 75,1364,8,75,1,75,1,75,1,75,1,75,1,75,1,75,1,75,1,75,1,75,1,75,1, + 75,1,75,1,75,1,75,1,75,1,75,1,75,1,75,1,75,1,75,5,75,1386,8,75,10, + 75,12,75,1389,9,75,1,75,1,75,1,75,1,75,1,75,1,75,4,75,1397,8,75, + 11,75,12,75,1398,1,75,1,75,1,75,1,75,3,75,1405,8,75,3,75,1407,8, + 75,1,76,1,76,1,76,1,76,1,76,1,77,1,77,1,77,1,77,1,77,1,77,1,77,1, + 78,1,78,1,79,1,79,1,80,1,80,1,80,1,80,1,80,1,80,1,80,1,80,3,80,1433, + 8,80,1,81,1,81,1,81,1,81,5,81,1439,8,81,10,81,12,81,1442,9,81,3, + 81,1444,8,81,1,81,1,81,1,82,1,82,1,82,1,82,1,83,1,83,1,83,1,83,5, + 83,1456,8,83,10,83,12,83,1459,9,83,3,83,1461,8,83,1,83,1,83,1,84, + 1,84,1,85,1,85,1,85,0,0,86,0,2,4,6,8,10,12,14,16,18,20,22,24,26, + 28,30,32,34,36,38,40,42,44,46,48,50,52,54,56,58,60,62,64,66,68,70, + 72,74,76,78,80,82,84,86,88,90,92,94,96,98,100,102,104,106,108,110, + 112,114,116,118,120,122,124,126,128,130,132,134,136,138,140,142, + 144,146,148,150,152,154,156,158,160,162,164,166,168,170,0,10,1,0, + 50,51,2,0,17,17,35,35,1,0,13,14,1,0,107,111,3,0,1,1,102,106,108, + 109,3,0,1,1,102,103,108,109,2,0,104,106,108,109,1,0,127,134,1,0, + 114,117,13,0,1,1,4,7,10,10,12,35,42,42,48,48,78,78,85,86,100,101, + 105,105,112,112,121,121,155,155,1593,0,184,1,0,0,0,2,186,1,0,0,0, + 4,196,1,0,0,0,6,200,1,0,0,0,8,225,1,0,0,0,10,237,1,0,0,0,12,239, + 1,0,0,0,14,246,1,0,0,0,16,253,1,0,0,0,18,264,1,0,0,0,20,266,1,0, + 0,0,22,279,1,0,0,0,24,311,1,0,0,0,26,341,1,0,0,0,28,343,1,0,0,0, + 30,347,1,0,0,0,32,365,1,0,0,0,34,367,1,0,0,0,36,380,1,0,0,0,38,382, + 1,0,0,0,40,395,1,0,0,0,42,416,1,0,0,0,44,466,1,0,0,0,46,469,1,0, + 0,0,48,494,1,0,0,0,50,531,1,0,0,0,52,542,1,0,0,0,54,547,1,0,0,0, + 56,575,1,0,0,0,58,577,1,0,0,0,60,587,1,0,0,0,62,599,1,0,0,0,64,646, + 1,0,0,0,66,712,1,0,0,0,68,714,1,0,0,0,70,719,1,0,0,0,72,751,1,0, + 0,0,74,776,1,0,0,0,76,780,1,0,0,0,78,797,1,0,0,0,80,799,1,0,0,0, + 82,811,1,0,0,0,84,824,1,0,0,0,86,827,1,0,0,0,88,847,1,0,0,0,90,849, + 1,0,0,0,92,857,1,0,0,0,94,949,1,0,0,0,96,951,1,0,0,0,98,962,1,0, + 0,0,100,964,1,0,0,0,102,969,1,0,0,0,104,971,1,0,0,0,106,1000,1,0, + 0,0,108,1002,1,0,0,0,110,1011,1,0,0,0,112,1036,1,0,0,0,114,1066, + 1,0,0,0,116,1075,1,0,0,0,118,1077,1,0,0,0,120,1085,1,0,0,0,122,1110, + 1,0,0,0,124,1112,1,0,0,0,126,1124,1,0,0,0,128,1131,1,0,0,0,130,1145, + 1,0,0,0,132,1155,1,0,0,0,134,1171,1,0,0,0,136,1219,1,0,0,0,138,1221, + 1,0,0,0,140,1224,1,0,0,0,142,1226,1,0,0,0,144,1228,1,0,0,0,146,1336, + 1,0,0,0,148,1338,1,0,0,0,150,1406,1,0,0,0,152,1408,1,0,0,0,154,1413, + 1,0,0,0,156,1420,1,0,0,0,158,1422,1,0,0,0,160,1432,1,0,0,0,162,1434, + 1,0,0,0,164,1447,1,0,0,0,166,1451,1,0,0,0,168,1464,1,0,0,0,170,1466, + 1,0,0,0,172,173,3,6,3,0,173,174,5,0,0,1,174,185,1,0,0,0,175,176, + 3,22,11,0,176,177,5,0,0,1,177,185,1,0,0,0,178,179,3,54,27,0,179, + 180,5,0,0,1,180,185,1,0,0,0,181,182,3,2,1,0,182,183,5,0,0,1,183, + 185,1,0,0,0,184,172,1,0,0,0,184,175,1,0,0,0,184,178,1,0,0,0,184, + 181,1,0,0,0,185,1,1,0,0,0,186,187,5,42,0,0,187,191,5,143,0,0,188, + 190,3,8,4,0,189,188,1,0,0,0,190,193,1,0,0,0,191,189,1,0,0,0,191, + 192,1,0,0,0,192,194,1,0,0,0,193,191,1,0,0,0,194,195,5,144,0,0,195, + 3,1,0,0,0,196,197,5,43,0,0,197,198,3,170,85,0,198,199,5,140,0,0, + 199,5,1,0,0,0,200,201,5,11,0,0,201,202,3,168,84,0,202,203,5,90,0, + 0,203,204,3,170,85,0,204,205,5,81,0,0,205,206,3,170,85,0,206,207, + 5,80,0,0,207,208,3,170,85,0,208,212,5,143,0,0,209,211,3,8,4,0,210, + 209,1,0,0,0,211,214,1,0,0,0,212,210,1,0,0,0,212,213,1,0,0,0,213, + 215,1,0,0,0,214,212,1,0,0,0,215,216,5,144,0,0,216,7,1,0,0,0,217, + 226,3,4,2,0,218,226,3,20,10,0,219,226,3,10,5,0,220,226,3,100,50, + 0,221,226,3,112,56,0,222,226,3,148,74,0,223,226,3,60,30,0,224,226, + 3,34,17,0,225,217,1,0,0,0,225,218,1,0,0,0,225,219,1,0,0,0,225,220, + 1,0,0,0,225,221,1,0,0,0,225,222,1,0,0,0,225,223,1,0,0,0,225,224, + 1,0,0,0,226,9,1,0,0,0,227,228,5,43,0,0,228,229,5,47,0,0,229,230, + 3,168,84,0,230,231,5,140,0,0,231,238,1,0,0,0,232,233,5,43,0,0,233, + 234,5,49,0,0,234,235,3,168,84,0,235,236,5,140,0,0,236,238,1,0,0, + 0,237,227,1,0,0,0,237,232,1,0,0,0,238,11,1,0,0,0,239,240,5,44,0, + 0,240,241,5,46,0,0,241,242,3,168,84,0,242,243,5,90,0,0,243,244,3, + 170,85,0,244,245,5,140,0,0,245,13,1,0,0,0,246,247,5,44,0,0,247,248, + 5,47,0,0,248,249,3,168,84,0,249,250,5,81,0,0,250,251,3,170,85,0, + 251,252,5,140,0,0,252,15,1,0,0,0,253,254,5,45,0,0,254,255,3,168, + 84,0,255,256,5,90,0,0,256,257,3,170,85,0,257,258,5,140,0,0,258,17, + 1,0,0,0,259,265,3,10,5,0,260,265,3,12,6,0,261,265,3,14,7,0,262,265, + 3,16,8,0,263,265,3,34,17,0,264,259,1,0,0,0,264,260,1,0,0,0,264,261, + 1,0,0,0,264,262,1,0,0,0,264,263,1,0,0,0,265,19,1,0,0,0,266,267,5, + 46,0,0,267,268,3,168,84,0,268,269,5,90,0,0,269,272,3,170,85,0,270, + 271,5,91,0,0,271,273,3,170,85,0,272,270,1,0,0,0,272,273,1,0,0,0, + 273,274,1,0,0,0,274,275,5,140,0,0,275,21,1,0,0,0,276,278,3,18,9, + 0,277,276,1,0,0,0,278,281,1,0,0,0,279,277,1,0,0,0,279,280,1,0,0, + 0,280,282,1,0,0,0,281,279,1,0,0,0,282,283,5,47,0,0,283,285,3,168, + 84,0,284,286,3,24,12,0,285,284,1,0,0,0,285,286,1,0,0,0,286,287,1, + 0,0,0,287,288,5,90,0,0,288,289,3,170,85,0,289,290,5,81,0,0,290,300, + 3,170,85,0,291,292,5,95,0,0,292,297,3,28,14,0,293,294,5,141,0,0, + 294,296,3,28,14,0,295,293,1,0,0,0,296,299,1,0,0,0,297,295,1,0,0, + 0,297,298,1,0,0,0,298,301,1,0,0,0,299,297,1,0,0,0,300,291,1,0,0, + 0,300,301,1,0,0,0,301,302,1,0,0,0,302,306,5,143,0,0,303,305,3,36, + 18,0,304,303,1,0,0,0,305,308,1,0,0,0,306,304,1,0,0,0,306,307,1,0, + 0,0,307,309,1,0,0,0,308,306,1,0,0,0,309,310,5,144,0,0,310,23,1,0, + 0,0,311,312,5,149,0,0,312,317,3,26,13,0,313,314,5,141,0,0,314,316, + 3,26,13,0,315,313,1,0,0,0,316,319,1,0,0,0,317,315,1,0,0,0,317,318, + 1,0,0,0,318,320,1,0,0,0,319,317,1,0,0,0,320,321,5,150,0,0,321,25, + 1,0,0,0,322,323,5,50,0,0,323,326,3,168,84,0,324,325,5,139,0,0,325, + 327,5,39,0,0,326,324,1,0,0,0,326,327,1,0,0,0,327,342,1,0,0,0,328, + 329,5,38,0,0,329,339,3,168,84,0,330,331,5,40,0,0,331,336,3,28,14, + 0,332,333,5,151,0,0,333,335,3,28,14,0,334,332,1,0,0,0,335,338,1, + 0,0,0,336,334,1,0,0,0,336,337,1,0,0,0,337,340,1,0,0,0,338,336,1, + 0,0,0,339,330,1,0,0,0,339,340,1,0,0,0,340,342,1,0,0,0,341,322,1, + 0,0,0,341,328,1,0,0,0,342,27,1,0,0,0,343,345,3,168,84,0,344,346, + 3,30,15,0,345,344,1,0,0,0,345,346,1,0,0,0,346,29,1,0,0,0,347,348, + 5,149,0,0,348,353,3,32,16,0,349,350,5,141,0,0,350,352,3,32,16,0, + 351,349,1,0,0,0,352,355,1,0,0,0,353,351,1,0,0,0,353,354,1,0,0,0, + 354,356,1,0,0,0,355,353,1,0,0,0,356,357,5,150,0,0,357,31,1,0,0,0, + 358,359,5,46,0,0,359,366,3,168,84,0,360,361,5,47,0,0,361,366,3,28, + 14,0,362,363,5,38,0,0,363,366,3,168,84,0,364,366,3,150,75,0,365, + 358,1,0,0,0,365,360,1,0,0,0,365,362,1,0,0,0,365,364,1,0,0,0,366, + 33,1,0,0,0,367,368,5,37,0,0,368,370,3,168,84,0,369,371,3,24,12,0, + 370,369,1,0,0,0,370,371,1,0,0,0,371,372,1,0,0,0,372,373,5,152,0, + 0,373,374,3,150,75,0,374,375,5,140,0,0,375,35,1,0,0,0,376,381,3, + 42,21,0,377,381,3,46,23,0,378,381,3,40,20,0,379,381,3,38,19,0,380, + 376,1,0,0,0,380,377,1,0,0,0,380,378,1,0,0,0,380,379,1,0,0,0,381, + 37,1,0,0,0,382,383,5,2,0,0,383,384,3,168,84,0,384,385,5,90,0,0,385, + 386,3,170,85,0,386,387,5,3,0,0,387,390,3,170,85,0,388,389,5,5,0, + 0,389,391,3,52,26,0,390,388,1,0,0,0,390,391,1,0,0,0,391,392,1,0, + 0,0,392,393,5,140,0,0,393,39,1,0,0,0,394,396,5,61,0,0,395,394,1, + 0,0,0,395,396,1,0,0,0,396,397,1,0,0,0,397,398,5,53,0,0,398,399,3, + 168,84,0,399,400,5,90,0,0,400,401,3,170,85,0,401,402,5,139,0,0,402, + 403,3,150,75,0,403,404,5,138,0,0,404,405,3,150,75,0,405,406,5,143, + 0,0,406,407,5,107,0,0,407,408,5,90,0,0,408,410,3,170,85,0,409,411, + 3,78,39,0,410,409,1,0,0,0,410,411,1,0,0,0,411,412,1,0,0,0,412,413, + 5,140,0,0,413,414,5,144,0,0,414,41,1,0,0,0,415,417,3,48,24,0,416, + 415,1,0,0,0,416,417,1,0,0,0,417,418,1,0,0,0,418,419,7,0,0,0,419, + 420,3,168,84,0,420,421,5,90,0,0,421,422,3,170,85,0,422,423,5,139, + 0,0,423,424,3,150,75,0,424,428,5,143,0,0,425,427,3,44,22,0,426,425, + 1,0,0,0,427,430,1,0,0,0,428,426,1,0,0,0,428,429,1,0,0,0,429,431, + 1,0,0,0,430,428,1,0,0,0,431,432,5,144,0,0,432,43,1,0,0,0,433,434, + 5,97,0,0,434,435,5,90,0,0,435,437,3,170,85,0,436,438,3,78,39,0,437, + 436,1,0,0,0,437,438,1,0,0,0,438,439,1,0,0,0,439,440,5,140,0,0,440, + 467,1,0,0,0,441,442,5,98,0,0,442,443,5,90,0,0,443,445,3,170,85,0, + 444,446,3,78,39,0,445,444,1,0,0,0,445,446,1,0,0,0,446,447,1,0,0, + 0,447,448,5,140,0,0,448,467,1,0,0,0,449,450,5,1,0,0,450,451,5,90, + 0,0,451,453,3,170,85,0,452,454,3,78,39,0,453,452,1,0,0,0,453,454, + 1,0,0,0,454,455,1,0,0,0,455,456,5,140,0,0,456,467,1,0,0,0,457,458, + 5,99,0,0,458,459,5,100,0,0,459,460,5,90,0,0,460,461,3,170,85,0,461, + 462,5,101,0,0,462,463,5,90,0,0,463,464,3,170,85,0,464,465,5,140, + 0,0,465,467,1,0,0,0,466,433,1,0,0,0,466,441,1,0,0,0,466,449,1,0, + 0,0,466,457,1,0,0,0,467,45,1,0,0,0,468,470,3,48,24,0,469,468,1,0, + 0,0,469,470,1,0,0,0,470,471,1,0,0,0,471,472,5,52,0,0,472,473,3,168, + 84,0,473,474,5,90,0,0,474,475,3,170,85,0,475,476,5,139,0,0,476,477, + 3,158,79,0,477,479,3,52,26,0,478,480,5,118,0,0,479,478,1,0,0,0,479, + 480,1,0,0,0,480,483,1,0,0,0,481,482,5,88,0,0,482,484,3,170,85,0, + 483,481,1,0,0,0,483,484,1,0,0,0,484,485,1,0,0,0,485,489,5,143,0, + 0,486,488,3,50,25,0,487,486,1,0,0,0,488,491,1,0,0,0,489,487,1,0, + 0,0,489,490,1,0,0,0,490,492,1,0,0,0,491,489,1,0,0,0,492,493,5,144, + 0,0,493,47,1,0,0,0,494,496,5,34,0,0,495,497,7,1,0,0,496,495,1,0, + 0,0,496,497,1,0,0,0,497,49,1,0,0,0,498,499,5,104,0,0,499,500,5,90, + 0,0,500,502,3,170,85,0,501,503,3,78,39,0,502,501,1,0,0,0,502,503, + 1,0,0,0,503,504,1,0,0,0,504,505,5,140,0,0,505,532,1,0,0,0,506,507, + 5,105,0,0,507,508,5,90,0,0,508,510,3,170,85,0,509,511,3,78,39,0, + 510,509,1,0,0,0,510,511,1,0,0,0,511,512,1,0,0,0,512,513,5,140,0, + 0,513,532,1,0,0,0,514,515,5,106,0,0,515,516,5,90,0,0,516,518,3,170, + 85,0,517,519,3,78,39,0,518,517,1,0,0,0,518,519,1,0,0,0,519,520,1, + 0,0,0,520,521,5,140,0,0,521,532,1,0,0,0,522,523,5,99,0,0,523,524, + 5,100,0,0,524,525,5,90,0,0,525,526,3,170,85,0,526,527,5,101,0,0, + 527,528,5,90,0,0,528,529,3,170,85,0,529,530,5,140,0,0,530,532,1, + 0,0,0,531,498,1,0,0,0,531,506,1,0,0,0,531,514,1,0,0,0,531,522,1, + 0,0,0,532,51,1,0,0,0,533,534,5,46,0,0,534,543,3,168,84,0,535,536, + 5,47,0,0,536,538,3,168,84,0,537,539,3,30,15,0,538,537,1,0,0,0,538, + 539,1,0,0,0,539,543,1,0,0,0,540,541,5,38,0,0,541,543,3,168,84,0, + 542,533,1,0,0,0,542,535,1,0,0,0,542,540,1,0,0,0,543,53,1,0,0,0,544, + 546,3,18,9,0,545,544,1,0,0,0,546,549,1,0,0,0,547,545,1,0,0,0,547, + 548,1,0,0,0,548,550,1,0,0,0,549,547,1,0,0,0,550,551,5,49,0,0,551, + 552,3,168,84,0,552,553,5,90,0,0,553,554,3,170,85,0,554,555,5,81, + 0,0,555,558,3,170,85,0,556,557,5,82,0,0,557,559,5,153,0,0,558,556, + 1,0,0,0,558,559,1,0,0,0,559,560,1,0,0,0,560,564,5,143,0,0,561,563, + 3,56,28,0,562,561,1,0,0,0,563,566,1,0,0,0,564,562,1,0,0,0,564,565, + 1,0,0,0,565,567,1,0,0,0,566,564,1,0,0,0,567,568,5,144,0,0,568,55, + 1,0,0,0,569,576,3,70,35,0,570,576,3,72,36,0,571,576,3,82,41,0,572, + 576,3,62,31,0,573,576,3,64,32,0,574,576,3,58,29,0,575,569,1,0,0, + 0,575,570,1,0,0,0,575,571,1,0,0,0,575,572,1,0,0,0,575,573,1,0,0, + 0,575,574,1,0,0,0,576,57,1,0,0,0,577,578,5,2,0,0,578,579,3,168,84, + 0,579,580,5,90,0,0,580,581,3,170,85,0,581,582,5,40,0,0,582,583,3, + 28,14,0,583,584,5,142,0,0,584,585,3,168,84,0,585,586,5,140,0,0,586, + 59,1,0,0,0,587,588,5,4,0,0,588,589,3,168,84,0,589,590,5,90,0,0,590, + 591,3,170,85,0,591,592,5,6,0,0,592,593,3,28,14,0,593,594,5,69,0, + 0,594,595,3,168,84,0,595,596,5,142,0,0,596,597,3,168,84,0,597,598, + 5,140,0,0,598,61,1,0,0,0,599,601,5,24,0,0,600,602,5,42,0,0,601,600, + 1,0,0,0,601,602,1,0,0,0,602,603,1,0,0,0,603,605,3,168,84,0,604,606, + 3,24,12,0,605,604,1,0,0,0,605,606,1,0,0,0,606,607,1,0,0,0,607,608, + 5,90,0,0,608,609,3,170,85,0,609,611,5,27,0,0,610,612,5,11,0,0,611, + 610,1,0,0,0,611,612,1,0,0,0,612,613,1,0,0,0,613,614,3,28,14,0,614, + 615,5,28,0,0,615,644,3,170,85,0,616,617,5,53,0,0,617,618,3,170,85, + 0,618,622,5,143,0,0,619,621,3,66,33,0,620,619,1,0,0,0,621,624,1, + 0,0,0,622,620,1,0,0,0,622,623,1,0,0,0,623,625,1,0,0,0,624,622,1, + 0,0,0,625,626,5,144,0,0,626,645,1,0,0,0,627,628,5,42,0,0,628,631, + 3,170,85,0,629,630,5,53,0,0,630,632,3,170,85,0,631,629,1,0,0,0,631, + 632,1,0,0,0,632,642,1,0,0,0,633,637,5,143,0,0,634,636,3,66,33,0, + 635,634,1,0,0,0,636,639,1,0,0,0,637,635,1,0,0,0,637,638,1,0,0,0, + 638,640,1,0,0,0,639,637,1,0,0,0,640,643,5,144,0,0,641,643,5,140, + 0,0,642,633,1,0,0,0,642,641,1,0,0,0,643,645,1,0,0,0,644,616,1,0, + 0,0,644,627,1,0,0,0,645,63,1,0,0,0,646,647,5,24,0,0,647,648,3,168, + 84,0,648,649,5,90,0,0,649,650,3,170,85,0,650,651,5,26,0,0,651,652, + 3,168,84,0,652,653,3,30,15,0,653,654,5,140,0,0,654,65,1,0,0,0,655, + 656,5,18,0,0,656,713,5,140,0,0,657,658,5,19,0,0,658,659,5,20,0,0, + 659,660,3,168,84,0,660,661,5,140,0,0,661,713,1,0,0,0,662,663,5,29, + 0,0,663,664,3,170,85,0,664,665,5,140,0,0,665,713,1,0,0,0,666,667, + 5,29,0,0,667,668,3,168,84,0,668,669,5,142,0,0,669,679,3,168,84,0, + 670,671,5,68,0,0,671,675,5,143,0,0,672,674,3,68,34,0,673,672,1,0, + 0,0,674,677,1,0,0,0,675,673,1,0,0,0,675,676,1,0,0,0,676,678,1,0, + 0,0,677,675,1,0,0,0,678,680,5,144,0,0,679,670,1,0,0,0,679,680,1, + 0,0,0,680,681,1,0,0,0,681,682,5,140,0,0,682,713,1,0,0,0,683,685, + 5,30,0,0,684,686,5,38,0,0,685,684,1,0,0,0,685,686,1,0,0,0,686,687, + 1,0,0,0,687,688,3,168,84,0,688,689,5,59,0,0,689,690,3,28,14,0,690, + 691,5,140,0,0,691,713,1,0,0,0,692,693,5,31,0,0,693,694,3,168,84, + 0,694,695,5,153,0,0,695,696,5,140,0,0,696,713,1,0,0,0,697,698,5, + 32,0,0,698,699,3,28,14,0,699,700,5,142,0,0,700,701,3,168,84,0,701, + 702,3,168,84,0,702,703,3,170,85,0,703,704,5,140,0,0,704,713,1,0, + 0,0,705,706,5,99,0,0,706,713,5,140,0,0,707,708,5,33,0,0,708,709, + 5,153,0,0,709,710,3,170,85,0,710,711,5,140,0,0,711,713,1,0,0,0,712, + 655,1,0,0,0,712,657,1,0,0,0,712,662,1,0,0,0,712,666,1,0,0,0,712, + 683,1,0,0,0,712,692,1,0,0,0,712,697,1,0,0,0,712,705,1,0,0,0,712, + 707,1,0,0,0,713,67,1,0,0,0,714,715,3,168,84,0,715,716,5,62,0,0,716, + 717,3,168,84,0,717,718,5,140,0,0,718,69,1,0,0,0,719,720,5,53,0,0, + 720,722,3,168,84,0,721,723,3,24,12,0,722,721,1,0,0,0,722,723,1,0, + 0,0,723,724,1,0,0,0,724,725,5,90,0,0,725,726,3,170,85,0,726,727, + 5,139,0,0,727,728,3,150,75,0,728,729,5,138,0,0,729,730,3,150,75, + 0,730,731,5,92,0,0,731,733,3,86,43,0,732,734,3,78,39,0,733,732,1, + 0,0,0,733,734,1,0,0,0,734,736,1,0,0,0,735,737,3,84,42,0,736,735, + 1,0,0,0,736,737,1,0,0,0,737,738,1,0,0,0,738,739,5,94,0,0,739,741, + 3,88,44,0,740,742,3,92,46,0,741,740,1,0,0,0,741,742,1,0,0,0,742, + 744,1,0,0,0,743,745,3,76,38,0,744,743,1,0,0,0,744,745,1,0,0,0,745, + 747,1,0,0,0,746,748,3,74,37,0,747,746,1,0,0,0,747,748,1,0,0,0,748, + 749,1,0,0,0,749,750,5,140,0,0,750,71,1,0,0,0,751,752,5,54,0,0,752, + 754,3,168,84,0,753,755,3,24,12,0,754,753,1,0,0,0,754,755,1,0,0,0, + 755,756,1,0,0,0,756,757,5,90,0,0,757,758,3,170,85,0,758,759,5,139, + 0,0,759,760,3,150,75,0,760,761,5,138,0,0,761,763,3,150,75,0,762, + 764,3,78,39,0,763,762,1,0,0,0,763,764,1,0,0,0,764,766,1,0,0,0,765, + 767,3,92,46,0,766,765,1,0,0,0,766,767,1,0,0,0,767,769,1,0,0,0,768, + 770,3,76,38,0,769,768,1,0,0,0,769,770,1,0,0,0,770,772,1,0,0,0,771, + 773,3,74,37,0,772,771,1,0,0,0,772,773,1,0,0,0,773,774,1,0,0,0,774, + 775,5,140,0,0,775,73,1,0,0,0,776,777,5,70,0,0,777,778,5,69,0,0,778, + 779,3,168,84,0,779,75,1,0,0,0,780,781,5,9,0,0,781,782,5,7,0,0,782, + 783,3,168,84,0,783,784,5,69,0,0,784,785,3,170,85,0,785,77,1,0,0, + 0,786,787,5,12,0,0,787,798,5,15,0,0,788,789,5,12,0,0,789,795,7,2, + 0,0,790,791,5,16,0,0,791,792,5,145,0,0,792,793,3,90,45,0,793,794, + 5,146,0,0,794,796,1,0,0,0,795,790,1,0,0,0,795,796,1,0,0,0,796,798, + 1,0,0,0,797,786,1,0,0,0,797,788,1,0,0,0,798,79,1,0,0,0,799,800,5, + 69,0,0,800,801,5,145,0,0,801,806,3,132,66,0,802,803,5,141,0,0,803, + 805,3,132,66,0,804,802,1,0,0,0,805,808,1,0,0,0,806,804,1,0,0,0,806, + 807,1,0,0,0,807,809,1,0,0,0,808,806,1,0,0,0,809,810,5,146,0,0,810, + 81,1,0,0,0,811,812,5,55,0,0,812,813,3,168,84,0,813,814,5,90,0,0, + 814,815,3,170,85,0,815,816,5,56,0,0,816,817,3,168,84,0,817,818,5, + 139,0,0,818,820,3,150,75,0,819,821,3,92,46,0,820,819,1,0,0,0,820, + 821,1,0,0,0,821,822,1,0,0,0,822,823,5,140,0,0,823,83,1,0,0,0,824, + 825,5,93,0,0,825,826,3,150,75,0,826,85,1,0,0,0,827,828,7,3,0,0,828, + 87,1,0,0,0,829,848,5,96,0,0,830,831,5,46,0,0,831,848,3,168,84,0, + 832,833,5,38,0,0,833,848,3,168,84,0,834,835,5,48,0,0,835,844,5,145, + 0,0,836,841,3,28,14,0,837,838,5,141,0,0,838,840,3,28,14,0,839,837, + 1,0,0,0,840,843,1,0,0,0,841,839,1,0,0,0,841,842,1,0,0,0,842,845, + 1,0,0,0,843,841,1,0,0,0,844,836,1,0,0,0,844,845,1,0,0,0,845,846, + 1,0,0,0,846,848,5,146,0,0,847,829,1,0,0,0,847,830,1,0,0,0,847,832, + 1,0,0,0,847,834,1,0,0,0,848,89,1,0,0,0,849,854,3,168,84,0,850,851, + 5,141,0,0,851,853,3,168,84,0,852,850,1,0,0,0,853,856,1,0,0,0,854, + 852,1,0,0,0,854,855,1,0,0,0,855,91,1,0,0,0,856,854,1,0,0,0,857,858, + 5,95,0,0,858,862,5,143,0,0,859,861,3,94,47,0,860,859,1,0,0,0,861, + 864,1,0,0,0,862,860,1,0,0,0,862,863,1,0,0,0,863,865,1,0,0,0,864, + 862,1,0,0,0,865,866,5,144,0,0,866,93,1,0,0,0,867,868,5,25,0,0,868, + 869,3,168,84,0,869,870,5,90,0,0,870,871,3,170,85,0,871,872,5,145, + 0,0,872,873,3,90,45,0,873,874,5,146,0,0,874,875,5,140,0,0,875,950, + 1,0,0,0,876,877,5,65,0,0,877,878,3,168,84,0,878,879,5,90,0,0,879, + 880,3,170,85,0,880,881,5,139,0,0,881,882,3,150,75,0,882,883,3,96, + 48,0,883,884,5,140,0,0,884,950,1,0,0,0,885,886,5,66,0,0,886,887, + 3,168,84,0,887,888,5,90,0,0,888,889,3,170,85,0,889,890,5,139,0,0, + 890,891,3,158,79,0,891,894,3,52,26,0,892,893,5,88,0,0,893,895,3, + 170,85,0,894,892,1,0,0,0,894,895,1,0,0,0,895,896,1,0,0,0,896,897, + 3,96,48,0,897,898,5,140,0,0,898,950,1,0,0,0,899,900,5,47,0,0,900, + 901,3,168,84,0,901,902,5,90,0,0,902,903,3,170,85,0,903,904,5,139, + 0,0,904,906,3,168,84,0,905,907,3,30,15,0,906,905,1,0,0,0,906,907, + 1,0,0,0,907,909,1,0,0,0,908,910,3,80,40,0,909,908,1,0,0,0,909,910, + 1,0,0,0,910,913,1,0,0,0,911,912,5,20,0,0,912,914,5,57,0,0,913,911, + 1,0,0,0,913,914,1,0,0,0,914,915,1,0,0,0,915,916,5,140,0,0,916,950, + 1,0,0,0,917,918,5,7,0,0,918,919,3,168,84,0,919,920,5,90,0,0,920, + 921,3,170,85,0,921,922,5,139,0,0,922,923,3,168,84,0,923,924,5,57, + 0,0,924,925,3,150,75,0,925,926,5,140,0,0,926,950,1,0,0,0,927,928, + 5,55,0,0,928,929,3,168,84,0,929,930,5,90,0,0,930,931,3,170,85,0, + 931,932,5,139,0,0,932,935,3,168,84,0,933,934,5,57,0,0,934,936,3, + 150,75,0,935,933,1,0,0,0,935,936,1,0,0,0,936,937,1,0,0,0,937,938, + 5,140,0,0,938,950,1,0,0,0,939,940,5,24,0,0,940,941,3,168,84,0,941, + 942,5,90,0,0,942,943,3,170,85,0,943,944,5,139,0,0,944,945,3,168, + 84,0,945,946,5,142,0,0,946,947,3,168,84,0,947,948,5,140,0,0,948, + 950,1,0,0,0,949,867,1,0,0,0,949,876,1,0,0,0,949,885,1,0,0,0,949, + 899,1,0,0,0,949,917,1,0,0,0,949,927,1,0,0,0,949,939,1,0,0,0,950, + 95,1,0,0,0,951,952,5,145,0,0,952,957,3,98,49,0,953,954,5,141,0,0, + 954,956,3,98,49,0,955,953,1,0,0,0,956,959,1,0,0,0,957,955,1,0,0, + 0,957,958,1,0,0,0,958,960,1,0,0,0,959,957,1,0,0,0,960,961,5,146, + 0,0,961,97,1,0,0,0,962,963,7,4,0,0,963,99,1,0,0,0,964,965,5,64,0, + 0,965,966,3,102,51,0,966,101,1,0,0,0,967,970,3,104,52,0,968,970, + 3,108,54,0,969,967,1,0,0,0,969,968,1,0,0,0,970,103,1,0,0,0,971,972, + 5,65,0,0,972,973,3,168,84,0,973,974,5,90,0,0,974,975,3,170,85,0, + 975,976,5,75,0,0,976,977,3,168,84,0,977,978,5,139,0,0,978,979,3, + 150,75,0,979,980,5,76,0,0,980,983,3,106,53,0,981,982,5,77,0,0,982, + 984,3,160,80,0,983,981,1,0,0,0,983,984,1,0,0,0,984,985,1,0,0,0,985, + 986,5,140,0,0,986,105,1,0,0,0,987,1001,5,112,0,0,988,989,5,113,0, + 0,989,990,5,147,0,0,990,991,3,150,75,0,991,992,5,148,0,0,992,1001, + 1,0,0,0,993,994,5,85,0,0,994,1001,3,162,81,0,995,996,5,86,0,0,996, + 997,5,147,0,0,997,998,3,150,75,0,998,999,5,148,0,0,999,1001,1,0, + 0,0,1000,987,1,0,0,0,1000,988,1,0,0,0,1000,993,1,0,0,0,1000,995, + 1,0,0,0,1001,107,1,0,0,0,1002,1003,5,66,0,0,1003,1004,3,168,84,0, + 1004,1005,5,90,0,0,1005,1006,3,170,85,0,1006,1007,5,143,0,0,1007, + 1008,3,110,55,0,1008,1009,3,110,55,0,1009,1010,5,144,0,0,1010,109, + 1,0,0,0,1011,1012,3,52,26,0,1012,1013,5,67,0,0,1013,1014,3,168,84, + 0,1014,1015,5,90,0,0,1015,1016,3,170,85,0,1016,1018,3,158,79,0,1017, + 1019,5,118,0,0,1018,1017,1,0,0,0,1018,1019,1,0,0,0,1019,1022,1,0, + 0,0,1020,1021,5,83,0,0,1021,1023,3,170,85,0,1022,1020,1,0,0,0,1022, + 1023,1,0,0,0,1023,1025,1,0,0,0,1024,1026,5,87,0,0,1025,1024,1,0, + 0,0,1025,1026,1,0,0,0,1026,1029,1,0,0,0,1027,1028,5,88,0,0,1028, + 1030,3,170,85,0,1029,1027,1,0,0,0,1029,1030,1,0,0,0,1030,1032,1, + 0,0,0,1031,1033,5,89,0,0,1032,1031,1,0,0,0,1032,1033,1,0,0,0,1033, + 1034,1,0,0,0,1034,1035,5,140,0,0,1035,111,1,0,0,0,1036,1038,5,58, + 0,0,1037,1039,3,24,12,0,1038,1037,1,0,0,0,1038,1039,1,0,0,0,1039, + 1040,1,0,0,0,1040,1042,3,168,84,0,1041,1043,3,114,57,0,1042,1041, + 1,0,0,0,1042,1043,1,0,0,0,1043,1044,1,0,0,0,1044,1045,5,59,0,0,1045, + 1047,3,168,84,0,1046,1048,3,30,15,0,1047,1046,1,0,0,0,1047,1048, + 1,0,0,0,1048,1051,1,0,0,0,1049,1050,5,90,0,0,1050,1052,3,170,85, + 0,1051,1049,1,0,0,0,1051,1052,1,0,0,0,1052,1055,1,0,0,0,1053,1054, + 5,82,0,0,1054,1056,5,153,0,0,1055,1053,1,0,0,0,1055,1056,1,0,0,0, + 1056,1057,1,0,0,0,1057,1061,5,143,0,0,1058,1060,3,116,58,0,1059, + 1058,1,0,0,0,1060,1063,1,0,0,0,1061,1059,1,0,0,0,1061,1062,1,0,0, + 0,1062,1064,1,0,0,0,1063,1061,1,0,0,0,1064,1065,5,144,0,0,1065,113, + 1,0,0,0,1066,1067,3,30,15,0,1067,115,1,0,0,0,1068,1069,5,63,0,0, + 1069,1076,3,102,51,0,1070,1076,3,130,65,0,1071,1076,3,126,63,0,1072, + 1076,3,120,60,0,1073,1076,3,128,64,0,1074,1076,3,118,59,0,1075,1068, + 1,0,0,0,1075,1070,1,0,0,0,1075,1071,1,0,0,0,1075,1072,1,0,0,0,1075, + 1073,1,0,0,0,1075,1074,1,0,0,0,1076,117,1,0,0,0,1077,1078,5,60,0, + 0,1078,1079,3,168,84,0,1079,1080,5,62,0,0,1080,1081,3,168,84,0,1081, + 1082,5,142,0,0,1082,1083,3,168,84,0,1083,1084,5,140,0,0,1084,119, + 1,0,0,0,1085,1086,5,10,0,0,1086,1090,5,143,0,0,1087,1089,3,122,61, + 0,1088,1087,1,0,0,0,1089,1092,1,0,0,0,1090,1088,1,0,0,0,1090,1091, + 1,0,0,0,1091,1093,1,0,0,0,1092,1090,1,0,0,0,1093,1094,5,144,0,0, + 1094,1095,5,140,0,0,1095,121,1,0,0,0,1096,1097,3,168,84,0,1097,1098, + 5,62,0,0,1098,1099,5,65,0,0,1099,1100,3,168,84,0,1100,1101,5,140, + 0,0,1101,1111,1,0,0,0,1102,1103,3,168,84,0,1103,1104,5,62,0,0,1104, + 1105,5,66,0,0,1105,1106,3,168,84,0,1106,1107,5,142,0,0,1107,1108, + 3,168,84,0,1108,1109,5,140,0,0,1109,1111,1,0,0,0,1110,1096,1,0,0, + 0,1110,1102,1,0,0,0,1111,123,1,0,0,0,1112,1113,3,28,14,0,1113,1114, + 5,20,0,0,1114,1119,5,57,0,0,1115,1116,5,142,0,0,1116,1118,3,168, + 84,0,1117,1115,1,0,0,0,1118,1121,1,0,0,0,1119,1117,1,0,0,0,1119, + 1120,1,0,0,0,1120,1122,1,0,0,0,1121,1119,1,0,0,0,1122,1123,5,140, + 0,0,1123,125,1,0,0,0,1124,1125,5,60,0,0,1125,1126,3,168,84,0,1126, + 1127,5,62,0,0,1127,1128,5,65,0,0,1128,1129,3,168,84,0,1129,1130, + 5,140,0,0,1130,127,1,0,0,0,1131,1132,5,72,0,0,1132,1133,3,168,84, + 0,1133,1134,5,73,0,0,1134,1135,5,74,0,0,1135,1136,5,69,0,0,1136, + 1137,5,55,0,0,1137,1138,3,168,84,0,1138,1139,5,71,0,0,1139,1140, + 5,66,0,0,1140,1141,3,168,84,0,1141,1142,5,142,0,0,1142,1143,3,168, + 84,0,1143,1144,5,140,0,0,1144,129,1,0,0,0,1145,1146,5,60,0,0,1146, + 1147,3,132,66,0,1147,1148,5,62,0,0,1148,1151,3,136,68,0,1149,1150, + 5,36,0,0,1150,1152,3,170,85,0,1151,1149,1,0,0,0,1151,1152,1,0,0, + 0,1152,1153,1,0,0,0,1153,1154,5,140,0,0,1154,131,1,0,0,0,1155,1156, + 3,168,84,0,1156,1157,5,142,0,0,1157,1158,3,134,67,0,1158,133,1,0, + 0,0,1159,1172,3,168,84,0,1160,1172,5,107,0,0,1161,1172,5,97,0,0, + 1162,1172,5,98,0,0,1163,1172,5,1,0,0,1164,1172,5,104,0,0,1165,1172, + 5,105,0,0,1166,1172,5,106,0,0,1167,1172,5,108,0,0,1168,1172,5,109, + 0,0,1169,1172,5,110,0,0,1170,1172,5,111,0,0,1171,1159,1,0,0,0,1171, + 1160,1,0,0,0,1171,1161,1,0,0,0,1171,1162,1,0,0,0,1171,1163,1,0,0, + 0,1171,1164,1,0,0,0,1171,1165,1,0,0,0,1171,1166,1,0,0,0,1171,1167, + 1,0,0,0,1171,1168,1,0,0,0,1171,1169,1,0,0,0,1171,1170,1,0,0,0,1172, + 135,1,0,0,0,1173,1174,5,84,0,0,1174,1175,5,142,0,0,1175,1220,3,168, + 84,0,1176,1177,5,85,0,0,1177,1178,3,168,84,0,1178,1179,5,142,0,0, + 1179,1180,3,168,84,0,1180,1220,1,0,0,0,1181,1182,5,86,0,0,1182,1183, + 3,168,84,0,1183,1184,5,142,0,0,1184,1185,3,168,84,0,1185,1220,1, + 0,0,0,1186,1187,5,65,0,0,1187,1188,3,168,84,0,1188,1189,5,142,0, + 0,1189,1190,3,140,70,0,1190,1220,1,0,0,0,1191,1192,5,66,0,0,1192, + 1193,3,168,84,0,1193,1194,5,142,0,0,1194,1195,3,168,84,0,1195,1196, + 5,142,0,0,1196,1197,3,142,71,0,1197,1220,1,0,0,0,1198,1199,5,47, + 0,0,1199,1201,3,168,84,0,1200,1202,3,30,15,0,1201,1200,1,0,0,0,1201, + 1202,1,0,0,0,1202,1203,1,0,0,0,1203,1204,5,142,0,0,1204,1205,3,132, + 66,0,1205,1220,1,0,0,0,1206,1207,5,49,0,0,1207,1208,3,168,84,0,1208, + 1209,5,142,0,0,1209,1211,3,168,84,0,1210,1212,3,30,15,0,1211,1210, + 1,0,0,0,1211,1212,1,0,0,0,1212,1214,1,0,0,0,1213,1215,3,144,72,0, + 1214,1213,1,0,0,0,1214,1215,1,0,0,0,1215,1217,1,0,0,0,1216,1218, + 3,138,69,0,1217,1216,1,0,0,0,1217,1218,1,0,0,0,1218,1220,1,0,0,0, + 1219,1173,1,0,0,0,1219,1176,1,0,0,0,1219,1181,1,0,0,0,1219,1186, + 1,0,0,0,1219,1191,1,0,0,0,1219,1198,1,0,0,0,1219,1206,1,0,0,0,1220, + 137,1,0,0,0,1221,1222,5,70,0,0,1222,1223,3,144,72,0,1223,139,1,0, + 0,0,1224,1225,7,5,0,0,1225,141,1,0,0,0,1226,1227,7,6,0,0,1227,143, + 1,0,0,0,1228,1229,5,68,0,0,1229,1233,5,143,0,0,1230,1232,3,146,73, + 0,1231,1230,1,0,0,0,1232,1235,1,0,0,0,1233,1231,1,0,0,0,1233,1234, + 1,0,0,0,1234,1236,1,0,0,0,1235,1233,1,0,0,0,1236,1237,5,144,0,0, + 1237,145,1,0,0,0,1238,1239,3,168,84,0,1239,1240,5,62,0,0,1240,1241, + 5,11,0,0,1241,1242,5,25,0,0,1242,1243,5,145,0,0,1243,1244,3,90,45, + 0,1244,1245,5,146,0,0,1245,1246,5,140,0,0,1246,1337,1,0,0,0,1247, + 1248,3,168,84,0,1248,1249,5,62,0,0,1249,1250,5,65,0,0,1250,1257, + 3,168,84,0,1251,1252,5,71,0,0,1252,1253,5,66,0,0,1253,1254,3,168, + 84,0,1254,1255,5,142,0,0,1255,1256,3,168,84,0,1256,1258,1,0,0,0, + 1257,1251,1,0,0,0,1257,1258,1,0,0,0,1258,1259,1,0,0,0,1259,1260, + 5,140,0,0,1260,1337,1,0,0,0,1261,1262,3,168,84,0,1262,1263,5,62, + 0,0,1263,1264,5,66,0,0,1264,1265,3,168,84,0,1265,1266,5,142,0,0, + 1266,1273,3,168,84,0,1267,1268,5,71,0,0,1268,1269,5,66,0,0,1269, + 1270,3,168,84,0,1270,1271,5,142,0,0,1271,1272,3,168,84,0,1272,1274, + 1,0,0,0,1273,1267,1,0,0,0,1273,1274,1,0,0,0,1274,1275,1,0,0,0,1275, + 1276,5,140,0,0,1276,1337,1,0,0,0,1277,1278,3,168,84,0,1278,1279, + 5,62,0,0,1279,1280,5,47,0,0,1280,1282,3,168,84,0,1281,1283,3,30, + 15,0,1282,1281,1,0,0,0,1282,1283,1,0,0,0,1283,1292,1,0,0,0,1284, + 1285,5,71,0,0,1285,1286,5,66,0,0,1286,1287,3,168,84,0,1287,1288, + 5,142,0,0,1288,1289,3,168,84,0,1289,1293,1,0,0,0,1290,1291,5,20, + 0,0,1291,1293,5,57,0,0,1292,1284,1,0,0,0,1292,1290,1,0,0,0,1292, + 1293,1,0,0,0,1293,1294,1,0,0,0,1294,1295,5,140,0,0,1295,1337,1,0, + 0,0,1296,1297,3,168,84,0,1297,1298,5,62,0,0,1298,1299,5,7,0,0,1299, + 1300,3,168,84,0,1300,1301,5,68,0,0,1301,1305,5,143,0,0,1302,1304, + 3,124,62,0,1303,1302,1,0,0,0,1304,1307,1,0,0,0,1305,1303,1,0,0,0, + 1305,1306,1,0,0,0,1306,1308,1,0,0,0,1307,1305,1,0,0,0,1308,1309, + 5,144,0,0,1309,1310,5,140,0,0,1310,1337,1,0,0,0,1311,1312,3,168, + 84,0,1312,1313,5,62,0,0,1313,1314,5,55,0,0,1314,1315,3,168,84,0, + 1315,1316,5,140,0,0,1316,1337,1,0,0,0,1317,1318,3,168,84,0,1318, + 1320,5,62,0,0,1319,1321,5,11,0,0,1320,1319,1,0,0,0,1320,1321,1,0, + 0,0,1321,1322,1,0,0,0,1322,1323,5,24,0,0,1323,1324,3,168,84,0,1324, + 1325,5,142,0,0,1325,1332,3,168,84,0,1326,1327,5,71,0,0,1327,1328, + 5,66,0,0,1328,1329,3,168,84,0,1329,1330,5,142,0,0,1330,1331,3,168, + 84,0,1331,1333,1,0,0,0,1332,1326,1,0,0,0,1332,1333,1,0,0,0,1333, + 1334,1,0,0,0,1334,1335,5,140,0,0,1335,1337,1,0,0,0,1336,1238,1,0, + 0,0,1336,1247,1,0,0,0,1336,1261,1,0,0,0,1336,1277,1,0,0,0,1336,1296, + 1,0,0,0,1336,1311,1,0,0,0,1336,1317,1,0,0,0,1337,147,1,0,0,0,1338, + 1339,5,55,0,0,1339,1340,3,168,84,0,1340,1341,5,62,0,0,1341,1342, + 3,168,84,0,1342,1343,5,142,0,0,1343,1345,3,168,84,0,1344,1346,3, + 144,72,0,1345,1344,1,0,0,0,1345,1346,1,0,0,0,1346,1347,1,0,0,0,1347, + 1348,5,140,0,0,1348,149,1,0,0,0,1349,1407,3,156,78,0,1350,1407,5, + 119,0,0,1351,1407,5,120,0,0,1352,1353,5,121,0,0,1353,1407,3,170, + 85,0,1354,1355,5,122,0,0,1355,1356,5,149,0,0,1356,1357,3,168,84, + 0,1357,1358,5,150,0,0,1358,1407,1,0,0,0,1359,1360,5,123,0,0,1360, + 1361,5,149,0,0,1361,1363,3,168,84,0,1362,1364,3,30,15,0,1363,1362, + 1,0,0,0,1363,1364,1,0,0,0,1364,1365,1,0,0,0,1365,1366,5,150,0,0, + 1366,1407,1,0,0,0,1367,1368,5,41,0,0,1368,1369,5,149,0,0,1369,1370, + 3,168,84,0,1370,1371,5,150,0,0,1371,1407,1,0,0,0,1372,1373,5,124, + 0,0,1373,1374,5,149,0,0,1374,1375,3,150,75,0,1375,1376,5,150,0,0, + 1376,1407,1,0,0,0,1377,1378,5,125,0,0,1378,1379,5,149,0,0,1379,1380, + 3,150,75,0,1380,1381,5,150,0,0,1381,1407,1,0,0,0,1382,1383,5,126, + 0,0,1383,1387,5,143,0,0,1384,1386,3,152,76,0,1385,1384,1,0,0,0,1386, + 1389,1,0,0,0,1387,1385,1,0,0,0,1387,1388,1,0,0,0,1388,1390,1,0,0, + 0,1389,1387,1,0,0,0,1390,1407,5,144,0,0,1391,1392,5,8,0,0,1392,1393, + 5,90,0,0,1393,1394,3,170,85,0,1394,1396,5,143,0,0,1395,1397,3,154, + 77,0,1396,1395,1,0,0,0,1397,1398,1,0,0,0,1398,1396,1,0,0,0,1398, + 1399,1,0,0,0,1399,1400,1,0,0,0,1400,1401,5,144,0,0,1401,1407,1,0, + 0,0,1402,1404,3,168,84,0,1403,1405,3,30,15,0,1404,1403,1,0,0,0,1404, + 1405,1,0,0,0,1405,1407,1,0,0,0,1406,1349,1,0,0,0,1406,1350,1,0,0, + 0,1406,1351,1,0,0,0,1406,1352,1,0,0,0,1406,1354,1,0,0,0,1406,1359, + 1,0,0,0,1406,1367,1,0,0,0,1406,1372,1,0,0,0,1406,1377,1,0,0,0,1406, + 1382,1,0,0,0,1406,1391,1,0,0,0,1406,1402,1,0,0,0,1407,151,1,0,0, + 0,1408,1409,3,168,84,0,1409,1410,5,139,0,0,1410,1411,3,150,75,0, + 1411,1412,5,140,0,0,1412,153,1,0,0,0,1413,1414,3,168,84,0,1414,1415, + 5,90,0,0,1415,1416,3,170,85,0,1416,1417,5,139,0,0,1417,1418,3,150, + 75,0,1418,1419,5,140,0,0,1419,155,1,0,0,0,1420,1421,7,7,0,0,1421, + 157,1,0,0,0,1422,1423,7,8,0,0,1423,159,1,0,0,0,1424,1433,3,170,85, + 0,1425,1433,5,153,0,0,1426,1433,5,154,0,0,1427,1433,5,135,0,0,1428, + 1433,5,136,0,0,1429,1433,5,137,0,0,1430,1433,3,162,81,0,1431,1433, + 3,166,83,0,1432,1424,1,0,0,0,1432,1425,1,0,0,0,1432,1426,1,0,0,0, + 1432,1427,1,0,0,0,1432,1428,1,0,0,0,1432,1429,1,0,0,0,1432,1430, + 1,0,0,0,1432,1431,1,0,0,0,1433,161,1,0,0,0,1434,1443,5,143,0,0,1435, + 1440,3,164,82,0,1436,1437,5,141,0,0,1437,1439,3,164,82,0,1438,1436, + 1,0,0,0,1439,1442,1,0,0,0,1440,1438,1,0,0,0,1440,1441,1,0,0,0,1441, + 1444,1,0,0,0,1442,1440,1,0,0,0,1443,1435,1,0,0,0,1443,1444,1,0,0, + 0,1444,1445,1,0,0,0,1445,1446,5,144,0,0,1446,163,1,0,0,0,1447,1448, + 3,170,85,0,1448,1449,5,139,0,0,1449,1450,3,160,80,0,1450,165,1,0, + 0,0,1451,1460,5,145,0,0,1452,1457,3,160,80,0,1453,1454,5,141,0,0, + 1454,1456,3,160,80,0,1455,1453,1,0,0,0,1456,1459,1,0,0,0,1457,1455, + 1,0,0,0,1457,1458,1,0,0,0,1458,1461,1,0,0,0,1459,1457,1,0,0,0,1460, + 1452,1,0,0,0,1460,1461,1,0,0,0,1461,1462,1,0,0,0,1462,1463,5,146, + 0,0,1463,167,1,0,0,0,1464,1465,7,9,0,0,1465,169,1,0,0,0,1466,1467, + 5,156,0,0,1467,171,1,0,0,0,130,184,191,212,225,237,264,272,279,285, + 297,300,306,317,326,336,339,341,345,353,365,370,380,390,395,410, + 416,428,437,445,453,466,469,479,483,489,496,502,510,518,531,538, + 542,547,558,564,575,601,605,611,622,631,637,642,644,675,679,685, + 712,722,733,736,741,744,747,754,763,766,769,772,795,797,806,820, + 841,844,847,854,862,894,906,909,913,935,949,957,969,983,1000,1018, + 1022,1025,1029,1032,1038,1042,1047,1051,1055,1061,1075,1090,1110, + 1119,1151,1171,1201,1211,1214,1217,1219,1233,1257,1273,1282,1292, + 1305,1320,1332,1336,1345,1363,1387,1398,1404,1406,1432,1440,1443, + 1457,1460 ]; private static __ATN: antlr.ATN; @@ -4971,6 +6447,9 @@ export class WorkspaceItemContext extends antlr.ParserRuleContext { public constructorBindingDecl(): ConstructorBindingDeclContext | null { return this.getRuleContext(0, ConstructorBindingDeclContext); } + public presentationDecl(): PresentationDeclContext | null { + return this.getRuleContext(0, PresentationDeclContext); + } public typeAliasDecl(): TypeAliasDeclContext | null { return this.getRuleContext(0, TypeAliasDeclContext); } @@ -5089,6 +6568,38 @@ export class ExternalInterfaceDeclContext extends antlr.ParserRuleContext { } +export class SymbolDeclContext extends antlr.ParserRuleContext { + public constructor(parent: antlr.ParserRuleContext | null, invokingState: number) { + super(parent, invokingState); + } + public SYMBOL(): antlr.TerminalNode { + return this.getToken(QuixosCapabilityParser.SYMBOL, 0)!; + } + public identifier(): IdentifierContext { + return this.getRuleContext(0, IdentifierContext)!; + } + public ID(): antlr.TerminalNode { + return this.getToken(QuixosCapabilityParser.ID, 0)!; + } + public stringLiteral(): StringLiteralContext { + return this.getRuleContext(0, StringLiteralContext)!; + } + public SEMI(): antlr.TerminalNode { + return this.getToken(QuixosCapabilityParser.SEMI, 0)!; + } + public override get ruleIndex(): number { + return QuixosCapabilityParser.RULE_symbolDecl; + } + public override accept(visitor: QuixosCapabilityVisitor): Result | null { + if (visitor.visitSymbolDecl) { + return visitor.visitSymbolDecl(this); + } else { + return visitor.visitChildren(this); + } + } +} + + export class ResourcePreambleContext extends antlr.ParserRuleContext { public constructor(parent: antlr.ParserRuleContext | null, invokingState: number) { super(parent, invokingState); @@ -5102,6 +6613,9 @@ export class ResourcePreambleContext extends antlr.ParserRuleContext { public externalInterfaceDecl(): ExternalInterfaceDeclContext | null { return this.getRuleContext(0, ExternalInterfaceDeclContext); } + public symbolDecl(): SymbolDeclContext | null { + return this.getRuleContext(0, SymbolDeclContext); + } public typeAliasDecl(): TypeAliasDeclContext | null { return this.getRuleContext(0, TypeAliasDeclContext); } @@ -5486,6 +7000,9 @@ export class InterfaceMemberContext extends antlr.ParserRuleContext { public operationMember(): OperationMemberContext | null { return this.getRuleContext(0, OperationMemberContext); } + public moduleMember(): ModuleMemberContext | null { + return this.getRuleContext(0, ModuleMemberContext); + } public override get ruleIndex(): number { return QuixosCapabilityParser.RULE_interfaceMember; } @@ -5499,6 +7016,53 @@ export class InterfaceMemberContext extends antlr.ParserRuleContext { } +export class ModuleMemberContext extends antlr.ParserRuleContext { + public constructor(parent: antlr.ParserRuleContext | null, invokingState: number) { + super(parent, invokingState); + } + public MODULE(): antlr.TerminalNode { + return this.getToken(QuixosCapabilityParser.MODULE, 0)!; + } + public identifier(): IdentifierContext { + return this.getRuleContext(0, IdentifierContext)!; + } + public ID(): antlr.TerminalNode { + return this.getToken(QuixosCapabilityParser.ID, 0)!; + } + public stringLiteral(): StringLiteralContext[]; + public stringLiteral(i: number): StringLiteralContext | null; + public stringLiteral(i?: number): StringLiteralContext[] | StringLiteralContext | null { + if (i === undefined) { + return this.getRuleContexts(StringLiteralContext); + } + + return this.getRuleContext(i, StringLiteralContext); + } + public CONTRACT(): antlr.TerminalNode { + return this.getToken(QuixosCapabilityParser.CONTRACT, 0)!; + } + public SEMI(): antlr.TerminalNode { + return this.getToken(QuixosCapabilityParser.SEMI, 0)!; + } + public SUBJECT(): antlr.TerminalNode | null { + return this.getToken(QuixosCapabilityParser.SUBJECT, 0); + } + public targetConstraint(): TargetConstraintContext | null { + return this.getRuleContext(0, TargetConstraintContext); + } + public override get ruleIndex(): number { + return QuixosCapabilityParser.RULE_moduleMember; + } + public override accept(visitor: QuixosCapabilityVisitor): Result | null { + if (visitor.visitModuleMember) { + return visitor.visitModuleMember(this); + } else { + return visitor.visitChildren(this); + } + } +} + + export class OperationMemberContext extends antlr.ParserRuleContext { public constructor(parent: antlr.ParserRuleContext | null, invokingState: number) { super(parent, invokingState); @@ -5557,6 +7121,9 @@ export class OperationMemberContext extends antlr.ParserRuleContext { public STATIC(): antlr.TerminalNode | null { return this.getToken(QuixosCapabilityParser.STATIC, 0); } + public executionClause(): ExecutionClauseContext | null { + return this.getRuleContext(0, ExecutionClauseContext); + } public override get ruleIndex(): number { return QuixosCapabilityParser.RULE_operationMember; } @@ -5574,9 +7141,6 @@ export class ValueMemberContext extends antlr.ParserRuleContext { public constructor(parent: antlr.ParserRuleContext | null, invokingState: number) { super(parent, invokingState); } - public VALUE(): antlr.TerminalNode { - return this.getToken(QuixosCapabilityParser.VALUE, 0)!; - } public identifier(): IdentifierContext { return this.getRuleContext(0, IdentifierContext)!; } @@ -5598,6 +7162,12 @@ export class ValueMemberContext extends antlr.ParserRuleContext { public RBRACE(): antlr.TerminalNode { return this.getToken(QuixosCapabilityParser.RBRACE, 0)!; } + public VALUE(): antlr.TerminalNode | null { + return this.getToken(QuixosCapabilityParser.VALUE, 0); + } + public FIELD(): antlr.TerminalNode | null { + return this.getToken(QuixosCapabilityParser.FIELD, 0); + } public queryReadContract(): QueryReadContractContext | null { return this.getRuleContext(0, QueryReadContractContext); } @@ -5651,9 +7221,15 @@ export class ValueMemberOperationContext extends antlr.ParserRuleContext { public SEMI(): antlr.TerminalNode { return this.getToken(QuixosCapabilityParser.SEMI, 0)!; } + public executionClause(): ExecutionClauseContext | null { + return this.getRuleContext(0, ExecutionClauseContext); + } public SET(): antlr.TerminalNode | null { return this.getToken(QuixosCapabilityParser.SET, 0); } + public EDIT(): antlr.TerminalNode | null { + return this.getToken(QuixosCapabilityParser.EDIT, 0); + } public WATCH(): antlr.TerminalNode | null { return this.getToken(QuixosCapabilityParser.WATCH, 0); } @@ -5754,6 +7330,9 @@ export class QueryReadContractContext extends antlr.ParserRuleContext { public RPC(): antlr.TerminalNode | null { return this.getToken(QuixosCapabilityParser.RPC, 0); } + public PORTABLE(): antlr.TerminalNode | null { + return this.getToken(QuixosCapabilityParser.PORTABLE, 0); + } public override get ruleIndex(): number { return QuixosCapabilityParser.RULE_queryReadContract; } @@ -5795,6 +7374,9 @@ export class RelationshipOperationContext extends antlr.ParserRuleContext { public SEMI(): antlr.TerminalNode { return this.getToken(QuixosCapabilityParser.SEMI, 0)!; } + public executionClause(): ExecutionClauseContext | null { + return this.getRuleContext(0, ExecutionClauseContext); + } public CONNECT(): antlr.TerminalNode | null { return this.getToken(QuixosCapabilityParser.CONNECT, 0); } @@ -5942,6 +7524,9 @@ export class PackageExportContext extends antlr.ParserRuleContext { public packageQuerySpecialization(): PackageQuerySpecializationContext | null { return this.getRuleContext(0, PackageQuerySpecializationContext); } + public moduleExport(): ModuleExportContext | null { + return this.getRuleContext(0, ModuleExportContext); + } public override get ruleIndex(): number { return QuixosCapabilityParser.RULE_packageExport; } @@ -5955,6 +7540,103 @@ export class PackageExportContext extends antlr.ParserRuleContext { } +export class ModuleExportContext extends antlr.ParserRuleContext { + public constructor(parent: antlr.ParserRuleContext | null, invokingState: number) { + super(parent, invokingState); + } + public MODULE(): antlr.TerminalNode { + return this.getToken(QuixosCapabilityParser.MODULE, 0)!; + } + public identifier(): IdentifierContext[]; + public identifier(i: number): IdentifierContext | null; + public identifier(i?: number): IdentifierContext[] | IdentifierContext | null { + if (i === undefined) { + return this.getRuleContexts(IdentifierContext); + } + + return this.getRuleContext(i, IdentifierContext); + } + public ID(): antlr.TerminalNode { + return this.getToken(QuixosCapabilityParser.ID, 0)!; + } + public stringLiteral(): StringLiteralContext { + return this.getRuleContext(0, StringLiteralContext)!; + } + public IMPLEMENTS(): antlr.TerminalNode { + return this.getToken(QuixosCapabilityParser.IMPLEMENTS, 0)!; + } + public interfaceType(): InterfaceTypeContext { + return this.getRuleContext(0, InterfaceTypeContext)!; + } + public DOT(): antlr.TerminalNode { + return this.getToken(QuixosCapabilityParser.DOT, 0)!; + } + public SEMI(): antlr.TerminalNode { + return this.getToken(QuixosCapabilityParser.SEMI, 0)!; + } + public override get ruleIndex(): number { + return QuixosCapabilityParser.RULE_moduleExport; + } + public override accept(visitor: QuixosCapabilityVisitor): Result | null { + if (visitor.visitModuleExport) { + return visitor.visitModuleExport(this); + } else { + return visitor.visitChildren(this); + } + } +} + + +export class PresentationDeclContext extends antlr.ParserRuleContext { + public constructor(parent: antlr.ParserRuleContext | null, invokingState: number) { + super(parent, invokingState); + } + public PRESENTATION(): antlr.TerminalNode { + return this.getToken(QuixosCapabilityParser.PRESENTATION, 0)!; + } + public identifier(): IdentifierContext[]; + public identifier(i: number): IdentifierContext | null; + public identifier(i?: number): IdentifierContext[] | IdentifierContext | null { + if (i === undefined) { + return this.getRuleContexts(IdentifierContext); + } + + return this.getRuleContext(i, IdentifierContext); + } + public ID(): antlr.TerminalNode { + return this.getToken(QuixosCapabilityParser.ID, 0)!; + } + public stringLiteral(): StringLiteralContext { + return this.getRuleContext(0, StringLiteralContext)!; + } + public FOR(): antlr.TerminalNode { + return this.getToken(QuixosCapabilityParser.FOR, 0)!; + } + public interfaceType(): InterfaceTypeContext { + return this.getRuleContext(0, InterfaceTypeContext)!; + } + public USING(): antlr.TerminalNode { + return this.getToken(QuixosCapabilityParser.USING, 0)!; + } + public DOT(): antlr.TerminalNode { + return this.getToken(QuixosCapabilityParser.DOT, 0)!; + } + public SEMI(): antlr.TerminalNode { + return this.getToken(QuixosCapabilityParser.SEMI, 0)!; + } + public override get ruleIndex(): number { + return QuixosCapabilityParser.RULE_presentationDecl; + } + public override accept(visitor: QuixosCapabilityVisitor): Result | null { + if (visitor.visitPresentationDecl) { + return visitor.visitPresentationDecl(this); + } else { + return visitor.visitChildren(this); + } + } +} + + export class PackageQueryContext extends antlr.ParserRuleContext { public constructor(parent: antlr.ParserRuleContext | null, invokingState: number) { super(parent, invokingState); @@ -5986,18 +7668,33 @@ export class PackageQueryContext extends antlr.ParserRuleContext { public DOCUMENT(): antlr.TerminalNode { return this.getToken(QuixosCapabilityParser.DOCUMENT, 0)!; } - public OPERATION(): antlr.TerminalNode { - return this.getToken(QuixosCapabilityParser.OPERATION, 0)!; + public OPERATION(): antlr.TerminalNode | null { + return this.getToken(QuixosCapabilityParser.OPERATION, 0); } - public LBRACE(): antlr.TerminalNode { - return this.getToken(QuixosCapabilityParser.LBRACE, 0)!; + public LBRACE(): antlr.TerminalNode | null { + return this.getToken(QuixosCapabilityParser.LBRACE, 0); } - public RBRACE(): antlr.TerminalNode { - return this.getToken(QuixosCapabilityParser.RBRACE, 0)!; + public RBRACE(): antlr.TerminalNode | null { + return this.getToken(QuixosCapabilityParser.RBRACE, 0); + } + public FRAGMENT(): antlr.TerminalNode[]; + public FRAGMENT(i: number): antlr.TerminalNode | null; + public FRAGMENT(i?: number): antlr.TerminalNode | null | antlr.TerminalNode[] { + if (i === undefined) { + return this.getTokens(QuixosCapabilityParser.FRAGMENT); + } else { + return this.getToken(QuixosCapabilityParser.FRAGMENT, i); + } } public typeParameters(): TypeParametersContext | null { return this.getRuleContext(0, TypeParametersContext); } + public WORKSPACE(): antlr.TerminalNode | null { + return this.getToken(QuixosCapabilityParser.WORKSPACE, 0); + } + public SEMI(): antlr.TerminalNode | null { + return this.getToken(QuixosCapabilityParser.SEMI, 0); + } public queryClause(): QueryClauseContext[]; public queryClause(i: number): QueryClauseContext | null; public queryClause(i?: number): QueryClauseContext[] | QueryClauseContext | null { @@ -6068,17 +7765,17 @@ export class QueryClauseContext extends antlr.ParserRuleContext { public constructor(parent: antlr.ParserRuleContext | null, invokingState: number) { super(parent, invokingState); } - public FRAGMENTS(): antlr.TerminalNode | null { - return this.getToken(QuixosCapabilityParser.FRAGMENTS, 0); - } - public stringLiteral(): StringLiteralContext | null { - return this.getRuleContext(0, StringLiteralContext); + public REPLICA(): antlr.TerminalNode | null { + return this.getToken(QuixosCapabilityParser.REPLICA, 0); } public SEMI(): antlr.TerminalNode { return this.getToken(QuixosCapabilityParser.SEMI, 0)!; } - public VIEW(): antlr.TerminalNode | null { - return this.getToken(QuixosCapabilityParser.VIEW, 0); + public LOCAL(): antlr.TerminalNode | null { + return this.getToken(QuixosCapabilityParser.LOCAL, 0); + } + public FROM(): antlr.TerminalNode | null { + return this.getToken(QuixosCapabilityParser.FROM, 0); } public identifier(): IdentifierContext[]; public identifier(i: number): IdentifierContext | null; @@ -6089,6 +7786,36 @@ export class QueryClauseContext extends antlr.ParserRuleContext { return this.getRuleContext(i, IdentifierContext); } + public FRAGMENTS(): antlr.TerminalNode | null { + return this.getToken(QuixosCapabilityParser.FRAGMENTS, 0); + } + public stringLiteral(): StringLiteralContext | null { + return this.getRuleContext(0, StringLiteralContext); + } + public DOT(): antlr.TerminalNode | null { + return this.getToken(QuixosCapabilityParser.DOT, 0); + } + public WITH(): antlr.TerminalNode | null { + return this.getToken(QuixosCapabilityParser.WITH, 0); + } + public LBRACE(): antlr.TerminalNode | null { + return this.getToken(QuixosCapabilityParser.LBRACE, 0); + } + public RBRACE(): antlr.TerminalNode | null { + return this.getToken(QuixosCapabilityParser.RBRACE, 0); + } + public fragmentVariable(): FragmentVariableContext[]; + public fragmentVariable(i: number): FragmentVariableContext | null; + public fragmentVariable(i?: number): FragmentVariableContext[] | FragmentVariableContext | null { + if (i === undefined) { + return this.getRuleContexts(FragmentVariableContext); + } + + return this.getRuleContext(i, FragmentVariableContext); + } + public VIEW(): antlr.TerminalNode | null { + return this.getToken(QuixosCapabilityParser.VIEW, 0); + } public AS(): antlr.TerminalNode | null { return this.getToken(QuixosCapabilityParser.AS, 0); } @@ -6107,9 +7834,6 @@ export class QueryClauseContext extends antlr.ParserRuleContext { public ALLOW(): antlr.TerminalNode | null { return this.getToken(QuixosCapabilityParser.ALLOW, 0); } - public DOT(): antlr.TerminalNode | null { - return this.getToken(QuixosCapabilityParser.DOT, 0); - } public WATCH(): antlr.TerminalNode | null { return this.getToken(QuixosCapabilityParser.WATCH, 0); } @@ -6129,6 +7853,38 @@ export class QueryClauseContext extends antlr.ParserRuleContext { } +export class FragmentVariableContext extends antlr.ParserRuleContext { + public constructor(parent: antlr.ParserRuleContext | null, invokingState: number) { + super(parent, invokingState); + } + public identifier(): IdentifierContext[]; + public identifier(i: number): IdentifierContext | null; + public identifier(i?: number): IdentifierContext[] | IdentifierContext | null { + if (i === undefined) { + return this.getRuleContexts(IdentifierContext); + } + + return this.getRuleContext(i, IdentifierContext); + } + public TO(): antlr.TerminalNode { + return this.getToken(QuixosCapabilityParser.TO, 0)!; + } + public SEMI(): antlr.TerminalNode { + return this.getToken(QuixosCapabilityParser.SEMI, 0)!; + } + public override get ruleIndex(): number { + return QuixosCapabilityParser.RULE_fragmentVariable; + } + public override accept(visitor: QuixosCapabilityVisitor): Result | null { + if (visitor.visitFragmentVariable) { + return visitor.visitFragmentVariable(this); + } else { + return visitor.visitChildren(this); + } + } +} + + export class PackageOperationExportContext extends antlr.ParserRuleContext { public constructor(parent: antlr.ParserRuleContext | null, invokingState: number) { super(parent, invokingState); @@ -6178,12 +7934,21 @@ export class PackageOperationExportContext extends antlr.ParserRuleContext { public typeParameters(): TypeParametersContext | null { return this.getRuleContext(0, TypeParametersContext); } + public executionClause(): ExecutionClauseContext | null { + return this.getRuleContext(0, ExecutionClauseContext); + } public eventClause(): EventClauseContext | null { return this.getRuleContext(0, EventClauseContext); } public dependencyBlock(): DependencyBlockContext | null { return this.getRuleContext(0, DependencyBlockContext); } + public preparationClause(): PreparationClauseContext | null { + return this.getRuleContext(0, PreparationClauseContext); + } + public acceptanceClause(): AcceptanceClauseContext | null { + return this.getRuleContext(0, AcceptanceClauseContext); + } public override get ruleIndex(): number { return QuixosCapabilityParser.RULE_packageOperationExport; } @@ -6234,9 +7999,18 @@ export class PackageFunctionExportContext extends antlr.ParserRuleContext { public typeParameters(): TypeParametersContext | null { return this.getRuleContext(0, TypeParametersContext); } + public executionClause(): ExecutionClauseContext | null { + return this.getRuleContext(0, ExecutionClauseContext); + } public dependencyBlock(): DependencyBlockContext | null { return this.getRuleContext(0, DependencyBlockContext); } + public preparationClause(): PreparationClauseContext | null { + return this.getRuleContext(0, PreparationClauseContext); + } + public acceptanceClause(): AcceptanceClauseContext | null { + return this.getRuleContext(0, AcceptanceClauseContext); + } public override get ruleIndex(): number { return QuixosCapabilityParser.RULE_packageFunctionExport; } @@ -6250,6 +8024,149 @@ export class PackageFunctionExportContext extends antlr.ParserRuleContext { } +export class AcceptanceClauseContext extends antlr.ParserRuleContext { + public constructor(parent: antlr.ParserRuleContext | null, invokingState: number) { + super(parent, invokingState); + } + public ACCEPT(): antlr.TerminalNode { + return this.getToken(QuixosCapabilityParser.ACCEPT, 0)!; + } + public USING(): antlr.TerminalNode { + return this.getToken(QuixosCapabilityParser.USING, 0)!; + } + public identifier(): IdentifierContext { + return this.getRuleContext(0, IdentifierContext)!; + } + public override get ruleIndex(): number { + return QuixosCapabilityParser.RULE_acceptanceClause; + } + public override accept(visitor: QuixosCapabilityVisitor): Result | null { + if (visitor.visitAcceptanceClause) { + return visitor.visitAcceptanceClause(this); + } else { + return visitor.visitChildren(this); + } + } +} + + +export class PreparationClauseContext extends antlr.ParserRuleContext { + public constructor(parent: antlr.ParserRuleContext | null, invokingState: number) { + super(parent, invokingState); + } + public PREPARES(): antlr.TerminalNode { + return this.getToken(QuixosCapabilityParser.PREPARES, 0)!; + } + public CREATION(): antlr.TerminalNode { + return this.getToken(QuixosCapabilityParser.CREATION, 0)!; + } + public identifier(): IdentifierContext { + return this.getRuleContext(0, IdentifierContext)!; + } + public USING(): antlr.TerminalNode { + return this.getToken(QuixosCapabilityParser.USING, 0)!; + } + public stringLiteral(): StringLiteralContext { + return this.getRuleContext(0, StringLiteralContext)!; + } + public override get ruleIndex(): number { + return QuixosCapabilityParser.RULE_preparationClause; + } + public override accept(visitor: QuixosCapabilityVisitor): Result | null { + if (visitor.visitPreparationClause) { + return visitor.visitPreparationClause(this); + } else { + return visitor.visitChildren(this); + } + } +} + + +export class ExecutionClauseContext extends antlr.ParserRuleContext { + public constructor(parent: antlr.ParserRuleContext | null, invokingState: number) { + super(parent, invokingState); + } + public EXECUTION(): antlr.TerminalNode { + return this.getToken(QuixosCapabilityParser.EXECUTION, 0)!; + } + public REMOTE(): antlr.TerminalNode | null { + return this.getToken(QuixosCapabilityParser.REMOTE, 0); + } + public SNAPSHOT(): antlr.TerminalNode | null { + return this.getToken(QuixosCapabilityParser.SNAPSHOT, 0); + } + public TRANSACTION(): antlr.TerminalNode | null { + return this.getToken(QuixosCapabilityParser.TRANSACTION, 0); + } + public TARGETS(): antlr.TerminalNode | null { + return this.getToken(QuixosCapabilityParser.TARGETS, 0); + } + public LBRACK(): antlr.TerminalNode | null { + return this.getToken(QuixosCapabilityParser.LBRACK, 0); + } + public identifierList(): IdentifierListContext | null { + return this.getRuleContext(0, IdentifierListContext); + } + public RBRACK(): antlr.TerminalNode | null { + return this.getToken(QuixosCapabilityParser.RBRACK, 0); + } + public override get ruleIndex(): number { + return QuixosCapabilityParser.RULE_executionClause; + } + public override accept(visitor: QuixosCapabilityVisitor): Result | null { + if (visitor.visitExecutionClause) { + return visitor.visitExecutionClause(this); + } else { + return visitor.visitChildren(this); + } + } +} + + +export class SelectedOperationsContext extends antlr.ParserRuleContext { + public constructor(parent: antlr.ParserRuleContext | null, invokingState: number) { + super(parent, invokingState); + } + public USING(): antlr.TerminalNode { + return this.getToken(QuixosCapabilityParser.USING, 0)!; + } + public LBRACK(): antlr.TerminalNode { + return this.getToken(QuixosCapabilityParser.LBRACK, 0)!; + } + public memberOperationRef(): MemberOperationRefContext[]; + public memberOperationRef(i: number): MemberOperationRefContext | null; + public memberOperationRef(i?: number): MemberOperationRefContext[] | MemberOperationRefContext | null { + if (i === undefined) { + return this.getRuleContexts(MemberOperationRefContext); + } + + return this.getRuleContext(i, MemberOperationRefContext); + } + public RBRACK(): antlr.TerminalNode { + return this.getToken(QuixosCapabilityParser.RBRACK, 0)!; + } + public COMMA(): antlr.TerminalNode[]; + public COMMA(i: number): antlr.TerminalNode | null; + public COMMA(i?: number): antlr.TerminalNode | null | antlr.TerminalNode[] { + if (i === undefined) { + return this.getTokens(QuixosCapabilityParser.COMMA); + } else { + return this.getToken(QuixosCapabilityParser.COMMA, i); + } + } + public override get ruleIndex(): number { + return QuixosCapabilityParser.RULE_selectedOperations; + } + public override accept(visitor: QuixosCapabilityVisitor): Result | null { + if (visitor.visitSelectedOperations) { + return visitor.visitSelectedOperations(this); + } else { + return visitor.visitChildren(this); + } + } +} + + export class PackageConstructorExportContext extends antlr.ParserRuleContext { public constructor(parent: antlr.ParserRuleContext | null, invokingState: number) { super(parent, invokingState); @@ -6485,8 +8402,8 @@ export class DependencyPortContext extends antlr.ParserRuleContext { public constructor(parent: antlr.ParserRuleContext | null, invokingState: number) { super(parent, invokingState); } - public STATE(): antlr.TerminalNode | null { - return this.getToken(QuixosCapabilityParser.STATE, 0); + public REFLECTION(): antlr.TerminalNode | null { + return this.getToken(QuixosCapabilityParser.REFLECTION, 0); } public identifier(): IdentifierContext[]; public identifier(i: number): IdentifierContext | null; @@ -6500,11 +8417,32 @@ export class DependencyPortContext extends antlr.ParserRuleContext { public ID(): antlr.TerminalNode { return this.getToken(QuixosCapabilityParser.ID, 0)!; } - public stringLiteral(): StringLiteralContext { - return this.getRuleContext(0, StringLiteralContext)!; + public stringLiteral(): StringLiteralContext[]; + public stringLiteral(i: number): StringLiteralContext | null; + public stringLiteral(i?: number): StringLiteralContext[] | StringLiteralContext | null { + if (i === undefined) { + return this.getRuleContexts(StringLiteralContext); + } + + return this.getRuleContext(i, StringLiteralContext); } - public COLON(): antlr.TerminalNode { - return this.getToken(QuixosCapabilityParser.COLON, 0)!; + public LBRACK(): antlr.TerminalNode | null { + return this.getToken(QuixosCapabilityParser.LBRACK, 0); + } + public identifierList(): IdentifierListContext | null { + return this.getRuleContext(0, IdentifierListContext); + } + public RBRACK(): antlr.TerminalNode | null { + return this.getToken(QuixosCapabilityParser.RBRACK, 0); + } + public SEMI(): antlr.TerminalNode { + return this.getToken(QuixosCapabilityParser.SEMI, 0)!; + } + public STATE(): antlr.TerminalNode | null { + return this.getToken(QuixosCapabilityParser.STATE, 0); + } + public COLON(): antlr.TerminalNode | null { + return this.getToken(QuixosCapabilityParser.COLON, 0); } public valueType(): ValueTypeContext | null { return this.getRuleContext(0, ValueTypeContext); @@ -6512,9 +8450,6 @@ export class DependencyPortContext extends antlr.ParserRuleContext { public primitiveList(): PrimitiveListContext | null { return this.getRuleContext(0, PrimitiveListContext); } - public SEMI(): antlr.TerminalNode { - return this.getToken(QuixosCapabilityParser.SEMI, 0)!; - } public EDGE(): antlr.TerminalNode | null { return this.getToken(QuixosCapabilityParser.EDGE, 0); } @@ -6524,18 +8459,30 @@ export class DependencyPortContext extends antlr.ParserRuleContext { public targetConstraint(): TargetConstraintContext | null { return this.getRuleContext(0, TargetConstraintContext); } + public KEYED(): antlr.TerminalNode | null { + return this.getToken(QuixosCapabilityParser.KEYED, 0); + } public INTERFACE(): antlr.TerminalNode | null { return this.getToken(QuixosCapabilityParser.INTERFACE, 0); } public typeArguments(): TypeArgumentsContext | null { return this.getRuleContext(0, TypeArgumentsContext); } - public CONSTRUCTOR(): antlr.TerminalNode | null { - return this.getToken(QuixosCapabilityParser.CONSTRUCTOR, 0); + public selectedOperations(): SelectedOperationsContext | null { + return this.getRuleContext(0, SelectedOperationsContext); + } + public FROM(): antlr.TerminalNode | null { + return this.getToken(QuixosCapabilityParser.FROM, 0); } public INPUT(): antlr.TerminalNode | null { return this.getToken(QuixosCapabilityParser.INPUT, 0); } + public CREATION(): antlr.TerminalNode | null { + return this.getToken(QuixosCapabilityParser.CREATION, 0); + } + public CONSTRUCTOR(): antlr.TerminalNode | null { + return this.getToken(QuixosCapabilityParser.CONSTRUCTOR, 0); + } public QUERY(): antlr.TerminalNode | null { return this.getToken(QuixosCapabilityParser.QUERY, 0); } @@ -6606,6 +8553,9 @@ export class PrimitiveContext extends antlr.ParserRuleContext { public WRITE(): antlr.TerminalNode | null { return this.getToken(QuixosCapabilityParser.WRITE, 0); } + public EDIT(): antlr.TerminalNode | null { + return this.getToken(QuixosCapabilityParser.EDIT, 0); + } public RESOLVE(): antlr.TerminalNode | null { return this.getToken(QuixosCapabilityParser.RESOLVE, 0); } @@ -6743,8 +8693,8 @@ export class StoragePolicyContext extends antlr.ParserRuleContext { public constructor(parent: antlr.ParserRuleContext | null, invokingState: number) { super(parent, invokingState); } - public OPTIMISTIC_REGISTER(): antlr.TerminalNode | null { - return this.getToken(QuixosCapabilityParser.OPTIMISTIC_REGISTER, 0); + public REGISTER(): antlr.TerminalNode | null { + return this.getToken(QuixosCapabilityParser.REGISTER, 0); } public CRDT(): antlr.TerminalNode | null { return this.getToken(QuixosCapabilityParser.CRDT, 0); @@ -6758,6 +8708,15 @@ export class StoragePolicyContext extends antlr.ParserRuleContext { public RPAREN(): antlr.TerminalNode | null { return this.getToken(QuixosCapabilityParser.RPAREN, 0); } + public JOURNAL(): antlr.TerminalNode | null { + return this.getToken(QuixosCapabilityParser.JOURNAL, 0); + } + public jsonObject(): JsonObjectContext | null { + return this.getRuleContext(0, JsonObjectContext); + } + public SUBSCRIPTION(): antlr.TerminalNode | null { + return this.getToken(QuixosCapabilityParser.SUBSCRIPTION, 0); + } public override get ruleIndex(): number { return QuixosCapabilityParser.RULE_storagePolicy; } @@ -6899,6 +8858,12 @@ export class ConformanceDeclContext extends antlr.ParserRuleContext { public RBRACE(): antlr.TerminalNode { return this.getToken(QuixosCapabilityParser.RBRACE, 0)!; } + public typeParameters(): TypeParametersContext | null { + return this.getRuleContext(0, TypeParametersContext); + } + public conformanceSourceArguments(): ConformanceSourceArgumentsContext | null { + return this.getRuleContext(0, ConformanceSourceArgumentsContext); + } public typeArguments(): TypeArgumentsContext | null { return this.getRuleContext(0, TypeArgumentsContext); } @@ -6936,6 +8901,26 @@ export class ConformanceDeclContext extends antlr.ParserRuleContext { } +export class ConformanceSourceArgumentsContext extends antlr.ParserRuleContext { + public constructor(parent: antlr.ParserRuleContext | null, invokingState: number) { + super(parent, invokingState); + } + public typeArguments(): TypeArgumentsContext { + return this.getRuleContext(0, TypeArgumentsContext)!; + } + public override get ruleIndex(): number { + return QuixosCapabilityParser.RULE_conformanceSourceArguments; + } + public override accept(visitor: QuixosCapabilityVisitor): Result | null { + if (visitor.visitConformanceSourceArguments) { + return visitor.visitConformanceSourceArguments(this); + } else { + return visitor.visitChildren(this); + } + } +} + + export class ConformanceItemContext extends antlr.ParserRuleContext { public constructor(parent: antlr.ParserRuleContext | null, invokingState: number) { super(parent, invokingState); @@ -6952,9 +8937,15 @@ export class ConformanceItemContext extends antlr.ParserRuleContext { public stateFieldBindingDecl(): StateFieldBindingDeclContext | null { return this.getRuleContext(0, StateFieldBindingDeclContext); } + public initializationDecl(): InitializationDeclContext | null { + return this.getRuleContext(0, InitializationDeclContext); + } public relationshipMaterializationDecl(): RelationshipMaterializationDeclContext | null { return this.getRuleContext(0, RelationshipMaterializationDeclContext); } + public moduleBindingDecl(): ModuleBindingDeclContext | null { + return this.getRuleContext(0, ModuleBindingDeclContext); + } public override get ruleIndex(): number { return QuixosCapabilityParser.RULE_conformanceItem; } @@ -6968,6 +8959,170 @@ export class ConformanceItemContext extends antlr.ParserRuleContext { } +export class ModuleBindingDeclContext extends antlr.ParserRuleContext { + public constructor(parent: antlr.ParserRuleContext | null, invokingState: number) { + super(parent, invokingState); + } + public BIND(): antlr.TerminalNode { + return this.getToken(QuixosCapabilityParser.BIND, 0)!; + } + public identifier(): IdentifierContext[]; + public identifier(i: number): IdentifierContext | null; + public identifier(i?: number): IdentifierContext[] | IdentifierContext | null { + if (i === undefined) { + return this.getRuleContexts(IdentifierContext); + } + + return this.getRuleContext(i, IdentifierContext); + } + public TO(): antlr.TerminalNode { + return this.getToken(QuixosCapabilityParser.TO, 0)!; + } + public DOT(): antlr.TerminalNode { + return this.getToken(QuixosCapabilityParser.DOT, 0)!; + } + public SEMI(): antlr.TerminalNode { + return this.getToken(QuixosCapabilityParser.SEMI, 0)!; + } + public override get ruleIndex(): number { + return QuixosCapabilityParser.RULE_moduleBindingDecl; + } + public override accept(visitor: QuixosCapabilityVisitor): Result | null { + if (visitor.visitModuleBindingDecl) { + return visitor.visitModuleBindingDecl(this); + } else { + return visitor.visitChildren(this); + } + } +} + + +export class InitializationDeclContext extends antlr.ParserRuleContext { + public constructor(parent: antlr.ParserRuleContext | null, invokingState: number) { + super(parent, invokingState); + } + public INITIALIZE(): antlr.TerminalNode { + return this.getToken(QuixosCapabilityParser.INITIALIZE, 0)!; + } + public LBRACE(): antlr.TerminalNode { + return this.getToken(QuixosCapabilityParser.LBRACE, 0)!; + } + public RBRACE(): antlr.TerminalNode { + return this.getToken(QuixosCapabilityParser.RBRACE, 0)!; + } + public SEMI(): antlr.TerminalNode { + return this.getToken(QuixosCapabilityParser.SEMI, 0)!; + } + public initializationField(): InitializationFieldContext[]; + public initializationField(i: number): InitializationFieldContext | null; + public initializationField(i?: number): InitializationFieldContext[] | InitializationFieldContext | null { + if (i === undefined) { + return this.getRuleContexts(InitializationFieldContext); + } + + return this.getRuleContext(i, InitializationFieldContext); + } + public override get ruleIndex(): number { + return QuixosCapabilityParser.RULE_initializationDecl; + } + public override accept(visitor: QuixosCapabilityVisitor): Result | null { + if (visitor.visitInitializationDecl) { + return visitor.visitInitializationDecl(this); + } else { + return visitor.visitChildren(this); + } + } +} + + +export class InitializationFieldContext extends antlr.ParserRuleContext { + public constructor(parent: antlr.ParserRuleContext | null, invokingState: number) { + super(parent, invokingState); + } + public identifier(): IdentifierContext[]; + public identifier(i: number): IdentifierContext | null; + public identifier(i?: number): IdentifierContext[] | IdentifierContext | null { + if (i === undefined) { + return this.getRuleContexts(IdentifierContext); + } + + return this.getRuleContext(i, IdentifierContext); + } + public TO(): antlr.TerminalNode { + return this.getToken(QuixosCapabilityParser.TO, 0)!; + } + public STATE(): antlr.TerminalNode | null { + return this.getToken(QuixosCapabilityParser.STATE, 0); + } + public SEMI(): antlr.TerminalNode { + return this.getToken(QuixosCapabilityParser.SEMI, 0)!; + } + public EDGE(): antlr.TerminalNode | null { + return this.getToken(QuixosCapabilityParser.EDGE, 0); + } + public DOT(): antlr.TerminalNode | null { + return this.getToken(QuixosCapabilityParser.DOT, 0); + } + public override get ruleIndex(): number { + return QuixosCapabilityParser.RULE_initializationField; + } + public override accept(visitor: QuixosCapabilityVisitor): Result | null { + if (visitor.visitInitializationField) { + return visitor.visitInitializationField(this); + } else { + return visitor.visitChildren(this); + } + } +} + + +export class CreationOwnerMappingContext extends antlr.ParserRuleContext { + public constructor(parent: antlr.ParserRuleContext | null, invokingState: number) { + super(parent, invokingState); + } + public interfaceType(): InterfaceTypeContext { + return this.getRuleContext(0, InterfaceTypeContext)!; + } + public FROM(): antlr.TerminalNode { + return this.getToken(QuixosCapabilityParser.FROM, 0)!; + } + public INPUT(): antlr.TerminalNode { + return this.getToken(QuixosCapabilityParser.INPUT, 0)!; + } + public SEMI(): antlr.TerminalNode { + return this.getToken(QuixosCapabilityParser.SEMI, 0)!; + } + public DOT(): antlr.TerminalNode[]; + public DOT(i: number): antlr.TerminalNode | null; + public DOT(i?: number): antlr.TerminalNode | null | antlr.TerminalNode[] { + if (i === undefined) { + return this.getTokens(QuixosCapabilityParser.DOT); + } else { + return this.getToken(QuixosCapabilityParser.DOT, i); + } + } + public identifier(): IdentifierContext[]; + public identifier(i: number): IdentifierContext | null; + public identifier(i?: number): IdentifierContext[] | IdentifierContext | null { + if (i === undefined) { + return this.getRuleContexts(IdentifierContext); + } + + return this.getRuleContext(i, IdentifierContext); + } + public override get ruleIndex(): number { + return QuixosCapabilityParser.RULE_creationOwnerMapping; + } + public override accept(visitor: QuixosCapabilityVisitor): Result | null { + if (visitor.visitCreationOwnerMapping) { + return visitor.visitCreationOwnerMapping(this); + } else { + return visitor.visitChildren(this); + } + } +} + + export class StateFieldBindingDeclContext extends antlr.ParserRuleContext { public constructor(parent: antlr.ParserRuleContext | null, invokingState: number) { super(parent, invokingState); @@ -7139,6 +9294,9 @@ export class OperationNameContext extends antlr.ParserRuleContext { public SET(): antlr.TerminalNode | null { return this.getToken(QuixosCapabilityParser.SET, 0); } + public EDIT(): antlr.TerminalNode | null { + return this.getToken(QuixosCapabilityParser.EDIT, 0); + } public RESOLVE(): antlr.TerminalNode | null { return this.getToken(QuixosCapabilityParser.RESOLVE, 0); } @@ -7177,17 +9335,8 @@ export class OperationProviderContext extends antlr.ParserRuleContext { public constructor(parent: antlr.ParserRuleContext | null, invokingState: number) { super(parent, invokingState); } - public STATE(): antlr.TerminalNode | null { - return this.getToken(QuixosCapabilityParser.STATE, 0); - } - public identifier(): IdentifierContext[]; - public identifier(i: number): IdentifierContext | null; - public identifier(i?: number): IdentifierContext[] | IdentifierContext | null { - if (i === undefined) { - return this.getRuleContexts(IdentifierContext); - } - - return this.getRuleContext(i, IdentifierContext); + public LIFECYCLE(): antlr.TerminalNode | null { + return this.getToken(QuixosCapabilityParser.LIFECYCLE, 0); } public DOT(): antlr.TerminalNode[]; public DOT(i: number): antlr.TerminalNode | null; @@ -7198,6 +9347,24 @@ export class OperationProviderContext extends antlr.ParserRuleContext { return this.getToken(QuixosCapabilityParser.DOT, i); } } + public identifier(): IdentifierContext[]; + public identifier(i: number): IdentifierContext | null; + public identifier(i?: number): IdentifierContext[] | IdentifierContext | null { + if (i === undefined) { + return this.getRuleContexts(IdentifierContext); + } + + return this.getRuleContext(i, IdentifierContext); + } + public JOURNAL(): antlr.TerminalNode | null { + return this.getToken(QuixosCapabilityParser.JOURNAL, 0); + } + public SUBSCRIPTION(): antlr.TerminalNode | null { + return this.getToken(QuixosCapabilityParser.SUBSCRIPTION, 0); + } + public STATE(): antlr.TerminalNode | null { + return this.getToken(QuixosCapabilityParser.STATE, 0); + } public statePrimitive(): StatePrimitiveContext | null { return this.getRuleContext(0, StatePrimitiveContext); } @@ -7207,15 +9374,24 @@ export class OperationProviderContext extends antlr.ParserRuleContext { public edgePrimitive(): EdgePrimitiveContext | null { return this.getRuleContext(0, EdgePrimitiveContext); } - public PACKAGE(): antlr.TerminalNode | null { - return this.getToken(QuixosCapabilityParser.PACKAGE, 0); + public INTERFACE(): antlr.TerminalNode | null { + return this.getToken(QuixosCapabilityParser.INTERFACE, 0); + } + public memberOperationRef(): MemberOperationRefContext | null { + return this.getRuleContext(0, MemberOperationRefContext); } public typeArguments(): TypeArgumentsContext | null { return this.getRuleContext(0, TypeArgumentsContext); } + public PACKAGE(): antlr.TerminalNode | null { + return this.getToken(QuixosCapabilityParser.PACKAGE, 0); + } public dependencyBindingBlock(): DependencyBindingBlockContext | null { return this.getRuleContext(0, DependencyBindingBlockContext); } + public acceptanceBinding(): AcceptanceBindingContext | null { + return this.getRuleContext(0, AcceptanceBindingContext); + } public override get ruleIndex(): number { return QuixosCapabilityParser.RULE_operationProvider; } @@ -7229,6 +9405,29 @@ export class OperationProviderContext extends antlr.ParserRuleContext { } +export class AcceptanceBindingContext extends antlr.ParserRuleContext { + public constructor(parent: antlr.ParserRuleContext | null, invokingState: number) { + super(parent, invokingState); + } + public ACCEPT(): antlr.TerminalNode { + return this.getToken(QuixosCapabilityParser.ACCEPT, 0)!; + } + public dependencyBindingBlock(): DependencyBindingBlockContext { + return this.getRuleContext(0, DependencyBindingBlockContext)!; + } + public override get ruleIndex(): number { + return QuixosCapabilityParser.RULE_acceptanceBinding; + } + public override accept(visitor: QuixosCapabilityVisitor): Result | null { + if (visitor.visitAcceptanceBinding) { + return visitor.visitAcceptanceBinding(this); + } else { + return visitor.visitChildren(this); + } + } +} + + export class StatePrimitiveContext extends antlr.ParserRuleContext { public constructor(parent: antlr.ParserRuleContext | null, invokingState: number) { super(parent, invokingState); @@ -7239,6 +9438,9 @@ export class StatePrimitiveContext extends antlr.ParserRuleContext { public WRITE(): antlr.TerminalNode | null { return this.getToken(QuixosCapabilityParser.WRITE, 0); } + public EDIT(): antlr.TerminalNode | null { + return this.getToken(QuixosCapabilityParser.EDIT, 0); + } public WATCH_START(): antlr.TerminalNode | null { return this.getToken(QuixosCapabilityParser.WATCH_START, 0); } @@ -7341,12 +9543,27 @@ export class DependencyBindingContext extends antlr.ParserRuleContext { public TO(): antlr.TerminalNode { return this.getToken(QuixosCapabilityParser.TO, 0)!; } - public STATE(): antlr.TerminalNode | null { - return this.getToken(QuixosCapabilityParser.STATE, 0); + public WORKSPACE(): antlr.TerminalNode | null { + return this.getToken(QuixosCapabilityParser.WORKSPACE, 0); + } + public REFLECTION(): antlr.TerminalNode | null { + return this.getToken(QuixosCapabilityParser.REFLECTION, 0); + } + public LBRACK(): antlr.TerminalNode | null { + return this.getToken(QuixosCapabilityParser.LBRACK, 0); + } + public identifierList(): IdentifierListContext | null { + return this.getRuleContext(0, IdentifierListContext); + } + public RBRACK(): antlr.TerminalNode | null { + return this.getToken(QuixosCapabilityParser.RBRACK, 0); } public SEMI(): antlr.TerminalNode { return this.getToken(QuixosCapabilityParser.SEMI, 0)!; } + public STATE(): antlr.TerminalNode | null { + return this.getToken(QuixosCapabilityParser.STATE, 0); + } public VIA(): antlr.TerminalNode | null { return this.getToken(QuixosCapabilityParser.VIA, 0); } @@ -7374,6 +9591,33 @@ export class DependencyBindingContext extends antlr.ParserRuleContext { public typeArguments(): TypeArgumentsContext | null { return this.getRuleContext(0, TypeArgumentsContext); } + public FROM(): antlr.TerminalNode | null { + return this.getToken(QuixosCapabilityParser.FROM, 0); + } + public INPUT(): antlr.TerminalNode | null { + return this.getToken(QuixosCapabilityParser.INPUT, 0); + } + public CREATION(): antlr.TerminalNode | null { + return this.getToken(QuixosCapabilityParser.CREATION, 0); + } + public WITH(): antlr.TerminalNode | null { + return this.getToken(QuixosCapabilityParser.WITH, 0); + } + public LBRACE(): antlr.TerminalNode | null { + return this.getToken(QuixosCapabilityParser.LBRACE, 0); + } + public RBRACE(): antlr.TerminalNode | null { + return this.getToken(QuixosCapabilityParser.RBRACE, 0); + } + public creationOwnerMapping(): CreationOwnerMappingContext[]; + public creationOwnerMapping(i: number): CreationOwnerMappingContext | null; + public creationOwnerMapping(i?: number): CreationOwnerMappingContext[] | CreationOwnerMappingContext | null { + if (i === undefined) { + return this.getRuleContexts(CreationOwnerMappingContext); + } + + return this.getRuleContext(i, CreationOwnerMappingContext); + } public CONSTRUCTOR(): antlr.TerminalNode | null { return this.getToken(QuixosCapabilityParser.CONSTRUCTOR, 0); } @@ -7501,6 +9745,21 @@ export class ValueTypeContext extends antlr.ParserRuleContext { return this.getRuleContext(i, RecordFieldContext); } + public VARIANT(): antlr.TerminalNode | null { + return this.getToken(QuixosCapabilityParser.VARIANT, 0); + } + public ID(): antlr.TerminalNode | null { + return this.getToken(QuixosCapabilityParser.ID, 0); + } + public variantCase(): VariantCaseContext[]; + public variantCase(i: number): VariantCaseContext | null; + public variantCase(i?: number): VariantCaseContext[] | VariantCaseContext | null { + if (i === undefined) { + return this.getRuleContexts(VariantCaseContext); + } + + return this.getRuleContext(i, VariantCaseContext); + } public override get ruleIndex(): number { return QuixosCapabilityParser.RULE_valueType; } @@ -7543,6 +9802,41 @@ export class RecordFieldContext extends antlr.ParserRuleContext { } +export class VariantCaseContext extends antlr.ParserRuleContext { + public constructor(parent: antlr.ParserRuleContext | null, invokingState: number) { + super(parent, invokingState); + } + public identifier(): IdentifierContext { + return this.getRuleContext(0, IdentifierContext)!; + } + public ID(): antlr.TerminalNode { + return this.getToken(QuixosCapabilityParser.ID, 0)!; + } + public stringLiteral(): StringLiteralContext { + return this.getRuleContext(0, StringLiteralContext)!; + } + public COLON(): antlr.TerminalNode { + return this.getToken(QuixosCapabilityParser.COLON, 0)!; + } + public valueType(): ValueTypeContext { + return this.getRuleContext(0, ValueTypeContext)!; + } + public SEMI(): antlr.TerminalNode { + return this.getToken(QuixosCapabilityParser.SEMI, 0)!; + } + public override get ruleIndex(): number { + return QuixosCapabilityParser.RULE_variantCase; + } + public override accept(visitor: QuixosCapabilityVisitor): Result | null { + if (visitor.visitVariantCase) { + return visitor.visitVariantCase(this); + } else { + return visitor.visitChildren(this); + } + } +} + + export class ScalarTypeContext extends antlr.ParserRuleContext { public constructor(parent: antlr.ParserRuleContext | null, invokingState: number) { super(parent, invokingState); @@ -7769,6 +10063,57 @@ export class IdentifierContext extends antlr.ParserRuleContext { public IDENTIFIER(): antlr.TerminalNode | null { return this.getToken(QuixosCapabilityParser.IDENTIFIER, 0); } + public START(): antlr.TerminalNode | null { + return this.getToken(QuixosCapabilityParser.START, 0); + } + public STOP(): antlr.TerminalNode | null { + return this.getToken(QuixosCapabilityParser.STOP, 0); + } + public EDIT(): antlr.TerminalNode | null { + return this.getToken(QuixosCapabilityParser.EDIT, 0); + } + public CREATION(): antlr.TerminalNode | null { + return this.getToken(QuixosCapabilityParser.CREATION, 0); + } + public INITIALIZE(): antlr.TerminalNode | null { + return this.getToken(QuixosCapabilityParser.INITIALIZE, 0); + } + public EXECUTION(): antlr.TerminalNode | null { + return this.getToken(QuixosCapabilityParser.EXECUTION, 0); + } + public SNAPSHOT(): antlr.TerminalNode | null { + return this.getToken(QuixosCapabilityParser.SNAPSHOT, 0); + } + public TRANSACTION(): antlr.TerminalNode | null { + return this.getToken(QuixosCapabilityParser.TRANSACTION, 0); + } + public REMOTE(): antlr.TerminalNode | null { + return this.getToken(QuixosCapabilityParser.REMOTE, 0); + } + public TARGETS(): antlr.TerminalNode | null { + return this.getToken(QuixosCapabilityParser.TARGETS, 0); + } + public PORTABLE(): antlr.TerminalNode | null { + return this.getToken(QuixosCapabilityParser.PORTABLE, 0); + } + public REPLICA(): antlr.TerminalNode | null { + return this.getToken(QuixosCapabilityParser.REPLICA, 0); + } + public LOCAL(): antlr.TerminalNode | null { + return this.getToken(QuixosCapabilityParser.LOCAL, 0); + } + public FROM(): antlr.TerminalNode | null { + return this.getToken(QuixosCapabilityParser.FROM, 0); + } + public REGISTER(): antlr.TerminalNode | null { + return this.getToken(QuixosCapabilityParser.REGISTER, 0); + } + public INTERFACES(): antlr.TerminalNode | null { + return this.getToken(QuixosCapabilityParser.INTERFACES, 0); + } + public REFLECTION(): antlr.TerminalNode | null { + return this.getToken(QuixosCapabilityParser.REFLECTION, 0); + } public SOURCE(): antlr.TerminalNode | null { return this.getToken(QuixosCapabilityParser.SOURCE, 0); } @@ -7787,6 +10132,9 @@ export class IdentifierContext extends antlr.ParserRuleContext { public FRAGMENTS(): antlr.TerminalNode | null { return this.getToken(QuixosCapabilityParser.FRAGMENTS, 0); } + public FRAGMENT(): antlr.TerminalNode | null { + return this.getToken(QuixosCapabilityParser.FRAGMENT, 0); + } public VIEW(): antlr.TerminalNode | null { return this.getToken(QuixosCapabilityParser.VIEW, 0); } @@ -7814,6 +10162,27 @@ export class IdentifierContext extends antlr.ParserRuleContext { public DEADLINE_MS(): antlr.TerminalNode | null { return this.getToken(QuixosCapabilityParser.DEADLINE_MS, 0); } + public PRESENTATION(): antlr.TerminalNode | null { + return this.getToken(QuixosCapabilityParser.PRESENTATION, 0); + } + public SUBJECT(): antlr.TerminalNode | null { + return this.getToken(QuixosCapabilityParser.SUBJECT, 0); + } + public MESSAGE(): antlr.TerminalNode | null { + return this.getToken(QuixosCapabilityParser.MESSAGE, 0); + } + public JOURNAL(): antlr.TerminalNode | null { + return this.getToken(QuixosCapabilityParser.JOURNAL, 0); + } + public SUBSCRIPTION(): antlr.TerminalNode | null { + return this.getToken(QuixosCapabilityParser.SUBSCRIPTION, 0); + } + public CONNECT(): antlr.TerminalNode | null { + return this.getToken(QuixosCapabilityParser.CONNECT, 0); + } + public FOR(): antlr.TerminalNode | null { + return this.getToken(QuixosCapabilityParser.FOR, 0); + } public override get ruleIndex(): number { return QuixosCapabilityParser.RULE_identifier; } diff --git a/src/capability-language/generated/QuixosCapabilityVisitor.ts b/src/capability-language/generated/QuixosCapabilityVisitor.ts index c852287..6d51e99 100644 --- a/src/capability-language/generated/QuixosCapabilityVisitor.ts +++ b/src/capability-language/generated/QuixosCapabilityVisitor.ts @@ -10,6 +10,7 @@ import { WorkspaceItemContext } from "./QuixosCapabilityParser.js"; import { ResourceImportDeclContext } from "./QuixosCapabilityParser.js"; import { ExternalAtomDeclContext } from "./QuixosCapabilityParser.js"; import { ExternalInterfaceDeclContext } from "./QuixosCapabilityParser.js"; +import { SymbolDeclContext } from "./QuixosCapabilityParser.js"; import { ResourcePreambleContext } from "./QuixosCapabilityParser.js"; import { AtomDeclContext } from "./QuixosCapabilityParser.js"; import { InterfaceResourceDeclContext } from "./QuixosCapabilityParser.js"; @@ -20,6 +21,7 @@ import { TypeArgumentsContext } from "./QuixosCapabilityParser.js"; import { TypeArgumentContext } from "./QuixosCapabilityParser.js"; import { TypeAliasDeclContext } from "./QuixosCapabilityParser.js"; import { InterfaceMemberContext } from "./QuixosCapabilityParser.js"; +import { ModuleMemberContext } from "./QuixosCapabilityParser.js"; import { OperationMemberContext } from "./QuixosCapabilityParser.js"; import { ValueMemberContext } from "./QuixosCapabilityParser.js"; import { ValueMemberOperationContext } from "./QuixosCapabilityParser.js"; @@ -29,11 +31,18 @@ import { RelationshipOperationContext } from "./QuixosCapabilityParser.js"; import { TargetConstraintContext } from "./QuixosCapabilityParser.js"; import { PackageResourceDeclContext } from "./QuixosCapabilityParser.js"; import { PackageExportContext } from "./QuixosCapabilityParser.js"; +import { ModuleExportContext } from "./QuixosCapabilityParser.js"; +import { PresentationDeclContext } from "./QuixosCapabilityParser.js"; import { PackageQueryContext } from "./QuixosCapabilityParser.js"; import { PackageQuerySpecializationContext } from "./QuixosCapabilityParser.js"; import { QueryClauseContext } from "./QuixosCapabilityParser.js"; +import { FragmentVariableContext } from "./QuixosCapabilityParser.js"; import { PackageOperationExportContext } from "./QuixosCapabilityParser.js"; import { PackageFunctionExportContext } from "./QuixosCapabilityParser.js"; +import { AcceptanceClauseContext } from "./QuixosCapabilityParser.js"; +import { PreparationClauseContext } from "./QuixosCapabilityParser.js"; +import { ExecutionClauseContext } from "./QuixosCapabilityParser.js"; +import { SelectedOperationsContext } from "./QuixosCapabilityParser.js"; import { PackageConstructorExportContext } from "./QuixosCapabilityParser.js"; import { EventClauseContext } from "./QuixosCapabilityParser.js"; import { OperationModeContext } from "./QuixosCapabilityParser.js"; @@ -50,13 +59,19 @@ import { StoragePolicyContext } from "./QuixosCapabilityParser.js"; import { EdgeDeclContext } from "./QuixosCapabilityParser.js"; import { EdgeEndpointContext } from "./QuixosCapabilityParser.js"; import { ConformanceDeclContext } from "./QuixosCapabilityParser.js"; +import { ConformanceSourceArgumentsContext } from "./QuixosCapabilityParser.js"; import { ConformanceItemContext } from "./QuixosCapabilityParser.js"; +import { ModuleBindingDeclContext } from "./QuixosCapabilityParser.js"; +import { InitializationDeclContext } from "./QuixosCapabilityParser.js"; +import { InitializationFieldContext } from "./QuixosCapabilityParser.js"; +import { CreationOwnerMappingContext } from "./QuixosCapabilityParser.js"; import { StateFieldBindingDeclContext } from "./QuixosCapabilityParser.js"; import { RelationshipMaterializationDeclContext } from "./QuixosCapabilityParser.js"; import { OperationBindingDeclContext } from "./QuixosCapabilityParser.js"; import { MemberOperationRefContext } from "./QuixosCapabilityParser.js"; import { OperationNameContext } from "./QuixosCapabilityParser.js"; import { OperationProviderContext } from "./QuixosCapabilityParser.js"; +import { AcceptanceBindingContext } from "./QuixosCapabilityParser.js"; import { StatePrimitiveContext } from "./QuixosCapabilityParser.js"; import { EdgePrimitiveContext } from "./QuixosCapabilityParser.js"; import { DependencyBindingBlockContext } from "./QuixosCapabilityParser.js"; @@ -64,6 +79,7 @@ import { DependencyBindingContext } from "./QuixosCapabilityParser.js"; import { ConstructorBindingDeclContext } from "./QuixosCapabilityParser.js"; import { ValueTypeContext } from "./QuixosCapabilityParser.js"; import { RecordFieldContext } from "./QuixosCapabilityParser.js"; +import { VariantCaseContext } from "./QuixosCapabilityParser.js"; import { ScalarTypeContext } from "./QuixosCapabilityParser.js"; import { CardinalityContext } from "./QuixosCapabilityParser.js"; import { JsonLiteralContext } from "./QuixosCapabilityParser.js"; @@ -130,6 +146,12 @@ export class QuixosCapabilityVisitor extends AbstractParseTreeVisitor Result; + /** + * Visit a parse tree produced by `QuixosCapabilityParser.symbolDecl`. + * @param ctx the parse tree + * @return the visitor result + */ + visitSymbolDecl?: (ctx: SymbolDeclContext) => Result; /** * Visit a parse tree produced by `QuixosCapabilityParser.resourcePreamble`. * @param ctx the parse tree @@ -190,6 +212,12 @@ export class QuixosCapabilityVisitor extends AbstractParseTreeVisitor Result; + /** + * Visit a parse tree produced by `QuixosCapabilityParser.moduleMember`. + * @param ctx the parse tree + * @return the visitor result + */ + visitModuleMember?: (ctx: ModuleMemberContext) => Result; /** * Visit a parse tree produced by `QuixosCapabilityParser.operationMember`. * @param ctx the parse tree @@ -244,6 +272,18 @@ export class QuixosCapabilityVisitor extends AbstractParseTreeVisitor Result; + /** + * Visit a parse tree produced by `QuixosCapabilityParser.moduleExport`. + * @param ctx the parse tree + * @return the visitor result + */ + visitModuleExport?: (ctx: ModuleExportContext) => Result; + /** + * Visit a parse tree produced by `QuixosCapabilityParser.presentationDecl`. + * @param ctx the parse tree + * @return the visitor result + */ + visitPresentationDecl?: (ctx: PresentationDeclContext) => Result; /** * Visit a parse tree produced by `QuixosCapabilityParser.packageQuery`. * @param ctx the parse tree @@ -262,6 +302,12 @@ export class QuixosCapabilityVisitor extends AbstractParseTreeVisitor Result; + /** + * Visit a parse tree produced by `QuixosCapabilityParser.fragmentVariable`. + * @param ctx the parse tree + * @return the visitor result + */ + visitFragmentVariable?: (ctx: FragmentVariableContext) => Result; /** * Visit a parse tree produced by `QuixosCapabilityParser.packageOperationExport`. * @param ctx the parse tree @@ -274,6 +320,30 @@ export class QuixosCapabilityVisitor extends AbstractParseTreeVisitor Result; + /** + * Visit a parse tree produced by `QuixosCapabilityParser.acceptanceClause`. + * @param ctx the parse tree + * @return the visitor result + */ + visitAcceptanceClause?: (ctx: AcceptanceClauseContext) => Result; + /** + * Visit a parse tree produced by `QuixosCapabilityParser.preparationClause`. + * @param ctx the parse tree + * @return the visitor result + */ + visitPreparationClause?: (ctx: PreparationClauseContext) => Result; + /** + * Visit a parse tree produced by `QuixosCapabilityParser.executionClause`. + * @param ctx the parse tree + * @return the visitor result + */ + visitExecutionClause?: (ctx: ExecutionClauseContext) => Result; + /** + * Visit a parse tree produced by `QuixosCapabilityParser.selectedOperations`. + * @param ctx the parse tree + * @return the visitor result + */ + visitSelectedOperations?: (ctx: SelectedOperationsContext) => Result; /** * Visit a parse tree produced by `QuixosCapabilityParser.packageConstructorExport`. * @param ctx the parse tree @@ -370,12 +440,42 @@ export class QuixosCapabilityVisitor extends AbstractParseTreeVisitor Result; + /** + * Visit a parse tree produced by `QuixosCapabilityParser.conformanceSourceArguments`. + * @param ctx the parse tree + * @return the visitor result + */ + visitConformanceSourceArguments?: (ctx: ConformanceSourceArgumentsContext) => Result; /** * Visit a parse tree produced by `QuixosCapabilityParser.conformanceItem`. * @param ctx the parse tree * @return the visitor result */ visitConformanceItem?: (ctx: ConformanceItemContext) => Result; + /** + * Visit a parse tree produced by `QuixosCapabilityParser.moduleBindingDecl`. + * @param ctx the parse tree + * @return the visitor result + */ + visitModuleBindingDecl?: (ctx: ModuleBindingDeclContext) => Result; + /** + * Visit a parse tree produced by `QuixosCapabilityParser.initializationDecl`. + * @param ctx the parse tree + * @return the visitor result + */ + visitInitializationDecl?: (ctx: InitializationDeclContext) => Result; + /** + * Visit a parse tree produced by `QuixosCapabilityParser.initializationField`. + * @param ctx the parse tree + * @return the visitor result + */ + visitInitializationField?: (ctx: InitializationFieldContext) => Result; + /** + * Visit a parse tree produced by `QuixosCapabilityParser.creationOwnerMapping`. + * @param ctx the parse tree + * @return the visitor result + */ + visitCreationOwnerMapping?: (ctx: CreationOwnerMappingContext) => Result; /** * Visit a parse tree produced by `QuixosCapabilityParser.stateFieldBindingDecl`. * @param ctx the parse tree @@ -412,6 +512,12 @@ export class QuixosCapabilityVisitor extends AbstractParseTreeVisitor Result; + /** + * Visit a parse tree produced by `QuixosCapabilityParser.acceptanceBinding`. + * @param ctx the parse tree + * @return the visitor result + */ + visitAcceptanceBinding?: (ctx: AcceptanceBindingContext) => Result; /** * Visit a parse tree produced by `QuixosCapabilityParser.statePrimitive`. * @param ctx the parse tree @@ -454,6 +560,12 @@ export class QuixosCapabilityVisitor extends AbstractParseTreeVisitor Result; + /** + * Visit a parse tree produced by `QuixosCapabilityParser.variantCase`. + * @param ctx the parse tree + * @return the visitor result + */ + visitVariantCase?: (ctx: VariantCaseContext) => Result; /** * Visit a parse tree produced by `QuixosCapabilityParser.scalarType`. * @param ctx the parse tree diff --git a/src/capability-language/generic-types.ts b/src/capability-language/generic-types.ts index 4a3f83b..f220304 100644 --- a/src/capability-language/generic-types.ts +++ b/src/capability-language/generic-types.ts @@ -34,6 +34,7 @@ export class GenericSourceTypes { readonly applications = new Map(); readonly aliases = new Map(); parameters = new Map(); + bindings = new Map(); self?: AtomId; private active: string[] = []; private applicationCount = 0; @@ -42,7 +43,7 @@ export class GenericSourceTypes { environment(): GenericTypeEnvironment { return { - arguments: new Map(), + arguments: this.bindings, aliases: this.aliases, self: this.self, applyInterface: (id, args) => this.apply(id, args).revisionId, @@ -108,6 +109,8 @@ export class GenericSourceTypes { return parameter?.kind === "value" && Boolean(parameter.storable); } if (type.kind === "list" || type.kind === "optional") return storable(type.value, depth + 1); + if (type.kind === "record") return Object.values(type.fields).every((field) => storable(field, depth + 1)); + if (type.kind === "variant") return Object.values(type.cases).every((entry) => storable(entry.value, depth + 1)); if (type.kind === "alias") { const alias = aliases.get(type.definitionId); if (!alias || alias.parameters.length !== type.arguments.length) return false; @@ -299,6 +302,24 @@ export class GenericSourceTypes { } expression(context: ValueTypeContext): ValueTypeExpression { + if (context.VARIANT()) { + const entries = context.variantCase().map( + (entry) => + [ + entry.identifier().getText(), + { + id: literal(entry.stringLiteral()), + value: this.expression(entry.valueType()), + }, + ] as const, + ); + if ( + new Set(entries.map(([label]) => label)).size !== entries.length || + new Set(entries.map(([, entry]) => entry.id)).size !== entries.length + ) + throw new GenericTypeError("duplicate-variant-case", "variant", "Variant labels and case IDs must be unique"); + return { kind: "variant", id: literal(context.stringLiteral()!), cases: Object.fromEntries(entries) }; + } if (context.scalarType()) return { kind: "scalar", @@ -318,6 +339,12 @@ export class GenericSourceTypes { return { kind: "record", fields: Object.fromEntries(fields) }; } const name = context.identifier()!.getText(); + if (name === "change" && context.typeArguments()) { + const args = this.arguments(context.typeArguments()); + if (args.length !== 1 || args[0]!.kind !== "value") + throw new GenericTypeError("invalid-change-type", name, "change requires one materialized value type"); + return { kind: "change", value: args[0]!.type }; + } if (context.ATOM_REF()) return { kind: "object-ref", expectation: this.atom(name) }; if (context.INTERFACE_REF()) return { diff --git a/src/capability-language/git-resolver.ts b/src/capability-language/git-resolver.ts index c87dcfc..5175e5c 100644 --- a/src/capability-language/git-resolver.ts +++ b/src/capability-language/git-resolver.ts @@ -38,7 +38,7 @@ export const createGitCapabilityResolver = async (options: { }>; }; for (const entry of document.resources ?? []) { - if (entry.kind !== "interface" && entry.kind !== "package") { + if (entry.kind !== "interface" && entry.kind !== "package" && entry.kind !== "build") { throw new Error(`Snapshot map has unsupported resource kind ${entry.kind}`); } const key = sourceKey(entry.kind, entry.repository, entry.commit); diff --git a/src/capability-language/implementation-edit.ts b/src/capability-language/implementation-edit.ts index 85ffb9b..5b8cd53 100644 --- a/src/capability-language/implementation-edit.ts +++ b/src/capability-language/implementation-edit.ts @@ -7,7 +7,7 @@ const node = (value: unknown): value is Node => * Ambiguous/custom wiring is left alone with an actionable error. */ export function addImplementation( text: string, - factory: "createRuntime" | "serveMigration", + factory: "createRuntime" | "serveMigration" | "createRegistry", key: string, importPath: string, migrationOnly = false, diff --git a/src/capability-language/parser.ts b/src/capability-language/parser.ts index 0a75ba8..13d7caa 100644 --- a/src/capability-language/parser.ts +++ b/src/capability-language/parser.ts @@ -1,3 +1,4 @@ +import { createHash } from "node:crypto"; import { BaseErrorListener, CharStream, @@ -13,6 +14,7 @@ import { cardinalityValueType, compileWorkspaceRevision, valueType, + type ExecutionContract, type AtomId, type BoundDependency, type CompiledWorkspaceRevision, @@ -45,6 +47,8 @@ import { type GenericDependencyPort, specializePackageExport, } from "../capability-model/index.js"; +import { expandConformanceRules, type ConformanceRule } from "../capability-model/conformance-rules.js"; +import type { ClosedTypeArgument, TypeParameter } from "../capability-model/generics.js"; import { GenericSourceTypes } from "./generic-types.js"; import { defaultQueryBudgets, @@ -57,6 +61,7 @@ import { specializeQueryTemplate } from "../query/templates.js"; import { QuixosCapabilityLexer } from "./generated/QuixosCapabilityLexer.js"; import { QuixosCapabilityParser, + type ExecutionClauseContext, type AttachmentDeclContext, type ConformanceDeclContext, type DependencyBindingBlockContext, @@ -200,6 +205,7 @@ interface LoweringState { fileName: string; diagnostics: CapabilitySourceDiagnostic[]; atoms: Map; + symbols: Map; interfaces: Map; packages: Map; attachments: Map; @@ -311,6 +317,7 @@ const lowerValueType = (state: LoweringState, context: ValueTypeContext | null): throw new Error("Missing value type after a successful parse"); } if ( + context.VARIANT() || context.REF() || context.typeArguments() || (context.identifier() && !context.ATOM_REF() && !context.INTERFACE_REF()) @@ -361,6 +368,57 @@ const lowerValueType = (state: LoweringState, context: ValueTypeContext | null): return valueType.unit; }; +const lowerSelectedOperations = ( + state: LoweringState, + context: DependencyPortContext, + target: InterfaceSymbol | undefined, +): { operations?: InterfaceOperation["id"][] } => { + const selected = context.selectedOperations(); + if (!selected) return {}; + const operations = selected.memberOperationRef().flatMap((reference) => { + const name = identifier(reference.identifier()); + const member = target && requireSymbol(state, target.members, name, reference, "interface member"); + const operation = + member && + requireSymbol(state, member.operations, text(reference.operationName()), reference, "interface operation"); + return operation ? [operation] : []; + }); + if (new Set(operations).size !== operations.length) + loweringIssue(state, context, "duplicate-operation-selection", "Interface operation selection contains duplicates"); + return { operations }; +}; + +const lowerExecution = ( + state: LoweringState, + context: ExecutionClauseContext | null, + implementation = false, +): { execution?: ExecutionContract } => { + if (!context) return {}; + if (!implementation && context.identifierList()) + loweringIssue( + state, + context, + "interface-execution-targets", + "Interfaces declare effects; execution targets belong to package implementations", + ); + const targets = context + .identifierList() + ?.identifier() + .map((node) => { + const name = identifier(node); + if (name === "server") return name; + return requireSymbol(state, state.symbols, name, node, "nominal execution target") ?? `unresolved:${name}`; + }) ?? ["server"]; + return { + execution: context.REMOTE() + ? { kind: "remote" } + : { + kind: context.SNAPSHOT() ? "snapshot" : "transaction", + targets: implementation ? targets : ["server"], + }, + }; +}; + const valueOperation = (displayName: string, id: InterfaceOperation["id"], value: ValueType): InterfaceOperation => { switch (displayName) { case "get": @@ -371,6 +429,8 @@ const valueOperation = (displayName: string, id: InterfaceOperation["id"], value outputType: value, mode: "call", }; + case "edit": + return { id, displayName, inputType: { kind: "change", value }, outputType: valueType.unit, mode: "call" }; case "set": return { id, @@ -406,6 +466,7 @@ const relationshipOperation = ( id: InterfaceOperation["id"], target: EdgeEndpointConstraint, cardinality: EdgeCardinality, + keyType?: "string" | "boolean" | "int64", ): InterfaceOperation => { const targetType = target.kind === "atom" ? valueType.atomRef(target.atomId) : valueType.interfaceRef(target.interfaceRevisionId); @@ -424,7 +485,16 @@ const relationshipOperation = ( return { id, displayName, - inputType: targetType, + inputType: + keyType && displayName === "connect" + ? { + kind: "record", + fields: { + mapKey: { kind: "scalar", name: keyType === "boolean" ? "bool" : keyType }, + target: targetType, + }, + } + : targetType, outputType: valueType.unit, mode: "call", }; @@ -456,13 +526,20 @@ const lowerValueMember = (state: LoweringState, context: ValueMemberContext): In const operations: InterfaceOperation[] = []; for (const operation of context.valueMemberOperation()) { if (operation.GET()) { - operations.push( - valueOperation("get", capabilityId.operation(stringValue(operation.stringLiteral(0))), memberValueType), - ); + operations.push({ + ...valueOperation("get", capabilityId.operation(stringValue(operation.stringLiteral(0))), memberValueType), + ...lowerExecution(state, operation.executionClause()), + }); + } else if (operation.EDIT()) { + operations.push({ + ...valueOperation("edit", capabilityId.operation(stringValue(operation.stringLiteral(0))), memberValueType), + ...lowerExecution(state, operation.executionClause()), + }); } else if (operation.SET()) { - operations.push( - valueOperation("set", capabilityId.operation(stringValue(operation.stringLiteral(0))), memberValueType), - ); + operations.push({ + ...valueOperation("set", capabilityId.operation(stringValue(operation.stringLiteral(0))), memberValueType), + ...lowerExecution(state, operation.executionClause()), + }); } else { operations.push( valueOperation("watch-start", capabilityId.operation(stringValue(operation.stringLiteral(0))), memberValueType), @@ -472,10 +549,15 @@ const lowerValueMember = (state: LoweringState, context: ValueMemberContext): In } return { kind: "value", + ...(context.FIELD() ? { nativeField: true as const } : {}), ...(context.queryReadContract() ? { queryRead: { - execution: context.queryReadContract()!.RPC() ? ("rpc-permitted" as const) : ("native" as const), + execution: context.queryReadContract()!.RPC() + ? ("rpc-permitted" as const) + : context.queryReadContract()!.PORTABLE() + ? ("portable" as const) + : ("native" as const), }, } : {}), @@ -495,6 +577,9 @@ const lowerRelationshipMember = ( return undefined; } const cardinality = lowerCardinality(context.cardinality()); + const keyType = context.KEYED() + ? (stringValue(context.stringLiteral(1)) as "string" | "boolean" | "int64") + : undefined; const operations: InterfaceOperation[] = []; for (const operation of context.relationshipOperation()) { if (operation.RESOLVE()) { @@ -504,6 +589,7 @@ const lowerRelationshipMember = ( capabilityId.operation(stringValue(operation.stringLiteral(0))), target, cardinality, + keyType, ), ); } else if (operation.CONNECT()) { @@ -513,6 +599,7 @@ const lowerRelationshipMember = ( capabilityId.operation(stringValue(operation.stringLiteral(0))), target, cardinality, + keyType, ), ); } else if (operation.DISCONNECT()) { @@ -522,6 +609,7 @@ const lowerRelationshipMember = ( capabilityId.operation(stringValue(operation.stringLiteral(0))), target, cardinality, + keyType, ), ); } else { @@ -531,15 +619,19 @@ const lowerRelationshipMember = ( capabilityId.operation(stringValue(operation.stringLiteral(0))), target, cardinality, + keyType, ), relationshipOperation( "watch-stop", capabilityId.operation(stringValue(operation.stringLiteral(1))), target, cardinality, + keyType, ), ); } + if (operation.executionClause()) + Object.assign(operations[operations.length - 1]!, lowerExecution(state, operation.executionClause())); } return { kind: "relationship", @@ -547,7 +639,11 @@ const lowerRelationshipMember = ( ...(context.queryReadContract() ? { queryRead: { - execution: context.queryReadContract()!.RPC() ? ("rpc-permitted" as const) : ("native" as const), + execution: context.queryReadContract()!.RPC() + ? ("rpc-permitted" as const) + : context.queryReadContract()!.PORTABLE() + ? ("portable" as const) + : ("native" as const), }, } : {}), @@ -577,6 +673,7 @@ const lowerOperationMember = (state: LoweringState, context: OperationMemberCont outputType, mode: "call", ...(context.STATIC() ? { scope: "class" as const } : {}), + ...lowerExecution(state, context.executionClause()), }, ], }; @@ -588,7 +685,19 @@ const lowerInterface = ( source: SourceRevision, ): InterfaceRevision => { const alias = identifier(context.identifier()); - const members = context.interfaceMember().flatMap((entry) => { + const members = context.interfaceMember().flatMap((entry) => { + const module = entry.moduleMember(); + if (module) + return [ + { + kind: "module" as const, + id: capabilityId.member(stringValue(module.stringLiteral(0))), + displayName: identifier(module.identifier()), + contract: stringValue(module.stringLiteral(1)), + ...(module.targetConstraint() ? { subject: state.types.target(module.targetConstraint()!) } : {}), + operations: [], + }, + ]; const value = entry.valueMember(); if (value) { return [lowerValueMember(state, value)]; @@ -691,15 +800,30 @@ const lowerInterfaceTemplate = ( const members: InterfaceMember[] = context .interfaceMember() .map((entry) => { + const module = entry.moduleMember(); + if (module) + return { + kind: "module" as const, + id: capabilityId.member(stringValue(module.stringLiteral(0))), + displayName: identifier(module.identifier()), + contract: stringValue(module.stringLiteral(1)), + ...(module.targetConstraint() ? { subject: types.targetExpression(module.targetConstraint()!) } : {}), + operations: [], + }; const value = entry.valueMember(); if (value) { const type = types.expression(value.valueType()); return { kind: "value", + ...(value.FIELD() ? { nativeField: true as const } : {}), ...(value.queryReadContract() ? { queryRead: { - execution: value.queryReadContract()!.RPC() ? ("rpc-permitted" as const) : ("native" as const), + execution: value.queryReadContract()!.RPC() + ? ("rpc-permitted" as const) + : value.queryReadContract()!.PORTABLE() + ? ("portable" as const) + : ("native" as const), }, } : {}), @@ -708,8 +832,27 @@ const lowerInterfaceTemplate = ( valueType: type, operations: value.valueMemberOperation().flatMap((operation) => { const id = capabilityId.operation(stringValue(operation.stringLiteral(0))); - if (operation.GET()) return [signature("get", id, valueType.unit, type)]; - if (operation.SET()) return [signature("set", id, type, valueType.unit)]; + if (operation.GET()) + return [ + { + ...signature("get", id, valueType.unit, type), + ...lowerExecution(state, operation.executionClause()), + }, + ]; + if (operation.EDIT()) + return [ + { + ...signature("edit", id, { kind: "change", value: type }, valueType.unit), + ...lowerExecution(state, operation.executionClause()), + }, + ]; + if (operation.SET()) + return [ + { + ...signature("set", id, type, valueType.unit), + ...lowerExecution(state, operation.executionClause()), + }, + ]; return [ signature("watch-start", id, valueType.unit, valueType.watchHandle, "watch-start", type), signature( @@ -742,6 +885,7 @@ const lowerInterfaceTemplate = ( outputType, ), ...(operation.STATIC() ? { scope: "class" as const } : {}), + ...lowerExecution(state, operation.executionClause()), }, ], }; @@ -750,6 +894,15 @@ const lowerInterfaceTemplate = ( const target = types.targetExpression(relationship.targetConstraint()); const targetType: ValueTypeExpression = { kind: "object-ref", expectation: target }; const cardinality = lowerCardinality(relationship.cardinality()); + const keyType = relationship.KEYED() + ? (stringValue(relationship.stringLiteral(1)) as "string" | "boolean" | "int64") + : undefined; + const keyedTarget: ValueTypeExpression = keyType + ? { + kind: "record", + fields: { mapKey: { kind: "scalar", name: keyType === "boolean" ? "bool" : keyType }, target: targetType }, + } + : targetType; const resolved: ValueTypeExpression = cardinality === "exactly-one" ? targetType @@ -762,7 +915,11 @@ const lowerInterfaceTemplate = ( ...(relationship.queryReadContract() ? { queryRead: { - execution: relationship.queryReadContract()!.RPC() ? ("rpc-permitted" as const) : ("native" as const), + execution: relationship.queryReadContract()!.RPC() + ? ("rpc-permitted" as const) + : relationship.queryReadContract()!.PORTABLE() + ? ("portable" as const) + : ("native" as const), }, } : {}), @@ -771,11 +928,28 @@ const lowerInterfaceTemplate = ( target, cardinality, ordered: Boolean(relationship.ORDERED()), + ...(keyType ? { keyType } : {}), operations: relationship.relationshipOperation().flatMap((operation) => { const id = capabilityId.operation(stringValue(operation.stringLiteral(0))); - if (operation.RESOLVE()) return [signature("resolve", id, valueType.unit, resolved)]; + if (operation.RESOLVE()) + return [ + { + ...signature("resolve", id, valueType.unit, resolved), + ...lowerExecution(state, operation.executionClause()), + }, + ]; if (operation.CONNECT() || operation.DISCONNECT()) - return [signature(operation.CONNECT() ? "connect" : "disconnect", id, targetType, valueType.unit)]; + return [ + { + ...signature( + operation.CONNECT() ? "connect" : "disconnect", + id, + operation.CONNECT() ? keyedTarget : targetType, + valueType.unit, + ), + ...lowerExecution(state, operation.executionClause()), + }, + ]; return [ signature("watch-start", id, valueType.unit, valueType.watchHandle, "watch-start", resolved), signature( @@ -848,7 +1022,13 @@ const lowerQueryReference = ( const lowerDependencyPort = (state: LoweringState, context: DependencyPortContext): DependencyPort | undefined => { const name = identifier(context.identifier(0)); - const id = capabilityId.dependencyPort(stringValue(context.stringLiteral())); + const id = capabilityId.dependencyPort(stringValue(context.stringLiteral(0))); + if (context.REFLECTION()) + return { + id, + displayName: name, + requirement: { kind: "reflection", selectors: context.identifierList()!.identifier().map(identifier) }, + }; if (context.QUERY()) { const query = lowerQueryReference( state, @@ -863,7 +1043,9 @@ const lowerDependencyPort = (state: LoweringState, context: DependencyPortContex .primitiveList()! .primitive() .map((entry) => text(entry)); - const invalid = primitives.filter((entry) => !["read", "write", "watch-start", "watch-stop"].includes(entry)); + const invalid = primitives.filter( + (entry) => !["read", "write", "edit", "watch-start", "watch-stop"].includes(entry), + ); if (invalid.length > 0) { loweringIssue(state, context, "invalid-port-primitive", `State port cannot request ${invalid.join(", ")}`); } @@ -899,6 +1081,9 @@ const lowerDependencyPort = (state: LoweringState, context: DependencyPortContex kind: "edge", target, cardinality: lowerCardinality(context.cardinality()), + ...(context.KEYED() + ? { keyType: stringValue(context.stringLiteral(1)) as "string" | "boolean" | "int64" } + : {}), primitives: primitives.filter((entry) => !invalid.includes(entry)) as EdgePrimitive[], }, }; @@ -927,11 +1112,21 @@ const lowerDependencyPort = (state: LoweringState, context: DependencyPortContex requirement: { kind: "interface", interfaceRevisionId: target.revisionId, + ...lowerSelectedOperations(state, context, target), + ...(context.FROM() ? { receiver: "input" as const } : {}), }, } : undefined; } const atomId = requireSymbol(state, state.atoms, targetName, context, "atom"); + if (context.CREATION()) + return atomId + ? { + id, + displayName: name, + requirement: { kind: "creation", atomId, inputType: lowerValueType(state, context.valueType()) }, + } + : undefined; return atomId ? { id, @@ -1030,6 +1225,7 @@ const lowerPackageOperation = ( inputType: lowerValueType(state, context.valueType(0)), outputType: lowerValueType(state, context.valueType(1)), mode: text(context.operationMode()) as InterfaceOperationMode, + ...lowerExecution(state, context.executionClause(), true), eventType: event ? lowerValueType(state, event.valueType()) : undefined, receiverRequirement: lowerReceiver(state, context.receiverRequirement()), dependencyPorts: ports, @@ -1046,6 +1242,7 @@ const lowerPackageFunction = ( const alias = identifier(context.identifier()); const entry: PackageExport = { kind: "function", + ...lowerExecution(state, context.executionClause(), true), id: capabilityId.packageExport(stringValue(context.stringLiteral())), displayName: alias, inputType: lowerValueType(state, context.valueType(0)), @@ -1089,7 +1286,7 @@ const lowerGenericPackageExport = ( const parameters = state.types.declareParameters(context.typeParameters(), JSON.stringify(["export", id])); const dependencyPorts = (context.dependencyBlock()?.dependencyPort() ?? []).map((port): GenericDependencyPort => { const base = { - id: capabilityId.dependencyPort(stringValue(port.stringLiteral())), + id: capabilityId.dependencyPort(stringValue(port.stringLiteral(0))), displayName: identifier(port.identifier(0)), }; const primitives = @@ -1097,6 +1294,11 @@ const lowerGenericPackageExport = ( .primitiveList() ?.primitive() .map((item) => text(item)) ?? []; + if (port.REFLECTION()) + return { + ...base, + requirement: { kind: "reflection", selectors: port.identifierList()!.identifier().map(identifier) }, + }; if (port.QUERY()) { const query = lowerQueryReference(state, identifier(port.identifier(1)), identifier(port.identifier(2)), port); if (!query) @@ -1123,6 +1325,7 @@ const lowerGenericPackageExport = ( kind: "edge", target: state.types.targetExpression(port.targetConstraint()!), cardinality: lowerCardinality(port.cardinality()!), + ...(port.KEYED() ? { keyType: stringValue(port.stringLiteral(1)) as "string" | "boolean" | "int64" } : {}), primitives: primitives as EdgePrimitive[], }, }; @@ -1132,6 +1335,8 @@ const lowerGenericPackageExport = ( requirement: { kind: "interface", application: state.types.interfaceByName(identifier(port.identifier(1)), port.typeArguments()), + ...lowerSelectedOperations(state, port, state.interfaces.get(identifier(port.identifier(1)))), + ...(port.FROM() ? { receiver: "input" as const } : {}), }, }; const name = identifier(port.identifier(1)), @@ -1144,7 +1349,11 @@ const lowerGenericPackageExport = ( throw new GenericTypeError("missing-constructor-input", id, "Constructor ports require an explicit input type"); return { ...base, - requirement: { kind: "constructor", target, inputType: state.types.expression(port.valueType()!) }, + requirement: { + kind: port.CREATION() ? "creation" : "constructor", + target, + inputType: state.types.expression(port.valueType()!), + }, }; }); const operation = "receiverRequirement" in context ? context : undefined; @@ -1175,6 +1384,7 @@ const lowerGenericPackageExport = ( displayName: identifier(context.identifier()), parameters, kind: operation ? "operation" : "function", + ...lowerExecution(state, context.executionClause(), true), inputType: state.types.expression(context.valueType(0)!), outputType: state.types.expression(context.valueType(1)!), dependencyPorts, @@ -1217,12 +1427,39 @@ const lowerPackage = ( }), ); const genericExports: GenericPackageExport[] = []; + const modules: NonNullable = []; const queries: QueryDeclaration[] = []; const queryTemplates: QueryTemplate[] = []; const exports = context.packageExport().flatMap((exportContext): PackageExport[] => { + const module = exportContext.moduleExport(); + if (module) { + const interfaceRevisionId = new TypeSubstitution(state.types.environment()).application( + state.types.interface(module.interfaceType()), + ); + const contract = state.types.definitions.get(interfaceRevisionId); + const member = contract?.members.find((m) => m.displayName === identifier(module.identifier(1))); + if (member?.kind !== "module") + loweringIssue(state, module, "invalid-module-export", "Module export must implement an imported module member"); + else + modules.push({ + id: stringValue(module.stringLiteral()), + displayName: identifier(module.identifier(0)), + interfaceRevisionId, + memberId: member.id, + }); + return []; + } if (exportContext.packageQuerySpecialization()) return []; const query = exportContext.packageQuery(); if (query) { + const isFragment = query.FRAGMENT().length > 0; + if (isFragment && (query.FRAGMENT().length !== 2 || query.typeParameters() || query.WORKSPACE())) + loweringIssue( + state, + query, + "invalid-query-fragment", + "Export a fragment on an exact object interface; generic fragments and workspace fragment roots are not supported", + ); const previousParameters = state.types.parameters; state.types.parameters = new Map(); try { @@ -1244,9 +1481,21 @@ const lowerPackage = ( const declaration: QueryDeclaration = { id: stringValue(query.stringLiteral(0)), displayName: identifier(query.identifier()), - root: close(query.interfaceType()), + root: { kind: query.WORKSPACE() ? "workspace-interface" : "object", interface: close(query.interfaceType()) }, document: stringValue(query.stringLiteral(1)), - operation: stringValue(query.stringLiteral(2)), + operation: isFragment + ? query.stringLiteral(3) + ? stringValue(query.stringLiteral(3)) + : "QuixosFragment" + : stringValue(query.stringLiteral(2)), + ...(isFragment + ? { + fragment: { + name: stringValue(query.stringLiteral(2)), + ...(query.stringLiteral(3) ? { operation: stringValue(query.stringLiteral(3)) } : {}), + }, + } + : {}), fragments: [], views: [], allowances: [], @@ -1264,8 +1513,47 @@ const lowerPackage = ( }; const assigned = new Set(); for (const clause of query.queryClause()) { - if (clause.FRAGMENTS()) declaration.fragments.push(stringValue(clause.stringLiteral()!)); - else if (clause.VIEW()) { + if (clause.REPLICA() || clause.LOCAL()) { + if (declaration.placement) + loweringIssue(state, clause, "duplicate-query-placement", "Declare query placement once"); + else if (clause.REPLICA()) declaration.placement = { kind: "replica" }; + else { + const name = identifier(clause.identifier(0)); + const sourceQueryId = requireSymbol( + state, + state.packages.get(alias)!.queries!, + name, + clause, + "replica query", + ); + if (sourceQueryId) declaration.placement = { kind: "local", sourceQueryId }; + } + } else if (clause.FRAGMENTS()) { + if (clause.stringLiteral()) declaration.fragments.push(stringValue(clause.stringLiteral()!)); + else { + const imported = lowerQueryReference( + state, + identifier(clause.identifier(0)), + identifier(clause.identifier(1)), + clause, + ); + if (imported) { + const variables: Record = {}; + for (const mapping of clause.fragmentVariable()) { + const name = identifier(mapping.identifier(0)); + if (Object.hasOwn(variables, name)) + loweringIssue(state, mapping, "duplicate-fragment-variable", `Repeated fragment variable ${name}`); + variables[name] = identifier(mapping.identifier(1)); + } + (declaration.importedFragments ??= []).push({ + packageRevisionId: imported.packageRevisionId, + packageAlias: identifier(clause.identifier(0)), + fragmentId: imported.queryId, + variables, + }); + } + } + } else if (clause.VIEW()) { const name = identifier(clause.identifier(0)); if (parameters.length) { const parameter = state.types.parameters.get(name); @@ -1292,12 +1580,12 @@ const lowerPackage = ( const name = identifier(clause.identifier(0)); const key = budgetNames[name], n = Number(clause.INTEGER()!.getText()); - if (!key || assigned.has(name) || !Number.isSafeInteger(n) || n < 1 || n > defaultQueryBudgets[key]) + if (!key || assigned.has(name) || !Number.isSafeInteger(n) || n < 1 || n > 0xffffffff) loweringIssue( state, clause, "invalid-query-budget", - `Invalid, duplicate, or excessive query budget ${name}`, + `Invalid, duplicate, or out-of-range query budget ${name}`, ); else { declaration.budgets[key] = n; @@ -1372,10 +1660,27 @@ const lowerPackage = ( queryTemplates.push({ declaration: common, parameters, - root: expressions.get(root)!, + root: { kind: root.kind, interface: expressions.get(root.interface)! }, views: templateViews, allowances: templateAllowances, }); + } else if (isFragment) { + if (declaration.placement || declaration.polling || declaration.allowances.length) + loweringIssue( + state, + query, + "invalid-query-fragment", + "Fragments are local projections with generated admission; remote allowances, polling and explicit placement are not supported", + ); + const sourceQueryId = `${declaration.id}:fragment-inputs`; + queries.push({ + ...structuredClone(declaration), + id: sourceQueryId, + displayName: `${declaration.displayName}$inputs`, + placement: { kind: "replica" }, + fragment: { ...declaration.fragment!, admission: true }, + }); + queries.push({ ...declaration, placement: { kind: "local", sourceQueryId } }); } else queries.push(declaration); return []; } finally { @@ -1385,6 +1690,20 @@ const lowerPackage = ( const operation = exportContext.packageOperationExport(); const generic = operation ?? exportContext.packageFunctionExport(); if (generic?.typeParameters()) { + if (generic.preparationClause()) + loweringIssue( + state, + generic, + "unsupported-generic-preparation", + "Creation preparation currently requires a closed export", + ); + if (generic.acceptanceClause()) + loweringIssue( + state, + generic, + "unsupported-generic-acceptance", + "Acceptance hooks currently require a closed export", + ); genericExports.push(atGenericSource(generic, () => lowerGenericPackageExport(state, generic))); return []; } @@ -1406,6 +1725,35 @@ const lowerPackage = ( state.types.self = previousSelf; } }); + // Resolve after all exports have been declared, so checks can follow bodies. + for (const source of context.packageExport()) { + const declaration = source.packageOperationExport() ?? source.packageFunctionExport(); + const preparation = declaration?.preparationClause(); + if (declaration && preparation && !declaration.typeParameters()) { + const entry = exports.find((entry) => entry.displayName === identifier(declaration.identifier())); + const port = entry?.dependencyPorts.find((port) => port.displayName === identifier(preparation.identifier())); + if (!entry || port?.requirement.kind !== "creation") + loweringIssue( + state, + preparation, + "invalid-creation-preparation", + "Preparation must name a creation port of this export", + ); + else entry.preparation = { port: port.id, key: stringValue(preparation.stringLiteral()) }; + } + const clause = declaration?.acceptanceClause(); + if (!declaration || !clause || declaration.typeParameters()) continue; + const entry = exports.find((entry) => entry.displayName === identifier(declaration.identifier())); + const check = exports.find((entry) => entry.displayName === identifier(clause.identifier())); + if (!check) + loweringIssue( + state, + clause, + "unknown-acceptance-export", + "Acceptance hook must name a closed export in this package", + ); + else if (entry) entry.acceptanceExportId = check.id; + } for (const entry of context.packageExport()) { const specialized = entry.packageQuerySpecialization(); if (!specialized) continue; @@ -1441,6 +1789,7 @@ const lowerPackage = ( displayName: alias, source, exports, + ...(modules.length ? { modules } : {}), ...(queries.length ? { queries } : {}), ...(queryTemplates.length ? { queryTemplates } : {}), ...(queries.some((query) => query.argumentRequirements?.length) @@ -1453,7 +1802,8 @@ const lowerPackage = ( const lowerState = (state: LoweringState, context: StateDeclContext): StateSlotDefinition => { const atomName = identifier(context.identifier(1)); const atomId = - requireSymbol(state, state.atoms, atomName, context, "atom") ?? capabilityId.atom(`unresolved:${atomName}`); + (atomName === "Self" ? state.types.self : requireSymbol(state, state.atoms, atomName, context, "atom")) ?? + capabilityId.atom(`unresolved:${atomName}`); const policy = context.storagePolicy(); const defaultContext = context.jsonLiteral(); let defaultValue: unknown; @@ -1466,12 +1816,16 @@ const lowerState = (state: LoweringState, context: StateDeclContext): StateSlotD attachedTo: atomId, displayName: identifier(context.identifier(0)), valueType: lowerValueType(state, context.valueType()), - storagePolicy: policy.OPTIMISTIC_REGISTER() - ? { kind: "optimistic-register" } - : { - kind: "crdt-document", - updateType: lowerValueType(state, policy.valueType()), - }, + storagePolicy: policy.REGISTER() + ? { kind: "register" } + : policy.JOURNAL() + ? { kind: "journal", limits: JSON.parse(text(policy.jsonObject())) } + : policy.SUBSCRIPTION() + ? { kind: "subscription", payload: lowerValueType(state, policy.valueType()) } + : { + kind: "crdt-document", + updateType: lowerValueType(state, policy.valueType()), + }, defaultValue, }; }; @@ -1575,11 +1929,25 @@ const lowerBoundDependencies = ( ); return projectionId ? { edgeTypeId: attachment.attachment.id, projectionId } : undefined; }; + if (entry.REFLECTION()) + return [ + { portId, binding: { kind: "reflection", selectors: entry.identifierList()!.identifier().map(identifier) } }, + ]; if (entry.QUERY()) { const query = lowerQueryReference(state, identifier(entry.identifier(1)), identifier(entry.identifier(2)), entry); const via = entry.VIA() ? traversal(identifier(entry.identifier(3)), identifier(entry.identifier(4))) : undefined; return query && (!entry.VIA() || via) - ? [{ portId, binding: { kind: "query", ...query, ...(via ? { via } : {}) } }] + ? [ + { + portId, + binding: { + kind: "query", + ...query, + scope: entry.WORKSPACE() ? "workspace" : "object", + ...(via ? { via } : {}), + }, + }, + ] : []; } if (entry.STATE()) { @@ -1651,6 +2019,7 @@ const lowerBoundDependencies = ( binding: { kind: "interface", interfaceRevisionId: interfaceSymbol.revisionId, + ...(entry.FROM() ? { receiver: "input" as const } : {}), ...(via ? { via } : {}), }, }, @@ -1658,7 +2027,36 @@ const lowerBoundDependencies = ( : []; } const atomName = identifier(entry.identifier(1)); - const atomId = requireSymbol(state, state.atoms, atomName, entry, "atom"); + const parameter = state.types.parameters.get(atomName); + const argument = parameter && state.types.bindings.get(parameter.id); + const atomId = + atomName === "Self" + ? state.types.self + : argument?.kind === "object" && argument.target.kind === "atom" + ? argument.target.atomId + : parameter + ? undefined + : requireSymbol(state, state.atoms, atomName, entry, "atom"); + if (!atomId && (parameter || atomName === "Self")) + loweringIssue(state, entry, "invalid-creation-target", "Creation requires a concrete atom argument"); + if (entry.CREATION()) + return atomId + ? [ + { + portId, + binding: { + kind: "creation", + atomId, + owners: entry.creationOwnerMapping().map((owner) => ({ + interfaceRevisionId: new TypeSubstitution(state.types.environment()).application( + state.types.interface(owner.interfaceType()), + ), + inputPath: owner.identifier().map(identifier), + })), + }, + }, + ] + : []; return atomId ? [{ portId, binding: { kind: "constructor", atomId } }] : []; }); }; @@ -1702,18 +2100,291 @@ const lowerRelationshipMaterialization = ( : undefined; }; -const lowerConformance = ( +const withConformanceTypes = ( state: LoweringState, + atom: AtomId | undefined, + parameters: TypeParameter[], + bindings: Map, + run: () => T, +): T => { + const previous = { self: state.types.self, parameters: state.types.parameters, bindings: state.types.bindings }; + state.types.self = atom; + state.types.parameters = new Map(parameters.map((p) => [p.name, p])); + state.types.bindings = bindings; + try { + return run(); + } finally { + Object.assign(state.types, previous); + } +}; + +const lowerConformanceRule = (state: LoweringState, context: ConformanceDeclContext): ConformanceRule | undefined => { + if (!context.stringLiteral()) { + loweringIssue(state, context, "missing-rule-id", "Interface conformance rules require a stable ID"); + return undefined; + } + return withConformanceTypes(state, undefined, [], new Map(), () => { + const id = stringValue(context.stringLiteral()!); + const parameters = state.types.declareParameters( + context.typeParameters(), + JSON.stringify(["conformance-rule", id]), + ); + const source = state.types.interfaceByName( + identifier(context.identifier(0)), + context.conformanceSourceArguments()?.typeArguments() ?? null, + ); + const target = state.types.interfaceByName(identifier(context.identifier(1)), context.typeArguments()); + const targetDefinition = state.types.definitions.get(target.definitionId)!; + const applications = [source, target]; + const members = targetDefinition.template?.members ?? targetDefinition.members; + const privateStorage = new Map(); + const privateIds = new Set(); + for (const item of context.conformanceItem()) { + const declaration = item.attachmentDecl(); + if (!declaration) continue; + const stateDeclaration = declaration.stateDecl(); + const edgeDeclaration = declaration.edgeDecl(); + const name = identifier(stateDeclaration ? stateDeclaration.identifier(0) : edgeDeclaration!.identifier()); + const authoredId = stringValue((stateDeclaration ?? edgeDeclaration)!.stringLiteral()); + if (privateStorage.has(name)) + loweringIssue(state, declaration, "duplicate-symbol", `Duplicate private attachment ${name}`); + if (privateIds.has(authoredId)) + loweringIssue(state, declaration, "duplicate-attachment", `Duplicate private attachment ID ${authoredId}`); + privateStorage.set(name, stateDeclaration ? "state" : "edge"); + privateIds.add(authoredId); + if (stateDeclaration && identifier(stateDeclaration.identifier(1)) !== "Self") + loweringIssue(state, stateDeclaration, "invalid-rule-storage", "Rule private state must attach to Self"); + if (stateDeclaration) { + try { + state.types.expression(stateDeclaration.valueType()); + if (stateDeclaration.storagePolicy().valueType()) + state.types.expression(stateDeclaration.storagePolicy().valueType()!); + } catch (error) { + if (error instanceof GenericTypeError) loweringIssue(state, stateDeclaration, error.code, error.message); + else throw error; + } + } + if ( + edgeDeclaration && + !edgeDeclaration + .edgeEndpoint() + .some((endpoint) => identifier(endpoint.targetConstraint().identifier()) === "Self") + ) + loweringIssue(state, edgeDeclaration, "invalid-rule-storage", "Rule private edge must have a Self endpoint"); + for (const endpoint of edgeDeclaration?.edgeEndpoint() ?? []) { + try { + state.types.targetExpression(endpoint.targetConstraint()); + } catch (error) { + if (error instanceof GenericTypeError) loweringIssue(state, endpoint, error.code, error.message); + else throw error; + } + } + } + if (context.conformanceItem().filter((item) => item.initializationDecl()).length > 1) + loweringIssue(state, context, "duplicate-initialization", "Declare one initialization mapping per rule owner"); + for (const item of context.conformanceItem()) { + const field = item.stateFieldBindingDecl(); + if (field) { + const member = members.find((entry) => entry.displayName === identifier(field.identifier(0))); + if (member?.kind !== "value" || privateStorage.get(identifier(field.identifier(1))) !== "state") + loweringIssue( + state, + field, + "invalid-state-field-binding", + "Field shorthand requires a value member and rule-private state", + ); + } + const initialization = item.initializationDecl(); + for (const entry of initialization?.initializationField() ?? []) { + const kind = privateStorage.get(identifier(entry.identifier(1))); + if (!kind || (entry.STATE() && kind !== "state") || (entry.EDGE() && kind !== "edge")) + loweringIssue( + state, + entry, + "invalid-initialization", + "Initialization must name matching rule-private storage", + ); + if (kind === "edge" && entry.EDGE()) { + const edge = context + .conformanceItem() + .flatMap((candidate) => candidate.attachmentDecl()?.edgeDecl() ?? []) + .find((candidate) => identifier(candidate.identifier()) === identifier(entry.identifier(1))); + if ( + !edge + ?.edgeEndpoint() + .some((endpoint) => identifier(endpoint.identifier()) === identifier(entry.identifier(2))) + ) + loweringIssue(state, entry, "unknown-symbol", "Unknown rule-private edge projection"); + } + } + } + for (const item of context.conformanceItem()) { + if (item.relationshipMaterializationDecl()) { + loweringIssue(state, item, "unsupported-rule-storage", "Rule relationship materialization is not supported"); + continue; + } + const module = item.moduleBindingDecl(); + if (module) { + const pkg = requireSymbol(state, state.packages, identifier(module.identifier(1)), module, "package"); + const member = members.find((m) => m.displayName === identifier(module.identifier(0))); + if ( + member?.kind !== "module" || + !pkg?.definition?.modules?.some((m) => m.displayName === identifier(module.identifier(2))) + ) + loweringIssue(state, module, "invalid-module-binding", "Expected a module member and imported module export"); + } + const binding = item.operationBindingDecl(); + if (!binding) continue; + const ref = binding.memberOperationRef(); + const member = members.find((m) => m.displayName === identifier(ref.identifier())); + if (!member?.operations.some((op) => op.displayName === text(ref.operationName()))) + loweringIssue(state, ref, "unknown-operation", "Unknown target interface member operation"); + const provider = binding.operationProvider(); + if (provider.INTERFACE()) { + const application = state.types.interfaceByName(identifier(provider.identifier(0)), provider.typeArguments()); + applications.push(application); + const definition = state.types.definitions.get(application.definitionId)!; + const targetRef = provider.memberOperationRef()!; + if ( + !(definition.template?.members ?? definition.members) + .find((m) => m.displayName === identifier(targetRef.identifier())) + ?.operations.some((op) => op.displayName === text(targetRef.operationName())) + ) + loweringIssue(state, provider, "unknown-operation", "Unknown forwarding member operation"); + } else if (provider.PACKAGE()) { + const pkg = requireSymbol(state, state.packages, identifier(provider.identifier(0)), provider, "package"); + const name = identifier(provider.identifier(1)); + if (pkg && !pkg.exports.has(name)) + loweringIssue(state, provider, "unknown-export", `Unknown package export ${name}`); + const arguments_ = state.types.arguments(provider.typeArguments()); + const exportDefinition = pkg?.definition?.genericExports?.find((entry) => entry.displayName === name); + const expected = exportDefinition?.parameters ?? []; + if (expected.length !== arguments_.length || expected.some((p, i) => p.kind !== arguments_[i]?.kind)) + loweringIssue(state, provider, "parameter-kind", "Package export argument kinds or arity do not match"); + for (const block of [ + provider.dependencyBindingBlock(), + provider.acceptanceBinding()?.dependencyBindingBlock(), + ]) { + for (const dependency of block?.dependencyBinding() ?? []) { + if (dependency.INTERFACE()) + applications.push( + state.types.interfaceByName(identifier(dependency.identifier(1)), dependency.typeArguments()), + ); + } + } + } else if (provider.STATE() || provider.JOURNAL() || provider.SUBSCRIPTION() || provider.EDGE()) { + const name = identifier(provider.identifier(0)); + const expected = provider.STATE() || provider.JOURNAL() || provider.SUBSCRIPTION() ? "state" : "edge"; + // Concrete attachments are registered before rules, so unused rules can + // also check public traversal without waiting for an application. + if (provider.EDGE() && !privateStorage.has(name)) { + const external = requireSymbol(state, state.attachments, name, provider, "attachment"); + const projection = + external?.attachment.kind === "edge" + ? external.attachment.endpoints.find( + (endpoint) => endpoint.displayName === identifier(provider.identifier(1)), + ) + : undefined; + if (external && (!projection?.publicTraversal || text(provider.edgePrimitive()) !== "resolve")) + loweringIssue( + state, + provider, + "invalid-rule-storage-binding", + "External rule storage access requires a public edge resolve projection", + ); + continue; + } + if (privateStorage.get(name) !== expected) + loweringIssue( + state, + provider, + "invalid-rule-storage-binding", + `Rule provider must name private ${expected} ${name}`, + ); + if (provider.EDGE()) { + const edge = context + .conformanceItem() + .flatMap((candidate) => candidate.attachmentDecl()?.edgeDecl() ?? []) + .find((candidate) => identifier(candidate.identifier()) === name); + if ( + !edge + ?.edgeEndpoint() + .some((endpoint) => identifier(endpoint.identifier()) === identifier(provider.identifier(1))) + ) + loweringIssue(state, provider, "unknown-symbol", "Unknown rule-private edge projection"); + } + } else { + loweringIssue( + state, + provider, + "unsupported-rule-provider", + "Rule operations forward an interface or bind a package export", + ); + } + } + state.types.validateTemplate({ + ...targetDefinition, + template: { + parameters, + members: [], + requires: applications, + usesSelf: false, + aliases: [...state.types.aliases.values()], + }, + }); + return { id, parameters, source, target }; + }); +}; + +const lowerConformanceBody = ( + parent: LoweringState, context: ConformanceDeclContext, privateAttachments: PersistentAttachment[], + implementingAtom?: AtomId, + ruleApplicationId?: string, ): WorkspaceRevision["conformances"][number] | undefined => { + const state = { + ...parent, + interfaces: new Map(parent.interfaces), + attachments: ruleApplicationId ? new Map(parent.attachments) : parent.attachments, + }; const atomName = identifier(context.identifier(0)); const interfaceName = identifier(context.identifier(1)); - const atomId = requireSymbol(state, state.atoms, atomName, context, "atom"); + const atomId = implementingAtom ?? requireSymbol(state, state.atoms, atomName, context, "atom"); let interfaceSymbol = requireSymbol(state, state.interfaces, interfaceName, context, "interface"); if (!atomId || !interfaceSymbol) { return undefined; } + if (ruleApplicationId) { + privateAttachments = []; + for (const item of context.conformanceItem()) { + const declaration = item.attachmentDecl(); + if (!declaration) continue; + const attachment = lowerAttachment(state, declaration); + if (!attachment) continue; + const authoredId = attachment.id; + const scopedId = createHash("sha256") + .update(JSON.stringify(["quixos-rule-attachment-v1", ruleApplicationId, authoredId])) + .digest("hex"); + if (attachment.kind === "state") { + attachment.id = capabilityId.slot(`rule:sha256:${scopedId}`); + } else { + attachment.id = capabilityId.edgeType(`rule:sha256:${scopedId}`); + for (const endpoint of attachment.endpoints) { + const projectionHash = createHash("sha256") + .update(JSON.stringify(["quixos-rule-projection-v1", ruleApplicationId, authoredId, endpoint.projectionId])) + .digest("hex"); + endpoint.projectionId = capabilityId.edgeProjection(`rule:sha256:${projectionHash}`); + } + } + privateAttachments.push(attachment); + // A rule application owns a lexical scope. A same-named attachment in + // another owner must neither prevent this declaration nor receive bindings. + const name = identifier(declaration.stateDecl()?.identifier(0) ?? declaration.edgeDecl()!.identifier()); + state.attachments.delete(name); + registerAttachment(state, declaration, attachment); + } + } const originalSymbol = interfaceSymbol; if (context.typeArguments() || interfaceSymbol.definition?.template) { const previousSelf = state.types.self; @@ -1727,6 +2398,25 @@ const lowerConformance = ( state.types.self = previousSelf; } } + const moduleBindings: NonNullable = []; + for (const item of context.conformanceItem()) { + const binding = item.moduleBindingDecl(); + if (!binding) continue; + const memberName = identifier(binding.identifier(0)); + const member = interfaceSymbol.definition?.members.find((m) => m.displayName === memberName); + const pkg = requireSymbol(state, state.packages, identifier(binding.identifier(1)), binding, "package"); + const module = pkg?.definition?.modules?.find((m) => m.displayName === identifier(binding.identifier(2))); + if (member?.kind !== "module" || !module || !pkg) { + loweringIssue( + state, + binding, + "invalid-module-binding", + "Expected a module member bound to an imported module export", + ); + continue; + } + moduleBindings.push({ memberId: member.id, packageRevisionId: pkg.revisionId, moduleId: module.id }); + } const operationBindings = context .conformanceItem() .flatMap((item) => { @@ -1753,6 +2443,7 @@ const lowerConformance = ( const primitives = { get: "read", set: "write", + edit: "edit", "watch-start": "watch-start", "watch-stop": "watch-stop", } as const; @@ -1777,6 +2468,111 @@ const lowerConformance = ( return []; } const provider = bindingContext.operationProvider(); + if (provider.SUBSCRIPTION()) { + const attachment = requireSymbol( + state, + state.attachments, + identifier(provider.identifier(0)), + provider, + "attachment", + ); + const primitive = identifier(provider.identifier(1)); + if ( + !attachment || + attachment.attachment.kind !== "state" || + attachment.attachment.storagePolicy.kind !== "subscription" || + !["attach", "status", "pause", "resume", "retire"].includes(primitive) + ) { + loweringIssue( + state, + provider, + "invalid-subscription-binding", + "Expected native subscription storage and a public subscription primitive", + ); + return []; + } + return [ + { + operationId, + binding: { + kind: "subscription", + slotId: attachment.attachment.id, + primitive: primitive as "attach" | "status" | "pause" | "resume" | "retire", + }, + }, + ]; + } + if (provider.JOURNAL()) { + const attachment = requireSymbol( + state, + state.attachments, + identifier(provider.identifier(0)), + provider, + "attachment", + ); + const primitive = identifier(provider.identifier(1)); + if ( + !attachment || + attachment.attachment.kind !== "state" || + attachment.attachment.storagePolicy.kind !== "journal" || + !["publish", "readPage", "head"].includes(primitive) + ) { + loweringIssue( + state, + provider, + "invalid-journal-binding", + "Expected a native journal and publish, readPage or head", + ); + return []; + } + return [ + { + operationId, + binding: { + kind: "journal", + slotId: attachment.attachment.id, + primitive: primitive as "publish" | "readPage" | "head", + }, + }, + ]; + } + if (provider.LIFECYCLE()) { + if (identifier(provider.identifier(0)) !== "delete") { + loweringIssue(state, provider, "invalid-lifecycle-binding", "Only lifecycle.delete is supported"); + return []; + } + return [{ operationId, binding: { kind: "lifecycle", primitive: "delete" } }]; + } + if (provider.INTERFACE()) { + const targetName = identifier(provider.identifier(0)); + let target = requireSymbol(state, state.interfaces, targetName, provider, "interface"); + if (!target) return []; + if (provider.typeArguments() || target.definition?.template) { + const id = new TypeSubstitution({ ...state.types.environment(), self: atomId }).application( + state.types.interfaceByName(targetName, provider.typeArguments()), + ); + target = interfaceSymbolFor(state.types.definitions.get(id)!); + } + const ref = provider.memberOperationRef()!; + const member = target.members.get(identifier(ref.identifier())); + const targetOperation = member?.operations.get(text(ref.operationName())); + if (!targetOperation) { + loweringIssue( + state, + provider, + "unknown-operation", + "Forwarding target must name an interface member operation", + ); + return []; + } + return [ + { + operationId, + binding: { kind: "interface", interfaceRevisionId: target.revisionId, operationId: targetOperation }, + ...(bindingContext.stringLiteral() ? { queryReason: stringValue(bindingContext.stringLiteral()!) } : {}), + }, + ]; + } if (provider.STATE()) { const attachmentName = identifier(provider.identifier(0)); const attachment = requireSymbol(state, state.attachments, attachmentName, provider, "attachment"); @@ -1855,6 +2651,22 @@ const lowerConformance = ( } else if (provider.typeArguments()) { throw new GenericTypeError("type-arity", exportName, "Non-generic export takes no type arguments"); } + const packageExport = packageSymbol?.definition?.exports.find((entry) => entry.id === exportSymbol?.exportId); + const check = packageSymbol?.definition?.exports.find((entry) => entry.id === packageExport?.acceptanceExportId); + const acceptanceBlock = provider.acceptanceBinding()?.dependencyBindingBlock(); + if (acceptanceBlock && !check) + loweringIssue( + state, + acceptanceBlock, + "unexpected-acceptance-binding", + "Export does not declare an acceptance hook", + ); + const acceptanceDependencies = check + ? lowerBoundDependencies(state, acceptanceBlock ?? undefined, { + exportId: check.id, + ports: new Map(check.dependencyPorts.map((port) => [port.displayName, port.id])), + }) + : undefined; return packageSymbol && exportSymbol ? [ { @@ -1864,6 +2676,7 @@ const lowerConformance = ( kind: "package" as const, packageRevisionId: packageSymbol.revisionId, exportId: exportSymbol.exportId, + ...(acceptanceDependencies ? { acceptanceDependencies } : {}), dependencies: lowerBoundDependencies( state, provider.dependencyBindingBlock() ?? undefined, @@ -1880,14 +2693,50 @@ const lowerConformance = ( const lowered = lowerRelationshipMaterialization(state, interfaceName, materialization); return lowered ? [lowered] : []; }); + const initializers = context + .conformanceItem() + .flatMap((item) => (item.initializationDecl() ? [item.initializationDecl()!] : [])); + if (initializers.length > 1) + loweringIssue(state, context, "duplicate-initialization", "Declare one initialization mapping per owner"); + const initialization = initializers.flatMap((initializer) => + initializer.initializationField().flatMap((field): import("../capability-model/types.js").InitializationField[] => { + const input = identifier(field.identifier(0)); + const attachment = requireSymbol( + state, + state.attachments, + identifier(field.identifier(1)), + field, + "initialization attachment", + ); + if (!attachment) return []; + if (field.STATE() && attachment.attachment.kind === "state") + return [{ input, binding: { kind: "state", slotId: attachment.attachment.id } }]; + if (field.EDGE() && attachment.attachment.kind === "edge") { + const projectionId = requireSymbol( + state, + attachment.projections, + identifier(field.identifier(2)), + field, + "initialization projection", + ); + return projectionId + ? [{ input, binding: { kind: "edge", edgeTypeId: attachment.attachment.id, projectionId } }] + : []; + } + loweringIssue(state, field, "invalid-initialization", "Initialization attachment kind does not match"); + return []; + }), + ); state.interfaces.set(interfaceName, originalSymbol); return { + ...(initializers.length ? { initialization } : {}), atomId, interfaceRevisionId: interfaceSymbol.revisionId, ...(context.stringLiteral() ? { id: capabilityId.conformance(stringValue(context.stringLiteral()!)) } : {}), ...(context.INTEGER() ? { semanticMajor: Number(context.INTEGER()!.getText()) } : {}), privateAttachments, operationBindings, + ...(moduleBindings.length ? { moduleBindings } : {}), relationshipMaterializations, }; }; @@ -1897,7 +2746,7 @@ const interfaceSymbolFor = (revision: InterfaceRevision): InterfaceSymbol => ({ revisionId: revision.revisionId, contractAvailable: true, members: new Map( - revision.members.map((member) => [ + (revision.template?.members ?? revision.members).map((member) => [ member.displayName, { memberId: member.id, @@ -2061,7 +2910,7 @@ const lowerWorkspace = ( } } const conformance = item.conformanceDecl(); - if (conformance) { + if (conformance && !state.interfaces.has(identifier(conformance.identifier(0)))) { state.types.self = state.atoms.get(identifier(conformance.identifier(0))); const attachments = conformance.conformanceItem().flatMap((entry) => { const declaration = entry.attachmentDecl(); @@ -2080,16 +2929,93 @@ const lowerWorkspace = ( } } - const conformances = items.flatMap((item) => { - const context = item.conformanceDecl(); - if (!context) { - return []; + const contexts = items.flatMap((item) => item.conformanceDecl() ?? []); + const rules = new Map(); + const direct: ConformanceDeclContext[] = []; + for (const declaration of contexts) { + const name = identifier(declaration.identifier(0)); + if (state.interfaces.has(name) && state.atoms.has(name)) { + loweringIssue(state, declaration, "ambiguous-conformance-source", name + " names both an atom and an interface"); + continue; } - const conformance = atGenericSource(context, () => - lowerConformance(state, context, privateAttachments.get(context) ?? []), + if (state.interfaces.has(name)) { + const rule = atGenericSource(declaration, () => lowerConformanceRule(state, declaration)); + if (rule) rules.set(rule, declaration); + } else { + if (declaration.typeParameters() || declaration.conformanceSourceArguments()) + loweringIssue(state, declaration, "invalid-conformance-source", "Only interface rules take source parameters"); + direct.push(declaration); + } + } + const origins = new Map(); + const conformances = direct.flatMap((declaration) => { + const atom = state.atoms.get(identifier(declaration.identifier(0))); + const result = atGenericSource(declaration, () => + withConformanceTypes(state, atom, [], new Map(), () => + lowerConformanceBody(state, declaration, privateAttachments.get(declaration) ?? []), + ), ); - return conformance ? [conformance] : []; + if (result) origins.set(result, declaration); + return result ? [result] : []; }); + if (!state.diagnostics.length) { + const applications = expandConformanceRules( + [...rules.keys()], + conformances, + state.types.definitions, + (atom, bindings) => ({ + ...state.types.environment(), + self: atom, + arguments: bindings, + applyInterface: (id, args) => + withConformanceTypes(state, atom, [], bindings, () => state.types.apply(id, args).revisionId), + }), + ); + for (const application of applications) { + const declaration = rules.get(application.rule)!; + const result = atGenericSource(declaration, () => + withConformanceTypes(state, application.atomId, application.rule.parameters, application.bindings, () => + lowerConformanceBody(state, declaration, [], application.atomId, application.id), + ), + ); + if (result) { + const generated = { + ...result, + id: application.id, + ruleOrigin: { + ruleId: application.rule.id, + sourceInterfaceRevisionId: application.sourceId, + line: declaration.start?.line ?? 0, + column: declaration.start?.column ?? 0, + }, + }; + conformances.push(generated); + origins.set(generated, declaration); + } + } + } + const candidates = new Map(); + for (const conformance of conformances) { + const key = JSON.stringify([conformance.atomId, conformance.interfaceRevisionId]); + const previous = candidates.get(key); + if (previous) { + const describe = (c: typeof conformance) => + c.ruleOrigin + ? "rule " + c.ruleOrigin.ruleId + " from " + c.ruleOrigin.sourceInterfaceRevisionId + : "conformance " + (c.id ?? "(unnamed)"); + const message = + "Duplicate conformance for " + + conformance.atomId + + " as " + + conformance.interfaceRevisionId + + ": " + + describe(previous) + + " conflicts with " + + describe(conformance); + for (const candidate of [previous, conformance]) + loweringIssue(state, origins.get(candidate)!, "duplicate-conformance", message); + } else candidates.set(key, conformance); + } const constructors = items.flatMap((item) => { const constructor = item.constructorBindingDecl(); @@ -2120,6 +3046,30 @@ const lowerWorkspace = ( : []; }); + const presentations: NonNullable = items.flatMap((item) => { + const presentation = item.presentationDecl(); + if (!presentation) return []; + const packageName = identifier(presentation.identifier(1)); + const componentName = identifier(presentation.identifier(2)); + const pkg = requireSymbol(state, state.packages, packageName, presentation, "package"); + const component = pkg?.definition?.modules?.find((entry) => entry.displayName === componentName); + if (!component) { + loweringIssue(state, presentation, "unknown-component", `Unknown component ${packageName}.${componentName}`); + return []; + } + return [ + { + id: stringValue(presentation.stringLiteral()), + displayName: identifier(presentation.identifier(0)), + subject: new TypeSubstitution(state.types.environment()).application( + state.types.interface(presentation.interfaceType()), + ), + packageRevisionId: pkg!.revisionId, + componentId: component.id, + }, + ]; + }); + return { imports, workspace: { @@ -2140,6 +3090,7 @@ const lowerWorkspace = ( ), conformances, constructors, + ...(presentations.length ? { presentations } : {}), }, }; }; @@ -2170,6 +3121,7 @@ const newLoweringState = (fileName: string, diagnostics: CapabilitySourceDiagnos atoms, types: new GenericSourceTypes(atoms), interfaces: new Map(), + symbols: new Map(), packages: new Map(), attachments: new Map(), }; @@ -2318,6 +3270,20 @@ const compileCapabilityResourceSourceInternal = ( const externalAtoms = externalAtomsFrom(state, preamble.atoms); const imports = registerImports(state, preamble.imports, environment); const externalInterfaces = externalInterfacesFrom(state, preamble.interfaces); + for (const declaration of (interfaceContext ?? packageContext)! + .resourcePreamble() + .flatMap((entry) => entry.symbolDecl() ?? [])) { + const name = identifier(declaration.identifier()); + const identity = stringValue(declaration.stringLiteral()); + if (name === "server" || identity === "server" || !identity.trim()) + loweringIssue( + state, + declaration, + "invalid-symbol", + "Nominal symbols require a nonempty identity distinct from the server executor", + ); + declareSymbol(state, state.symbols, name, identity, declaration, "symbol"); + } state.types.declareAliases(preamble.aliases); let resource: CapabilityResource; @@ -2373,6 +3339,22 @@ const compileCapabilityResourceSourceInternal = ( externalInterfaces, revision: lowerPackage(state, context, options.source), }; + if (resource.revision.modules?.length) { + resource.revision.clientPackages = Object.fromEntries( + imports.flatMap((entry) => { + if (entry.kind !== "package") return []; + const imported = options.environment?.packages?.get(entry.binding); + return imported ? [[entry.binding, imported.revisionId]] : []; + }), + ); + resource.revision.clientInterfaces = Object.fromEntries( + imports.flatMap((entry) => { + if (entry.kind !== "interface") return []; + const contract = options.environment?.interfaces?.get(entry.binding); + return contract && !contract.template ? [[entry.binding, contract.revisionId]] : []; + }), + ); + } } if (resource.kind === "interface") { @@ -2412,12 +3394,12 @@ const compileCapabilityResourceSourceInternal = ( portNames.add(port.displayName); const requirement = port.requirement; if (requirement.kind === "state") { - if (requirement.primitives.some((p) => !["read", "write", "watch-start", "watch-stop"].includes(p))) + if (requirement.primitives.some((p) => !["read", "write", "edit", "watch-start", "watch-stop"].includes(p))) throw new GenericTypeError("invalid-port-primitive", port.displayName, "Invalid state primitive"); types.push(requirement.valueType); } if (requirement.kind === "edge") types.push({ kind: "object-ref", expectation: requirement.target }); - if (requirement.kind === "constructor") + if (requirement.kind === "constructor" || requirement.kind === "creation") types.push(requirement.inputType, { kind: "object-ref", expectation: requirement.target }); if (requirement.kind === "interface") requires.push(requirement.application); } diff --git a/src/capability-language/pin-upgrades.ts b/src/capability-language/pin-upgrades.ts index 5748d98..21b24f9 100644 --- a/src/capability-language/pin-upgrades.ts +++ b/src/capability-language/pin-upgrades.ts @@ -19,7 +19,7 @@ import { planEvolution, type WorkspaceRevision, type EvolutionReview } from "../ import { withFileLock } from "./file-lock.js"; const execFile = promisify(callback); type Source = { repository: string; commit: string }; -export type UpgradeNode = { kind: "workspace" | "package" | "interface"; directory: string; source: Source }; +export type UpgradeNode = { kind: "workspace" | "package" | "interface" | "build"; directory: string; source: Source }; export type UpgradeSpec = { nodes: UpgradeNode[]; quixos?: Source; @@ -105,7 +105,7 @@ export const discoverUpgradeSpec = async (workbench: string): Promise ({ + ...graph.resources.map((entry: { kind: "interface" | "package" | "build"; directory: string; source: Source }) => ({ kind: entry.kind, source: entry.source, directory: path.relative(workbench, path.resolve(workbench, entry.directory)), @@ -139,7 +139,7 @@ export const planPinUpgrades = async (workbenchPath: string, spec: UpgradeSpec): const keys = new Map(); for (const node of spec.nodes) { validSource(node.source); - if (!["workspace", "package", "interface"].includes(node.kind) || keys.has(sourceKey(node))) + if (!["workspace", "package", "interface", "build"].includes(node.kind) || keys.has(sourceKey(node))) throw new Error("Duplicate/invalid upgrade resource identity"); keys.set(sourceKey(node), node.directory); } @@ -423,14 +423,14 @@ export const applyPinUpgrades = async ( const directory = await location(plan.workbench, node.directory); const standalone = await compileCapabilityResourceRepository({ rootDirectory: directory, - kind: node.kind as "package" | "interface", + kind: node.kind as "package" | "interface" | "build", source, resolveResource, }); for (const dependency of standalone.resources) resources.set(dependency.key, dependency); resources.set(key, { key, - kind: node.kind as "package" | "interface", + kind: node.kind as "package" | "interface" | "build", source, directory, lock: standalone.lock, @@ -450,11 +450,15 @@ export const applyPinUpgrades = async ( kind: node.kind, source: node.source, directory: node.directory, - resourceId: - node.resource.kind === "interface" - ? node.resource.revision.interfaceId - : node.resource.revision.packageId, - revisionId: node.resource.revision.revisionId, + ...(node.resource.kind === "build" + ? {} + : { + resourceId: + node.resource.kind === "interface" + ? node.resource.revision.interfaceId + : node.resource.revision.packageId, + revisionId: node.resource.revision.revisionId, + }), dependencies: [...node.dependencies.entries()].map(([binding, dependency]) => ({ binding, resourceKey: dependency.key, diff --git a/src/capability-language/profile.ts b/src/capability-language/profile.ts new file mode 100644 index 0000000..5ff6c79 --- /dev/null +++ b/src/capability-language/profile.ts @@ -0,0 +1,5 @@ +/** Observations belong in logs, never in content-addressed compiler outputs. */ +export const profile = (phase: string, started: number, details: Record = {}) => { + if (process.env.QUIXOS_TRACE_PROFILE === "1") + console.error(`[qx-profile] ${JSON.stringify({ phase, ms: Math.round(performance.now() - started), ...details })}`); +}; diff --git a/src/capability-language/query-generator.ts b/src/capability-language/query-generator.ts new file mode 100644 index 0000000..25d57fe --- /dev/null +++ b/src/capability-language/query-generator.ts @@ -0,0 +1,18 @@ +import path from "node:path"; +import { pathToFileURL } from "node:url"; +import type { PackageQueryGenerator } from "./assembly.js"; + +/** The build invoker selects this executable explicitly. Never resolve a + * generator by a capability name or execute a module discovered in user QX. */ +export async function loadQueryGenerator(filename?: string): Promise { + if (!filename) return undefined; + const module = await import(pathToFileURL(path.resolve(filename)).href); + if (typeof module.generatePackageQueries !== "function") + throw new Error("Query generator must export generatePackageQueries"); + const generator: PackageQueryGenerator = module.generatePackageQueries; + if (module.completeWorkspace !== undefined) { + if (typeof module.completeWorkspace !== "function") throw new Error("completeWorkspace must be a function"); + generator.completeWorkspace = module.completeWorkspace; + } + return generator; +} diff --git a/src/capability-language/query-scaffold.ts b/src/capability-language/query-scaffold.ts new file mode 100644 index 0000000..541941d --- /dev/null +++ b/src/capability-language/query-scaffold.ts @@ -0,0 +1,79 @@ +import fs from "node:fs/promises"; +import path from "node:path"; +import { parse } from "graphql"; +import type { ScaffoldRecipe } from "./scaffold-recipes.js"; +import type { StructuralRequest } from "./structural-plan.js"; +import { parseQx, walkSyntax } from "./source.js"; + +/** Insert the authored query and its document together; placement never changes implicitly. */ +export async function scaffoldQuery(root: string, prefix: string, spec: ScaffoldRecipe): Promise { + if ( + !spec.name || + !/^[A-Za-z_][A-Za-z0-9_]*$/.test(spec.name) || + !spec.id || + !spec.declaration || + typeof spec.queryDocument !== "string" + ) + throw new Error("Query scaffold requires name, id, declaration and queryDocument"); + const source = `package Draft id "draft" revision "draft@1" { ${spec.declaration} }`; + const syntax = parseQx(source); + const pkg = syntax.root.children.find((node) => node.kind === "packageResourceDecl"); + const queries = [...walkSyntax(syntax.root)].filter((node) => node.kind === "packageQuery"); + const query = queries[0]; + if ( + syntax.diagnostics.length || + syntax.root.children.length !== 1 || + !pkg || + queries.length !== 1 || + !query || + pkg.children.filter((node) => node.kind === "packageExport").length !== 1 || + pkg.children.some((node) => !["identifier", "stringLiteral", "packageExport"].includes(node.kind)) + ) + throw new Error("Expected one QX query declaration"); + const identifier = query.children.find((node) => node.kind === "identifier")!; + const literals = query.children + .filter((node) => node.kind === "stringLiteral") + .map((node) => JSON.parse(source.slice(node.start, node.end)) as string); + const [id, document, operation] = literals; + if (source.slice(identifier.start, identifier.end) !== spec.name || id !== spec.id) + throw new Error("Query declaration name/ID must match its registration"); + if (!document || !/^queries\/(?:[A-Za-z0-9_-]+\/)*[A-Za-z0-9_-]+\.graphql$/.test(document)) + throw new Error("Query document must be a contained queries/*.graphql path"); + const graphql = parse(spec.queryDocument); + const operations = graphql.definitions.filter((definition) => definition.kind === "OperationDefinition"); + if (operations.length !== 1 || operations[0]!.operation !== "query" || operations[0]!.name?.value !== operation) + throw new Error("GraphQL document must contain the declared named query operation"); + const authored = await fs.readFile(path.join(root, prefix, "package.qx"), "utf8"); + const existing = parseQx(authored); + const parent = existing.root.children.find((node) => node.kind === "packageResourceDecl"); + if (existing.diagnostics.length || !parent) throw new Error("Fix package.qx before adding a query"); + for (const node of parent.children.filter((node) => node.kind === "packageExport").flatMap((node) => node.children)) { + const name = node.children.find((child) => child.kind === "identifier"); + const literal = node.children.find((child) => child.kind === "stringLiteral"); + if ( + (name && authored.slice(name.start, name.end) === spec.name) || + (literal && JSON.parse(authored.slice(literal.start, literal.end)) === spec.id) + ) + throw new Error("New query requires a unique name and ID"); + } + const parentID = parent.children.find((node) => node.kind === "stringLiteral")!; + return { + kind: "package", + source: spec.source, + resourceRoot: spec.directory, + validation: "syntax", + files: [ + { + file: prefix + "package.qx", + edits: [ + { + operation: "append", + parent: { kind: "packageResourceDecl", id: JSON.parse(authored.slice(parentID.start, parentID.end)) }, + source: spec.declaration, + }, + ], + }, + { file: prefix + document, create: spec.queryDocument }, + ], + }; +} diff --git a/src/capability-language/resource-cli.ts b/src/capability-language/resource-cli.ts index 77c8de3..4f40494 100644 --- a/src/capability-language/resource-cli.ts +++ b/src/capability-language/resource-cli.ts @@ -5,10 +5,13 @@ import process from "node:process"; import { bindingSchema } from "../bindings/index.js"; import { compileCapabilityResourceRepository, type ResolvedCapabilityResource } from "./assembly.js"; import { createGitCapabilityResolver } from "./git-resolver.js"; +import { loadQueryGenerator } from "./query-generator.js"; +import { loadCheckedResources } from "./checked-resources.js"; -const usage = `usage: quixos-resource-compile --root DIRECTORY --kind interface|package +const usage = `usage: quixos-resource-compile --root DIRECTORY --kind interface|package|build --repository URL --commit GIT_REV --checkout-root DIRECTORY [--snapshot-map PATH] [--snapshot-only true] [--graph-out PATH] [--schema-out PATH] + [--query-generator BUILD_MODULE_PATH] [--checked-resources NIX_MAP] Resolves a standalone resource's recursive dependency-lock graph, validates its interface.qx or package.qx manifest, and emits the checked resource as JSON.`; @@ -30,6 +33,8 @@ const parseArgs = (args: string[]) => { "--snapshot-only", "--graph-out", "--schema-out", + "--query-generator", + "--checked-resources", ].includes(key) || values.has(key) ) @@ -41,7 +46,13 @@ const parseArgs = (args: string[]) => { const repository = values.get("--repository"); const commit = values.get("--commit")?.toLowerCase(); const checkoutRoot = values.get("--checkout-root"); - if (!rootDirectory || !repository || !commit || !checkoutRoot || (kind !== "interface" && kind !== "package")) { + if ( + !rootDirectory || + !repository || + !commit || + !checkoutRoot || + (kind !== "interface" && kind !== "package" && kind !== "build") + ) { throw new Error(usage); } if (!/^([0-9a-f]{40}|[0-9a-f]{64})$/.test(commit)) { @@ -59,6 +70,8 @@ const parseArgs = (args: string[]) => { snapshotOnly: values.get("--snapshot-only") === "true", graphOut: values.get("--graph-out"), schemaOut: values.get("--schema-out"), + queryGenerator: values.get("--query-generator"), + checkedResources: values.get("--checked-resources"), } as const; }; @@ -67,9 +80,13 @@ const graphEntry = (node: ResolvedCapabilityResource) => ({ kind: node.kind, source: node.source, directory: node.directory, - resourceId: - node.resource.kind === "interface" ? node.resource.revision.interfaceId : node.resource.revision.packageId, - revisionId: node.resource.revision.revisionId, + ...(node.resource.kind === "build" + ? {} + : { + resourceId: + node.resource.kind === "interface" ? node.resource.revision.interfaceId : node.resource.revision.packageId, + revisionId: node.resource.revision.revisionId, + }), dependencies: [...node.dependencies.entries()].map(([binding, dependency]) => ({ binding, resourceKey: dependency.key, @@ -96,6 +113,8 @@ const main = async () => { commit: options.commit, }, resolveResource, + generateQueries: await loadQueryGenerator(options.queryGenerator), + checkedResource: await loadCheckedResources(options.checkedResources), }); if (options.graphOut) { await writeFile( diff --git a/src/capability-language/scaffold-recipes.ts b/src/capability-language/scaffold-recipes.ts index 13f6f07..5906221 100644 --- a/src/capability-language/scaffold-recipes.ts +++ b/src/capability-language/scaffold-recipes.ts @@ -1,43 +1,26 @@ -import fs from "node:fs/promises"; -import path from "node:path"; -import { contentDigest } from "../capability-model/evolution.js"; -import { - validateMigrationCatalog, - type MigrationCatalog, - type MigrationDeclaration, -} from "../capability-model/migrations.js"; -import { formatQuixosLock, type GitSource } from "../resource-lock/index.js"; -import { parseQx, walkSyntax } from "./source.js"; +import type { GitSource } from "../resource-lock/index.js"; import type { StructuralRequest } from "./structural-plan.js"; -import { addImplementation } from "./implementation-edit.js"; -import { reactPlatformTypes } from "../bindings/react-platform.js"; - +import { executionScaffold, isExecutionScaffold } from "./execution-scaffold.js"; +import { scaffoldController } from "./controller-scaffold.js"; +import { scaffoldComponent } from "./component-scaffold.js"; +import { scaffoldQuery } from "./query-scaffold.js"; type Source = { repository: string; commit: string }; -type PackageEditModel = { - generatedBy: "qx-scaffold-v1"; - name: string; - id: string; - revision: string; - exports: { name: string; id: string; file: string; migration?: boolean }[]; -}; export type ScaffoldRecipe = { - template?: "typescript" | "typescript-react"; + template?: "typescript-execution" | "typescript-component"; source: Source; directory?: string; name?: string; id?: string; revision?: string; declaration?: string; - /** Initial authored files for a composed recipe; only used for a new package. */ + queryDocument?: string; initialFiles?: Record; - tools?: { quixos: Source; protocol: Source; helpers: Source; sdk: Source }; - nixifyPluginUrl?: string; - migration?: Omit & { contracts: Record }; + controller?: { registry?: string; subject: string; snapshot: string; remote: string; input: "unit" | "value" }; + tools?: { quixos: Source; protocol: Source; helpers: Source; sdk?: Source }; }; -const json = (value: unknown) => `${JSON.stringify(value, null, 2)}\n`; const source = (value: Source): GitSource => { if (!value || typeof value.repository !== "string" || typeof value.commit !== "string") - throw new Error("Scaffold requires an exact source identity; use qx-workspace from a registered repository"); + throw Error("Scaffold requires an exact source identity; use a registered repository"); const url = new URL(value.repository); if ( url.protocol !== "https:" || @@ -47,375 +30,36 @@ const source = (value: Source): GitSource => { url.hash || !/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/.test(value.commit) ) - throw new Error("Scaffolds require credential-free HTTPS sources and full exact commits"); + throw Error("Scaffolds require credential-free HTTPS sources and full exact commits"); return { resolver: "git", ...value }; }; const nixSource = (value: Source) => `git+${source(value).repository}?ref=refs/tags/quixos-reachability/${value.commit}&rev=${value.commit}`; -const nixString = (value: string) => JSON.stringify(value).replaceAll("${", "\\${"); -const safeName = (name: string | undefined): string => { - if (!name || !/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)) throw new Error("Scaffold requires a simple authored name"); - return name; -}; -const ownedJson = async (root: string, file: string): Promise => { - const target = path.join(root, file); - if (!(await fs.realpath(target)).startsWith(`${await fs.realpath(root)}/`)) - throw new Error("Scaffold input escapes repository"); - const value = JSON.parse(await fs.readFile(target, "utf8")); - if (value.generatedBy !== "qx-scaffold-v1") throw new Error(`Not scaffold-owned: ${file}`); - return value; -}; - -/** Recipes describe structural edits; planStructure owns validation/journaling. - * Implementation files are created once; later edits preserve authored wiring. */ -export const scaffoldRecipe = async ( +/** One-shot edits; structural planning retains interrupted work and authored files. */ +export async function scaffoldRecipe( root: string, - command: "package" | "function" | "migration" | "refresh", + command: "package" | "function" | "component" | "controller" | "query" | "migration" | "refresh", spec: ScaffoldRecipe, -): Promise => { +): Promise { if (command === "refresh") - throw new Error( - "Scaffold refresh has been removed. Edit declarations and typed server wiring directly, then run qx-workspace check. Use scaffold function to add a declaration and handler together.", + throw Error( + "Scaffold refresh is removed. Edit authored code and run check; add entries with scaffold function/component/query.", + ); + if (command === "migration") + throw Error( + "Old-workspace migration scaffolding is retired. Current disposable workspaces do not require migration code.", ); source(spec.source); if (spec.directory && !/^[A-Za-z0-9_-][A-Za-z0-9_.-]*(?:\/[A-Za-z0-9_-][A-Za-z0-9_.-]*)*$/.test(spec.directory)) - throw new Error("Scaffold directory must be contained"); - const prefix = spec.directory ? `${spec.directory}/` : ""; - const files: StructuralRequest["files"] = []; - const create = (file: string, content: string) => files.push({ file: prefix + file, create: content }); - const generated = (file: string, content: string) => files.push({ file: prefix + file, generated: content }); - let packageModel: PackageEditModel; - let catalog: MigrationCatalog & { generatedBy: "qx-scaffold-v1" }; - if (command === "package") { - const name = safeName(spec.name); - if (spec.template && !["typescript", "typescript-react"].includes(spec.template)) - throw new Error("Unknown package template"); - const react = spec.template === "typescript-react"; - if (!spec.id || !spec.revision || !spec.tools) - throw new Error("Package scaffold requires id, revision, and exact quixos/protocol/helpers/sdk tool sources"); - Object.values(spec.tools).forEach(source); - packageModel = { generatedBy: "qx-scaffold-v1", name, id: spec.id, revision: spec.revision, exports: [] }; - catalog = { generatedBy: "qx-scaffold-v1", schemaVersion: 1, contracts: {}, migrations: [] }; - if (react) - packageModel.exports.push({ name: "sourceGet", id: `export:${name}:source`, file: "src/impl/sourceGet.ts" }); - create( - "package.qx", - `package ${name} id ${JSON.stringify(spec.id)} revision ${JSON.stringify(spec.revision)} {\n${react ? ` function sourceGet id ${JSON.stringify(`export:${name}:source`)} : unit -> string;\n` : ""}}\n`, - ); - create("quixos.lock", formatQuixosLock({ formatVersion: 1, quixos: source(spec.tools.quixos), resources: [] })); - create( - "package.json", - json({ - name: `@quixos/${name.toLowerCase()}`, - version: "0.1.0", - private: true, - type: "module", - packageManager: "yarn@4.18.0", - scripts: { build: "tsc -p tsconfig.json", typecheck: "tsc --noEmit" }, - dependencies: { - "@quixos/camino-package-runtime": `${spec.tools.sdk.repository}#commit=${spec.tools.sdk.commit}`, - }, - devDependencies: { - "@types/node": "^24", - typescript: "^7.0.2", - ...(react ? { react: "^18.3.1", "@types/react": "^18.3.12", esbuild: "^0.25.12" } : {}), - }, - }), - ); - create( - "tsconfig.json", - json({ - compilerOptions: { - target: "ES2023", - module: "NodeNext", - moduleResolution: "NodeNext", - strict: true, - types: ["node", ...(react ? ["react"] : [])], - outDir: "dist", - rootDir: "src", - skipLibCheck: true, - ...(react ? { jsx: "react-jsx", esModuleInterop: true } : {}), - }, - include: ["src/**/*.ts", "src/**/*.tsx"], - }), - ); - if (react) { - create( - "src/component.tsx", - `// Add a checked props export, select it in quixos.check.json options.react,\n// then use its generated ReactResults type. Scaffold bundle does this for you.\nexport default function Component(_props: {camino: Record; render: unknown; dispatch: (action: unknown) => void}) {\n return

${name}

;\n}\n`, - ); - create( - "src/impl/sourceGet.ts", - `import componentSource from "../component.js?browser-source";\nimport type {Implementation} from "../gen/qx.js";\nexport const handler: Implementation["sourceGet"] = () => componentSource;\n`, - ); - create( - "src/browser-assets.d.ts", - `declare module "*?browser-source" { const source: string; export default source; }\ndeclare module "*.css" {}\n`, - ); - create("src/gen/web-studio-react-runtime.d.ts", reactPlatformTypes); - } - create(".gitignore", "node_modules/\ndist/\n.quixos/\nresult\n.yarn/install-state.gz\n"); - create( - ".yarnrc.yml", - `nodeLinker: node-modules\nenableScripts: true\nnpmMinimalAgeGate: 0\napprovedGitRepositories:\n - ${JSON.stringify(spec.tools.sdk.repository)}\nsupportedArchitectures:\n os: [current, linux]\n cpu: [current, x64, arm64]\n libc: [current, glibc]\n`, - ); - const nixifyPluginUrl = - spec.nixifyPluginUrl ?? - "https://gitea-external.egads.tutti.syntaxblitz.net/quixos/yarn-plugin-nixify-patched/raw/commit/4528fdd20b30d869262443b3f044549810e75fb8/dist/yarn-plugin-nixify.js"; - const plugin = new URL(nixifyPluginUrl); - if ( - plugin.protocol !== "https:" || - plugin.username || - plugin.password || - plugin.search || - plugin.hash || - !/\/commit\/[a-f0-9]{40,64}\//.test(plugin.pathname) - ) - throw new Error("Nixify plugin must have an exact credential-free HTTPS commit URL"); - generated("quixos.toolchain.json", json({ generatedBy: "qx-scaffold-v1", nixifyPluginUrl })); - create( - "quixos.check.json", - json({ - backend: "typescript", - bindingOutput: "src/gen/qx.ts", - ...(react - ? { - options: { react: { propsExports: [] } }, - } - : {}), - }), - ); - create( - "flake.nix", - `{ - inputs.protocol.url = ${nixString(nixSource(spec.tools.protocol))}; - inputs.nixpkgs.follows = "protocol/nixpkgs"; - inputs.flake-utils.follows = "protocol/flake-utils"; - inputs.helpers = { url = ${nixString(nixSource(spec.tools.helpers))}; flake = false; }; - outputs = inputs@{ self, protocol, nixpkgs, flake-utils, helpers, ... }: - (import (toString helpers + "/quixos-package-helpers.nix")).mkCaminoTsYarnNixifyFlake { - inherit inputs nixpkgs flake-utils; packageRoot = ./.; - bundle = { entry = ${JSON.stringify(react ? "src/server.ts" : "dist/server.js")}; ${react ? "browserSources = true;" : ""} }; - migrationEntrypoint = "dist/migrate.js"; - installServer = { libexecName = ${JSON.stringify(name.toLowerCase())}; descriptorPath = "descriptor.quixos-package.txtpb"; }; - }; -}\n`, - ); - } else { - catalog = await ownedJson(root, prefix + "quixos.migrations.json"); - // Derive the edit model from authored declarations; it is never persisted. - const authored = await fs.readFile(path.join(root, prefix, "package.qx"), "utf8"); - const syntax = parseQx(authored); - if (syntax.diagnostics.length) - throw new Error("Cannot scaffold into an invalid package.qx; fix the reported syntax first"); - const declaration = [...walkSyntax(syntax.root)].find((node) => node.kind === "packageResourceDecl"); - if (!declaration) throw new Error("Expected a package declaration"); - const text = (node: typeof declaration) => authored.slice(node.start, node.end); - const literals = declaration.children.filter((node) => node.kind === "stringLiteral"); - packageModel = { - generatedBy: "qx-scaffold-v1", - name: text(declaration.children.find((node) => node.kind === "identifier")!), - id: JSON.parse(text(literals[0])), - revision: JSON.parse(text(literals[1])), - exports: [], - }; - for (const node of walkSyntax(declaration)) { - if (!["packageFunctionExport", "packageOperationExport", "packageConstructorExport"].includes(node.kind)) - continue; - const name = safeName(text(node.children.find((child) => child.kind === "identifier")!)); - const id = JSON.parse(text(node.children.find((child) => child.kind === "stringLiteral")!)); - if (packageModel.exports.some((entry) => entry.id === id || entry.name === name)) - throw new Error("Duplicate package export name or ID"); - const migration = catalog.migrations.find((entry) => entry.implementation.exportId === id); - packageModel.exports.push({ - name, - id, - file: migration?.implementation.file ?? `src/impl/${name}.ts`, - ...(migration ? { migration: true } : {}), - }); - } - { - const name = safeName(spec.name); - if (!spec.id || packageModel.exports.some((entry) => entry.id === spec.id || entry.name === name)) - throw new Error("New export requires a unique name and ID"); - const declaration = spec.declaration ?? `function ${name} id ${JSON.stringify(spec.id)} : unit -> unit;`; - const parsed = parseQx(`package Scaffold id "scaffold" revision "scaffold@1" { ${declaration} }`); - const exports = [...walkSyntax(parsed.root)].filter((node) => - ["packageFunctionExport", "packageOperationExport", "packageConstructorExport"].includes(node.kind), - ); - if (parsed.diagnostics.length || exports.length !== 1) - throw new Error("Expected one valid package export declaration"); - const wrapped = `package Scaffold id "scaffold" revision "scaffold@1" { ${declaration} }`; - const node = exports[0]; - const derived = [...walkSyntax(node)].some((entry) => entry.kind === "eventClause"); - if (command === "migration" && (node.kind !== "packageFunctionExport" || spec.declaration)) - throw new Error( - "Migration exports use the scaffold's unit function declaration and dedicated migration entrypoint", - ); - if ( - wrapped.slice( - node.children.find((child) => child.kind === "identifier")!.start, - node.children.find((child) => child.kind === "identifier")!.end, - ) !== name || - JSON.parse( - wrapped.slice( - node.children.find((child) => child.kind === "stringLiteral")!.start, - node.children.find((child) => child.kind === "stringLiteral")!.end, - ), - ) !== spec.id - ) - throw new Error("Declaration name/ID must match its registration"); - files.push({ - file: prefix + "package.qx", - edits: [ - { operation: "append", parent: { kind: "packageResourceDecl", id: packageModel.id }, source: declaration }, - ], - }); - const file = `src/${command === "migration" ? "migrations" : "impl"}/${name}.ts`; - const implementation = - command === "migration" - ? `import type {MigrationContext} from "@quixos/camino-package-runtime";\nexport const handler = async (_context: MigrationContext): Promise => { throw new Error(${JSON.stringify(`Implement migration ${name}`)}); };\n` - : `import type {Implementation} from "../gen/qx.js";\nexport const handler: Implementation[${JSON.stringify(name)}] = ${derived ? '{kind: "derived", get: ' : ""}async (_context) => { throw new Error(${JSON.stringify(`Implement ${name}`)}); }${derived ? "}" : ""};\n`; - create(file, implementation); - packageModel.exports.push({ name, id: spec.id, file, ...(command === "migration" ? { migration: true } : {}) }); - if (command === "migration") { - if (!spec.migration) - throw new Error("Migration scaffold requires retained contracts and an explicit transition"); - const { contracts, ...transition } = spec.migration; - for (const [digest, contract] of Object.entries(contracts)) { - if ( - contentDigest(contract) !== digest || - (catalog.contracts[digest] && contentDigest(catalog.contracts[digest]) !== digest) - ) - throw new Error("Retained migration contract mismatch"); - catalog.contracts[digest] = contract; - } - catalog.migrations.push({ - ...transition, - implementation: { exportId: spec.id, file, digest: contentDigest(implementation) }, - }); - } - } - } - validateMigrationCatalog(catalog, new Set(packageModel.exports.map((entry) => entry.id))); - generated("quixos.migrations.json", json(catalog)); - if (command !== "package") { - const entry = packageModel.exports.at(-1)!; - const file = prefix + "src/server.ts"; - const before = await fs.readFile(path.join(root, file), "utf8"); - files.push({ - file, - expected: before, - replace: addImplementation( - before, - "createRuntime", - entry.name, - `./${entry.file.slice(4, -3)}.js`, - !!entry.migration, - ), - }); - if (entry.migration) { - const file = prefix + "src/migrate.ts"; - const before = await fs.readFile(path.join(root, file), "utf8"); - files.push({ - file, - expected: before, - replace: addImplementation(before, "serveMigration", entry.id, `./${entry.file.slice(4, -3)}.js`), - }); - } - } else { - create( - "src/server.ts", - `import {servePackageRuntime} from "@quixos/camino-package-runtime";\nimport {createRuntime} from "./gen/qx.js";\n` + - packageModel.exports - .filter((entry) => !entry.migration) - .map( - (entry, index) => - `import {handler as impl${index}} from ${JSON.stringify(`./${entry.file.slice(4, -3)}.js`)};\n`, - ) - .join("") + - `servePackageRuntime(createRuntime({\n` + - packageModel.exports - .map( - (entry) => - ` ${JSON.stringify(entry.name)}: ${entry.migration ? 'async () => { throw new Error("Migration-only export"); }' : `impl${packageModel.exports.filter((value) => !value.migration).indexOf(entry)}`},`, - ) - .join("\n") + - `\n}));\n`, - ); - const migrations = packageModel.exports.filter((entry) => entry.migration); - create( - "src/migrate.ts", - `import {serveMigration} from "@quixos/camino-package-runtime";\n` + - migrations - .map( - (entry, index) => - `import {handler as impl${index}} from ${JSON.stringify(`./${entry.file.slice(4, -3)}.js`)};\n`, - ) - .join("") + - `await serveMigration({${migrations.map((entry, index) => `${JSON.stringify(entry.id)}: impl${index}`).join(", ")}});\n`, - ); - } - generated( - "descriptor.quixos-package.txtpb", - `# Generated by qx-scaffold-v1\npackage_id: ${JSON.stringify(packageModel.id)}\npackage_revision_id: ${JSON.stringify(packageModel.revision)}\nruntime_protocol_version: "quixos-capabilities-v1"\n` + - packageModel.exports - .map( - (entry) => - `exports: { export_id: ${JSON.stringify(entry.id)} runtime_symbol: ${JSON.stringify(entry.name)} }\n`, - ) - .join(""), - ); - if (spec.initialFiles) { - if (command !== "package") throw new Error("initialFiles is only valid when creating a package"); - const authored = spec.initialFiles["package.qx"]; - if (authored !== undefined) { - const parsed = parseQx(authored); - const declaration = [...walkSyntax(parsed.root)].find((n) => n.kind === "packageResourceDecl"); - const literals = declaration?.children.filter((n) => n.kind === "stringLiteral") ?? []; - const identifier = declaration?.children.find((n) => n.kind === "identifier"); - if ( - parsed.diagnostics.length || - literals.length !== 2 || - !identifier || - authored.slice(identifier.start, identifier.end) !== spec.name || - JSON.parse(authored.slice(literals[0].start, literals[0].end)) !== spec.id || - JSON.parse(authored.slice(literals[1].start, literals[1].end)) !== spec.revision - ) - throw new Error("Initial package declaration must match the provisioned name, ID and revision"); - } - for (const [file, content] of Object.entries(spec.initialFiles)) { - if ( - typeof content !== "string" || - ["quixos.lock", "flake.nix", "quixos.toolchain.json", "package.json"].includes(file) - ) - throw new Error(`Not an initial authored file: ${file}`); - if (file === "quixos.check.json") { - const check = JSON.parse(content); - if ( - check.backend !== "typescript" || - check.bindingOutput !== "src/gen/qx.ts" || - Object.keys(check).some((key) => !["backend", "bindingOutput", "options"].includes(key)) - ) - throw new Error( - "Initial check configuration may customize binding options, not the scaffold verification backend or output", - ); - } - const existing = files.findIndex((entry) => entry.file === prefix + file); - if (existing >= 0) files.splice(existing, 1); - create(file, content); - } - // A composed React recipe supplies its own modules and complete server. - if (reactRecipe(spec)) { - for (const file of ["src/component.tsx", "src/impl/sourceGet.ts", "descriptor.quixos-package.txtpb"]) - if (!(file in spec.initialFiles)) { - const index = files.findIndex((entry) => entry.file === prefix + file); - if (index >= 0) files.splice(index, 1); - } - } - } - return { kind: "package", source: spec.source, resourceRoot: spec.directory, validation: "syntax", files }; -}; - -const reactRecipe = (spec: ScaffoldRecipe) => - spec.template === "typescript-react" && spec.initialFiles?.["package.qx"] && spec.initialFiles?.["src/server.ts"]; + throw Error("Scaffold directory must be contained"); + if (spec.template && !["typescript-execution", "typescript-component"].includes(spec.template)) + throw Error("Use typescript-execution or typescript-component; old server/source-string presets are retired"); + const prefix = spec.directory ? spec.directory + "/" : ""; + if (command !== "package" && !(await isExecutionScaffold(root, prefix))) + throw Error("Scaffolding requires a checked execution package"); + if (spec.tools) Object.values(spec.tools).forEach(source); + if (command === "controller") return scaffoldController(root, prefix, spec); + if (command === "component") return scaffoldComponent(root, prefix, spec); + if (command === "query") return scaffoldQuery(root, prefix, spec); + return executionScaffold(root, command, spec, prefix, nixSource); +} diff --git a/src/capability-language/source-loader.ts b/src/capability-language/source-loader.ts index 89d63e5..01f086e 100644 --- a/src/capability-language/source-loader.ts +++ b/src/capability-language/source-loader.ts @@ -86,3 +86,21 @@ export const resolveQxSources = async (read: (name: string) => Promise, }; export const loadQxSources = (root: string) => resolveQxSources((name) => readQxSource(root, name)); + +/** The same validated closure used by compilation, for installation and pinning. */ +export const qxSourceManifest = async (root: string) => { + const resolved = await loadQxSources(root); + const syntax = parseQx(resolved.source); + const imports = [...walkSyntax(syntax.root)] + .filter((node) => node.kind === "resourceImportDecl") + .map((node) => { + const name = node.children.find((child) => child.kind === "identifier")!; + const kind = syntax.tokens.some( + (token) => token.start >= node.start && token.end <= node.end && token.kind === "INTERFACE", + ) + ? ("interface" as const) + : ("package" as const); + return { kind, binding: resolved.source.slice(name.start, name.end) }; + }); + return { sourceFiles: resolved.sourceFiles, imports }; +}; diff --git a/src/capability-language/structural-edits.ts b/src/capability-language/structural-edits.ts index c73b1a7..2dcc38d 100644 --- a/src/capability-language/structural-edits.ts +++ b/src/capability-language/structural-edits.ts @@ -15,7 +15,7 @@ export type StructuralEdit = | { operation: "quixos-pin"; source: { repository: string; commit: string } } | { operation: "dependency"; - kind: "interface" | "package"; + kind: "interface" | "package" | "build"; name: string; source: { repository: string; commit: string } | null; }; @@ -29,6 +29,8 @@ const selectable = new Set([ "packageResourceDecl", "atomDecl", "valueMember", + "moduleMember", + "moduleExport", "relationshipMember", "operationMember", "packageOperationExport", @@ -155,7 +157,7 @@ export const editStructure = (source: string, edit: StructuralEdit): string => { if (edit.operation === "dependency") { const parsed = parseQuixosLockDocument(source); if (!parsed.ok) throw new Error("Cannot scaffold an invalid lockfile"); - if (!["interface", "package"].includes(edit.kind) || !/^[A-Za-z_][A-Za-z0-9_]*$/.test(edit.name)) + if (!["interface", "package", "build"].includes(edit.kind) || !/^[A-Za-z_][A-Za-z0-9_]*$/.test(edit.name)) throw new Error("Invalid dependency selector"); const parser = new QuixosLockParser(new CommonTokenStream(new QuixosLockLexer(CharStream.fromString(source)))); const tree = parser.document(); @@ -208,7 +210,8 @@ export const editStructure = (source: string, edit: StructuralEdit): string => { return source; } const identifiers = node.children.filter((entry) => entry.kind === "identifier"); - const position = identifiers[identifiers.length - 1].end; + const targetArguments = node.children.find((entry) => entry.kind === "typeArguments"); + const position = targetArguments?.end ?? identifiers[identifiers.length - 1].end; result = applySourceEdits(source, [{ start: position, end: position, text: ` id ${JSON.stringify(edit.id)}` }]); } else if (edit.operation === "append") { const closing = syntax.tokens.find((token) => token.kind === "RBRACE" && token.end === node.end); diff --git a/src/capability-language/structural-plan.ts b/src/capability-language/structural-plan.ts index f831f1c..6b03c5f 100644 --- a/src/capability-language/structural-plan.ts +++ b/src/capability-language/structural-plan.ts @@ -13,7 +13,7 @@ import { parseQuixosLockDocument } from "../resource-lock/index.js"; import { withFileLock } from "./file-lock.js"; export type StructuralRequest = { - kind: "workspace" | "interface" | "package"; + kind: "workspace" | "interface" | "package" | "build"; source?: { repository: string; commit: string }; resourceRoot?: string; validation?: "syntax" | "resource-graph"; @@ -28,7 +28,7 @@ type Change = { file: string; before: string | null; after: string; mode: number type Journal = { schemaVersion: 1; id: string; root: string; phase: "prepared" | "complete"; changes: Change[] }; const safeFile = (file: string) => { if ( - !/^(?:[A-Za-z0-9_-][A-Za-z0-9_.-]*\/)*(?:\.gitignore|\.yarnrc.yml|[A-Za-z0-9_-][A-Za-z0-9_.-]*\.(?:qx|graphql|lock|ts|tsx|css|mjs|json|nix|txtpb|md))$/.test( + !/^(?:[A-Za-z0-9_-][A-Za-z0-9_.-]*\/)*(?:\.gitignore|\.yarnrc\.yml|\.yarn\/plugins\/[A-Za-z0-9_-][A-Za-z0-9_.-]*\.cjs|[A-Za-z0-9_-][A-Za-z0-9_.-]*\.(?:qx|graphql|lock|ts|tsx|css|mjs|json|nix|txtpb|md))$/.test( file, ) || file.split("/").some((part) => [".git", ".jj", ".quixos", "node_modules"].includes(part)) @@ -141,7 +141,7 @@ export const planStructure = async (rootPath: string, request: StructuralRequest for (const change of changes) { if (change.file.endsWith(".qx") && parseQx(change.after, change.file).diagnostics.length) throw new Error(`Invalid QX syntax in ${change.file}`); - if (change.file.endsWith(".lock") && !parseQuixosLockDocument(change.after, change.file).ok) + if (path.basename(change.file) === "quixos.lock" && !parseQuixosLockDocument(change.after, change.file).ok) throw new Error(`Invalid lock syntax in ${change.file}`); } } else { @@ -159,25 +159,30 @@ export const planStructure = async (rootPath: string, request: StructuralRequest const resourceRoot = path.join(snapshot.directory, request.resourceRoot ?? ""); if (request.kind === "workspace") await compileWorkspaceRepository({ rootDirectory: resourceRoot, resolveResource }); - else if (["package", "interface"].includes(request.kind) && request.source) { + else if (["package", "interface", "build"].includes(request.kind) && request.source) { const compiled = await compileCapabilityResourceRepository({ rootDirectory: resourceRoot, - kind: request.kind as "package" | "interface", + kind: request.kind as "package" | "interface" | "build", source: { resolver: "git", ...request.source }, resolveResource, }); if (compiled.resource.kind === "package") { const configuration = JSON.parse(await fs.readFile(path.join(resourceRoot, "quixos.check.json"), "utf8")); - const artifacts = [ - { - file: configuration.bindingOutput as string, - after: generateTypeScriptBindings( - bindingSchema(compiled), - compiled.resource.revision.revisionId, - configuration.options, - ), - }, - ]; + // Execution bindings are immutable build outputs owned by checkedArtifacts, + // not source edits produced by a structural preview. + const artifacts = + configuration.backend === "execution" + ? [] + : [ + { + file: configuration.bindingOutput as string, + after: generateTypeScriptBindings( + bindingSchema(compiled), + compiled.resource.revision.revisionId, + configuration.options, + ), + }, + ]; for (const artifact of artifacts) { const file = request.resourceRoot ? `${request.resourceRoot}/${artifact.file}` : artifact.file; safeFile(file); diff --git a/src/capability-language/tool-cli.ts b/src/capability-language/tool-cli.ts index c407d07..9e85703 100644 --- a/src/capability-language/tool-cli.ts +++ b/src/capability-language/tool-cli.ts @@ -2,7 +2,7 @@ import { readFile, writeFile, mkdtemp, rm, realpath } from "node:fs/promises"; import { parseQx, formatQx, lintQx } from "./source.js"; import { scaffoldAtom } from "./scaffold.js"; -import { loadQxSources } from "./source-loader.js"; +import { loadQxSources, qxSourceManifest } from "./source-loader.js"; import { createGitCapabilityResolver } from "./git-resolver.js"; import { planEvolution } from "../capability-model/index.js"; import { snapshotRepository } from "./candidate-check.js"; @@ -54,6 +54,11 @@ const planSummary = (plan: Awaited>) => ({ const main = async () => { const [command, ...args] = process.argv.slice(2); + if (command === "source-manifest") { + if (args.length !== 1) throw new Error("source-manifest WORKSPACE_ROOT"); + process.stdout.write(JSON.stringify(await qxSourceManifest(args[0])) + "\n"); + return; + } if (command === "scaffold-validate-qx") { if (args.length !== 1) throw new Error("scaffold-validate-qx SOURCES_JSON"); const sources = JSON.parse(args[0]); @@ -192,16 +197,16 @@ const main = async () => { } if (command === "check-committed" && !args.includes("--help")) { const [kind, repository, commit, log, ...extra] = args; - if (!["workspace", "interface", "package"].includes(kind) || !log || extra.length) - throw new Error("usage: quixos-qx check-committed workspace|interface|package REPOSITORY COMMIT LOG_FILE"); + if (!["workspace", "interface", "package", "build"].includes(kind) || !log || extra.length) + throw new Error("usage: quixos-qx check-committed workspace|interface|package|build REPOSITORY COMMIT LOG_FILE"); process.stdout.write( - `${await buildImmutableCandidate({ repository, commit }, kind as "workspace" | "interface" | "package", log)}\n`, + `${await buildImmutableCandidate({ repository, commit }, kind as "workspace" | "interface" | "package" | "build", log)}\n`, ); return; } if (!command || command === "--help" || args.includes("--help")) { process.stdout.write( - "quixos-qx: author-check, author-contract, converge, worklist, inspect, resources, check-committed, source-baseline, scaffold-package, scaffold-interface, scaffold-function, scaffold-dependency, scaffold-structure, scaffold-resume, pin-upgrade, parse, lint, format\n" + + "quixos-qx: author-check, author-contract, converge, worklist, inspect, resources, check-committed, source-baseline, scaffold-package, scaffold-interface, scaffold-build, scaffold-function, scaffold-query, scaffold-dependency, scaffold-structure, scaffold-resume, pin-upgrade, parse, lint, format\n" + "inspect WORKBENCH [RESOURCE] shows provisional contracts, with explicit historical fallback; never verification evidence.\n" + "resources WORKBENCH lists registered editable repositories. Use qx-workspace for the workspace authoring workflow.\n", ); @@ -226,7 +231,7 @@ const main = async () => { if (flags.length && !flags[0].startsWith("--")) { repository = flags.shift(); commit = flags.shift(); - } else if (root && ["interface", "package"].includes(kind) && name) { + } else if (root && ["interface", "package", "build"].includes(kind) && name) { const context = await authoringContext(root); const alias = await realpath(path.join(context.workbench, `${kind}s`, name)).catch(() => null); const matches = context.resources.filter( @@ -256,15 +261,17 @@ const main = async () => { } if ( !root || - !["interface", "package"].includes(kind) || + !["interface", "package", "build"].includes(kind) || !/^[A-Za-z_][A-Za-z0-9_]*$/.test(name ?? "") || !repository || !commit || flags.some((flag) => flag !== "--write") ) - throw new Error("usage: quixos-qx scaffold-dependency ROOT interface|package NAME REPOSITORY COMMIT [--write]"); - const resourceKind = kind as "package" | "interface"; - let entrypoint: "workspace" | "package" | "interface" | undefined; + throw new Error( + "usage: quixos-qx scaffold-dependency ROOT interface|package|build NAME REPOSITORY COMMIT [--write]", + ); + const resourceKind = kind as "package" | "interface" | "build"; + let entrypoint: "workspace" | "package" | "interface" | "build" | undefined; for (const candidate of ["workspace", "package", "interface"] as const) { try { await readFile(path.join(root, `${candidate}.qx`)); @@ -274,7 +281,14 @@ const main = async () => { if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error; } } - if (!entrypoint) throw new Error("No QX repository entrypoint"); + if (!entrypoint) { + const context = await authoringContext(root); + const selected = context.resources.find( + (entry) => path.join(context.workbench, entry.directory) === path.resolve(root), + ); + if (selected?.kind !== "build") throw new Error("No QX repository entrypoint"); + entrypoint = "build"; + } const lock = await loadQuixosLock(path.join(root, "quixos.lock")); if (!lock.ok) throw new Error("Invalid resource lock"); let target = "quixos.lock"; @@ -288,7 +302,9 @@ const main = async () => { source: await authorSource(root), validation: "syntax", files: [ - { file: `${entrypoint}.qx`, edits: [{ operation: "import", kind: resourceKind, name }] }, + ...(entrypoint !== "build" && resourceKind !== "build" + ? [{ file: `${entrypoint}.qx`, edits: [{ operation: "import" as const, kind: resourceKind, name }] }] + : []), { file: target, edits: [{ operation: "dependency", kind: resourceKind, name, source: { repository, commit } }], @@ -301,26 +317,45 @@ const main = async () => { ); return; } - if (command === "scaffold-interface") { + if (command === "scaffold-interface" || command === "scaffold-build") { + const isBuild = command === "scaffold-build"; const [root, specFile, ...flags] = args; if (!root || !specFile || flags.some((flag) => flag !== "--write")) - throw new Error("usage: quixos-qx scaffold-interface ROOT SPEC_JSON [--write]"); + throw new Error("usage: quixos-qx scaffold-interface|scaffold-build ROOT SPEC_JSON [--write]"); const spec = (await readSpec(specFile)) as ScaffoldRecipe; - if (!spec.name || !/^[A-Za-z_][A-Za-z0-9_]*$/.test(spec.name) || !spec.id || !spec.revision || !spec.tools?.quixos) - throw new Error("Interface scaffold requires name, id, revision and Quixos toolchain source"); + if ( + !spec.name || + !/^[A-Za-z_][A-Za-z0-9_]*$/.test(spec.name) || + (!isBuild && (!spec.id || !spec.revision)) || + !spec.tools?.quixos + ) + throw new Error( + isBuild + ? "Build scaffold requires name and Quixos toolchain source" + : "Interface scaffold requires name, id, revision and Quixos toolchain source", + ); const request: StructuralRequest = { - kind: "interface", + kind: isBuild ? "build" : "interface", // Like package scaffolding, declaration creation is provisional. Imports // can be attached next; the normal check verifies the complete graph. validation: "syntax", source: spec.source, files: [ - { - file: "interface.qx", - create: - spec.declaration ?? - `interface ${spec.name} id ${JSON.stringify(spec.id)} revision ${JSON.stringify(spec.revision)} {\n}\n`, - }, + ...(!isBuild + ? [ + { + file: "interface.qx", + create: + spec.declaration ?? + `interface ${spec.name} id ${JSON.stringify(spec.id)} revision ${JSON.stringify(spec.revision)} {\n}\n`, + }, + ] + : [ + { + file: "README.md", + create: `# ${spec.name}\n\nManaged build dependency. Consumers select sources or Nix outputs through context.dependencies.\n`, + }, + ]), { file: "quixos.lock", create: formatQuixosLock({ @@ -385,9 +420,20 @@ const main = async () => { } if (command === "scaffold-refresh") throw new Error( - "Refresh is no longer required: edit declarations and typed implementation wiring, then run qx-workspace check. Dependency installation uses scaffold install.", + "Refresh is no longer required: edit declarations and typed implementation wiring, then run qx-workspace check. Dependencies are provided by checkedArtifacts.", ); - if (["scaffold-package", "scaffold-function", "scaffold-migration", "scaffold-install"].includes(command)) { + if (command === "scaffold-install") + throw Error("Execution dependencies are supplied by checkedArtifacts; run workspace-root check instead"); + if ( + [ + "scaffold-package", + "scaffold-function", + "scaffold-query", + "scaffold-component", + "scaffold-controller", + "scaffold-migration", + ].includes(command) + ) { const [root, specFile, ...flags] = args; let spec: ScaffoldRecipe; if (command === "scaffold-function" && /^[A-Za-z_][A-Za-z0-9_]*$/.test(specFile ?? "")) { @@ -420,46 +466,24 @@ const main = async () => { flags.some((flag) => !["--write", "--install"].includes(flag)) || (flags.includes("--install") && !flags.includes("--write")) ) - throw new Error( - "usage: quixos-qx scaffold-package|function|migration|refresh ROOT SPEC_JSON [--write [--install]]", - ); + throw new Error("usage: quixos-qx scaffold-package|function|query|component|controller ROOT SPEC_JSON [--write]"); const plan = command === "scaffold-install" ? undefined : await planStructure( root, - await scaffoldRecipe(root, command.slice(9) as "package" | "function" | "migration", spec), + await scaffoldRecipe( + root, + command.slice(9) as "package" | "function" | "query" | "component" | "controller" | "migration", + spec, + ), process.env.QUIXOS_SNAPSHOT_MAP, ); + if (flags.includes("--install")) + throw Error( + "Execution dependencies are supplied by checkedArtifacts; run workspace-root check instead of scaffold install", + ); const applied = plan && flags.includes("--write") ? await applyStructure(plan) : undefined; - if (flags.includes("--install")) { - const cwd = path.resolve(root, spec.directory ?? ""); - const toolchain = JSON.parse(await readFile(path.join(cwd, "quixos.toolchain.json"), "utf8")); - if (toolchain.generatedBy !== "qx-scaffold-v1" || typeof toolchain.nixifyPluginUrl !== "string") - throw new Error("Missing scaffold toolchain"); - for (const [executable, args] of [ - ["corepack", ["yarn", "plugin", "import", toolchain.nixifyPluginUrl]], - ["corepack", ["yarn", "config", "set", "generateDefaultNix", "false"]], - ["corepack", ["yarn", "config", "set", "individualNixPackaging", "true"]], - ["corepack", ["yarn", "install"]], - ] as const) { - const result = spawnSync(executable, [...args], { cwd, stdio: ["inherit", 2, 2] }); - if (result.error || result.status !== 0) - throw new Error( - `Scaffold files retained; ${executable} ${args.join(" ")} failed: ${result.error?.message ?? result.status}`, - ); - } - try { - await readFile(path.join(cwd, "yarn-project.nix")); - } catch { - throw new Error( - "Nixify did not generate yarn-project.nix. It skips repositories under the OS temporary directory; use an ordinary workspace checkout and retry installation.", - ); - } - await snapshotCommit(cwd); - const locked = spawnSync("nix", ["flake", "lock"], { cwd, stdio: ["inherit", 2, 2] }); - if (locked.error || locked.status !== 0) throw new Error("Scaffold files retained; nix flake lock failed"); - } process.stdout.write( `${JSON.stringify({ ...(plan ? planSummary(plan) : { installed: true }), applied }, null, 2)}\n`, ); diff --git a/src/capability-language/workspace-cli.ts b/src/capability-language/workspace-cli.ts index f56f80b..1159d8c 100644 --- a/src/capability-language/workspace-cli.ts +++ b/src/capability-language/workspace-cli.ts @@ -4,6 +4,10 @@ import { readFile, writeFile } from "node:fs/promises"; import process from "node:process"; import { compileWorkspaceRepository } from "./assembly.js"; import { createGitCapabilityResolver } from "./git-resolver.js"; +import { loadQueryGenerator } from "./query-generator.js"; +import { loadCheckedResources } from "./checked-resources.js"; +import { profile } from "./profile.js"; +import { compileExecutionPlan, packageBuildContexts } from "../execution-world/index.js"; import { bindingSchema, specializeBindingSchema } from "../bindings/index.js"; import { planEvolution, @@ -15,7 +19,8 @@ import { const usage = `usage: quixos-workspace-compile --root DIRECTORY --checkout-root DIRECTORY [--snapshot-map PATH] [--graph-out PATH] [--workspace-id ID] [--workspace-revision-id ID] [--source-root-commit GIT_REV] [--baseline PLAN_JSON] [--evolution-out PATH] - [--reviews REVIEW_JSON] [--schemas-out PATH] + [--reviews REVIEW_JSON] [--schemas-out PATH] [--execution-plan-out PATH] [--build-contexts-out PATH] + [--query-generator BUILD_MODULE_PATH] [--checked-resources NIX_MAP] Resolves a workspace's recursive resource-lock graph, clones every exact resource revision, validates standalone interface/package manifests, and emits @@ -37,6 +42,8 @@ const parseArgs = (args: string[]) => { checkoutRoot, graphOut: values.get("--graph-out"), schemasOut: values.get("--schemas-out"), + executionPlanOut: values.get("--execution-plan-out"), + buildContextsOut: values.get("--build-contexts-out"), snapshotMap: values.get("--snapshot-map"), workspaceId: values.get("--workspace-id"), workspaceRevisionId: values.get("--workspace-revision-id"), @@ -44,6 +51,8 @@ const parseArgs = (args: string[]) => { baseline: values.get("--baseline"), evolutionOut: values.get("--evolution-out"), reviews: values.get("--reviews"), + queryGenerator: values.get("--query-generator"), + checkedResources: values.get("--checked-resources"), }; }; @@ -57,13 +66,17 @@ const main = async () => { checkoutRoot: options.checkoutRoot, snapshotMap: options.snapshotMap, }); + const assemblyStarted = performance.now(); const assembled = await compileWorkspaceRepository({ rootDirectory: options.rootDirectory, workspaceId: options.workspaceId, workspaceRevisionId: options.workspaceRevisionId, sourceRootCommit: options.sourceRootCommit, resolveResource, + generateQueries: await loadQueryGenerator(options.queryGenerator), + checkedResource: await loadCheckedResources(options.checkedResources), }); + profile("workspace-assembly", assemblyStarted, { resources: assembled.resources.length }); if (options.graphOut) { await writeFile( @@ -81,11 +94,15 @@ const main = async () => { kind: node.kind, source: node.source, directory: node.directory, - resourceId: - node.resource.kind === "interface" - ? node.resource.revision.interfaceId - : node.resource.revision.packageId, - revisionId: node.resource.revision.revisionId, + ...(node.resource.kind === "build" + ? {} + : { + resourceId: + node.resource.kind === "interface" + ? node.resource.revision.interfaceId + : node.resource.revision.packageId, + revisionId: node.resource.revision.revisionId, + }), dependencies: [...node.dependencies.entries()].map(([binding, dependency]) => ({ binding, resourceKey: dependency.key, @@ -98,16 +115,26 @@ const main = async () => { ); } const candidate = { ...assembled.workspace, executionContracts: runtimeContracts(assembled.workspace) }; + if (options.executionPlanOut || options.buildContextsOut) { + const executionStarted = performance.now(); + const plan = compileExecutionPlan(assembled.workspace); + if (options.executionPlanOut) await writeFile(options.executionPlanOut, `${JSON.stringify(plan, null, 2)}\n`); + if (options.buildContextsOut) + await writeFile(options.buildContextsOut, JSON.stringify(packageBuildContexts(plan.definition.bindingSchema))); + profile("execution-plan-and-contexts", executionStarted); + } if (options.schemasOut) { + const schemasStarted = performance.now(); const schemas: Record = {}; - for (const node of assembled.resources.filter((entry) => entry.kind === "package")) { + for (const node of assembled.capabilityResources.filter((entry) => entry.kind === "package")) { const closure = new Map(); const visit = (entry: typeof node) => { - if (closure.has(entry.key)) return; + if (closure.has(entry.key) || entry.kind === "build") return; closure.set(entry.key, entry); entry.dependencies.forEach(visit); }; visit(node); + if (node.resource.kind !== "package") continue; schemas[node.resource.revision.revisionId] = specializeBindingSchema( bindingSchema({ resources: [...closure.values()] }), candidate, @@ -115,6 +142,7 @@ const main = async () => { ); } await writeFile(options.schemasOut, JSON.stringify(schemas)); + profile("package-schemas", schemasStarted); } if (options.evolutionOut) { const baseline = options.baseline diff --git a/src/capability-model/conformance-rules.ts b/src/capability-model/conformance-rules.ts new file mode 100644 index 0000000..841e6b4 --- /dev/null +++ b/src/capability-model/conformance-rules.ts @@ -0,0 +1,208 @@ +import { createHash } from "node:crypto"; +import { + capabilityId, + type AtomId, + type Conformance, + type InterfaceRevision, + type InterfaceRevisionId, +} from "./types.js"; +import { + GenericTypeError, + TypeSubstitution, + bindTypeParameters, + canonicalTypeArgument, + type ClosedTypeArgument, + type GenericTypeEnvironment, + type InterfaceApplicationExpression, + type TypeParameter, +} from "./generics.js"; + +/** Compiler input only. Runtime plans contain ordinary closed conformances. */ +export interface ConformanceRule { + id: string; + parameters: TypeParameter[]; + source: InterfaceApplicationExpression; + target: InterfaceApplicationExpression; +} +export interface RuleApplication { + rule: ConformanceRule; + atomId: AtomId; + sourceId: InterfaceRevisionId; + targetId: InterfaceRevisionId; + bindings: Map; + id: Conformance["id"]; +} +const parameterId = (argument: ConformanceRule["source"]["arguments"][number]) => { + const value = argument.kind === "value" ? argument.type : argument.target; + return value.kind === "parameter" ? value.parameterId : undefined; +}; +const hasParameter = (node: unknown): boolean => + !!node && + typeof node === "object" && + (("kind" in node && node.kind === "parameter") || Object.values(node).some(hasParameter)); + +export function validateConformanceRules( + rules: readonly ConformanceRule[], + definitions: ReadonlyMap, +) { + const ids = new Set(); + const graph = new Map(); + for (const rule of rules) { + if (ids.has(rule.id)) throw new GenericTypeError("duplicate-rule-id", rule.id, `Duplicate rule ID ${rule.id}`); + ids.add(rule.id); + for (const application of [ + rule.source, + rule.target, + ...rule.parameters.flatMap((p) => (p.kind === "object" ? p.implements : [])), + ]) { + const definition = definitions.get(application.definitionId); + if (!definition) + throw new GenericTypeError("unknown-interface", rule.id, `Unknown interface ${application.definitionId}`); + const parameters = definition.template?.parameters ?? []; + if (parameters.length !== application.arguments.length) + throw new GenericTypeError("type-arity", rule.id, `Wrong arity for ${definition.displayName}`); + parameters.forEach((p, i) => { + if (p.kind !== application.arguments[i].kind) + throw new GenericTypeError( + "parameter-kind", + rule.id, + `Wrong argument kind for ${definition.displayName}.${p.name}`, + ); + }); + } + const captured = new Set(); + for (const argument of rule.source.arguments) { + const id = parameterId(argument); + if (id) captured.add(id); + else if (hasParameter(argument)) + throw new GenericTypeError( + "unsupported-rule-pattern", + rule.id, + "Capture whole source arguments, not nested type patterns", + ); + } + for (const p of rule.parameters) + if (!captured.has(p.id)) + throw new GenericTypeError( + "uncaptured-rule-parameter", + rule.id, + `Parameter ${p.name} must occur in the source application`, + ); + graph.set(rule.source.definitionId, [...(graph.get(rule.source.definitionId) ?? []), rule]); + } + const visited = new Set(); + const active = new Set(); + const visit = (id: string, trail: string[]) => { + if (active.has(id)) + throw new GenericTypeError( + "recursive-conformance-rule", + trail.at(-1) ?? id, + `Recursive conformance rules: ${trail.join(" -> ")}`, + ); + if (visited.has(id)) return; + active.add(id); + for (const rule of graph.get(id) ?? []) visit(rule.target.definitionId, [...trail, rule.id]); + active.delete(id); + visited.add(id); + }; + for (const id of graph.keys()) visit(id, []); +} + +/** Monotone closure: bounds only enable applications, never invent evidence. */ +export function expandConformanceRules( + rules: readonly ConformanceRule[], + initial: readonly Pick[], + definitions: ReadonlyMap, + environment: (atom: AtomId, bindings: Map) => GenericTypeEnvironment, +): RuleApplication[] { + validateConformanceRules(rules, definitions); + const key = (atom: string, iface: string) => JSON.stringify([atom, iface]); + const facts = new Map(initial.map((f) => [key(f.atomId, f.interfaceRevisionId), f])); + const applications = new Map(); + const implies = (actual: InterfaceRevisionId, required: InterfaceRevisionId, seen = new Set()): boolean => { + if (actual === required) return true; + if (seen.has(actual)) return false; + seen.add(actual); + return (definitions.get(actual)?.requiredInterfaces ?? []).some((id) => implies(id, required, seen)); + }; + const bySource = new Map(); + for (const rule of [...rules].sort((a, b) => a.id.localeCompare(b.id))) + bySource.set(rule.source.definitionId, [...(bySource.get(rule.source.definitionId) ?? []), rule]); + let changed = true; + while (changed) { + changed = false; + for (const fact of [...facts.values()].sort((a, b) => + key(a.atomId, a.interfaceRevisionId).localeCompare(key(b.atomId, b.interfaceRevisionId)), + )) { + const definition = definitions.get(fact.interfaceRevisionId); + if (!definition) continue; + for (const rule of bySource.get(definition.application?.definitionId ?? definition.revisionId) ?? []) { + const arguments_ = definition.application?.arguments ?? []; + const bindings = new Map(); + const env = environment(fact.atomId, bindings); + const substitution = new TypeSubstitution(env); + if ( + !rule.source.arguments.every((pattern, i) => { + const actual = arguments_[i]; + if (!actual || actual.kind !== pattern.kind) return false; + const id = parameterId(pattern); + if (!id) return canonicalTypeArgument(substitution.argument(pattern)) === canonicalTypeArgument(actual); + const previous = bindings.get(id); + if (previous && canonicalTypeArgument(previous) !== canonicalTypeArgument(actual)) return false; + bindings.set(id, actual); + return true; + }) + ) + continue; + try { + bindTypeParameters( + rule.parameters, + rule.parameters.map((p) => bindings.get(p.id)!), + { + ...env, + implementsInterface: (target, required) => + target.kind === "atom" + ? facts.has(key(target.atomId, required)) + : implies(target.interfaceRevisionId, required), + }, + rule.id, + ); + } catch (error) { + if (error instanceof GenericTypeError && ["unsatisfied-bound", "non-storable-argument"].includes(error.code)) + continue; + throw error; + } + const applicationKey = JSON.stringify([rule.id, fact.atomId, fact.interfaceRevisionId]); + if (applications.has(applicationKey)) continue; + if (applications.size >= 10000) + throw new GenericTypeError( + "rule-expansion-limit", + rule.id, + "Conformance rule expansion exceeds 10000 applications", + ); + const targetId = substitution.application(rule.target); + const id = capabilityId.conformance( + `conformance-rule:sha256:${createHash("sha256") + .update( + JSON.stringify(["quixos-conformance-rule-v1", rule.id, fact.atomId, fact.interfaceRevisionId, targetId]), + ) + .digest("hex")}`, + ); + applications.set(applicationKey, { + rule, + atomId: fact.atomId, + sourceId: fact.interfaceRevisionId, + targetId, + bindings, + id, + }); + const targetKey = key(fact.atomId, targetId); + if (!facts.has(targetKey)) { + facts.set(targetKey, { atomId: fact.atomId, interfaceRevisionId: targetId }); + changed = true; + } + } + } + } + return [...applications.values()]; +} diff --git a/src/capability-model/controllers.ts b/src/capability-model/controllers.ts new file mode 100644 index 0000000..b06acd9 --- /dev/null +++ b/src/capability-model/controllers.ts @@ -0,0 +1,128 @@ +import type { InterfaceRevision, PackageController, WorkspaceRevision } from "./types.js"; +import type { CapabilityValidationIssue } from "./validation.js"; +import { canonicalJson } from "./evolution.js"; + +export function validateControllers( + controllers: readonly PackageController[], + interfaces: readonly InterfaceRevision[], + prefix: string, + queries: readonly import("../query/types.js").CheckedQuery[] = [], +): CapabilityValidationIssue[] { + const issues: CapabilityValidationIssue[] = [], + ids = new Set(); + const fail = (path: string, message: string) => issues.push({ code: "invalid-controller" as const, path, message }); + for (const [index, controller] of controllers.entries()) { + const path = `${prefix}[${index}]`; + if (!controller.id.trim() || ids.has(controller.id)) + fail(`${path}.id`, "Controller identities must be nonempty and unique within the package"); + ids.add(controller.id); + if (controller.registry !== undefined) { + const query = queries.find((query) => query.declaration.id === controller.registry); + const selected = (entries: import("../query/types.js").QuerySelection[] | undefined, name: string) => + entries?.find((entry) => entry.name === name && entry.key === name && !entry.conditions.length); + const root = selected(query?.selection, "root"), + objects = selected(root?.selection, "objects"), + entries = selected(objects?.selection, "entries"), + node = selected(entries?.selection, "node"), + qx = selected(node?.selection, "_camino"), + ref = selected(qx?.selection, "ref"); + const all = objects?.arguments.all; + if ( + !query || + query.declaration.root.kind !== "workspace-interface" || + query.declaration.root.interface !== controller.subject || + query.declaration.placement?.kind === "local" || + query.effects.some((effect) => effect.execution === "rpc-permitted") || + query.variables.kind !== "record" || + Object.keys(query.variables.fields).length || + !ref || + all?.kind !== "literal" || + !/^\d+$/.test(String(all.value)) || + !Number.isSafeInteger(Number(all.value)) || + Number(all.value) < 1 || + Number(all.value) > 64 + ) + fail( + `${path}.registry`, + "Controller registry requires a native/portable workspace query over its subject, no variables, and root.objects(all: 1..64).entries.node._camino.ref without aliases or conditional selections", + ); + } + const iface = interfaces.find((i) => i.revisionId === controller.subject && !i.template); + if (!iface) { + fail(`${path}.subject`, "Controller subject must be an imported closed interface"); + continue; + } + const operations = iface.members.flatMap((m) => m.operations); + const snapshot = operations.find((o) => o.id === controller.snapshot), + remote = operations.find((o) => o.id === controller.remote); + const unit = (value: unknown) => canonicalJson(value) === canonicalJson({ kind: "builtin", name: "unit" }); + if ( + !snapshot || + snapshot.scope === "class" || + snapshot.mode !== "call" || + snapshot.execution?.kind !== "snapshot" || + !snapshot.execution.targets.includes("server") || + !unit(snapshot.inputType) + ) + fail(`${path}.snapshot`, "Controller snapshot must be a server snapshot object call with unit input"); + if ( + !remote || + remote.scope === "class" || + remote.mode !== "call" || + (remote.execution?.kind ?? "remote") !== "remote" + ) + fail(`${path}.remote`, "Controller target must be a remote object call"); + if (controller.input !== "unit" && controller.input !== "value") + fail(`${path}.input`, "Controller input must be unit or value"); + else if ( + remote && + (controller.input === "unit" + ? !unit(remote.inputType) + : snapshot && canonicalJson(snapshot.outputType) !== canonicalJson(remote.inputType)) + ) + fail(`${path}.input`, "Controller input mapping does not match the remote operation input"); + } + return issues; +} +export function validateWorkspaceControllers(workspace: WorkspaceRevision): CapabilityValidationIssue[] { + return workspace.packageImports.flatMap((pkg, index) => + validateControllers( + pkg.controllers ?? [], + workspace.interfaceImports, + `packageImports[${index}].controllers`, + pkg.checkedQueries, + ), + ); +} +/** The file is declarative metadata, not an executable registration callback. */ +export function parseControllerCatalog( + text: string, + interfaces: readonly InterfaceRevision[], + queries: readonly import("../query/types.js").CheckedQuery[] = [], +): PackageController[] { + if (Buffer.byteLength(text) > 1048576) throw new Error("Controller catalog exceeds 1 MiB"); + const value: unknown = JSON.parse(text); + const record = (value: unknown): value is Record => + !!value && typeof value === "object" && !Array.isArray(value); + if ( + !record(value) || + value.schemaVersion !== 1 || + Object.keys(value).some((k) => !["schemaVersion", "controllers"].includes(k)) || + !Array.isArray(value.controllers) || + value.controllers.length > 1000 + ) + throw new Error("Invalid controller catalog: expected schemaVersion 1 and at most 1000 controllers"); + const fields = ["id", "subject", "snapshot", "remote", "input"]; + for (const entry of value.controllers) + if ( + !record(entry) || + Object.keys(entry).some((key) => ![...fields, "registry"].includes(key)) || + (entry.registry !== undefined && typeof entry.registry !== "string") || + fields.some((key) => typeof entry[key] !== "string") + ) + throw new Error("Invalid controller entry: expected exactly id, subject, snapshot, remote, input strings"); + const controllers = value.controllers as PackageController[]; + const issues = validateControllers(controllers, interfaces, "controllers", queries); + if (issues.length) throw new Error(issues.map((i) => `${i.path}: ${i.message}`).join("\n")); + return controllers; +} diff --git a/src/capability-model/evolution.ts b/src/capability-model/evolution.ts index f276208..96d18b1 100644 --- a/src/capability-model/evolution.ts +++ b/src/capability-model/evolution.ts @@ -149,6 +149,10 @@ export const runtimeContracts = (workspace: WorkspaceRevision): RuntimeContract[ } if (binding.kind === "state") parent.dependencies.add(`attachment:${binding.slotId}`); if (binding.kind === "edge") parent.dependencies.add(`attachment:${binding.edgeTypeId}`); + if (binding.kind === "creation") { + for (const owner of binding.owners) + parent.dependencies.add(conformanceKey(binding.atomId, owner.interfaceRevisionId)); + } if (binding.kind === "constructor") { parent.dependencies.add(`constructor:${binding.atomId}`); const ctor = workspace.constructors.find((entry) => entry.atomId === binding.atomId); @@ -167,8 +171,22 @@ export const runtimeContracts = (workspace: WorkspaceRevision): RuntimeContract[ parent.dependencies.add(conformanceKey(conformance.atomId, conformance.interfaceRevisionId)); reviews?.add(conformanceIdentity(conformance)); for (const operation of conformance.operationBindings) { - if (operation.binding.kind !== "package") continue; - const revisionId = operation.binding.packageRevisionId; + let provider: Binding | undefined = operation.binding; + const visited = new Set(); + while (provider?.kind === "interface") { + const forward: Extract = provider; + const key = `${provider.interfaceRevisionId}\0${provider.operationId}`; + if (visited.has(key)) throw new Error("Forwarding cycle in evolution contract"); + visited.add(key); + const target = workspace.conformances.find( + (c) => c.atomId === conformance.atomId && c.interfaceRevisionId === forward.interfaceRevisionId, + ); + if (!target) throw new Error("Missing forwarding conformance in evolution contract"); + reviews?.add(conformanceIdentity(target)); + provider = target.operationBindings.find((entry) => entry.operationId === forward.operationId)?.binding; + } + if (provider?.kind !== "package") continue; + const revisionId = provider.packageRevisionId; const pkg = workspace.packageImports.find((entry) => entry.revisionId === revisionId); if (pkg) reviews?.add(pkg.packageId); } @@ -177,6 +195,11 @@ export const runtimeContracts = (workspace: WorkspaceRevision): RuntimeContract[ } }; const binding = (parent: GraphNode, value: Binding, atomId: string, context: unknown) => { + if (value.kind === "lifecycle") return; + if (value.kind === "journal" || value.kind === "subscription") { + dependency(parent, { kind: "state", slotId: value.slotId }, atomId); + return; + } if (value.kind !== "package") { dependency(parent, value, atomId); return; @@ -196,9 +219,17 @@ export const runtimeContracts = (workspace: WorkspaceRevision): RuntimeContract[ atomId: conformance.atomId, interfaceRevisionId: conformance.interfaceRevisionId, operations: sorted(conformance.operationBindings, (entry) => entry.operationId), + ...(conformance.moduleBindings?.length + ? { modules: sorted(conformance.moduleBindings, (entry) => entry.memberId) } + : {}), + ...(conformance.initialization + ? { initialization: sorted(conformance.initialization, (entry) => entry.input) } + : {}), materializations: sorted(conformance.relationshipMaterializations, (entry) => entry.memberId), }); parent.dependencies.add(`interface:${conformance.interfaceRevisionId}`); + for (const module of conformance.moduleBindings ?? []) + parent.dependencies.add(`package:${module.packageRevisionId}`); const contract = workspace.interfaceImports.find((entry) => entry.revisionId === conformance.interfaceRevisionId); for (const required of contract?.requiredInterfaces ?? []) parent.dependencies.add(conformanceKey(conformance.atomId, required)); @@ -345,6 +376,36 @@ export const planEvolution = ( ...(next ? { candidate: next } : {}), }); } + const semanticReview = assessSemanticReviews(baseline, candidate, options.reviews ?? [], checkerVersion, [ + ...nextRuntimes.values(), + ]); + const reviews = semanticReview.reviews; + blockers.push(...semanticReview.blockers); + return { + schemaVersion: 1, + baselineDigest: baseline ? contentDigest(baseline) : null, + candidateDigest, + checkerVersion, + runtimeActions, + storageChanges, + migrationRequired: storageChanges.filter((entry) => entry.requiresMigration).map((entry) => entry.id), + reviews, + packageChecks: runtimeActions + .filter((entry) => entry.candidate && entry.action !== "keep") + .map((entry) => ({ groupId: entry.groupId, contractDigest: entry.candidate!.digest })), + blockers, + }; +}; + +/** Review dependency contracts, without making any storage or process-placement decision. */ +function assessSemanticReviews( + baseline: WorkspaceRevision | null, + candidate: WorkspaceRevision, + receipts: readonly EvolutionReview[], + checkerVersion: string, + consumers: readonly RuntimeContract[], +): Pick { + const blockers: string[] = []; const providers = (workspace: WorkspaceRevision) => [ ...workspace.packageImports.map((entry) => ({ id: entry.packageId as string, @@ -371,7 +432,7 @@ export const planEvolution = ( } if (!old[0] || old[0].major === provider.major) continue; if (provider.major < old[0].major) blockers.push(`Semantic major decreases for ${provider.id}`); - for (const consumer of nextRuntimes.values()) { + for (const consumer of consumers) { if (consumer.packageId === provider.id || !consumer.reviewProviders.includes(provider.id)) continue; const requirement = { consumerId: consumer.groupId, @@ -386,7 +447,7 @@ export const planEvolution = ( consumer: consumer.digest, checkerVersion, }); - const accepted = (options.reviews ?? []).some( + const accepted = receipts.some( (entry) => entry.requirementDigest === requirementDigest && ["changed", "accepted-unchanged"].includes(entry.decision) && @@ -397,18 +458,27 @@ export const planEvolution = ( if (!accepted) blockers.push(`Semantic-major review required: ${consumer.groupId} consumes ${provider.id}`); } } + return { reviews, blockers }; +} + +export function reviewSemanticMajors( + baseline: WorkspaceRevision | null, + candidate: WorkspaceRevision, + receipts: readonly EvolutionReview[] = [], +) { + for (const workspace of baseline ? [baseline, candidate] : [candidate]) { + const issues = validateWorkspaceRevision(workspace); + if (issues.length) + throw new Error("Invalid semantic-review workspace: " + issues.map((issue) => issue.message).join("; ")); + } + if (baseline && baseline.workspaceId !== candidate.workspaceId) + throw new Error("Cannot review a different workspace"); + const checkerVersion = "quixos-semantic-review-v1"; + const report = assessSemanticReviews(baseline, candidate, receipts, checkerVersion, runtimeContracts(candidate)); return { - schemaVersion: 1, - baselineDigest: baseline ? contentDigest(baseline) : null, - candidateDigest, checkerVersion, - runtimeActions, - storageChanges, - migrationRequired: storageChanges.filter((entry) => entry.requiresMigration).map((entry) => entry.id), - reviews, - packageChecks: runtimeActions - .filter((entry) => entry.candidate && entry.action !== "keep") - .map((entry) => ({ groupId: entry.groupId, contractDigest: entry.candidate!.digest })), - blockers, + baselineDigest: baseline ? contentDigest(baseline) : null, + candidateDigest: contentDigest(candidate), + ...report, }; -}; +} diff --git a/src/capability-model/execution.ts b/src/capability-model/execution.ts new file mode 100644 index 0000000..55db3da --- /dev/null +++ b/src/capability-model/execution.ts @@ -0,0 +1,178 @@ +import type { ExecutionContract, InterfaceOperation, WorkspaceRevision } from "./types.js"; +import type { CapabilityValidationIssue } from "./validation.js"; +import { resolveForwardedBinding } from "./forwarding.js"; + +/** Validate declared effects and targets; this is not a JavaScript purity proof. */ +export function validateExecutionContracts(workspace: WorkspaceRevision): CapabilityValidationIssue[] { + const issues: CapabilityValidationIssue[] = []; + const fail = (path: string, message: string) => issues.push({ code: "invalid-execution-contract", path, message }); + const interfaces = new Map(workspace.interfaceImports.map((i) => [i.revisionId, i])); + const packages = new Map(workspace.packageImports.map((p) => [p.revisionId, p])); + const operations = (id: InterfaceOperation["id"], revision: WorkspaceRevision["interfaceImports"][number]) => + (revision.template?.members ?? revision.members).flatMap((m) => m.operations).find((o) => o.id === id); + const check = (contract: ExecutionContract | undefined, path: string) => { + if (!contract || contract.kind === "remote") return; + if ( + !["snapshot", "transaction"].includes(contract.kind) || + !Array.isArray(contract.targets) || + !contract.targets.length || + contract.targets.some((t) => typeof t !== "string" || !t.trim()) || + new Set(contract.targets).size !== contract.targets.length + ) + fail(path, "Portable execution requires a nonempty set of distinct target names"); + }; + const supports = ( + requested: ExecutionContract | undefined, + provided: ExecutionContract | undefined, + path: string, + ) => { + if (!requested || requested.kind === "remote") return; + if (!provided || provided.kind === "remote") { + fail(path, "Portable execution depends on a remote-only operation"); + return; + } + if (requested.kind === "snapshot" && provided.kind !== "snapshot") + fail(path, "Snapshot execution cannot perform a transaction"); + }; + for (const revision of workspace.interfaceImports) + for (const member of revision.template?.members ?? revision.members) { + for (const operation of member.operations) { + check(operation.execution, `${revision.revisionId}/${operation.id}`); + if (operation.execution && operation.execution.kind !== "remote" && operation.mode !== "call") + fail( + `${revision.revisionId}/${operation.id}`, + "Watch lifecycle operations are remote; portable reads use snapshots", + ); + } + if (member.kind !== "operation" && member.kind !== "module" && member.queryRead?.execution === "portable") { + const getter = member.operations.find((o) => o.displayName === (member.kind === "value" ? "get" : "resolve")); + if (getter?.execution?.kind !== "snapshot") + fail(`${revision.revisionId}/${member.id}`, "Queryable portable members require a snapshot getter"); + } + } + for (const revision of workspace.packageImports) { + for (const entry of [...revision.exports, ...(revision.genericExports ?? [])]) { + const path = `${revision.revisionId}/${entry.id}`; + check(entry.execution, path); + for (const port of entry.dependencyPorts) { + const requirement = port.requirement; + const portPath = `${path}/${port.id}`; + if (requirement.kind === "interface") { + const contract = interfaces.get( + "interfaceRevisionId" in requirement + ? requirement.interfaceRevisionId + : requirement.application.definitionId, + ); + const selected = requirement.operations; + if (requirement.receiver === "input") { + if (!entry.execution || entry.execution.kind === "remote" || !selected?.length) + fail(portPath, "Reference-selected calls require portable execution and an explicit selection"); + for (const id of selected ?? []) { + const operation = contract && operations(id, contract); + if (!operation?.execution || operation.execution.kind === "remote" || operation.scope === "class") + fail(portPath, "Reference-selected dependencies require portable object operations"); + } + } + if (selected && (!selected.length || new Set(selected).size !== selected.length)) + fail(portPath, "Selected interface operations must be nonempty and distinct"); + for (const id of selected ?? []) + if (contract && !operations(id, contract)) fail(portPath, `Unknown selected operation ${id}`); + if (entry.execution && entry.execution.kind !== "remote") { + if (!selected) fail(portPath, "Portable interface ports must select their required operations"); + for (const id of selected ?? []) + if (contract) supports(entry.execution, operations(id, contract)?.execution, portPath); + } + } + if (!entry.execution || entry.execution.kind === "remote") continue; + if (requirement.kind === "state" || requirement.kind === "edge") { + const read = requirement.kind === "state" ? "read" : "resolve"; + for (const primitive of requirement.primitives) { + if (primitive.startsWith("watch")) + fail(portPath, "Portable operations cannot acquire watch lifecycle primitives"); + else if (entry.execution.kind === "snapshot" && primitive !== read) + fail(portPath, "Snapshot operations cannot acquire write primitives"); + } + } else if (requirement.kind === "query") { + const query = packages.get(requirement.packageRevisionId)?.queries?.find((q) => q.id === requirement.queryId); + if (query?.placement?.kind !== "local") + fail(portPath, "Portable operations require a local query dependency"); + } else if (requirement.kind === "creation") { + if (entry.execution.kind !== "transaction") fail(portPath, "Object creation requires transaction execution"); + } else if (requirement.kind === "reflection") { + if (requirement.selectors.some((selector) => selector !== "presentations")) + fail(portPath, "Only checked presentation metadata is portable"); + } else if (requirement.kind !== "interface") + fail(portPath, `Portable execution does not support ${requirement.kind} dependencies`); + } + } + for (const query of revision.queries ?? []) + if (query.placement?.kind === "local") { + const sourceQueryId = query.placement.sourceQueryId; + const source = revision.queries?.find((q) => q.id === sourceQueryId); + if (source?.placement?.kind !== "replica") + fail(`${revision.revisionId}/${query.id}`, "Local queries must name a replica query in the same package"); + } + } + for (const conformance of workspace.conformances) { + const revision = interfaces.get(conformance.interfaceRevisionId); + if (!revision) continue; + for (const entry of conformance.operationBindings) { + const operation = operations(entry.operationId, revision); + if (!operation) continue; + const path = `${conformance.atomId}/${revision.revisionId}/${entry.operationId}`; + let binding; + try { + binding = resolveForwardedBinding( + workspace, + conformance.atomId, + revision.revisionId, + entry.operationId, + ).binding; + } catch { + continue; + } // Ordinary binding validation reports unresolved references and cycles. + if (entry.binding.kind === "interface") { + const target = interfaces.get(entry.binding.interfaceRevisionId); + if (target) supports(operation.execution, operations(entry.binding.operationId, target)?.execution, path); + } + if (binding.kind === "lifecycle") { + if ( + operation.execution?.kind !== "transaction" || + operation.execution.targets.length !== 1 || + operation.execution.targets[0] !== "server" + ) + fail( + path, + "Deletion requires an explicitly server-only transaction; partial replicas cannot enumerate incoming references", + ); + continue; + } + if (!operation.execution || operation.execution.kind === "remote") continue; + if (binding.kind === "journal") { + const expected = binding.primitive === "publish" ? "transaction" : "snapshot"; + if (operation.execution.kind !== expected) fail(path, "Native journal operation requires the matching effect"); + continue; + } + if (binding.kind === "subscription") { + const expected = binding.primitive === "status" ? "snapshot" : "transaction"; + if ( + operation.execution.kind !== expected || + (binding.primitive !== "status" && operation.execution.targets.some((target) => target !== "server")) + ) + fail(path, "Subscription controls require authoritative execution; status is a portable snapshot"); + continue; + } + if (binding.kind === "package") { + const provider = packages.get(binding.packageRevisionId)?.exports.find((e) => e.id === binding.exportId); + supports(operation.execution, provider?.execution, path); + } else { + const read = binding.kind === "state" ? "read" : "resolve"; + if (binding.primitive.startsWith("watch")) + fail(path, "Portable operations cannot bind watch lifecycle primitives"); + else if (operation.execution.kind === "snapshot" && binding.primitive !== read) + fail(path, "Snapshot operations cannot bind write primitives"); + } + } + } + return issues; +} diff --git a/src/capability-model/forwarding.ts b/src/capability-model/forwarding.ts new file mode 100644 index 0000000..7a76bde --- /dev/null +++ b/src/capability-model/forwarding.ts @@ -0,0 +1,36 @@ +import type { + AtomId, + Binding, + Conformance, + InterfaceRevisionId, + OperationBinding, + OperationId, + WorkspaceRevision, +} from "./types.js"; + +export type ConcreteBinding = Exclude; +export type ResolvedBinding = { conformance: Conformance; entry: OperationBinding; binding: ConcreteBinding }; + +/** Follow public operations on the same receiver, never private attachment names. */ +export function resolveForwardedBinding( + workspace: Pick, + atomId: AtomId, + interfaceRevisionId: InterfaceRevisionId, + operationId: OperationId, +): ResolvedBinding { + const visited = new Set(); + for (;;) { + const key = `${interfaceRevisionId}\0${operationId}`; + if (visited.has(key)) throw new Error(`Forwarding cycle at ${interfaceRevisionId}/${operationId}`); + visited.add(key); + const conformance = workspace.conformances.find( + (c) => c.atomId === atomId && c.interfaceRevisionId === interfaceRevisionId, + ); + const entry = conformance?.operationBindings.find((b) => b.operationId === operationId); + if (!conformance || !entry) + throw new Error(`Missing forwarding target ${atomId} as ${interfaceRevisionId}/${operationId}`); + if (entry.binding.kind !== "interface") return { conformance, entry, binding: entry.binding }; + interfaceRevisionId = entry.binding.interfaceRevisionId; + operationId = entry.binding.operationId; + } +} diff --git a/src/capability-model/generic-packages.ts b/src/capability-model/generic-packages.ts index 19faca4..648923a 100644 --- a/src/capability-model/generic-packages.ts +++ b/src/capability-model/generic-packages.ts @@ -22,7 +22,7 @@ import { export type GenericDependencyPort = Omit & { requirement: - | Extract + | Extract | { kind: "state"; valueType: ValueTypeExpression; @@ -32,13 +32,21 @@ export type GenericDependencyPort = Omit & { kind: "edge"; target: ObjectTypeExpression; cardinality: Extract["cardinality"]; + keyType?: "string" | "boolean" | "int64"; primitives: Extract["primitives"]; } - | { kind: "interface"; application: InterfaceApplicationExpression } + | { + kind: "interface"; + application: InterfaceApplicationExpression; + operations?: import("./types.js").OperationId[]; + receiver?: "input"; + } + | { kind: "creation"; target: ObjectTypeExpression; inputType: ValueTypeExpression } | { kind: "constructor"; target: ObjectTypeExpression; inputType: ValueTypeExpression }; }; export interface GenericPackageExport { + execution?: import("./types.js").ExecutionContract; id: PackageExportId; displayName: string; parameters: TypeParameter[]; @@ -68,9 +76,8 @@ export const specializePackageExport = ( const digest = createHash("sha256") .update( JSON.stringify([ - "quixos-package-specialization-v1", + "quixos-package-specialization-v2", pkg.revisionId, - [pkg.source.repository, pkg.source.commit], definition.id, arguments_.map(canonicalTypeArgument), environment.self ?? null, @@ -80,6 +87,7 @@ export const specializePackageExport = ( const ports: DependencyPort[] = definition.dependencyPorts.map((port) => { const requirement = port.requirement; switch (requirement.kind) { + case "reflection": case "query": return { ...port, requirement }; case "state": @@ -89,8 +97,14 @@ export const specializePackageExport = ( case "interface": return { ...port, - requirement: { kind: "interface", interfaceRevisionId: substitution.application(requirement.application) }, + requirement: { + kind: "interface", + interfaceRevisionId: substitution.application(requirement.application), + ...(requirement.operations ? { operations: requirement.operations } : {}), + ...(requirement.receiver ? { receiver: requirement.receiver } : {}), + }, }; + case "creation": case "constructor": { const target = substitution.object(requirement.target); if (target.kind !== "atom") @@ -102,7 +116,7 @@ export const specializePackageExport = ( return { ...port, requirement: { - kind: "constructor", + kind: requirement.kind, atomId: target.atomId, inputType: substitution.value(requirement.inputType), }, @@ -111,6 +125,7 @@ export const specializePackageExport = ( } }); const common = { + ...(definition.execution ? { execution: structuredClone(definition.execution) } : {}), id: capabilityId.packageExport(`export-application:sha256:${digest}`), displayName: `${definition.displayName}$${digest}`, inputType: substitution.value(definition.inputType), diff --git a/src/capability-model/generics.ts b/src/capability-model/generics.ts index 9955463..b0dae59 100644 --- a/src/capability-model/generics.ts +++ b/src/capability-model/generics.ts @@ -16,7 +16,8 @@ export type ValueTypeExpression = | Extract | { kind: "parameter"; parameterId: string } | { kind: "record"; fields: Record } - | { kind: "list" | "optional"; value: ValueTypeExpression } + | { kind: "variant"; id: string; cases: Record } + | { kind: "list" | "optional" | "change"; value: ValueTypeExpression } | { kind: "object-ref"; expectation: ObjectTypeExpression } | { kind: "alias"; definitionId: string; arguments: TypeArgumentExpression[] }; @@ -86,10 +87,18 @@ export const instantiateInterface = ( const members: InterfaceMember[] = template.members.map((member) => { const common = { id: member.id, displayName: member.displayName, operations: member.operations.map(operation) }; switch (member.kind) { + case "module": + return { + ...common, + kind: "module", + contract: member.contract, + ...(member.subject ? { subject: substitution.object(member.subject, member.displayName) } : {}), + }; case "value": return { ...common, kind: "value", + ...(member.nativeField ? { nativeField: true as const } : {}), ...(member.queryRead ? { queryRead: { ...member.queryRead } } : {}), valueType: substitution.value(member.valueType, member.displayName), }; @@ -184,10 +193,12 @@ export const isStorableType = ( case "list": case "optional": return visit(value.value, depth + 1); - // Records currently have RPC codecs, not ordinary-state persistence codecs. - // A generic bound must not promise storage that the installed model rejects. - case "record": + case "change": return false; + case "record": + return Object.values(value.fields).every((field) => visit(field, depth + 1)); + case "variant": + return Object.values(value.cases).every((entry) => visit(entry.value, depth + 1)); } }; return visit(type, 0); @@ -214,8 +225,17 @@ export const canonicalTypeArgument = (argument: ClosedTypeArgument): string => { return [value.kind, value.name]; case "message": return ["message", value.descriptorId]; + case "variant": + return [ + "variant", + value.id, + Object.keys(value.cases) + .sort() + .map((label) => [label, value.cases[label].id, type(value.cases[label].value, depth + 1)]), + ]; case "object-ref": return ["object-ref", target(value.expectation)]; + case "change": case "list": case "optional": return [value.kind, type(value.value, depth + 1)]; @@ -250,10 +270,8 @@ export interface AppliedInterfaceIdentity { export const appliedInterfaceId = (application: AppliedInterfaceIdentity): InterfaceRevisionId => { const encoding = JSON.stringify([ - "quixos-applied-interface-v1", + "quixos-applied-interface-v2", application.definitionId, - application.source.repository, - application.source.commit.toLowerCase(), application.arguments.map(canonicalTypeArgument), application.self ?? null, ]); @@ -317,12 +335,24 @@ export class TypeSubstitution { value(expression: ValueTypeExpression, path = "type", depth = 0): ValueType { this.budget.enter(path, depth); switch (expression.kind) { + case "variant": + return { + kind: "variant", + id: expression.id, + cases: Object.fromEntries( + Object.entries(expression.cases).map(([label, entry]) => [ + label, + { id: entry.id, value: this.value(entry.value, `${path}.${label}`, depth + 1) }, + ]), + ), + }; case "builtin": return { kind: "builtin", name: expression.name }; case "scalar": return { kind: "scalar", name: expression.name }; case "message": return { kind: "message", descriptorId: expression.descriptorId }; + case "change": case "list": case "optional": return { kind: expression.kind, value: this.value(expression.value, `${path}.${expression.kind}`, depth + 1) }; diff --git a/src/capability-model/index.ts b/src/capability-model/index.ts index 30449d6..5b2da47 100644 --- a/src/capability-model/index.ts +++ b/src/capability-model/index.ts @@ -4,3 +4,4 @@ export * from "./evolution.js"; export * from "./migrations.js"; export * from "./generics.js"; export * from "./generic-packages.js"; +export * from "./modules.js"; diff --git a/src/capability-model/initialization.ts b/src/capability-model/initialization.ts new file mode 100644 index 0000000..e1e57de --- /dev/null +++ b/src/capability-model/initialization.ts @@ -0,0 +1,139 @@ +import type { + BoundDependency, + Conformance, + DependencyPort, + InitializationField, + ValueType, + WorkspaceRevision, +} from "./types.js"; +import type { CapabilityValidationIssue } from "./validation.js"; +import { cardinalityValueType, valueTypesEqual } from "./validation.js"; + +/** Creation grants seed inputs on new objects, never general private-state authority. */ +export function validateInitialization(workspace: WorkspaceRevision): CapabilityValidationIssue[] { + const issues: CapabilityValidationIssue[] = []; + const fail = (path: string, message: string) => issues.push({ code: "invalid-initialization", path, message }); + const ownerKey = (owner: Conformance) => `${owner.atomId}/${owner.interfaceRevisionId}`; + const shapes = new Map(); + const fieldType = (owner: Conformance, field: InitializationField): ValueType | undefined => { + const binding = field.binding; + const attachment = owner.privateAttachments.find( + (a) => a.kind === binding.kind && a.id === (binding.kind === "state" ? binding.slotId : binding.edgeTypeId), + ); + if (!attachment) { + fail(ownerKey(owner), `Initialization input ${field.input} must name an attachment owned by this conformance`); + return; + } + if (attachment.kind === "state") { + if (["journal", "subscription"].includes(attachment.storagePolicy.kind)) { + fail(ownerKey(owner), "Native journals start empty and cannot be seeded as state"); + return; + } + return attachment.valueType; + } + if (binding.kind !== "edge") return; + const index = attachment.endpoints.findIndex((e) => e.projectionId === binding.projectionId); + const endpoint = attachment.endpoints[index]; + const target = attachment.endpoints[1 - index]; + if (!endpoint || !target || endpoint.constraint.kind !== "atom" || endpoint.constraint.atomId !== owner.atomId) { + fail(ownerKey(owner), `Initialization input ${field.input} must name an edge projection on the newborn atom`); + return; + } + if (target.keyType) { + fail( + ownerKey(owner), + "Initialize a keyed relationship through its keyed projection; inverse seeds cannot supply a map key", + ); + return; + } + if (endpoint.cardinality === "many" || endpoint.cardinality === "many-unique") { + const fields: Record = { + key: { kind: "scalar", name: "string" }, + target: cardinalityValueType(target.constraint, "exactly-one"), + }; + if (endpoint.keyType) + fields.mapKey = { kind: "scalar", name: endpoint.keyType === "boolean" ? "bool" : endpoint.keyType }; + return { kind: "list", value: { kind: "record", fields } }; + } + return cardinalityValueType(target.constraint, endpoint.cardinality); + }; + for (const owner of workspace.conformances) { + if (!owner.initialization) continue; + const fields: Record = Object.create(null); + const destinations = new Set(); + for (const field of owner.initialization) { + const path = `${ownerKey(owner)}/${field.input}`; + if (!field.input || Object.hasOwn(fields, field.input)) + fail(path, "Initialization input names must be nonempty and distinct"); + const destination = JSON.stringify(field.binding); + if (destinations.has(destination)) fail(path, "An attachment may have only one initialization input"); + destinations.add(destination); + const type = fieldType(owner, field); + if (type) fields[field.input] = type; + } + shapes.set(ownerKey(owner), { kind: "record", fields }); + } + const checkBindings = (ports: DependencyPort[], dependencies: BoundDependency[], path: string) => { + for (const dependency of dependencies) { + if (dependency.binding.kind !== "creation") continue; + const binding = dependency.binding; + const requirement = ports.find((p) => p.id === dependency.portId)?.requirement; + if (requirement?.kind !== "creation") continue; // General binding validation reports the kind mismatch. + const portPath = `${path}/${dependency.portId}`; + if (binding.atomId !== requirement.atomId) fail(portPath, `Creation port requires atom ${requirement.atomId}`); + const initialized = new Set(); + const supplied = new Set(); + for (const mapping of binding.owners) { + const key = `${binding.atomId}/${mapping.interfaceRevisionId}`; + if (supplied.has(key)) fail(portPath, `Duplicate initialization owner ${key}`); + supplied.add(key); + const owner = workspace.conformances.find((o) => ownerKey(o) === key); + const shape = shapes.get(key); + if (!owner || !shape) { + fail(portPath, `Owner ${key} has no declared initialization mapping`); + continue; + } + let seed: ValueType | undefined = requirement.inputType; + for (const component of mapping.inputPath) seed = seed?.kind === "record" ? seed.fields[component] : undefined; + if (!seed || !valueTypesEqual(seed, shape)) + fail( + portPath, + `Seed input${mapping.inputPath.map((p) => `.${p}`).join("")} does not match initialization inputs of ${key}`, + ); + for (const field of owner.initialization!) + if (field.binding.kind === "state") initialized.add(field.binding.slotId); + } + const states = [ + ...workspace.sharedAttachments, + ...workspace.conformances.flatMap((o) => o.privateAttachments), + ].filter((a) => a.kind === "state" && a.attachedTo === binding.atomId); + for (const state of states) + if ( + state.kind === "state" && + !["journal", "subscription"].includes(state.storagePolicy.kind) && + state.defaultValue === undefined && + !initialized.has(state.id) + ) + fail(portPath, `Required state ${state.id} has no default or declared seed mapping`); + } + }; + const exports = new Map( + workspace.packageImports.flatMap((p) => p.exports.map((e) => [`${p.revisionId}/${e.id}`, e] as const)), + ); + for (const owner of workspace.conformances) + for (const operation of owner.operationBindings) { + if (operation.binding.kind !== "package") continue; + const entry = exports.get(`${operation.binding.packageRevisionId}/${operation.binding.exportId}`); + if (entry) + checkBindings( + entry.dependencyPorts, + operation.binding.dependencies, + `${ownerKey(owner)}/${operation.operationId}`, + ); + } + for (const constructor of workspace.constructors) { + const entry = exports.get(`${constructor.packageRevisionId}/${constructor.exportId}`); + if (entry) checkBindings(entry.dependencyPorts, constructor.dependencies, `constructor/${constructor.atomId}`); + } + return issues; +} diff --git a/src/capability-model/journals.ts b/src/capability-model/journals.ts new file mode 100644 index 0000000..a9d355b --- /dev/null +++ b/src/capability-model/journals.ts @@ -0,0 +1,131 @@ +import { + valueType, + type JournalPrimitive, + type InterfaceOperation, + type InterfaceRevision, + type ObjectExpectation, + type OperationId, + type ValueType, +} from "./types.js"; +import { valueTypesEqual } from "./validation.js"; + +export const subscriptionStatusType: ValueType = { + kind: "record", + fields: { + status: valueType.string, + generation: valueType.uint64, + cursor: valueType.uint64, + failure: { + kind: "optional", + value: { + kind: "record", + fields: { publication: valueType.string, code: valueType.string, message: valueType.string }, + }, + }, + }, +}; + +/** Native subscription configuration is selected by the checked attach contract. + * No library/interface ID or handler method name is reserved by Camino. */ +export function subscriptionContract( + payload: ValueType, + parameters: ValueType, + attach: InterfaceOperation, + interfaces: readonly InterfaceRevision[], +): { source: ObjectExpectation; handler: ObjectExpectation; operation: OperationId } { + const fail = (message: string): never => { + throw new Error(message); + }; + if ( + attach.mode !== "call" || + attach.scope === "class" || + !valueTypesEqual(attach.outputType, valueType.unit) || + attach.inputType.kind !== "record" + ) + return fail("Subscription attachment must be an object call returning unit"); + const fields = attach.inputType.fields; + if ( + Object.keys(fields).sort().join(",") !== "handler,parameters,source,start" || + fields.source?.kind !== "object-ref" || + fields.handler?.kind !== "object-ref" || + fields.handler.expectation.kind !== "interface" || + !valueTypesEqual(fields.parameters!, parameters) + ) + return fail("Attachment needs typed source/handler references, matching parameters, and a start policy"); + const start = fields.start; + if ( + start?.kind !== "variant" || + Object.keys(start.cases).sort().join(",") !== "afterCursor,future,retained" || + !valueTypesEqual(start.cases.future!.value, valueType.unit) || + !valueTypesEqual(start.cases.retained!.value, valueType.unit) || + !valueTypesEqual(start.cases.afterCursor!.value, valueType.string) + ) + return fail("Subscription start requires future:unit, retained:unit and afterCursor:string variant cases"); + const handler = fields.handler.expectation; + const expected: ValueType = { kind: "record", fields: { event: payload, parameters } }; + const candidates = + interfaces + .find((iface) => iface.revisionId === handler.interfaceRevisionId) + ?.members.flatMap((member) => member.operations) + .filter( + (operation) => + operation.mode === "call" && + operation.scope !== "class" && + operation.execution?.kind === "transaction" && + operation.execution.targets.includes("server") && + valueTypesEqual(operation.inputType, expected) && + valueTypesEqual(operation.outputType, valueType.unit), + ) ?? []; + if (candidates.length !== 1) + return fail("Handler interface must select one unambiguous server transaction from {event, parameters} to unit"); + return { source: fields.source.expectation, handler, operation: candidates[0]!.id }; +} + +/** Journals persist checked values, including managed references. They cannot + * persist process handles, opaque messages or edit commands. */ +export function journalPayloadType(type: ValueType): boolean { + switch (type.kind) { + case "scalar": + case "object-ref": + return true; + case "builtin": + return type.name === "unit"; + case "list": + case "optional": + return journalPayloadType(type.value); + case "record": + return Object.values(type.fields).every(journalPayloadType); + case "variant": + return Object.values(type.cases).every((entry) => journalPayloadType(entry.value)); + default: + return false; + } +} +export function journalPrimitiveSignature( + payload: ValueType, + primitive: JournalPrimitive, +): Pick { + const optional = (value: ValueType): ValueType => ({ kind: "optional", value }); + if (primitive === "publish") + return { + mode: "call", + inputType: payload, + outputType: { kind: "record", fields: { publication: valueType.string } }, + }; + if (primitive === "head") return { mode: "call", inputType: valueType.unit, outputType: optional(valueType.uint64) }; + return { + mode: "call", + inputType: { kind: "record", fields: { after: optional(valueType.string), limit: valueType.uint32 } }, + outputType: { + kind: "record", + fields: { + entries: { + kind: "list", + value: { kind: "record", fields: { publication: valueType.string, position: valueType.uint64, payload } }, + }, + next: optional(valueType.string), + asOfHead: optional(valueType.uint64), + }, + }, + }; +} diff --git a/src/capability-model/modules.ts b/src/capability-model/modules.ts new file mode 100644 index 0000000..2d7b001 --- /dev/null +++ b/src/capability-model/modules.ts @@ -0,0 +1,82 @@ +import type { InterfaceRevision, ModuleInterfaceMember, PackageModule, WorkspaceRevision } from "./types.js"; +import type { CapabilityValidationIssue } from "./validation.js"; + +/** Public nominal contract lookup; does not resolve an executable artifact or grant access. */ +export function moduleContract( + interfaces: readonly InterfaceRevision[], + module: PackageModule, +): ModuleInterfaceMember | undefined { + const iface = interfaces.find((entry) => entry.revisionId === module.interfaceRevisionId); + if (!iface || iface.template) return undefined; + const member = iface.members.find((entry) => entry.id === module.memberId); + return member?.kind === "module" ? member : undefined; +} + +export function validateModules(workspace: WorkspaceRevision): CapabilityValidationIssue[] { + const issues: CapabilityValidationIssue[] = []; + const fail = (path: string, message: string) => issues.push({ code: "invalid-module-contract", path, message }); + for (const iface of workspace.interfaceImports) { + for (const member of iface.template?.members ?? iface.members) { + if (member.kind !== "module") continue; + const path = `${iface.revisionId}.${member.id}`; + if (!/^[a-z][a-z0-9-]*(?:\.[a-z][a-z0-9-]*)+\/[1-9][0-9]*$/.test(member.contract)) + fail(path, "Module contracts require a namespaced, versioned identifier"); + if (member.operations.length) fail(path, "A module member cannot declare callable operations"); + } + } + for (const pkg of workspace.packageImports) { + const ids = new Set(pkg.exports.map((entry) => entry.id)); + const names = new Set([ + ...pkg.exports.map((entry) => entry.displayName), + ...(pkg.genericExports ?? []).map((entry) => entry.displayName), + ...(pkg.queries ?? []).map((entry) => entry.displayName), + ...(pkg.queryTemplates ?? []).map((entry) => entry.declaration.displayName), + ]); + for (const module of pkg.modules ?? []) { + const path = `${pkg.revisionId}.${module.id}`; + if (!module.id.trim() || !module.displayName.trim() || ids.has(module.id) || names.has(module.displayName)) + fail(path, "Duplicate or empty module identity or package member name"); + ids.add(module.id); + names.add(module.displayName); + if (!moduleContract(workspace.interfaceImports, module)) + fail(path, "Module export must implement a closed imported module member"); + } + } + for (const conformance of workspace.conformances) { + const iface = workspace.interfaceImports.find((entry) => entry.revisionId === conformance.interfaceRevisionId); + if (!iface) continue; + const bound = new Set(); + for (const binding of conformance.moduleBindings ?? []) { + const path = `${conformance.id ?? conformance.atomId}.${binding.memberId}`; + const member = iface.members.find((entry) => entry.id === binding.memberId); + const pkg = workspace.packageImports.find((entry) => entry.revisionId === binding.packageRevisionId); + const module = pkg?.modules?.find((entry) => entry.id === binding.moduleId); + if (bound.has(binding.memberId)) fail(path, "Module member is bound more than once"); + bound.add(binding.memberId); + if ( + member?.kind !== "module" || + !module || + module.interfaceRevisionId !== iface.revisionId || + module.memberId !== member.id + ) { + fail(path, "Module binding must implement this exact nominal interface member"); + continue; + } + const subject = member.subject; + if ( + subject && + !(subject.kind === "atom" + ? subject.atomId === conformance.atomId + : workspace.conformances.some( + (entry) => + entry.atomId === conformance.atomId && entry.interfaceRevisionId === subject.interfaceRevisionId, + )) + ) + fail(path, "Implementing atom does not satisfy the module subject contract"); + } + for (const member of iface.members) + if (member.kind === "module" && !bound.has(member.id)) + fail(`${conformance.id ?? conformance.atomId}.${member.id}`, "Missing module member binding"); + } + return issues; +} diff --git a/src/capability-model/presentations.ts b/src/capability-model/presentations.ts new file mode 100644 index 0000000..b454b31 --- /dev/null +++ b/src/capability-model/presentations.ts @@ -0,0 +1,49 @@ +import type { WorkspaceRevision } from "./types.js"; +import type { CapabilityValidationIssue } from "./validation.js"; +import { moduleContract } from "./modules.js"; + +/** Presentation metadata has no graph authority and cannot name arbitrary module URLs. */ +export function validatePresentations(workspace: WorkspaceRevision): CapabilityValidationIssue[] { + const issues: CapabilityValidationIssue[] = []; + const fail = (path: string, message: string) => issues.push({ code: "invalid-presentation", path, message }); + const interfaces = new Map(workspace.interfaceImports.map((entry) => [entry.revisionId, entry])); + const packages = new Map(workspace.packageImports.map((entry) => [entry.revisionId, entry])); + const text = (value: string, path: string) => { + if (!value.trim()) fail(path, "Presentation identities and names must be nonempty"); + }; + const subject = (id: string, path: string) => { + const contract = interfaces.get(id as WorkspaceRevision["interfaceImports"][number]["revisionId"]); + if (!contract || contract.template) fail(path, `Expected an imported closed subject interface: ${id}`); + }; + for (const [index, pkg] of workspace.packageImports.entries()) { + for (const [alias, id] of Object.entries(pkg.clientInterfaces ?? {})) { + text(alias, `packageImports[${index}].clientInterfaces`); + subject(id, `packageImports[${index}].clientInterfaces.${alias}`); + } + } + const ids = new Set(), + names = new Set(); + for (const [index, presentation] of (workspace.presentations ?? []).entries()) { + const path = `presentations[${index}]`; + text(presentation.id, `${path}.id`); + text(presentation.displayName, `${path}.displayName`); + if (ids.has(presentation.id) || names.has(presentation.displayName)) + fail(path, "Duplicate presentation identity or name"); + ids.add(presentation.id); + names.add(presentation.displayName); + subject(presentation.subject, `${path}.subject`); + const module = packages + .get(presentation.packageRevisionId) + ?.modules?.find((entry) => entry.id === presentation.componentId); + if (module) { + const contract = moduleContract(workspace.interfaceImports, module); + if (!contract || !["org.quixos.react.subject-only/1", "org.quixos.react.component/2"].includes(contract.contract)) + fail(`${path}.componentId`, "Standalone presentation requires a supported React presentation contract"); + if (contract?.subject?.kind !== "interface" || contract.subject.interfaceRevisionId !== presentation.subject) + fail(`${path}.subject`, "Component requires its exact declared subject interface"); + continue; + } + fail(`${path}.componentId`, "Presentation names no installed package component"); + } + return issues; +} diff --git a/src/capability-model/types.ts b/src/capability-model/types.ts index be7c336..6487587 100644 --- a/src/capability-model/types.ts +++ b/src/capability-model/types.ts @@ -65,9 +65,11 @@ export type ValueType = | { kind: "scalar"; name: ScalarValueTypeName } | { kind: "message"; descriptorId: string } | { kind: "record"; fields: Record } + | { kind: "variant"; id: string; cases: Record } | { kind: "object-ref"; expectation: ObjectExpectation } | { kind: "optional"; value: ValueType } - | { kind: "list"; value: ValueType }; + | { kind: "list"; value: ValueType } + | { kind: "change"; value: ValueType }; export const valueType = { unit: { kind: "builtin", name: "unit" } as const, @@ -104,7 +106,11 @@ export interface AtomDefinition { export type InterfaceOperationMode = "call" | "watch-start" | "watch-stop" | "subscribe" | "unsubscribe"; +/** Omission has remote semantics; availability never strengthens an imported contract. */ +export type ExecutionContract = { kind: "remote" } | { kind: "snapshot" | "transaction"; targets: string[] }; + export interface InterfaceOperation { + execution?: ExecutionContract; id: OperationId; displayName: string; inputType: Type; @@ -116,7 +122,7 @@ export interface InterfaceOperation { eventType?: Type; } -export type QueryReadContract = { execution: "native" | "rpc-permitted" }; +export type QueryReadContract = { execution: "native" | "rpc-permitted" | "portable" }; interface InterfaceMemberBase { id: MemberId; @@ -126,6 +132,8 @@ interface InterfaceMemberBase { export interface ValueInterfaceMember extends InterfaceMemberBase { kind: "value"; + /** Every operation resolves directly to the same native state slot. */ + nativeField?: true; queryRead?: QueryReadContract; valueType: Type; } @@ -158,10 +166,21 @@ export interface OperationInterfaceMember extends InterfaceMem outputType: Type; } +/** A typed code artifact. Module bindings are neither calls nor storage fields. */ +export interface ModuleInterfaceMember< + Type = ValueType, + Target = EdgeEndpointConstraint, +> extends InterfaceMemberBase { + kind: "module"; + contract: string; + subject?: Target; +} + export type InterfaceMember = | ValueInterfaceMember | RelationshipInterfaceMember - | OperationInterfaceMember; + | OperationInterfaceMember + | ModuleInterfaceMember; export interface InterfaceRevision { interfaceId: InterfaceId; @@ -177,7 +196,19 @@ export interface InterfaceRevision { argumentRequirements?: { target: ObjectExpectation; required: InterfaceRevisionId }[]; } -export type StoragePolicy = { kind: "optimistic-register" } | { kind: "crdt-document"; updateType: ValueType }; +export interface JournalStorageLimits { + maxEntries: number; + maxBytes: number; + maxPublicationBytes: number; + maxProtectedSubscriptions: number; +} +export type StoragePolicy = + | { kind: "register" } + | { kind: "crdt-document"; updateType: ValueType } + | { kind: "journal"; limits: JournalStorageLimits } + | { kind: "subscription"; payload: ValueType }; +export type JournalPrimitive = "publish" | "readPage" | "head"; +export type SubscriptionPrimitive = "attach" | "status" | "pause" | "resume" | "retire"; export interface StateSlotDefinition { kind: "state"; @@ -210,7 +241,7 @@ export interface EdgeDefinition { export type PersistentAttachment = StateSlotDefinition | EdgeDefinition; -export type StatePrimitive = "read" | "write" | "watch-start" | "watch-stop"; +export type StatePrimitive = "read" | "write" | "edit" | "watch-start" | "watch-stop"; export type EdgePrimitive = "resolve" | "connect" | "disconnect" | "watch-start" | "watch-stop"; @@ -223,6 +254,7 @@ export type PackageReceiverRequirement = }; export type DependencyPortRequirement = + | { kind: "reflection"; selectors: string[] } | { kind: "query"; packageRevisionId: PackageRevisionId; queryId: string } | { kind: "state"; @@ -233,12 +265,17 @@ export type DependencyPortRequirement = kind: "edge"; target: EdgeEndpointConstraint; cardinality: EdgeCardinality; + keyType?: "string" | "boolean" | "int64"; primitives: EdgePrimitive[]; } | { kind: "interface"; interfaceRevisionId: InterfaceRevisionId; + /** Exact public operation identities, not implementation export names. */ + operations?: OperationId[]; + receiver?: "input"; } + | { kind: "creation"; atomId: AtomId; inputType: ValueType } | { kind: "constructor"; atomId: AtomId; inputType?: ValueType }; export interface DependencyPort { @@ -248,6 +285,11 @@ export interface DependencyPort { } interface PackageExportBase { + /** Returns this declared birth, with unchanged operation input as its initializer. */ + preparation?: { port: DependencyPortId; key: string }; + /** Required authoritative snapshot export in this package. */ + acceptanceExportId?: PackageExportId; + execution?: ExecutionContract; id: PackageExportId; displayName: string; inputType: ValueType; @@ -275,9 +317,40 @@ export interface PackageConstructorExport extends PackageExportBase { export type PackageExport = PackageOperationExport | PackageFunctionExport | PackageConstructorExport; +export interface PackageModule { + id: string; + displayName: string; + interfaceRevisionId: InterfaceRevisionId; + memberId: MemberId; +} + +export interface ModuleBinding { + memberId: MemberId; + packageRevisionId: PackageRevisionId; + moduleId: string; +} + +export interface WorkspacePresentation { + id: string; + displayName: string; + subject: InterfaceRevisionId; + packageRevisionId: PackageRevisionId; + componentId: string; +} + export interface PackageRevision { + /** Explicit build consumers own these artifacts. Generic checking and query + * execution attach no semantics to their keys or contents. */ + buildArtifacts?: Record; + modules?: PackageModule[]; + /** Closed interface imports exposed to this package's checked component bindings. */ + clientInterfaces?: Record; + /** Explicit package imports supplying module selections, never the whole workspace. */ + clientPackages?: Record; + controllers?: PackageController[]; queries?: import("../query/types.js").QueryDeclaration[]; checkedQueries?: import("../query/types.js").CheckedQuery[]; + queryCompositions?: import("../query/composition.js").CheckedQueryComposition[]; queryTemplates?: import("../query/types.js").QueryTemplate[]; genericExports?: import("./generic-packages.js").GenericPackageExport[]; argumentRequirements?: { target: ObjectExpectation; required: InterfaceRevisionId }[]; @@ -291,8 +364,26 @@ export interface PackageRevision { semanticMajor?: number; } +/** Checked adapter registration. Implementations remain ordinary object capabilities. */ +export interface PackageController { + /** Optional opt-in discovery: a bounded checked workspace query in this package. */ + registry?: string; + id: string; + subject: InterfaceRevisionId; + snapshot: OperationId; + remote: OperationId; + input: "unit" | "value"; +} + export type DependencyBinding = - | { kind: "query"; packageRevisionId: PackageRevisionId; queryId: string; via?: EdgeTraversal } + | { kind: "reflection"; selectors: string[]; via?: never } + | { + kind: "query"; + packageRevisionId: PackageRevisionId; + queryId: string; + scope: "object" | "workspace"; + via?: EdgeTraversal; + } | { kind: "state"; slotId: SlotId; via?: EdgeTraversal } | { kind: "edge"; @@ -304,6 +395,13 @@ export type DependencyBinding = kind: "interface"; interfaceRevisionId: InterfaceRevisionId; via?: EdgeTraversal; + receiver?: "input"; + } + | { + kind: "creation"; + atomId: AtomId; + owners: { interfaceRevisionId: InterfaceRevisionId; inputPath: string[] }[]; + via?: never; } | { kind: "constructor"; atomId: AtomId }; @@ -331,6 +429,14 @@ export interface RelationshipMaterialization { } export type Binding = + | { kind: "journal"; slotId: SlotId; primitive: JournalPrimitive } + | { kind: "subscription"; slotId: SlotId; primitive: SubscriptionPrimitive } + | { kind: "lifecycle"; primitive: "delete" } + | { + kind: "interface"; + interfaceRevisionId: InterfaceRevisionId; + operationId: OperationId; + } | { kind: "state"; slotId: SlotId; @@ -347,6 +453,7 @@ export type Binding = packageRevisionId: PackageRevisionId; exportId: PackageExportId; dependencies: BoundDependency[]; + acceptanceDependencies?: BoundDependency[]; }; export interface OperationBinding { @@ -356,7 +463,16 @@ export interface OperationBinding { queryReason?: string; } +export interface InitializationField { + input: string; + binding: { kind: "state"; slotId: SlotId } | { kind: "edge"; edgeTypeId: EdgeTypeId; projectionId: EdgeProjectionId }; +} + export interface Conformance { + /** Source provenance of a compiler-expanded rule, never runtime dispatch input. */ + ruleOrigin?: { ruleId: string; sourceInterfaceRevisionId: InterfaceRevisionId; line: number; column: number }; + moduleBindings?: ModuleBinding[]; + initialization?: InitializationField[]; /** Absent only for legacy assemblies awaiting explicit ownership enrollment. */ id?: ConformanceId; semanticMajor?: number; @@ -375,6 +491,7 @@ export interface AtomConstructorBinding { } export interface WorkspaceRevision { + presentations?: WorkspacePresentation[]; linkedQueries?: import("../query/link.js").LinkedQuery[]; id: WorkspaceRevisionId; workspaceId: WorkspaceId; diff --git a/src/capability-model/validation.ts b/src/capability-model/validation.ts index 979311e..518dff0 100644 --- a/src/capability-model/validation.ts +++ b/src/capability-model/validation.ts @@ -1,3 +1,16 @@ +import { isStorableType } from "./generics.js"; +import { + journalPayloadType, + journalPrimitiveSignature, + subscriptionContract, + subscriptionStatusType, +} from "./journals.js"; +import { validateInitialization } from "./initialization.js"; +import { validatePresentations } from "./presentations.js"; +import { validateModules } from "./modules.js"; +import { validateWorkspaceControllers } from "./controllers.js"; +import { validateExecutionContracts } from "./execution.js"; +import { resolveForwardedBinding, type ConcreteBinding } from "./forwarding.js"; import type { AtomConstructorBinding, AtomId, @@ -40,6 +53,11 @@ import { specializePackageExport } from "./generic-packages.js"; import { isDeepStrictEqual } from "node:util"; export type CapabilityValidationIssueCode = + | "invalid-controller" + | "invalid-presentation" + | "invalid-module-contract" + | "invalid-initialization" + | "invalid-execution-contract" | "invalid-query-contract" | "query-provider-reason-required" | "invalid-semantic-major" @@ -65,7 +83,9 @@ export type CapabilityValidationIssueCode = | "invalid-attachment" | "invalid-state-binding" | "invalid-edge-binding" + | "invalid-lifecycle-binding" | "invalid-package-binding" + | "invalid-forwarding-binding" | "invalid-dependency-binding" | "private-attachment-access" | "incompatible-package-receiver" @@ -134,6 +154,11 @@ const requireText = (issues: CapabilityValidationIssue[], value: string, path: s const typeLabel = (type: ValueType): string => { switch (type.kind) { + case "variant": + return `variant:${type.id}{${Object.entries(type.cases) + .sort(([a], [b]) => a.localeCompare(b)) + .map(([label, entry]) => `${label}:${entry.id}:${typeLabel(entry.value)}`) + .join(";")}}`; case "builtin": case "scalar": return type.name; @@ -148,6 +173,8 @@ const typeLabel = (type: ValueType): string => { return type.expectation.kind === "atom" ? `object:atom:${type.expectation.atomId}` : `object:interface:${type.expectation.interfaceRevisionId}`; + case "change": + return `change<${typeLabel(type.value)}>`; case "optional": return `optional<${typeLabel(type.value)}>`; case "list": @@ -160,6 +187,19 @@ export const valueTypesEqual = (left: ValueType, right: ValueType): boolean => { return false; } switch (left.kind) { + case "variant": { + const other = right as typeof left; + return ( + left.id === other.id && + Object.keys(left.cases).length === Object.keys(other.cases).length && + Object.entries(left.cases).every( + ([label, entry]) => + Object.hasOwn(other.cases, label) && + entry.id === other.cases[label].id && + valueTypesEqual(entry.value, other.cases[label].value), + ) + ); + } case "builtin": case "scalar": return left.name === (right as typeof left).name; @@ -183,6 +223,7 @@ export const valueTypesEqual = (left: ValueType, right: ValueType): boolean => { ? left.expectation.atomId === (other as typeof left.expectation).atomId : left.expectation.interfaceRevisionId === (other as typeof left.expectation).interfaceRevisionId; } + case "change": case "optional": case "list": return valueTypesEqual(left.value, (right as typeof left).value); @@ -215,6 +256,20 @@ const validateValueType = ( indexes: Pick, ) => { switch (type.kind) { + case "variant": { + requireText(issues, type.id, `${path}.id`, "Variant identity"); + const entries = Object.entries(type.cases), + ids = new Set(); + if (!entries.length || entries.length > 256) issue(issues, "invalid-value-type", path, "Use 1–256 variant cases"); + for (const [label, entry] of entries) { + if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(label)) issue(issues, "invalid-value-type", path, "Invalid variant label"); + requireText(issues, entry.id, `${path}.${label}.id`, "Case identity"); + if (ids.has(entry.id)) issue(issues, "invalid-value-type", path, "Duplicate case identity"); + ids.add(entry.id); + validateValueType(issues, entry.value, `${path}.${label}`, indexes); + } + return; + } case "record": for (const [name, field] of Object.entries(type.fields)) { if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)) @@ -233,6 +288,16 @@ const validateValueType = ( case "message": requireText(issues, type.descriptorId, `${path}.descriptorId`, "Message descriptor identity"); return; + case "change": + if (!isStorableType(type.value)) + issue( + issues, + "invalid-value-type", + path, + "change requires a storable materialized type, not references or nested changes", + ); + validateValueType(issues, type.value, path + ".value", indexes); + return; case "optional": case "list": validateValueType(issues, type.value, `${path}.value`, indexes); @@ -335,6 +400,8 @@ export const statePrimitiveSignature = (slot: StateSlotDefinition, primitive: St inputType: valueType.unit, outputType: slot.valueType, }; + case "edit": + return { mode: "call", inputType: { kind: "change", value: slot.valueType }, outputType: valueType.unit }; case "write": return { mode: "call", @@ -410,7 +477,19 @@ export const edgePrimitiveSignature = ( case "disconnect": return { mode: "call", - inputType: targetType, + inputType: + primitive === "connect" && projection.endpoint.keyType + ? { + kind: "record", + fields: { + mapKey: { + kind: "scalar", + name: projection.endpoint.keyType === "boolean" ? "bool" : projection.endpoint.keyType, + }, + target: targetType, + }, + } + : targetType, outputType: valueType.unit, }; case "watch-start": @@ -438,15 +517,32 @@ const atomSatisfiesConstraint = ( ? constraint.atomId === atomId : conformances.has(conformanceKey(atomId, constraint.interfaceRevisionId)); +const interfaceImplies = ( + indexes: ValidationIndexes, + actual: InterfaceRevisionId, + required: InterfaceRevisionId, + visited = new Set(), +): boolean => { + if (actual === required) return true; + if (visited.has(actual)) return false; + visited.add(actual); + return (indexes.interfaces.get(actual)?.revision.requiredInterfaces ?? []).some((entry) => + interfaceImplies(indexes, entry, required, visited), + ); +}; + const constraintSatisfiesConstraint = ( actual: EdgeEndpointConstraint, required: EdgeEndpointConstraint, - conformances: ReadonlyMap, + indexes: ValidationIndexes, ) => constraintsEqual(actual, required) || (actual.kind === "atom" && required.kind === "interface" && - atomSatisfiesConstraint(actual.atomId, required, conformances)); + atomSatisfiesConstraint(actual.atomId, required, indexes.conformances)) || + (actual.kind === "interface" && + required.kind === "interface" && + interfaceImplies(indexes, actual.interfaceRevisionId, required.interfaceRevisionId)); const canAccessAttachment = ( owner: AttachmentOwner, @@ -516,21 +612,49 @@ const collectIdentityIndexes = ( memberPath, "Keyed relationships require many cardinality and string, boolean or int64 keys", ); - if (member.kind !== "operation" && member.queryRead) { + if (member.kind !== "operation" && member.kind !== "module" && member.queryRead) { const contract = member.queryRead.execution; const getter = member.kind === "value" ? "get" : "resolve"; - if (contract !== "native" && contract !== "rpc-permitted") + if (contract !== "native" && contract !== "rpc-permitted" && contract !== "portable") 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"); + const queryValue = (type: ValueType): boolean => + type.kind === "scalar" || + (type.kind === "variant" && isStorableType(type)) || + ((type.kind === "optional" || type.kind === "list") && queryValue(type.value)) || + (type.kind === "record" && + Object.keys(type.fields).length > 0 && + Object.values(type.fields).every(queryValue)); + if (!queryValue(member.valueType)) + issue( + issues, + "invalid-query-contract", + memberPath, + "Queryable values require scalar leaves in optional, list, or nonempty record shapes", + ); } } + if (member.kind === "value" && member.nativeField) { + const names = member.operations.map((o) => o.displayName); + if ( + !isStorableType(member.valueType) || + !names.includes("get") || + names.some((n) => !["get", "set", "edit"].includes(n)) || + (names.includes("set") && names.includes("edit")) + ) + issue( + issues, + "invalid-state-binding", + memberPath, + "Fields require a storable value, a native getter and at most one setter or editor", + ); + if (member.queryRead && member.queryRead.execution !== "native") + issue(issues, "invalid-query-contract", memberPath, "Fields cannot have package-backed query reads"); + } requireText(issues, member.id, `${memberPath}.id`, "Member ID"); requireText(issues, member.displayName, `${memberPath}.displayName`, "Member name"); if (memberIds.has(member.id)) { @@ -669,7 +793,14 @@ const collectIdentityIndexes = ( issues, "duplicate-conformance", path, - `Duplicate conformance for ${conformance.atomId} as ${conformance.interfaceRevisionId}`, + `Duplicate conformance for ${conformance.atomId} as ${conformance.interfaceRevisionId}: ` + + [conformances.get(key)!, conformance] + .map((candidate) => + candidate.ruleOrigin + ? `rule ${candidate.ruleOrigin.ruleId} from ${candidate.ruleOrigin.sourceInterfaceRevisionId} at line ${candidate.ruleOrigin.line}` + : `conformance ${candidate.id ?? "(unnamed)"}`, + ) + .join(" conflicts with "), ); } else { conformances.set(key, conformance); @@ -750,24 +881,12 @@ const validateInterfaces = ( issues: CapabilityValidationIssue[], indexes: ValidationIndexes, ) => { - const implies = ( - actual: InterfaceRevisionId, - required: InterfaceRevisionId, - visited = new Set(), - ): boolean => { - if (actual === required) return true; - if (visited.has(actual)) return false; - visited.add(actual); - return (indexes.interfaces.get(actual)?.revision.requiredInterfaces ?? []).some((entry) => - implies(entry, required, visited), - ); - }; for (const [packageIndex, pkg] of workspace.packageImports.entries()) { for (const obligation of pkg.argumentRequirements ?? []) { const satisfied = obligation.target.kind === "atom" ? indexes.conformances.has(conformanceKey(obligation.target.atomId, obligation.required)) - : implies(obligation.target.interfaceRevisionId, obligation.required); + : interfaceImplies(indexes, obligation.target.interfaceRevisionId, obligation.required); if (!satisfied) issue( issues, @@ -782,7 +901,7 @@ const validateInterfaces = ( for (const required of revision.requiredInterfaces ?? []) { if (!indexes.interfaces.has(required)) issue(issues, "unresolved-reference", path, `Unknown prerequisite interface ${required}`); - else if (implies(required, revision.revisionId)) + else if (interfaceImplies(indexes, required, revision.revisionId)) issue( issues, "cyclic-conformance-requirement", @@ -794,7 +913,7 @@ const validateInterfaces = ( const satisfied = obligation.target.kind === "atom" ? indexes.conformances.has(conformanceKey(obligation.target.atomId, obligation.required)) - : implies(obligation.target.interfaceRevisionId, obligation.required); + : interfaceImplies(indexes, obligation.target.interfaceRevisionId, obligation.required); if (!satisfied) issue(issues, "unsatisfied-interface", path, `Generic argument does not implement ${obligation.required}`); } @@ -804,6 +923,8 @@ const validateInterfaces = ( validateValueType(issues, member.valueType, `${memberPath}.valueType`, indexes); } else if (member.kind === "relationship") { validateConstraint(issues, member.target, `${memberPath}.target`, indexes); + } else if (member.kind === "module") { + if (member.subject) validateConstraint(issues, member.subject, `${memberPath}.subject`, indexes); } else { validateValueType(issues, member.inputType, `${memberPath}.inputType`, indexes); validateValueType(issues, member.outputType, `${memberPath}.outputType`, indexes); @@ -831,22 +952,62 @@ const validateAttachments = (issues: CapabilityValidationIssue[], indexes: Valid validateValueType(issues, attachment.valueType, `${path}.valueType`, indexes); const containsRpcType = (type: ValueType): boolean => type.kind === "object-ref" || - type.kind === "record" || + type.kind === "change" || + (type.kind === "record" && Object.values(type.fields).some(containsRpcType)) || + (type.kind === "variant" && Object.values(type.cases).some((entry) => containsRpcType(entry.value))) || ((type.kind === "optional" || type.kind === "list") && containsRpcType(type.value)); if ( - containsRpcType(attachment.valueType) || + (!["journal", "subscription"].includes(attachment.storagePolicy.kind) && + containsRpcType(attachment.valueType)) || (attachment.storagePolicy.kind === "crdt-document" && containsRpcType(attachment.storagePolicy.updateType)) ) { issue( issues, "invalid-attachment", `${path}.valueType`, - "Managed object references belong in graph relationships, not ordinary state; record types are RPC-only", + "Managed object references belong in graph relationships; captured changes are transport-only", ); } if (attachment.storagePolicy.kind === "crdt-document") { validateValueType(issues, attachment.storagePolicy.updateType, `${path}.storagePolicy.updateType`, indexes); } + if (attachment.storagePolicy.kind === "journal") { + if (!journalPayloadType(attachment.valueType) || attachment.defaultValue !== undefined) + issue(issues, "invalid-attachment", path, "Journals require finite typed payloads and start empty"); + const limits = attachment.storagePolicy.limits; + const ceilings = { + maxEntries: 10000, + maxBytes: 1048576, + maxPublicationBytes: 262144, + maxProtectedSubscriptions: 256, + }; + if ( + !limits || + Object.keys(limits).length !== 4 || + Object.entries(ceilings).some( + ([key, ceiling]) => + !Number.isSafeInteger(limits[key as keyof typeof limits]) || + limits[key as keyof typeof limits] < 1 || + limits[key as keyof typeof limits] > ceiling, + ) || + limits.maxPublicationBytes > limits.maxBytes + ) + issue(issues, "invalid-attachment", path, "Invalid bounded journal limits"); + } + if (attachment.storagePolicy.kind === "subscription") { + validateValueType(issues, attachment.storagePolicy.payload, `${path}.storagePolicy.payload`, indexes); + if ( + !journalPayloadType(attachment.valueType) || + !journalPayloadType(attachment.storagePolicy.payload) || + attachment.defaultValue !== undefined + ) + issue( + issues, + "invalid-attachment", + path, + "Subscriptions require finite typed payloads/parameters and start unattached", + ); + } if (owner.kind === "conformance") { if (attachment.attachedTo !== owner.atomId) { issue( @@ -938,6 +1099,52 @@ const validatePackages = ( const exportPath = `${path}.exports[${exportIndex}]`; validateValueType(issues, entry.inputType, `${exportPath}.inputType`, indexes); validateValueType(issues, entry.outputType, `${exportPath}.outputType`, indexes); + if (entry.preparation) { + const port = entry.dependencyPorts.find((port) => port.id === entry.preparation!.port); + if ( + entry.execution?.kind !== "transaction" || + entry.kind === "constructor" || + (entry.kind === "operation" && entry.mode !== "call") || + !entry.preparation.key || + port?.requirement.kind !== "creation" || + !valueTypesEqual(port.requirement.inputType, entry.inputType) || + entry.outputType.kind !== "object-ref" || + (entry.outputType.expectation.kind === "atom" && + entry.outputType.expectation.atomId !== port.requirement.atomId) + ) + issue( + issues, + "invalid-dependency-binding", + exportPath, + "Prepared creation requires a transaction returning its declared creation, with matching captured input types and a fixed key", + ); + } + if (entry.acceptanceExportId !== undefined) { + const check = revision.exports.find((candidate) => candidate.id === entry.acceptanceExportId); + if ( + entry.execution?.kind !== "transaction" || + !entry.execution.targets.includes("server") || + !check || + check.execution?.kind !== "snapshot" || + check.execution.targets.length !== 1 || + check.execution.targets[0] !== "server" || + check.acceptanceExportId !== undefined || + entry.kind !== check.kind || + entry.kind === "constructor" || + !valueTypesEqual(entry.inputType, check.inputType) || + !valueTypesEqual(check.outputType, valueType.unit) || + (entry.kind === "operation" && + (check.kind !== "operation" || + check.mode !== "call" || + !isDeepStrictEqual(entry.receiverRequirement, check.receiverRequirement))) + ) + issue( + issues, + "invalid-operation", + `${exportPath}.acceptanceExportId`, + "Acceptance requires a server snapshot export with matching kind, receiver and input, unit output, and no further acceptance hook", + ); + } if (entry.kind === "operation") { validateOperationEventShape(issues, entry, exportPath); if (entry.eventType) { @@ -996,6 +1203,19 @@ const validatePackages = ( for (const [portIndex, port] of entry.dependencyPorts.entries()) { const portPath = `${exportPath}.dependencyPorts[${portIndex}]`; switch (port.requirement.kind) { + case "reflection": + if ( + !port.requirement.selectors.length || + new Set(port.requirement.selectors).size !== port.requirement.selectors.length || + port.requirement.selectors.some( + (kind) => + !["active", "atoms", "interfaces", "conformances", "queries", "indexes", "presentations"].includes( + kind, + ), + ) + ) + issue(issues, "invalid-dependency-binding", portPath, "Reflection requires distinct supported selectors"); + break; case "query": if ( !indexes.packages @@ -1029,6 +1249,7 @@ const validatePackages = ( ); } break; + case "creation": case "constructor": if (port.requirement.inputType) validateValueType(issues, port.requirement.inputType, `${portPath}.requirement.inputType`, indexes); @@ -1160,6 +1381,20 @@ const validateBoundDependencies = ( continue; } switch (requirement.kind) { + case "reflection": { + const selected = binding as Extract; + if ( + selected.via || + JSON.stringify([...selected.selectors].sort()) !== JSON.stringify([...requirement.selectors].sort()) + ) + issue( + issues, + "invalid-dependency-binding", + path, + "Reflection requires an explicit workspace binding with exactly the declared selectors", + ); + break; + } case "query": { const selected = binding as Extract; const query = params.indexes.packages @@ -1173,6 +1408,20 @@ const validateBoundDependencies = ( issue(issues, "invalid-dependency-binding", path, "Query port must bind its exact checked query export"); break; } + if (query.root.kind === "workspace-interface") { + if (selected.scope !== "workspace" || selected.via) + issue( + issues, + "invalid-dependency-binding", + path, + "Workspace-interface queries require an explicit workspace scope without an object traversal", + ); + break; + } + if (selected.scope !== "object") { + issue(issues, "invalid-dependency-binding", path, "Object-root queries require an object dependency binding"); + break; + } const traversal = selected.via ? validateTraversal( issues, @@ -1191,8 +1440,13 @@ const validateBoundDependencies = ( : [] : [params.atomId]; for (const atom of targets) - if (!params.indexes.conformances.has(conformanceKey(atom as AtomId, query.root))) - issue(issues, "unsatisfied-interface", path, `Query root ${atom} does not conform to ${query.root}`); + if (!params.indexes.conformances.has(conformanceKey(atom as AtomId, query.root.interface))) + issue( + issues, + "unsatisfied-interface", + path, + `Query root ${atom} does not conform to ${query.root.interface}`, + ); break; } case "state": { @@ -1208,6 +1462,13 @@ const validateBoundDependencies = ( break; } validateAttachmentAccess(issues, attachment, params.conformance, `${path}.binding.slotId`); + if (["journal", "subscription"].includes(attachment.attachment.storagePolicy.kind)) + issue( + issues, + "invalid-dependency-binding", + path, + "Journal storage requires checked journal interface calls, not ordinary state ports", + ); const traversal = stateBinding.via ? validateTraversal( issues, @@ -1237,6 +1498,12 @@ const validateBoundDependencies = ( : `State ${stateBinding.slotId} is not attached to receiver atom ${params.atomId}`, ); } + if ( + requirement.primitives.includes("edit") && + (attachment.attachment.storagePolicy.kind !== "crdt-document" || + !valueTypesEqual(attachment.attachment.valueType, attachment.attachment.storagePolicy.updateType)) + ) + issue(issues, "invalid-dependency-binding", path, "Edit ports require a matching CRDT slot"); if (!valueTypesEqual(requirement.valueType, attachment.attachment.valueType)) { issue( issues, @@ -1270,6 +1537,13 @@ const validateBoundDependencies = ( ); break; } + if (projection.target.keyType && requirement.primitives.includes("connect")) + issue( + issues, + "invalid-dependency-binding", + path, + "Connect through the keyed projection so the caller supplies a map key", + ); const traversal = edgeBinding.via ? validateTraversal( issues, @@ -1301,24 +1575,30 @@ const validateBoundDependencies = ( ); } if ( - !constraintSatisfiesConstraint( - projection.target.constraint, - requirement.target, - params.indexes.conformances, - ) || - requirement.cardinality !== projection.endpoint.cardinality + !constraintSatisfiesConstraint(projection.target.constraint, requirement.target, params.indexes) || + requirement.cardinality !== projection.endpoint.cardinality || + requirement.keyType !== projection.endpoint.keyType ) { issue( issues, "invalid-dependency-binding", `${path}.binding`, - `Edge port target or cardinality does not match projection ${edgeBinding.projectionId}`, + `Edge port target, cardinality or key type does not match projection ${edgeBinding.projectionId}`, ); } break; } case "interface": { const interfaceBinding = binding as Extract; + if (interfaceBinding.receiver !== requirement.receiver || (interfaceBinding.receiver && interfaceBinding.via)) { + issue( + issues, + "invalid-dependency-binding", + path, + "Reference-selected interface ports require an explicit matching from input binding", + ); + break; + } if (interfaceBinding.interfaceRevisionId !== requirement.interfaceRevisionId) { issue( issues, @@ -1328,6 +1608,24 @@ const validateBoundDependencies = ( ); break; } + const selected = requirement.operations?.map( + (id) => params.indexes.interfaces.get(requirement.interfaceRevisionId)?.operations.get(id)?.operation, + ); + if ( + selected?.some((op) => op?.scope === "class") && + (interfaceBinding.via || interfaceBinding.receiver || selected.some((op) => op?.scope !== "class")) + ) { + issue( + issues, + "invalid-dependency-binding", + path, + "Static interface dependencies require a receiverless selection of class operations on the bound atom", + ); + break; + } + // The reference supplies its own checked witness at invocation time. + // This is not a requirement that the current receiver implements the interface. + if (interfaceBinding.receiver === "input") break; const traversal = interfaceBinding.via ? validateTraversal( issues, @@ -1502,16 +1800,83 @@ const validateConformances = ( continue; } const operation = operationEntry.operation; - const binding = entry.binding; + let binding: ConcreteBinding; + if (entry.binding.kind === "interface") { + const target = indexes.interfaces + .get(entry.binding.interfaceRevisionId) + ?.operations.get(entry.binding.operationId)?.operation; + if (!target || !signaturesMatch(operation, target) || operation.scope !== target.scope) { + issue( + issues, + "invalid-forwarding-binding", + bindingPath, + "Forwarding requires matching signatures, modes and receiver scopes", + ); + continue; + } + try { + binding = resolveForwardedBinding( + workspace, + conformance.atomId, + conformance.interfaceRevisionId, + entry.operationId, + ).binding; + } catch (error) { + issue(issues, "invalid-forwarding-binding", bindingPath, String(error)); + continue; + } + } else binding = entry.binding; const member = operationEntry.member; + if (member.kind === "value" && member.nativeField) { + const primitive = { get: "read", set: "write", edit: "edit" }[operation.displayName]; + const getter = member.operations.find((o) => o.displayName === "get"); + let read: Binding | undefined; + try { + if (getter) + read = resolveForwardedBinding( + workspace, + conformance.atomId, + conformance.interfaceRevisionId, + getter.id, + ).binding; + } catch {} + if ( + binding.kind !== "state" || + binding.primitive !== primitive || + read?.kind !== "state" || + read.primitive !== "read" || + read.slotId !== binding.slotId + ) + issue( + issues, + "invalid-state-binding", + bindingPath, + "Field operations must resolve to the same native state slot", + ); + } + + if ( + member.kind === "value" && + operation.displayName === "edit" && + (binding.kind !== "state" || binding.primitive !== "edit") + ) + issue( + issues, + "invalid-state-binding", + bindingPath, + "CRDT edit operations must resolve to a native state edit capability", + ); if ( member.kind !== "operation" && + member.kind !== "module" && member.queryRead && operation.displayName === (member.kind === "value" ? "get" : "resolve") ) { const native = member.kind === "value" - ? binding.kind === "state" && binding.primitive === "read" + ? (binding.kind === "state" && binding.primitive === "read") || + (binding.kind === "journal" && binding.primitive === "head") || + (binding.kind === "subscription" && binding.primitive === "status") : binding.kind === "edge" && binding.primitive === "resolve"; if (!native && (member.queryRead.execution === "native" || binding.kind !== "package")) issue( @@ -1520,7 +1885,7 @@ const validateConformances = ( bindingPath, "This queryable contract requires a native read binding; a justification cannot weaken it", ); - else if (!native && !entry.queryReason?.trim()) + else if (!native && member.queryRead.execution === "rpc-permitted" && !entry.queryReason?.trim()) issue( issues, "query-provider-reason-required", @@ -1537,6 +1902,9 @@ const validateConformances = ( ); continue; } + // The target conformance validates the concrete provider and its private + // authority. Forwarding exposes that operation, not its attachment ports. + if (entry.binding.kind === "interface") continue; if (operation.scope === "class" && binding.kind !== "package") { issue( issues, @@ -1546,6 +1914,85 @@ const validateConformances = ( ); continue; } + if (binding.kind === "lifecycle") { + if ( + binding.primitive !== "delete" || + operationEntry.member.kind !== "operation" || + operation.mode !== "call" || + operation.inputType.kind !== "builtin" || + operation.inputType.name !== "unit" || + operation.outputType.kind !== "builtin" || + operation.outputType.name !== "unit" + ) + issue( + issues, + "invalid-lifecycle-binding", + bindingPath, + "Deletion implements an object call from unit to unit", + ); + continue; + } + if (binding.kind === "journal") { + const attachment = findAttachment(indexes, "state", binding.slotId); + if ( + !attachment || + attachment.attachment.kind !== "state" || + attachment.attachment.storagePolicy.kind !== "journal" + ) { + issue(issues, "invalid-state-binding", bindingPath, "Expected native journal storage"); + continue; + } + validateAttachmentAccess(issues, attachment, conformance, bindingPath); + if ( + attachment.attachment.attachedTo !== conformance.atomId || + !["publish", "readPage", "head"].includes(binding.primitive) || + !signaturesMatch(operation, journalPrimitiveSignature(attachment.attachment.valueType, binding.primitive)) + ) + issue( + issues, + "invalid-state-binding", + bindingPath, + "Journal primitive does not match this object operation's signature", + ); + continue; + } + if (binding.kind === "subscription") { + const attachment = findAttachment(indexes, "state", binding.slotId); + if ( + !attachment || + attachment.attachment.kind !== "state" || + attachment.attachment.storagePolicy.kind !== "subscription" + ) { + issue(issues, "invalid-state-binding", bindingPath, "Expected native subscription storage"); + continue; + } + validateAttachmentAccess(issues, attachment, conformance, bindingPath); + const slot = attachment.attachment; + if (slot.storagePolicy.kind !== "subscription") continue; + if (slot.attachedTo !== conformance.atomId) + issue(issues, "invalid-state-binding", bindingPath, "Subscription belongs to another atom"); + if (binding.primitive === "attach") { + try { + subscriptionContract( + slot.storagePolicy.payload, + slot.valueType, + operation, + [...indexes.interfaces.values()].map((value) => value.revision), + ); + } catch (error) { + issue(issues, "invalid-state-binding", bindingPath, String(error)); + } + } else if ( + !["status", "pause", "resume", "retire"].includes(binding.primitive) || + !signaturesMatch(operation, { + mode: "call", + inputType: valueType.unit, + outputType: binding.primitive === "status" ? subscriptionStatusType : valueType.unit, + }) + ) + issue(issues, "invalid-state-binding", bindingPath, "Subscription primitive signature mismatch"); + continue; + } if (binding.kind === "state") { const attachment = findAttachment(indexes, "state", binding.slotId); if (!attachment || attachment.attachment.kind !== "state") { @@ -1558,6 +2005,8 @@ const validateConformances = ( continue; } validateAttachmentAccess(issues, attachment, conformance, `${bindingPath}.binding.slotId`); + if (["journal", "subscription"].includes(attachment.attachment.storagePolicy.kind)) + issue(issues, "invalid-state-binding", bindingPath, "Use native journal primitives, not state access"); if (attachment.attachment.attachedTo !== conformance.atomId) { issue( issues, @@ -1574,6 +2023,17 @@ const validateConformances = ( "State primitives may only implement value members", ); } + if ( + binding.primitive === "edit" && + (attachment.attachment.storagePolicy.kind !== "crdt-document" || + !valueTypesEqual(attachment.attachment.valueType, attachment.attachment.storagePolicy.updateType)) + ) + issue( + issues, + "invalid-state-binding", + bindingPath, + "Edit requires a CRDT slot with a matching materialized profile type", + ); const expected = statePrimitiveSignature(attachment.attachment, binding.primitive); if (!signaturesMatch(operation, expected)) { issue( @@ -1598,6 +2058,13 @@ const validateConformances = ( continue; } const projection = edgeProjection(attachment.attachment, binding.projectionId); + if (projection?.target.keyType && binding.primitive === "connect") + issue( + issues, + "invalid-edge-binding", + bindingPath, + "Connect through the keyed projection so the caller supplies a map key", + ); // An owned endpoint may explicitly export read-only traversal, including // a native inverse relationship view. This never exports mutation rights. if (!(binding.primitive === "resolve" && projection?.endpoint.publicTraversal)) { @@ -1640,11 +2107,7 @@ const validateConformances = ( "Edge primitives may only implement relationship members", ); } else if ( - !constraintSatisfiesConstraint( - projection.target.constraint, - operationEntry.member.target, - indexes.conformances, - ) || + !constraintSatisfiesConstraint(projection.target.constraint, operationEntry.member.target, indexes) || operationEntry.member.cardinality !== projection.endpoint.cardinality || (operationEntry.member.keyType !== undefined && operationEntry.member.keyType !== projection.endpoint.keyType) ) { @@ -1652,7 +2115,7 @@ const validateConformances = ( issues, "invalid-edge-binding", `${bindingPath}.binding`, - "Edge projection target or cardinality does not match relationship member", + `Edge projection target or cardinality does not match ${conformance.atomId}/${conformance.interfaceRevisionId}/${operationEntry.member.displayName}`, ); } if (!signaturesMatch(operation, expected)) { @@ -1719,14 +2182,26 @@ const validateConformances = ( }); if ( operation.scope === "class" && - (binding.dependencies.some((entry) => entry.binding.kind !== "constructor") || - packageExport.dependencyPorts.some((entry) => entry.requirement.kind !== "constructor")) + packageExport.dependencyPorts.some(({ requirement }) => { + if (["constructor", "creation", "reflection"].includes(requirement.kind)) return false; + if (requirement.kind !== "interface") return true; + if (requirement.receiver === "input") return false; + const contract = indexes.interfaces.get(requirement.interfaceRevisionId); + return ( + !requirement.operations?.length || + !requirement.operations.every((id) => + contract?.revision.members + .flatMap((m) => m.operations) + .some((op) => op.id === id && op.scope === "class"), + ) + ); + }) ) { issue( issues, "invalid-package-binding", bindingPath, - "Class functions may inject constructors, not instance-dependent ports", + "Class functions require receiverless dependencies such as creation ports", ); } validateBoundDependencies(issues, { @@ -1739,8 +2214,65 @@ const validateConformances = ( requirementGraph, graphSourceKey: key, }); + const check = packageEntry.exports.get(packageExport.acceptanceExportId!); + if (check) { + validateBoundDependencies(issues, { + dependencies: binding.acceptanceDependencies ?? [], + dependencyPorts: check.dependencyPorts, + atomId: conformance.atomId, + conformance, + path: `${bindingPath}.binding.acceptance`, + indexes, + requirementGraph, + graphSourceKey: key, + }); + } else if (binding.acceptanceDependencies !== undefined) + issue(issues, "invalid-package-binding", bindingPath, "Acceptance ports require a declared acceptance export"); } + for (const member of interfaceEntry.revision.members) { + const start = member.operations.find((op) => op.mode === "watch-start"); + const stop = member.operations.find((op) => op.mode === "watch-stop"); + if (!start || !stop || ![start, stop].some((op) => bindings.get(op.id)?.kind === "interface")) continue; + try { + const a = resolveForwardedBinding(workspace, conformance.atomId, conformance.interfaceRevisionId, start.id); + const b = resolveForwardedBinding(workspace, conformance.atomId, conformance.interfaceRevisionId, stop.id); + const target = indexes.interfaces + .get(a.conformance.interfaceRevisionId) + ?.revision.members.find((m) => m.operations.some((op) => op.id === a.entry.operationId)); + if (a.conformance !== b.conformance || !target?.operations.some((op) => op.id === b.entry.operationId)) + issue( + issues, + "invalid-forwarding-binding", + path, + "Forwarded watch start/stop must resolve to the same target member", + ); + const get = member.operations.find((op) => op.displayName === (member.kind === "value" ? "get" : "resolve")); + if (get && (a.binding.kind === "state" || a.binding.kind === "edge")) { + const read = resolveForwardedBinding( + workspace, + conformance.atomId, + conformance.interfaceRevisionId, + get.id, + ).binding; + const matches = + a.binding.kind === "state" + ? read.kind === "state" && read.slotId === a.binding.slotId + : read.kind === "edge" && + read.edgeTypeId === a.binding.edgeTypeId && + read.projectionId === a.binding.projectionId; + if (!matches) + issue( + issues, + "invalid-forwarding-binding", + path, + "Native forwarded watch must observe its getter's storage", + ); + } + } catch (error) { + issue(issues, "invalid-forwarding-binding", path, String(error)); + } + } const materializedMembers = new Set(); for (const [materializationIndex, materialization] of (conformance.relationshipMaterializations ?? []).entries()) { const materializationPath = `${path}.relationshipMaterializations[${materializationIndex}]`; @@ -1966,6 +2498,13 @@ export const validateWorkspaceRevision = (workspace: WorkspaceRevision): Capabil validatePackages(workspace, issues, indexes); validateConformances(workspace, issues, indexes); validateConstructors(workspace, issues, indexes); + issues.push( + ...validateExecutionContracts(workspace), + ...validateInitialization(workspace), + ...validatePresentations(workspace), + ...validateModules(workspace), + ...validateWorkspaceControllers(workspace), + ); return issues; }; @@ -2044,7 +2583,10 @@ export const resolveOperationBinding = ( atomId: AtomId, interfaceRevisionId: InterfaceRevisionId, operationId: OperationId, -): Binding | undefined => resolveConformance(plan, atomId, interfaceRevisionId)?.operationBindings.get(operationId); +): ConcreteBinding | undefined => { + if (!resolveConformance(plan, atomId, interfaceRevisionId)?.operationBindings.has(operationId)) return undefined; + return resolveForwardedBinding(plan.source, atomId, interfaceRevisionId, operationId).binding; +}; export type ResolvedOperationPlan = | { @@ -2102,6 +2644,7 @@ export const resolveOperationPlan = ( attachment: owned as OwnedAttachment & { attachment: EdgeDefinition }, }; } + if (binding.kind === "lifecycle" || binding.kind === "journal" || binding.kind === "subscription") return undefined; // These native providers require the current kernel. const packageRevision = plan.packages.get(binding.packageRevisionId); const packageExport = packageRevision?.exports.find( (entry): entry is PackageOperationExport | PackageFunctionExport => @@ -2205,6 +2748,11 @@ export const computeCapabilityClosure = ( } break; } + case "creation": { + for (const owner of dependency.binding.owners) + queued.push({ atomId: dependency.binding.atomId, interfaceRevisionId: owner.interfaceRevisionId }); + break; + } case "constructor": { constructors.add(dependency.binding.atomId); const constructor = plan.constructors.get(dependency.binding.atomId); @@ -2235,12 +2783,22 @@ export const computeCapabilityClosure = ( for (const interfaceRevisionId of plan.interfaces.get(root.interfaceRevisionId)?.requiredInterfaces ?? []) { queued.push({ atomId: root.atomId, interfaceRevisionId }); } + for (const module of conformance.source.moduleBindings ?? []) { + packages.add(module.packageRevisionId); + const member = plan.interfaces + .get(root.interfaceRevisionId) + ?.members.find((member) => member.id === module.memberId); + if (member?.kind === "module" && member.subject?.kind === "interface") + queued.push({ atomId: root.atomId, interfaceRevisionId: member.subject.interfaceRevisionId }); + } for (const binding of conformance.operationBindings.values()) { - if (binding.kind === "state") { + if (binding.kind === "state" || binding.kind === "journal" || binding.kind === "subscription") { attachments.add(binding.slotId); } else if (binding.kind === "edge") { attachments.add(binding.edgeTypeId); - } else { + } else if (binding.kind === "interface") { + queued.push({ atomId: root.atomId, interfaceRevisionId: binding.interfaceRevisionId }); + } else if (binding.kind === "package") { packages.add(binding.packageRevisionId); includeDependencies(root.atomId, binding.dependencies); const packageRevision = plan.packages.get(binding.packageRevisionId); diff --git a/src/execution-world/README.md b/src/execution-world/README.md new file mode 100644 index 0000000..740aaa0 --- /dev/null +++ b/src/execution-world/README.md @@ -0,0 +1,141 @@ +# Closed execution plans and artifacts + +`compileExecutionPlan` validates a workspace, links its checked queries, resolves +public forwarding, and emits one versioned plan with an SHA-256 digest. It retains +exact interface/package revisions and generic applications, owner initialization, +storage definitions/profiles, selected operation identities, query placement, and +source provenance. `quixos-workspace-compile --execution-plan-out PATH` writes it. + +The compiler emits operation dependencies as a graph. A traversed interface port +retains its edge/projection and exact selected operations; native reads identify +the canonical storage inputs. Cycles do not become infinitely expanded paths. +Execution and admission must apply their runtime depth/work budgets when walking +this graph. The plan includes the full linked GraphQL selections, not just root +traversals or a list of fields. + +Local and replica queries must have the same root contract. A local query cannot +introduce a relationship path absent from its replica source; authors expand the +source selection explicitly. Portable getters may require extra canonical fields +and exactly-one dependency traversals, recorded in the dependency graph. Admission +resolves those inputs on the server for selected objects. This never changes the +source query's candidate membership, and does not claim a limited source is a +complete interface extent. An incomplete runtime projection remains a coverage +state, not permission to invent a missing value. + +A portable transaction cannot depend on a local presentation query, including +through a selector. Pass explicit references and preconditions as mutation input. +Otherwise an admitted top-N list could select different mutation targets locally +and at acceptance. Remote transactions can implement explicit server workflows. +This restriction does not ban local queries from snapshot selectors. Such query +ports are view-relative: both hosts must use the frame's admitted candidate +universe. An executor must report unavailable coverage when that view is absent, +not substitute a full-server extent. Query-port acquisition remains unavailable +until the query executor implements this rule; emitting its checked dependency +graph is not runtime support. + +## Artifacts and world identity + +`PackageArtifacts` describes one package output containing a process service and +optional target modules. Implementation IDs are stable manifest keys, separate +from export capability IDs and JavaScript registry names. Each implementation +covers its ABI, export map, entrypoint, and declared asset paths/digests/sizes/MIME. +Its digest excludes its own digest field. Paths must be normalized and relative. + +`checkExecutionWorld` verifies plan identity, supported codecs, exact source and +binding schema, service fallback, promised targets, declared exports, and all +supplied file bytes. The reader must confine paths/symlinks to the checked output +and honor its byte limit before allocation. `artifactFileReader` supplies that +adapter for trusted immutable output roots. `loadArtifactWorld` checks candidate +outputs without importing package code. `BrowserArtifactGateway` registers only +declared browser assets and verifies bytes before serving; Web Studio places it +behind workspace authentication. LF-05 still checks actual module +registries/imports and verifies build receipts; a manifest +claim alone does not establish the JavaScript exports or an isolated process. + +The returned `camino-world-v1` descriptor includes every declared implementation +and its digest, so executor choice does not change semantics or silently load new +code. Each binding key is the JSON pair `[packageRevisionId, implementationId]`; +there can be both a service and module for the server target. World hashing uses +the core codec's canonical ordering and domain-separated SHA-256 framing. The +monorepo `camino-execution-world` check compares the compiler and core implementations. +No machine-local Nix path enters the world descriptor. + +The current binding schema is the closed workspace schema. This is conservative: +an unrelated contract change can require checking/rebuilding several packages. +LF-05 may narrow it to exact checked per-package closures, but must use the same +schema for generation, receipts, and world validation. It must not substitute an +older package schema just because the source revision did not change. + +## Transaction-port ABI + +`quixos-transaction-ports-v1` uses coordinator-issued frame/invocation handles and +monotonic invocation-local sequence numbers. A package receives its export ID, +input, and optional receiver. Port calls name a declared port and its allowed +primitive or exact interface operation; they never select arbitrary private slots, +packages, or coordinator transactions. Creation uses an explicit stable call key. Interface calls into transaction +operations also require a stable key, checked after resolving the selected +operation. The coordinator extends the birth path; it never derives that path +from an asynchronous completion order or the transport sequence number. + +The coordinator resolves each port through the installed operation binding, +checks effects/selected methods, validates typed arguments/references, and joins +nested calls to the outer logical transaction. A nested failure poisons the outer domain +transaction even if application code catches it; frame cleanup cannot turn it into +a partial success. Process completion cannot independently commit. The coordinator +owns savepoints, cancellation, frame expiry, and rejection. These enforcement +mechanisms are LF-06/LF-08, not guarantees provided by TypeScript frame types. + +The service control vocabulary includes readiness, cancellation, and shutdown; +transport framing and generated adapters are LF-05/LF-08. Portable modules and +services implement the same declared operation contracts. ABI compatibility is +not a statement about package trust, co-location permission, or multi-user isolation. + +Portable package bindings are generated with `quixos-codegen-ts` options +`{"portable":{"target":"browser"}}` (or `server`). They expose `Contexts`, +`PortableImplementation`, checked codec declarations, and `createRegistry`. +Handlers import the shared replica core, never Replicache. Stable mutation keys +are mandatory for creation and selected nested transaction calls. Snapshot +contexts expose only their declared read methods; runtime frame checks remain +mandatory even when the TypeScript source passed compilation. + +The maintained reader source in `tools/local-first/reader/portable.ts` typechecks +against both generated targets without casts. The root +`camino-portable-bindings` qualification compares Node, Window, Worker and +serialized-port behavior. Its simulated channel tests adapters, not installed +operation acquisition or process isolation; those require the coordinator. + +Closed generic package applications use the same portable adapters. The checked +workspace specializes their input/output types and dependency ports before code +generation. Each application has its own export ID and generated handler name +(`name$`); handlers are concrete, so a string application +and a list-of-int64 application cannot accidentally share an untyped codec. +Common implementation helpers can still be ordinary generic TypeScript functions. +This does not implement universal runtime type application or legacy constructors. +The `camino-portable-bindings` check compiles positive/negative typed consumers and +executes both specializations in Node, Chromium Window, and Worker hosts. + +Use `selectArtifactImplementation(checkedManifest, selection)` to choose an +implementation by target, ABI, required export IDs, and optionally its exact ID. +Without an ID, exactly one candidate must match. Missing exports, wrong targets, +and ambiguous choices fail; manifest order never determines execution. This +selects already checked bytes and does not grant permission to run untrusted +package code in process. Multiple selected implementations remain part of the +same checked world's complete artifact inventory. + +```ts +const implementation = selectArtifactImplementation(manifest, { + target: "server", + compatibility: "quixos-service-v1", + id: "isolated-service", + exports: requiredExports, +}); +``` + +Consumers use the selected entrypoint, registry export and export coverage from +that manifest. The reader's Nix builder accepts per-package +`implementationSelection = { "package:reader-runtime@1" = { + browser = "browser-module"; server = "server-module"; service = "service"; +}; };` overrides. Its fixture requires one implementation per lane covering that +lane's needed exports; the selector itself permits a smaller requested subset. +The installation records the chosen service and module entries. Process lifecycle +and trusted-module placement still belong to the existing runner. diff --git a/src/execution-world/artifacts.ts b/src/execution-world/artifacts.ts new file mode 100644 index 0000000..e5fbcea --- /dev/null +++ b/src/execution-world/artifacts.ts @@ -0,0 +1,411 @@ +import { createHash } from "node:crypto"; +import { canonicalJson } from "../capability-model/evolution.js"; +import { type ExecutionPlan, type PlanPackage, ExecutionPlanError, hexDigest, executionVersions } from "./plan.js"; + +export interface ArtifactFile { + path: string; + digest: string; + bytes: number; + mediaType: string; +} +export interface ArtifactImplementation { + id: string; + target: string; + compatibility: "quixos-service-v1" | "quixos-js-module-v1"; + path: string; + ports: "quixos-transaction-ports-v1"; + registryExport?: string; + exports: string[]; + /** Every executable/imported local asset, including the entrypoint, must be declared. */ + files: ArtifactFile[]; + contentDigest: string; +} +export interface ArtifactGroup { + schemaVersion: 1; + packageRevisionId: string; + bindingDigest: string; + versions: ExecutionPlan["definition"]["versions"]; + implementations: ArtifactImplementation[]; + components?: ComponentArtifact[]; +} +/** Installation evidence binds a reusable group to the selected exact source. + * Compilation receipts never claim that a cache hit was built at a new pin. */ +export interface PackageArtifacts extends ArtifactGroup { + sourceDigest: string; +} +export const componentSharedModules = [ + "react", + "react-dom", + "react-dom/client", + "react/jsx-runtime", + "@quixos/camino-react", + "@quixos/camino-react/ui", +] as const; +export interface ComponentArtifact { + id: string; + subject: string; + compatibility: "org.quixos.react.esm/1"; + path: string; + exportName: string; + sharedModules: Record<(typeof componentSharedModules)[number], string>; + files: ArtifactFile[]; + contentDigest: string; +} +export interface ImplementationSelection { + target: string; + compatibility: ArtifactImplementation["compatibility"]; + /** Optional only when exactly one compatible implementation covers these exports. */ + id?: string; + exports: readonly string[]; +} +/** Select from a checked manifest; selection does not establish package trust or isolation. + * Never resolve ambiguity by manifest order or silently substitute a requested implementation. + */ +export function selectArtifactImplementation( + manifest: PackageArtifacts, + selection: ImplementationSelection, +): ArtifactImplementation { + const candidates = manifest.implementations.filter( + (entry) => + (selection.id === undefined || entry.id === selection.id) && + entry.target === selection.target && + entry.compatibility === selection.compatibility && + selection.exports.every((id) => entry.exports.includes(id)), + ); + if (candidates.length !== 1) + throw new ExecutionPlanError( + candidates.length ? "AMBIGUOUS_IMPLEMENTATION" : "IMPLEMENTATION_UNAVAILABLE", + manifest.packageRevisionId, + `${selection.id ?? "Select an explicit implementation ID"}: expected one ${selection.target}/${selection.compatibility} implementation covering the requested exports; found ${candidates.length}`, + ); + return candidates[0]!; +} +export interface CheckedWorld { + codec: "camino-world-v1"; + plan: string; + artifacts: { binding: string; target: string; digest: string }[]; + versions: Record; +} +const fail = (path: string, message: string): never => { + throw new ExecutionPlanError("INVALID_ARTIFACT", path, message); +}; +const object = (value: unknown, path: string): Record => { + if (!value || typeof value !== "object" || Array.isArray(value) || Object.getPrototypeOf(value) !== Object.prototype) + fail(path, "Expected a plain JSON record"); + return value as Record; +}; +const keys = (record: Record, required: string[], optional: string[], path: string) => { + if ( + required.some((key) => !Object.hasOwn(record, key)) || + Object.keys(record).some((key) => ![...required, ...optional].includes(key)) + ) + fail(path, "Unexpected or missing fields"); +}; +const text = (value: unknown, path: string): string => { + if ( + typeof value !== "string" || + !value.length || + value.length > 4096 || + value.includes("\0") || + Buffer.from(value, "utf8").toString("utf8") !== value + ) + fail(path, "Expected bounded nonempty text"); + return value as string; +}; +const digest = (value: unknown, path: string): string => { + if (typeof value !== "string" || !/^[0-9a-f]{64}$/.test(value)) fail(path, "Expected SHA-256 hex"); + return value as string; +}; +const array = (value: unknown, path: string): unknown[] => { + if (!Array.isArray(value) || value.length > 10000) fail(path, "Expected a bounded array"); + return value as unknown[]; +}; +const unique = (values: string[], path: string) => { + if (new Set(values).size !== values.length) fail(path, "Duplicate identities"); +}; +export function artifactPath(value: unknown): string { + const path = text(value, "artifact path"); + if ( + path.startsWith("/") || + path.includes("\\") || + path.split("/").some((part) => !part || part === "." || part === "..") || + /[\u0000-\u001f\u007f?#%]/.test(path) + ) + fail(path, "Expected a normalized relative artifact path"); + return path; +} +/** No self-reference: the digest covers ABI/export metadata and the digests of every declared file. */ +export function implementationDigest(implementation: Omit): string { + return hexDigest({ + ...implementation, + exports: [...implementation.exports].sort(), + files: [...implementation.files].sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0)), + }); +} +export function componentArtifactDigest(component: Omit): string { + return hexDigest({ + ...component, + files: [...component.files].sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0)), + }); +} +function parseArtifactFiles(value: unknown, path: string): ArtifactFile[] { + const files = array(value, path).map((value): ArtifactFile => { + const file = object(value, path); + keys(file, ["path", "digest", "bytes", "mediaType"], [], path); + if (!Number.isSafeInteger(file.bytes) || (file.bytes as number) < 0 || (file.bytes as number) > 256 * 1024 * 1024) + fail(path, "Artifact file exceeds size bound"); + return { + path: artifactPath(file.path), + digest: digest(file.digest, path), + bytes: file.bytes as number, + mediaType: text(file.mediaType, path), + }; + }); + unique( + files.map((file) => file.path), + path, + ); + return files.sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0)); +} +function parseComponent(value: unknown, index: number): ComponentArtifact { + const path = `components[${index}]`, + entry = object(value, path); + keys( + entry, + ["id", "subject", "compatibility", "path", "exportName", "sharedModules", "files", "contentDigest"], + [], + path, + ); + if (entry.compatibility !== "org.quixos.react.esm/1") fail(path, "Unsupported component ABI"); + const files = parseArtifactFiles(entry.files, `${path}.files`); + const shared = object(entry.sharedModules, `${path}.sharedModules`); + keys(shared, [...componentSharedModules], [], `${path}.sharedModules`); + const sharedModules = Object.fromEntries( + componentSharedModules.map((name) => [name, artifactPath(shared[name])]), + ) as ComponentArtifact["sharedModules"]; + const entryPath = artifactPath(entry.path), + exportName = text(entry.exportName, path); + if (!/^[$A-Z_a-z][$\w]*$/.test(exportName)) fail(path, "Expected a JavaScript component export name"); + for (const modulePath of [entryPath, ...Object.values(sharedModules)]) + if (!files.some((file) => file.path === modulePath && file.mediaType === "text/javascript")) + fail(path, "Component entry and shared modules require declared JavaScript files"); + const component: ComponentArtifact = { + id: text(entry.id, path), + subject: text(entry.subject, path), + compatibility: "org.quixos.react.esm/1", + path: entryPath, + exportName, + sharedModules, + files, + contentDigest: digest(entry.contentDigest, path), + }; + const { contentDigest, ...body } = component; + if (componentArtifactDigest(body) !== contentDigest) fail(path, "Component metadata digest mismatch"); + return component; +} +export function parseArtifactGroup(value: unknown): ArtifactGroup { + const record = object(value, "manifest"); + keys( + record, + ["schemaVersion", "packageRevisionId", "bindingDigest", "versions", "implementations"], + ["components"], + "manifest", + ); + if (record.schemaVersion !== 1) fail("manifest", "Unsupported artifact manifest version"); + const versions = object(record.versions, "versions"); + keys(versions, ["value", "reference", "ports", "crdt"], [], "versions"); + for (const [key, value] of Object.entries(versions)) text(value, `versions.${key}`); + const implementations = array(record.implementations, "implementations").map( + (value, index): ArtifactImplementation => { + const path = `implementations[${index}]`, + entry = object(value, path); + keys( + entry, + ["id", "target", "compatibility", "path", "ports", "exports", "files", "contentDigest"], + ["registryExport"], + path, + ); + if (entry.compatibility !== "quixos-service-v1" && entry.compatibility !== "quixos-js-module-v1") + fail(path, "Unsupported artifact ABI"); + if (entry.ports !== "quixos-transaction-ports-v1") fail(path, "Unsupported transaction port ABI"); + if ( + entry.compatibility === "quixos-service-v1" && + (entry.target !== "server" || entry.registryExport !== undefined) + ) + fail(path, "Service must target server and has no JS registry export"); + if (entry.compatibility === "quixos-js-module-v1") text(entry.registryExport, `${path}.registryExport`); + const exports = array(entry.exports, `${path}.exports`).map((value) => text(value, `${path}.exports`)); + unique(exports, `${path}.exports`); + const files = parseArtifactFiles(entry.files, `${path}.files`); + const entryPath = artifactPath(entry.path); + if (!files.some((file) => file.path === entryPath)) fail(path, "Entrypoint is not covered by the file manifest"); + const result: ArtifactImplementation = { + id: text(entry.id, path), + target: text(entry.target, path), + compatibility: entry.compatibility as ArtifactImplementation["compatibility"], + path: entryPath, + ports: "quixos-transaction-ports-v1", + exports: exports.sort(), + files: files.sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0)), + contentDigest: digest(entry.contentDigest, path), + ...(entry.registryExport !== undefined ? { registryExport: entry.registryExport as string } : {}), + }; + const { contentDigest, ...body } = result; + if (implementationDigest(body) !== contentDigest) fail(path, "Implementation metadata digest mismatch"); + return result; + }, + ); + unique( + implementations.map((implementation) => implementation.id), + "implementations", + ); + const components = + record.components === undefined ? undefined : array(record.components, "components").map(parseComponent); + if (components) + unique( + components.map((component) => component.id), + "components", + ); + return { + schemaVersion: 1, + packageRevisionId: text(record.packageRevisionId, "packageRevisionId"), + bindingDigest: digest(record.bindingDigest, "bindingDigest"), + versions: versions as unknown as PackageArtifacts["versions"], + implementations, + ...(components ? { components } : {}), + }; +} +export function parsePackageArtifacts(value: unknown): PackageArtifacts { + const { sourceDigest, ...group } = object(value, "manifest"); + return { ...parseArtifactGroup(group), sourceDigest: digest(sourceDigest, "sourceDigest") }; +} +export function checkPackageCoverage( + expected: Pick, + manifest: PackageArtifacts, +): void { + for (const module of expected.modules ?? []) + if (!expected.components?.some((component) => component.id === module.id)) + fail(module.id, `No artifact consumer for module contract ${module.contract} and its subject`); + if (expected.bindingDigest !== manifest.bindingDigest || expected.sourceDigest !== manifest.sourceDigest) + fail(expected.revision, "Substituted binding schema or source revision"); + for (const component of manifest.components ?? []) { + const declaration = expected.components?.find((entry) => entry.id === component.id); + if (!declaration || declaration.subject !== component.subject || declaration.runtime !== "react") + fail(component.id, "Component differs from its declared subject/runtime contract"); + } + for (const component of expected.components ?? []) + if (!manifest.components?.some((entry) => entry.id === component.id)) + fail(component.id, "Missing component artifact"); + for (const implementation of manifest.implementations) + for (const id of implementation.exports) { + const entry = expected.exports.find((e) => e.id === id); + if (!entry) fail(implementation.id, `Unknown export ${id}`); + if ( + implementation.target !== "server" && + (!entry?.execution || + entry.execution.kind === "remote" || + !entry.execution.targets.includes(implementation.target)) + ) + fail(implementation.id, `Export ${id} does not promise target ${implementation.target}`); + } + for (const entry of expected.exports) { + if (!manifest.implementations.some((i) => i.compatibility === "quixos-service-v1" && i.exports.includes(entry.id))) + fail(expected.revision, `Missing service fallback for ${entry.id}`); + if (entry.execution.kind !== "remote") + for (const target of entry.execution.targets) + if ( + !manifest.implementations.some( + (i) => + i.target === target && + i.exports.includes(entry.id) && + (target === "server" || i.compatibility !== "quixos-service-v1"), + ) + ) + fail(expected.revision, `Missing ${target} implementation for ${entry.id}`); + } +} +export type ArtifactReader = (packageRevisionId: string, path: string, maxBytes: number) => Promise; +/** Shared by standalone package checks and complete world installation. */ +export async function checkArtifactFiles(manifest: ArtifactGroup, read: ArtifactReader): Promise { + const files = new Map(); + for (const artifact of [...manifest.implementations, ...(manifest.components ?? [])]) + for (const file of artifact.files) { + const existing = files.get(file.path); + if (existing && canonicalJson(existing) !== canonicalJson(file)) fail(file.path, "Conflicting file descriptors"); + files.set(file.path, file); + } + for (const file of files.values()) { + const bytes = await read(manifest.packageRevisionId, file.path, file.bytes); + if (bytes.byteLength !== file.bytes || createHash("sha256").update(bytes).digest("hex") !== file.digest) + fail(file.path, "Artifact bytes do not match the checked content digest"); + } +} +/** Caller supplies a reader confined to the resolved immutable package output, including symlink checks. */ +export async function checkExecutionWorld( + plan: ExecutionPlan, + inputs: readonly unknown[], + read: ArtifactReader, +): Promise { + if (canonicalJson(plan.definition.versions) !== canonicalJson(executionVersions)) + fail("world", "Unsupported plan codec versions"); + if (hexDigest(plan.definition) !== plan.digest) fail("plan", "Plan digest mismatch"); + const manifests = inputs.map(parsePackageArtifacts); + unique( + manifests.map((m) => m.packageRevisionId), + "packages", + ); + if (manifests.length !== plan.definition.packages.length) fail("world", "Package closure differs from plan"); + const artifacts: CheckedWorld["artifacts"] = []; + for (const expected of plan.definition.packages) { + const manifest = + manifests.find((m) => m.packageRevisionId === expected.revision) ?? + fail(expected.revision, "Missing package artifact"); + checkPackageCoverage(expected, manifest); + if (canonicalJson(manifest.versions) !== canonicalJson(plan.definition.versions)) + fail(expected.revision, "Incompatible runtime codecs"); + await checkArtifactFiles(manifest, read); + for (const implementation of manifest.implementations) { + artifacts.push({ + binding: JSON.stringify([expected.revision, implementation.id]), + target: implementation.target, + digest: implementation.contentDigest, + }); + } + for (const component of manifest.components ?? []) { + artifacts.push({ + binding: JSON.stringify([expected.revision, "component", component.id]), + target: "browser-renderer", + digest: component.contentDigest, + }); + } + } + return { + codec: "camino-world-v1", + plan: plan.digest, + artifacts: artifacts.sort((a, b) => + a.binding < b.binding ? -1 : a.binding > b.binding ? 1 : a.target < b.target ? -1 : a.target > b.target ? 1 : 0, + ), + versions: { ...plan.definition.versions }, + }; +} +/** Node counterpart of replica-core worldDigest. The wire fixture checks cross-package agreement. */ +export function checkedWorldDigest(world: CheckedWorld): string { + const normalized = { + ...world, + artifacts: [...world.artifacts].sort((a, b) => + a.binding < b.binding ? -1 : a.binding > b.binding ? 1 : a.target < b.target ? -1 : a.target > b.target ? 1 : 0, + ), + }; + const json = canonicalJson(normalized), + content = createHash("sha256").update(json).digest("hex"); + const components = ["camino-world-v1", content].map((text) => Buffer.from(text, "utf8")); + const count = Buffer.alloc(4); + count.writeUInt32BE(components.length); + const pieces = [count]; + for (const bytes of components) { + const length = Buffer.alloc(4); + length.writeUInt32BE(bytes.length); + pieces.push(length, bytes); + } + return createHash("sha256").update(Buffer.concat(pieces)).digest("hex"); +} diff --git a/src/execution-world/build-contract.ts b/src/execution-world/build-contract.ts new file mode 100644 index 0000000..a656e7f --- /dev/null +++ b/src/execution-world/build-contract.ts @@ -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; + versions: typeof executionVersions; + bindings: BindingContractSchema; + queries: PublicQueryContract[]; + component?: string; + moduleInterfaces: { revisionId: string; definitionId?: string; members: string[] }[]; + diagnostics: string[]; + }; + digest: string; +} + +const normalize = (value: T): T => JSON.parse(canonicalJson(value)) as T; +const indexSchema = (schema: BindingSchema) => ({ + interfaces: new Map( + [...schema.interfaces, ...(schema.interfaceTemplates ?? [])].map((entry) => [entry.revisionId, entry]), + ), + packages: new Map(schema.packages.map((entry) => [entry.revisionId, entry])), +}); +export function packageBuildContexts( + schema: BindingSchema, +): Record { + 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, +): 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) => + [...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(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(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("; "), + ); +} diff --git a/src/execution-world/candidate.ts b/src/execution-world/candidate.ts new file mode 100644 index 0000000..1be6d37 --- /dev/null +++ b/src/execution-world/candidate.ts @@ -0,0 +1,169 @@ +import { graphExecutionRepresentation } from "./graph-execution.js"; +import { open, lstat } from "node:fs/promises"; +import { constants } from "node:fs"; +import { join } from "node:path"; +import { hexDigest, ExecutionPlanError, type ExecutionPlan } from "./plan.js"; +import { loadExecutionInstallation, type PackageInstallationSelection } from "./installation.js"; +const rootPattern = /^\/nix\/store\/[a-z0-9]{32}-[^/\s]+$/; +async function readJson(root: string, file: string): Promise { + const handle = await open(join(root, file), constants.O_RDONLY | constants.O_NOFOLLOW); + try { + const metadata = await handle.stat(); + if (!metadata.isFile() || metadata.size > 32 * 1024 * 1024) throw Error(`Invalid checked candidate file: ${file}`); + return JSON.parse(await handle.readFile("utf8")); + } finally { + await handle.close(); + } +} +/** Load the checked Nix output, preserving its original compilation and provenance. + * The selected toolchain validates generated code during the build. This loader + * checks stored identities and artifact/runtime contracts, never recompiles the + * historical candidate with whichever compiler happens to run the host. */ +export async function loadCheckedInstallationCandidate( + root: string, + selections: Readonly> = {}, +) { + if (!rootPattern.test(root) || !(await lstat(root)).isDirectory()) + throw Error("Candidate must be an immutable Nix output directory"); + const [candidate, plan, artifacts, source, entryRequest, kernel, queries, graph] = await Promise.all([ + readJson(root, "candidate.json"), + readJson(root, "execution-plan.json"), + readJson(root, "execution-artifacts.json"), + readJson(root, "source.json"), + readJson(root, "entry.json"), + readJson(root, "kernel.json"), + readJson(root, "queries.json"), + readJson(root, "graph-execution.json"), + ]); + const { executionContracts: _legacyContracts, ...workspace } = candidate; + if ( + plan?.definition?.codec !== "camino-execution-plan-v1" || + hexDigest(plan.definition) !== plan.digest || + hexDigest(workspace) !== hexDigest(plan.definition.workspace) + ) + throw new ExecutionPlanError( + "CANDIDATE_PLAN_MISMATCH", + root, + "Stored plan must match its digest and exact workspace source contract", + ); + if ( + source.kind !== "workspace" || + source.commit !== plan.definition.workspace.sourceRootCommit || + typeof source.repository !== "string" + ) + throw new ExecutionPlanError("CANDIDATE_SOURCE_MISMATCH", root, "Expected exact workspace source provenance"); + const repository = new URL(source.repository); + if ( + !["https:", "ssh:"].includes(repository.protocol) || + repository.username || + repository.password || + repository.hash || + repository.search + ) + throw new ExecutionPlanError("CANDIDATE_SOURCE_INVALID", root, "Expected a source URL without credentials"); + if ( + artifacts.codec !== "camino-checked-execution-v2" || + typeof artifacts.generator !== "string" || + !/^[0-9a-f]{64}$/.test(artifacts.generator) || + !artifacts.roots || + typeof artifacts.roots !== "object" || + Array.isArray(artifacts.roots) + ) + throw new ExecutionPlanError("CANDIDATE_ARTIFACTS_INVALID", root, "Expected checked package roots"); + for (const entry of Object.values(artifacts.roots)) + if (typeof entry !== "string" || !rootPattern.test(entry)) + throw new ExecutionPlanError( + "CANDIDATE_ARTIFACTS_INVALID", + root, + "Package artifacts must be immutable Nix roots", + ); + const installation = await loadExecutionInstallation(plan, artifacts.roots, selections); + const entry = compileWorkspaceEntry(plan, entryRequest); + for (const [name, program, codec] of [ + ["kernel", kernel, "camino-kernel-program-v1"], + ["queries", queries, "camino-query-program-v1"], + ] as const) + if (program?.codec !== codec || program.plan !== kernel.plan || hexDigest(program) !== artifacts.programs?.[name]) + throw new ExecutionPlanError( + "CANDIDATE_PROGRAM_MISMATCH", + name, + "Stored interpreter input must match the checked build and plan", + ); + const expectedGraph = graphExecutionRepresentation(plan, installation, kernel, queries); + if (hexDigest(graph) !== hexDigest(expectedGraph) || artifacts.execution !== hexDigest(graph)) + throw new ExecutionPlanError( + "CANDIDATE_EXECUTION_MISMATCH", + root, + "Stored graph execution differs from checked inputs", + ); + const provenance = { + workspaceRevision: workspace.id as string, + source: { repository: source.repository as string, commit: source.commit as string }, + generator: artifacts.generator as string, + packages: (plan as ExecutionPlan).definition.packages.map((pkg) => ({ + revision: pkg.revision, + source: pkg.source, + })), + }; + const reference = { + root, + digest: hexDigest({ + plan: plan.digest, + world: installation.world, + provenance, + roots: artifacts.roots, + entry, + programs: artifacts.programs, + }), + }; + return { + reference, + workspace: plan.definition.workspace.workspaceId, + world: hexDigest(graph), + plan: kernel.plan as string, + graphExecution: graph, + provenance, + entry, + kernel, + queries, + installation, + executionPlan: plan as ExecutionPlan, + }; +} + +export function compileWorkspaceEntry(plan: ExecutionPlan, entryRequest: unknown): { operation: string } | null { + let entry: { operation: string } | null = null; + if (entryRequest !== null) { + if ( + typeof entryRequest !== "object" || + Array.isArray(entryRequest) || + Object.keys(entryRequest).sort().join(",") !== "atom,interface,operation" + ) + throw new ExecutionPlanError( + "INVALID_ENTRY_FACTORY", + "entry", + "Entry declaration must name atom, interface and operation identities", + ); + const operation = plan.definition.operations.find( + (op) => + op.atomId === (entryRequest as Record).atom && + op.interfaceRevisionId === (entryRequest as Record).interface && + op.operationId === (entryRequest as Record).operation, + ); + if ( + !operation || + operation.scope !== "class" || + operation.execution.kind !== "transaction" || + operation.input.kind !== "builtin" || + operation.input.name !== "unit" || + operation.output.kind !== "object-ref" + ) + throw new ExecutionPlanError( + "INVALID_ENTRY_FACTORY", + "entry", + "Entry must be a checked unit-input class transaction returning one object reference", + ); + entry = { operation: operation.key }; + } + return entry; +} diff --git a/src/execution-world/client-contracts.ts b/src/execution-world/client-contracts.ts new file mode 100644 index 0000000..a258e4e --- /dev/null +++ b/src/execution-world/client-contracts.ts @@ -0,0 +1,337 @@ +import type { + InterfaceRevision, + InterfaceOperation, + ExecutionContract, + PackageRevision, +} from "../capability-model/types.js"; +import { canonicalJson } from "../capability-model/evolution.js"; +import { portableValueSchema, tsType } from "../bindings/portable.js"; +import { type ExecutionPlan, resolvePlannedOperation, hexDigest, ExecutionPlanError } from "./plan.js"; +import type { BindingSchema } from "../bindings/index.js"; +import type { PackageBuildContract } from "./build-contract.js"; +import { preparationArtifact, type ReactPreparationArtifact } from "../react-build/declarations.js"; +import { moduleTypeContext } from "./module-contracts.js"; + +/** Public shape only: no slot inventory, implementation binding, or workspace identity. */ +export interface PublicOperationContract { + interface: string; + operation: string; + scope: "object" | "class"; + execution: ExecutionContract; + input: unknown; + output: unknown; + fieldPolicy?: "register" | "crdt"; +} +export function publicOperationContract( + iface: InterfaceRevision, + operation: InterfaceOperation, +): PublicOperationContract { + if (iface.template || operation.mode !== "call" || !iface.members.some((m) => m.operations.includes(operation))) + throw new ExecutionPlanError( + "UNSUPPORTED_CLIENT_CONTRACT", + iface.revisionId, + "Expected a closed interface call operation", + ); + const execution = operation.execution ?? { kind: "remote" }; + const member = iface.members.find((m) => m.operations.includes(operation))!; + const fieldPolicy = + member.kind === "value" && operation.scope !== "class" && execution.kind === "transaction" + ? operation.displayName === "edit" + ? "crdt" + : member.nativeField && operation.displayName === "set" + ? "register" + : undefined + : undefined; + return { + ...(fieldPolicy ? { fieldPolicy } : {}), + interface: iface.revisionId, + operation: operation.id, + scope: operation.scope ?? "object", + execution: execution.kind === "remote" ? execution : { ...execution, targets: [...execution.targets].sort() }, + input: portableValueSchema(operation.inputType), + output: portableValueSchema(operation.outputType), + }; +} +/** Resolve a public contract to internal dispatch. This does not authorize the receiver. */ +export function linkInterfaceOperation(plan: ExecutionPlan, atom: string, contract: PublicOperationContract) { + if (hexDigest(plan.definition) !== plan.digest) + throw new ExecutionPlanError("INVALID_PLAN", atom, "Invalid execution plan digest"); + const iface = plan.definition.workspace.interfaceImports.find((i) => i.revisionId === contract.interface); + const operation = iface?.members.flatMap((m) => m.operations).find((o) => o.id === contract.operation); + if (!iface || !operation || canonicalJson(publicOperationContract(iface, operation)) !== canonicalJson(contract)) + throw new ExecutionPlanError( + "CLIENT_CONTRACT_MISMATCH", + `${contract.interface}/${contract.operation}`, + "Installed interface differs from the imported operation contract", + ); + return resolvePlannedOperation(plan, atom, contract.interface, contract.operation); +} + +export interface PublicQueryContract { + package: string; + query: string; + definitionDigest: string; + root: { kind: "object" | "workspace-interface"; interface: string }; + placement: "server" | "replica" | "local"; + sourceQuery?: string; + input: unknown; + output: unknown; +} +export function publicQueryContract(pkg: PackageRevision, queryId: string): PublicQueryContract { + const query = pkg.checkedQueries?.find((q) => q.declaration.id === queryId); + if (!query) + throw new ExecutionPlanError("UNCHECKED_QUERY", `${pkg.revisionId}/${queryId}`, "Expected a checked package query"); + const placement = query.declaration.placement; + return { + package: pkg.revisionId, + query: queryId, + definitionDigest: query.definitionDigest, + root: query.declaration.root, + placement: placement?.kind ?? "server", + ...(placement?.kind === "local" ? { sourceQuery: placement.sourceQueryId } : {}), + input: portableValueSchema(query.variables), + output: portableValueSchema(query.output), + }; +} +export function linkPackageQuery(plan: ExecutionPlan, contract: PublicQueryContract) { + if (hexDigest(plan.definition) !== plan.digest) + throw new ExecutionPlanError("INVALID_PLAN", contract.query, "Invalid execution plan digest"); + const pkg = plan.definition.workspace.packageImports.find((p) => p.revisionId === contract.package); + if (!pkg || canonicalJson(publicQueryContract(pkg, contract.query)) !== canonicalJson(contract)) + throw new ExecutionPlanError( + "CLIENT_QUERY_MISMATCH", + `${contract.package}/${contract.query}`, + "Installed query differs from the imported query contract", + ); + const query = plan.definition.queries.find( + (q) => q.query.packageRevisionId === contract.package && q.query.checked.declaration.id === contract.query, + ); + if (!query) throw new ExecutionPlanError("UNINSTALLED_QUERY", contract.query, "No installed query binding"); + return query; +} + +/** Generate only the explicitly imported interfaces. No concrete atom inventory is needed. */ +export function generateInterfaceClientBindings( + imports: Readonly>, + runtimeModule = "@quixos/camino-replica-core", + moduleProps?: (interfaceId: string, memberId: string) => string, +): string { + const entries = Object.entries(imports).map(([alias, iface]) => { + if (iface.template) + throw new ExecutionPlanError( + "OPEN_CLIENT_CONTRACT", + alias, + "Specialize the interface before generating a client", + ); + const members = iface.members.map((member) => { + if (member.kind === "module" && member.subject?.kind === "interface") { + const subject = member.subject.interfaceRevisionId; + return `${JSON.stringify(member.displayName)}: defineModuleMember<${JSON.stringify(subject)}, ${moduleProps?.(iface.revisionId, member.id) ?? "{}"}>(${JSON.stringify({ interface: iface.revisionId, member: member.id, subject, contract: member.contract, ...moduleTypeContext(iface) })})`; + } + const operations = member.operations + .filter((op) => op.mode === "call") + .map((op) => { + const contract = publicOperationContract(iface, op); + return contract.fieldPolicy && member.kind === "value" + ? `${JSON.stringify(op.displayName)}: defineFieldOperation<${tsType(member.valueType)}, ${JSON.stringify(contract.fieldPolicy)}, ${JSON.stringify(contract.execution)}>(${JSON.stringify(contract)})` + : `${JSON.stringify(op.displayName)}: defineInterfaceOperation<${tsType(op.inputType)}, ${tsType(op.outputType)}, ${JSON.stringify(contract.execution)}>(${JSON.stringify(contract)})`; + }); + return `${JSON.stringify(member.displayName)}: {${operations.join(",\n")}}`; + }); + return `${JSON.stringify(alias)}: {${members.join(",\n")}}`; + }); + return `// Generated from imported interface contracts. Regenerate; do not edit.\nimport { defineModuleMember, defineInterfaceOperation, defineFieldOperation, type PreparedChange, type ManagedReference } from ${JSON.stringify(runtimeModule)};\nexport const interfaces = {${entries.join(",\n")}} as const;\n`; +} + +export function generatePackageQueryClientBindings( + pkg: PackageRevision, + runtimeModule = "@quixos/camino-replica-core", + packages: readonly PackageRevision[] = [pkg], +): string { + const internal = + (pkg.buildArtifacts?.[preparationArtifact] as ReactPreparationArtifact | undefined)?.internalQueries ?? []; + pkg = { + ...pkg, + queries: pkg.queries?.filter((query) => !internal.includes(query.id)), + checkedQueries: pkg.checkedQueries?.filter((query) => !internal.includes(query.declaration.id)), + }; + const queries = (pkg.checkedQueries ?? []) + .filter((query) => !query.declaration.fragment) + .map( + (query) => + `${JSON.stringify(query.declaration.displayName)}: definePackageQuery<${tsType(query.variables)}, ${tsType(query.output)}, ${JSON.stringify(publicQueryContract(pkg, query.declaration.id).placement)}>(${JSON.stringify(publicQueryContract(pkg, query.declaration.id))})`, + ); + const emitFragment = (provider: PackageRevision, id: string) => { + const query = provider.checkedQueries?.find((query) => query.declaration.id === id); + if ( + !query?.declaration.fragment || + query.declaration.fragment.admission || + query.declaration.placement?.kind !== "local" || + query.output.kind !== "record" || + !query.output.fields.root + ) + throw new ExecutionPlanError("UNCHECKED_FRAGMENT", id, "Expected a checked local fragment export"); + const output = query.output.fields.root; + const descriptor = { + package: provider.revisionId, + fragment: id, + digest: query.definitionDigest, + subject: query.declaration.root.interface, + input: portableValueSchema(query.variables), + output: portableValueSchema(output), + admission: publicQueryContract(provider, query.declaration.placement.sourceQueryId), + view: publicQueryContract(provider, id), + }; + return `definePackageFragment<${tsType(query.variables)}, ${tsType(output)}, ${JSON.stringify(query.declaration.root.interface)}>(${JSON.stringify(descriptor)})`; + }; + const fragments = (pkg.checkedQueries ?? []) + .filter((query) => query.declaration.fragment && !query.declaration.fragment.admission) + .map((query) => `${JSON.stringify(query.declaration.displayName)}: ${emitFragment(pkg, query.declaration.id)}`); + const imports = new Map>(); + for (const query of pkg.queries ?? []) + for (const imported of query.importedFragments ?? []) { + if (imported.packageRevisionId === pkg.revisionId) continue; + const provider = packages.find((provider) => provider.revisionId === imported.packageRevisionId); + const name = provider?.queries?.find((query) => query.id === imported.fragmentId)?.displayName; + if (!provider || !name) + throw new ExecutionPlanError("UNCHECKED_FRAGMENT", imported.fragmentId, "Missing imported fragment provider"); + const alias = imported.packageAlias ?? provider.displayName; + if (pkg.queries?.some((query) => query.fragment && query.displayName === alias)) + throw new ExecutionPlanError( + "FRAGMENT_ALIAS_COLLISION", + alias, + "Imported fragment namespace conflicts with a local fragment export", + ); + const entries = imports.get(alias) ?? new Map(); + entries.set(name, emitFragment(provider, imported.fragmentId)); + imports.set(alias, entries); + } + for (const [alias, entries] of imports) + fragments.push( + `${JSON.stringify(alias)}: {${[...entries].map(([name, value]) => `${JSON.stringify(name)}: ${value}`).join(",")}}`, + ); + return `// Generated from checked package query contracts. Regenerate; do not edit.\nimport { definePackageQuery, definePackageFragment, type ManagedReference } from ${JSON.stringify(runtimeModule)};\nexport const queries = {${queries.join(",\n")}} as const;\nexport const fragments = {${fragments.join(",\n")}} as const;\n`; +} + +/** Receipt-covered bindings expose the package's imports, component subjects and + * declared queries, never the workspace's complete atom/interface inventory. */ +export function generateComponentClientBindings(plan: ExecutionPlan, packageRevision: string): string { + const shape = plan.definition.packages.find((pkg) => pkg.revision === packageRevision); + if (!shape) throw new ExecutionPlanError("NO_COMPONENT_CONTRACT", packageRevision, "Unknown package"); + return generatePackageComponentBindings(plan.definition.bindingSchema, shape); +} +export function generatePackageComponentBindings( + schema: BindingSchema, + shape: PackageBuildContract["definition"]["package"], +): string { + const packageRevision = shape.revision; + const pkg = schema.packages.find((pkg) => pkg.revisionId === packageRevision); + const components = shape.components; + const modules = shape.modules ?? []; + if (!pkg || !components?.length) + throw new ExecutionPlanError("NO_COMPONENT_CONTRACT", packageRevision, "Package declares no component entries"); + const byId = new Map(schema.interfaces.map((iface) => [iface.revisionId as string, iface])); + const find = (id: string) => { + const contract = byId.get(id); + if (!contract || contract.template) + throw new ExecutionPlanError("OPEN_CLIENT_CONTRACT", id, "Component contracts must be closed"); + return contract; + }; + const imports = Object.fromEntries( + Object.entries(pkg.clientInterfaces ?? {}).map(([alias, id]) => [alias, find(id)]), + ); + const subjects = Object.fromEntries(components.map((component) => [component.displayName, find(component.subject)])); + const props = (iface: string, member: string) => + `ExtraModuleProps<${JSON.stringify(JSON.stringify([iface, member]))}>`; + // Preserve closed type arguments for the artifact consumer. Interpretation of + // a particular module contract belongs to that consumer, not this compiler. + const moduleArguments = (iface: InterfaceRevision) => + `[${(iface.application?.arguments ?? []) + .map((argument) => { + if (argument.kind === "value") return tsType(argument.type); + const target = argument.target; + const reference = + target.kind === "atom" + ? `ManagedReference<"atom", ${JSON.stringify(target.atomId)}>` + : `ManagedReference<"interface", ${JSON.stringify(target.interfaceRevisionId)}>`; + const fields = + target.kind === "interface" + ? find(target.interfaceRevisionId).members.flatMap((member) => { + if ((member.kind !== "value" && member.kind !== "relationship") || !member.queryRead) return []; + const getter = member.operations.find( + (operation) => operation.displayName === (member.kind === "value" ? "get" : "resolve"), + ); + return getter ? [`readonly ${JSON.stringify(member.displayName)}: ${tsType(getter.outputType)}`] : []; + }) + : []; + return `{readonly reference: ${reference}; readonly values: {${fields.join(";")}}}`; + }) + .join(",")}]`; + const interfaces = generateInterfaceClientBindings(imports, "@quixos/camino-react", props); + const subjectBindings = generateInterfaceClientBindings(subjects, "@quixos/camino-react", props) + .split("\n") + .filter((line) => !line.startsWith("import ")) + .join("\n") + .replace("export const interfaces =", "export const subjects ="); + const queries = generatePackageQueryClientBindings(pkg, "@quixos/camino-react", schema.packages) + .split("\n") + .filter((line) => !line.startsWith("import ")) + .join("\n"); + return ( + interfaces + + '\nimport {createObjectRenderer,definePackageQuery,definePackageFragment,type ComponentType,type ComponentProps,type PresentationHandle} from "@quixos/camino-react";\n' + + subjectBindings + + "\n" + + queries + + `\nexport const providers = {${Object.entries(pkg.clientPackages ?? {}) + .map(([alias, id]) => { + const imported = schema.packages.find((entry) => entry.revisionId === id); + if (!imported) throw new ExecutionPlanError("UNINSTALLED_PACKAGE", id, "Missing imported module provider"); + return `${JSON.stringify(alias)}:{${(imported.modules ?? []).map((module) => `${JSON.stringify(module.displayName)}:${JSON.stringify({ packageRevision: id, id: module.id })}`).join(",")}}`; + }) + .join(",")}} as const;\n` + + `\nexport const renderers = {${Object.entries(imports) + .map(([name, iface]) => `${JSON.stringify(name)}:createObjectRenderer(${JSON.stringify(iface.revisionId)})`) + .join(",\n")}} as const;\n` + + "\nexport interface ModuleProps {}\n" + + "export type ExtraModuleProps = K extends keyof ModuleProps ? ModuleProps[K] : {};\n" + + "export interface Modules {\n" + + components + .map((component) => { + const module = modules.find((module) => module.id === component.id)!; + const key = JSON.stringify([module.interfaceRevisionId, module.memberId]); + const artifact = pkg.buildArtifacts?.["org.quixos.react.preparation/1"] as + | import("../react-build/declarations.js").ReactPreparationArtifact + | undefined; + const preparation = artifact?.modules.find((entry) => entry.module === module.id); + const composition = pkg.queryCompositions?.find((entry) => entry.id === preparation?.composition); + if (!preparation || !composition) + throw new ExecutionPlanError("MISSING_PRESENTATION_INPUTS", module.id, "Declare inputs in quixos.react.json"); + const children = preparation.children.map((child) => { + let extra = "{}"; + if (child.role) { + const iface = find(child.role.interface); + const member = iface.members.find( + (entry) => entry.id === child.role!.member || entry.displayName === child.role!.member, + )!; + extra = props(iface.revisionId, member.id); + } + const handle = `PresentationHandle<${JSON.stringify(child.subject)}, ${extra}>`; + return `readonly ${JSON.stringify(child.name)}: ${child.each ? `readonly ${handle}[]` : child.with || child.when ? `${handle} | null` : handle}`; + }); + const input = `${tsType(preparation.input ?? composition.output)} & {readonly children: {${children.join(";")}}}`; + return `${JSON.stringify(component.displayName)}: ComponentType & ExtraModuleProps<${JSON.stringify(key)}>>;`; + }) + .join("\n") + + "\n}\n" + ); +} +export function componentAssetDeclarations(): string { + return ( + "// Generated component asset modules. Regenerate; do not edit.\n" + + ["svg", "png", "jpg", "jpeg", "gif", "webp", "avif", "woff", "woff2", "ttf", "ico"] + .map((extension) => `declare module "*.${extension}" { const value: string; export default value; }`) + .join("\n") + + '\ndeclare module "*.css";\n' + ); +} diff --git a/src/execution-world/execution-groups.ts b/src/execution-world/execution-groups.ts new file mode 100644 index 0000000..2b07126 --- /dev/null +++ b/src/execution-world/execution-groups.ts @@ -0,0 +1,259 @@ +import { portableReflectionValue } from "./reflection.js"; +import type { + BoundDependency, + DependencyBinding, + PackageExport, + PersistentAttachment, +} from "../capability-model/types.js"; +import { type ExecutionInstallation, type ExecutionGroupIdentity } from "./installation.js"; +import { type ExecutionPlan, hexDigest, ExecutionPlanError } from "./plan.js"; + +export interface ExecutionEnvironment { + /** Explicit host placement. Merely including an optional module does not select it. */ + readonly placement: "service" | "module"; + /** Exact host executable/isolation inputs, computed by the trusted installer. */ + readonly hostDigest: string; + readonly policyDigest: string; + /** Identifier of the secret/config generation; never secret material. */ + readonly credentialGeneration: string; +} +export interface InstalledExecutionGroup extends ExecutionGroupIdentity { + readonly packageRevision: string; + readonly consumedPackages: readonly string[]; +} +/** Conservative outbound closure. Runtime graph values, reverse callers, source + * provenance and the workspace epoch are deliberately absent from this identity. */ +export function compileExecutionGroups( + plan: ExecutionPlan, + installation: ExecutionInstallation, + environments: Readonly>, +): InstalledExecutionGroup[] { + if (installation.plan !== plan.digest || hexDigest(plan.definition) !== plan.digest) + throw new ExecutionPlanError("GROUP_PLAN_MISMATCH", plan.digest, "Expected one checked installation"); + const workspace = plan.definition.workspace; + const packages = new Map(workspace.packageImports.map((p) => [p.revisionId as string, p])); + const operations = new Map(plan.definition.operations.map((op) => [op.key, op])); + const attachments = new Map( + [...workspace.sharedAttachments, ...workspace.conformances.flatMap((c) => c.privateAttachments)].map((a) => [ + a.id, + a, + ]), + ); + const logical = (revision: string) => { + const pkg = packages.get(revision); + if (!pkg) throw new ExecutionPlanError("GROUP_PACKAGE_MISSING", revision, "Uninstalled consumed package"); + return pkg.packageId; + }; + const environment = (revision: string) => { + const value = environments[revision]; + if ( + !value || + !["service", "module"].includes(value.placement) || + !/^[0-9a-f]{64}$/.test(value.hostDigest) || + !/^[0-9a-f]{64}$/.test(value.policyDigest) || + !value.credentialGeneration || + value.credentialGeneration.length > 4096 + ) + throw new ExecutionPlanError( + "GROUP_ENVIRONMENT_MISSING", + revision, + "Expected checked host/policy digests and a credential generation", + ); + return value; + }; + const implementations = new Map( + installation.packages.map((pkg) => { + if (!pkg.service && !pkg.module) return [pkg.revision, undefined] as const; + const implementation = pkg[environment(pkg.revision).placement]; + if (!implementation) + throw new ExecutionPlanError( + "GROUP_IMPLEMENTATION_MISSING", + pkg.revision, + "Host selected an unavailable implementation", + ); + return [pkg.revision, implementation] as const; + }), + ); + const attachmentContract = (value: PersistentAttachment) => { + const { displayName: _name, ...rest } = value; + return value.kind === "state" + ? rest + : { ...rest, endpoints: value.endpoints.map(({ displayName: _name, ...endpoint }) => endpoint) }; + }; + const bindingContract = (value: DependencyBinding) => + value.kind === "query" ? { ...value, packageRevisionId: logical(value.packageRevisionId) } : value; + return installation.packages + .filter((p) => !!implementations.get(p.revision)) + .map((root) => { + const consumed = new Map(), + visited = new Set(), + visitedQueries = new Set(), + consumedPackages = new Set(); + const remember = (key: string, value: unknown) => consumed.set(key, value); + const storage = (id: string) => { + const value = attachments.get(id); + if (!value) throw new ExecutionPlanError("GROUP_STORAGE_MISSING", id, "Uninstalled consumed storage"); + remember(`storage:${id}`, attachmentContract(value)); + }; + // Deletion may visit related objects and their owned state. Conservatively + // include the connected schema component, including possible future edges. + const lifecycle = (atom: string) => { + const visited = new Set(), + pending = [atom]; + const atoms = ( + constraint: Extract["endpoints"][number]["constraint"], + ) => + constraint.kind === "atom" + ? [constraint.atomId as string] + : workspace.conformances + .filter((c) => c.interfaceRevisionId === constraint.interfaceRevisionId) + .map((c) => c.atomId as string); + while (pending.length) { + const current = pending.pop()!; + if (visited.has(current)) continue; + visited.add(current); + for (const owner of workspace.conformances.filter((c) => c.atomId === current)) + for (const attachment of owner.privateAttachments) storage(attachment.id); + for (const attachment of attachments.values()) + if (attachment.kind === "edge" && attachment.endpoints.some((e) => atoms(e.constraint).includes(current))) { + storage(attachment.id); + pending.push(...attachment.endpoints.flatMap((e) => atoms(e.constraint))); + } + } + }; + const targets = (binding: DependencyBinding, atom: string): string[] => { + if (binding.kind === "interface" && binding.receiver === "input") + return workspace.conformances + .filter((c) => c.interfaceRevisionId === binding.interfaceRevisionId) + .map((c) => c.atomId); + if (!("via" in binding) || !binding.via) return [atom]; + storage(binding.via.edgeTypeId); + const edge = attachments.get(binding.via.edgeTypeId); + if (!edge || edge.kind !== "edge") + throw new ExecutionPlanError("GROUP_EDGE_MISSING", atom, "Missing traversal edge"); + const endpoint = edge.endpoints.find((e) => e.projectionId !== binding.via!.projectionId)!; + const constraint = endpoint.constraint; + return constraint.kind === "atom" + ? [constraint.atomId] + : workspace.conformances + .filter((c) => c.interfaceRevisionId === constraint.interfaceRevisionId) + .map((c) => c.atomId); + }; + const query = (revision: string, id: string) => { + const key = JSON.stringify([revision, id]); + if (visitedQueries.has(key)) return; + visitedQueries.add(key); + const value = plan.definition.queries.find((q) => q.id === key); + if (!value) throw new ExecutionPlanError("GROUP_QUERY_MISSING", key, "Missing consumed query"); + remember(`query:${JSON.stringify([logical(revision), id])}`, value.query.checked.definitionDigest); + for (const operation of value.operations) visit(operation); + }; + const dependencies = (entry: PackageExport, bindings: readonly BoundDependency[], atom: string) => { + for (const bound of bindings) { + const target = bound.binding, + port = entry.dependencyPorts.find((p) => p.id === bound.portId)!; + if ("via" in target && target.via) storage(target.via.edgeTypeId); + if (target.kind === "reflection") { + for (const selector of target.selectors) + remember("reflection:" + selector, portableReflectionValue(workspace, selector)); + } else if (target.kind === "state") storage(target.slotId); + else if (target.kind === "edge") storage(target.edgeTypeId); + else if (target.kind === "query") query(target.packageRevisionId, target.queryId); + else if (target.kind === "interface" && port.requirement.kind === "interface") { + const iface = workspace.interfaceImports.find((i) => i.revisionId === target.interfaceRevisionId)!; + for (const targetAtom of targets(target, atom)) + for (const op of port.requirement.operations ?? + iface.members.flatMap((m) => m.operations.map((o) => o.id))) + visit(JSON.stringify([targetAtom, target.interfaceRevisionId, op])); + } else if (target.kind === "creation") { + for (const seed of target.owners) { + const owner = workspace.conformances.find( + (c) => c.atomId === target.atomId && c.interfaceRevisionId === seed.interfaceRevisionId, + )!; + remember(`birth:${owner.id}`, { + id: owner.id, + atom: owner.atomId, + interface: owner.interfaceRevisionId, + initialization: owner.initialization ?? [], + }); + for (const attachment of owner.privateAttachments) storage(attachment.id); + } + } else + throw new ExecutionPlanError( + "GROUP_DEPENDENCY_UNSUPPORTED", + bound.portId, + "Cannot establish execution reuse for this dependency", + ); + } + }; + const visit = (key: string) => { + if (visited.has(key)) return; + visited.add(key); + const operation = operations.get(key); + if (!operation) throw new ExecutionPlanError("GROUP_OPERATION_MISSING", key, "Missing consumed operation"); + const binding = operation.binding; + if (binding.kind === "package") { + const pkg = packages.get(binding.packageRevisionId)!; + const implementation = implementations.get(pkg.revisionId); + if (!implementation) + throw new ExecutionPlanError( + "GROUP_IMPLEMENTATION_MISSING", + pkg.revisionId, + "Consumed package has no selected server implementation", + ); + consumedPackages.add(pkg.packageId); + remember(`implementation:${pkg.packageId}`, { + digest: implementation.contentDigest, + environment: environment(pkg.revisionId), + }); + const entry = pkg.exports.find((e) => e.id === binding.exportId)!; + const bindings = binding.dependencies.map((d) => ({ portId: d.portId, binding: bindingContract(d.binding) })); + const acceptance = (binding.acceptanceDependencies ?? []).map((d) => ({ + portId: d.portId, + binding: bindingContract(d.binding), + })); + const contract = { + input: operation.input, + output: operation.output, + execution: operation.execution, + scope: operation.scope, + binding: { + package: pkg.packageId, + export: binding.exportId, + dependencies: bindings, + acceptanceDependencies: acceptance, + }, + }; + // Deduplicate aliases of the same bound export, rather than keying its owner by reverse callers. + remember(`binding:${hexDigest(contract)}`, contract); + dependencies(entry, binding.dependencies, operation.atomId); + if (entry.acceptanceExportId) + dependencies( + pkg.exports.find((e) => e.id === entry.acceptanceExportId)!, + binding.acceptanceDependencies ?? [], + operation.atomId, + ); + } else { + remember(`operation:${key}`, operation); + if (binding.kind === "state") storage(binding.slotId); + if (binding.kind === "edge") storage(binding.edgeTypeId); + if (binding.kind === "lifecycle") lifecycle(operation.atomId); + } + }; + for (const operation of plan.definition.operations) + if (operation.binding.kind === "package" && operation.binding.packageRevisionId === root.revision) + visit(operation.key); + const own = implementations.get(root.revision)!; + const exports = packages.get(root.revision)!.exports.map(({ displayName: _name, dependencyPorts, ...entry }) => ({ + ...entry, + dependencyPorts: dependencyPorts.map(({ displayName: _name, ...port }) => port), + })); + const entries = [...consumed].sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0)); + return { + logicalId: logical(root.revision), + packageRevision: root.revision, + executionDigest: `sha256:${hexDigest({ implementation: own.contentDigest, exports, environment: environment(root.revision), versions: plan.definition.versions, consumed: entries })}`, + consumedPackages: [...consumedPackages].sort(), + }; + }); +} diff --git a/src/execution-world/files.ts b/src/execution-world/files.ts new file mode 100644 index 0000000..f5b627a --- /dev/null +++ b/src/execution-world/files.ts @@ -0,0 +1,174 @@ +import { constants } from "node:fs"; +import { lstat, open, realpath } from "node:fs/promises"; +import path from "node:path"; +import { createHash } from "node:crypto"; +import { + artifactPath, + parsePackageArtifacts, + checkExecutionWorld, + checkedWorldDigest, + type ArtifactReader, + type ArtifactFile, + type PackageArtifacts, + type CheckedWorld, +} from "./artifacts.js"; +import { type ExecutionPlan, ExecutionPlanError } from "./plan.js"; + +const maxFileBytes = 256 * 1024 * 1024; +const metadataLimit = 16 * 1024 * 1024; +const fail = (where: string, message: string): never => { + throw new ExecutionPlanError("ARTIFACT_FILE_INVALID", where, message); +}; +/** Roots must be trusted immutable output directories. Refuse symlinks, including + * interior directory links. Hash verification is still required before serving bytes. */ +export async function artifactFileReader(roots: Readonly>): Promise { + const directories = new Map(); + for (const [revision, root] of Object.entries(roots)) { + if (!path.isAbsolute(root) || path.normalize(root) !== root || (await realpath(root)) !== root) + fail(revision, "Output root must be a canonical absolute directory"); + const stat = await lstat(root); + if (!stat.isDirectory() || stat.isSymbolicLink()) fail(root, "Output root is not a directory"); + directories.set(revision, root); + } + return async (revision, relative, limit) => { + const root = directories.get(revision); + if (!root) fail(revision, "Unknown package output"); + artifactPath(relative); + if (!Number.isSafeInteger(limit) || limit < 0 || limit > maxFileBytes) fail(relative, "Invalid file byte limit"); + const components = relative.split("/"); + let resolved = root!; + for (let index = 0; index < components.length; index++) { + resolved = path.join(resolved, components[index]!); + const stat = await lstat(resolved); + if (stat.isSymbolicLink() || (index < components.length - 1 ? !stat.isDirectory() : !stat.isFile())) + fail(relative, "Only real directories and regular files are allowed"); + } + const handle = await open(resolved, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK); + try { + const stat = await handle.stat(); + if (!stat.isFile() || stat.size > limit) fail(relative, "File exceeds its declared bound"); + // A fixed allocation and one extra byte detect growth without readFile's unbounded allocation. + const buffer = Buffer.alloc(stat.size + 1); + let offset = 0; + while (offset < buffer.length) { + const { bytesRead } = await handle.read(buffer, offset, buffer.length - offset, offset); + if (!bytesRead) break; + offset += bytesRead; + } + if (offset !== stat.size) fail(relative, "File changed while reading"); + return buffer.subarray(0, offset); + } finally { + await handle.close(); + } + }; +} +export interface LoadedArtifactWorld { + world: CheckedWorld; + digest: string; + manifests: PackageArtifacts[]; + read: ArtifactReader; +} +/** Immutable-output loader. No module code is imported into the coordinator. */ +export async function loadArtifactWorld( + plan: ExecutionPlan, + roots: Readonly>, +): Promise { + const read = await artifactFileReader(roots), + manifests: PackageArtifacts[] = []; + const expected = plan.definition.packages.map((p) => p.revision).sort(); + if (JSON.stringify(Object.keys(roots).sort()) !== JSON.stringify(expected)) + fail("world", "Output roots must exactly match the installed plan"); + for (const revision of expected) { + const bytes = await read(revision, "share/quixos/package-artifacts.json", metadataLimit); + const manifest = parsePackageArtifacts(JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes))); + if (manifest.packageRevisionId !== revision) fail(revision, "Output belongs to another package revision"); + manifests.push(manifest); + } + const world = await checkExecutionWorld(plan, manifests, read); + return { world, digest: checkedWorldDigest(world), manifests, read }; +} +export interface BrowserArtifact { + url: string; + implementation: string; + packageRevisionId: string; + registryExport: string; + exports: readonly string[]; +} +export interface ArtifactResponse { + status: 200 | 304; + headers: Record; + body: Uint8Array; +} +/** Shared runtime imports use content identity, independent of the importing package/world. + * The file remains covered by every importing implementation's checked manifest. */ +export function contentArtifactUrl(file: Pick): string { + if (!/^[0-9a-f]{64}$/.test(file.digest)) fail(file.path, "Invalid content digest"); + artifactPath(file.path); + return `/__quixos/artifacts/content/${file.digest}/${encodeURIComponent(path.basename(file.path))}`; +} +/** This registry is placed behind the workspace gateway's existing authentication. + * Lookup uses an explicit URL table; request paths never become filesystem paths. */ +export class BrowserArtifactGateway { + readonly entries: readonly BrowserArtifact[]; + private readonly routes = new Map(); + private readonly read: ArtifactReader; + constructor(loaded: LoadedArtifactWorld) { + this.read = loaded.read; + const entries: BrowserArtifact[] = []; + for (const manifest of loaded.manifests) + for (const implementation of manifest.implementations) { + if ( + implementation.target !== "target:org.quixos.execution.javascript@1" || + implementation.compatibility !== "quixos-js-module-v1" + ) + continue; + const prefix = `/__quixos/artifacts/${loaded.digest}/${implementation.contentDigest}/`; + for (const file of implementation.files) { + if (!/^[a-zA-Z0-9.+-]+\/[a-zA-Z0-9.+-]+$/.test(file.mediaType)) fail(file.path, "Invalid asset MIME type"); + const url = prefix + file.path.split("/").map(encodeURIComponent).join("/"); + const previous = this.routes.get(url); + if ( + previous && + (previous.file.digest !== file.digest || + previous.file.bytes !== file.bytes || + previous.file.mediaType !== file.mediaType) + ) + fail(url, "Conflicting browser asset route"); + this.routes.set(url, { revision: manifest.packageRevisionId, file: Object.freeze({ ...file }) }); + // Exact shared-file names are a packaging convention, never a path lookup capability. + if (file.path.startsWith("share/quixos/shared/")) { + const contentUrl = contentArtifactUrl(file); + const existing = this.routes.get(contentUrl); + if (existing && (existing.file.bytes !== file.bytes || existing.file.mediaType !== file.mediaType)) + fail(contentUrl, "Conflicting shared asset metadata"); + this.routes.set(contentUrl, { revision: manifest.packageRevisionId, file: Object.freeze({ ...file }) }); + } + } + entries.push({ + url: prefix + implementation.path.split("/").map(encodeURIComponent).join("/"), + implementation: implementation.id, + packageRevisionId: manifest.packageRevisionId, + registryExport: implementation.registryExport!, + exports: Object.freeze([...implementation.exports]), + }); + } + this.entries = Object.freeze(entries.map((entry) => Object.freeze(entry))); + } + async response(url: string, ifNoneMatch?: string): Promise { + const route = this.routes.get(url); + if (!route) return undefined; + const bytes = await this.read(route.revision, route.file.path, route.file.bytes); + if (bytes.length !== route.file.bytes || createHash("sha256").update(bytes).digest("hex") !== route.file.digest) + fail(url, "Browser asset changed after world validation"); + const etag = `"sha256-${route.file.digest}"`; + const headers = { + "content-type": route.file.mediaType, + "cache-control": "private, max-age=31536000, immutable", + etag, + "x-content-type-options": "nosniff", + }; + return ifNoneMatch === etag + ? { status: 304, headers, body: new Uint8Array() } + : { status: 200, headers, body: bytes }; + } +} diff --git a/src/execution-world/graph-execution.ts b/src/execution-world/graph-execution.ts new file mode 100644 index 0000000..93fb031 --- /dev/null +++ b/src/execution-world/graph-execution.ts @@ -0,0 +1,59 @@ +import { ExecutionPlanError, hexDigest, type ExecutionPlan } from "./plan.js"; +import type { ExecutionInstallation } from "./installation.js"; + +/** Execution inputs, not build evidence. Nominal revision IDs remain dispatch + * keys: replacing a contract with a structurally similar one is not continuity. + * Host placement/policy/credentials are compared separately by execution groups. */ +export function graphExecutionRepresentation( + plan: ExecutionPlan, + installation: ExecutionInstallation, + kernel: unknown, + queries: unknown, +) { + if (installation.plan !== plan.digest || hexDigest(plan.definition) !== plan.digest) + throw new ExecutionPlanError("GRAPH_PLAN_MISMATCH", "graph", "Expected one checked installation"); + if ( + !kernel || + typeof kernel !== "object" || + Array.isArray(kernel) || + !queries || + typeof queries !== "object" || + Array.isArray(queries) + ) + throw new ExecutionPlanError("GRAPH_PROGRAM_MISMATCH", "graph", "Expected interpreter program records"); + const input = kernel as Record, + queryInput = queries as Record; + const { plan: key, ...program } = input; + if ( + input.codec !== "camino-kernel-program-v1" || + queryInput.codec !== "camino-query-program-v1" || + queryInput.plan !== key || + key !== hexDigest({ versions: plan.definition.versions, program }) + ) + throw new ExecutionPlanError("GRAPH_PROGRAM_MISMATCH", "graph", "Interpreter inputs do not match execution key"); + return { + codec: "camino-graph-execution-v1", + versions: plan.definition.versions, + kernel, + queries, + // Include every selected implementation, including acceptance/remote code + // and the browser dependency closure, not merely the public export names. + packages: installation.packages.map((pkg) => ({ + revision: pkg.revision, + service: pkg.service ?? null, + module: pkg.module ?? null, + browser: pkg.browserImplementation ?? null, + })), + controllers: plan.definition.packages.map((pkg) => ({ + revision: pkg.revision, + controllers: pkg.controllers ?? [], + })), + shared: { + core: installation.coreURL ?? null, + engine: installation.engineURL ?? null, + components: [ + ...new Map(installation.components.flatMap((component) => Object.entries(component.sharedModules))).entries(), + ].sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0)), + }, + }; +} diff --git a/src/execution-world/index.ts b/src/execution-world/index.ts new file mode 100644 index 0000000..1f1af96 --- /dev/null +++ b/src/execution-world/index.ts @@ -0,0 +1,27 @@ +export * from "./plan.js"; +export * from "./native-backends.js"; +export * from "./artifacts.js"; +export * from "./ports.js"; +export * from "./files.js"; +export * from "./installation.js"; +export * from "./client-contracts.js"; +export * from "./execution-groups.js"; + +export * from "./receipts.js"; + +export { generatePortableTypeScriptBindings, generateRemoteTypeScriptBindings } from "../bindings/portable.js"; + +export * from "./kernel.js"; +export * from "./projections.js"; +export * from "./queries.js"; +export * from "./query-document.js"; +export { generatePortableClientBindings } from "../bindings/portable-client.js"; +export * from "./candidate.js"; + +export { reviewSemanticMajors } from "../capability-model/evolution.js"; + +export * from "./public-reflection.js"; + +export * from "./graph-execution.js"; + +export * from "./build-contract.js"; diff --git a/src/execution-world/inspection.ts b/src/execution-world/inspection.ts new file mode 100644 index 0000000..1155b51 --- /dev/null +++ b/src/execution-world/inspection.ts @@ -0,0 +1,208 @@ +import { compileKernelProgram } from "./kernel.js"; +import { hexDigest, type ExecutionPlan } from "./plan.js"; +import type { PublicQueryContract } from "./client-contracts.js"; +import { portableValueSchema } from "../bindings/portable.js"; +const scalar = (name: string) => ({ kind: "scalar", name }); +const record = (fields: Record) => ({ kind: "record", fields }); +const optional = (value: unknown) => ({ kind: "optional", value }); +const selection = (name: string, extra: Record = {}) => ({ + name, + key: name, + arguments: {}, + conditions: [], + selection: [], + ...extra, +}); +const variable = (name: string) => ({ kind: "variable", name }); +const meta = () => selection("_camino", { selection: [selection("ref")] }); +export const inspectionPackage = "org.quixos.inspection/1"; +export interface InspectionTemplate { + interface: string; + members: { + id: string; + name: string; + key: string; + flag: string; + lane: "local" | "server"; + many: boolean; + relationship: boolean; + }[]; + inputs?: PublicQueryContract; + view?: PublicQueryContract; + server?: PublicQueryContract; + completeInputs?: PublicQueryContract; + completeView?: PublicQueryContract; +} +/** A finite installed template per interface/lane, not a per-client query compiler. + * Flags select public cache boundaries; normal scope identity bounds specialization. */ +export function compileInspectionTemplates(plan: ExecutionPlan) { + const kernel = compileKernelProgram(plan) as { + operations: { + key: string; + atom: string; + interface: string; + operation: string; + effect: string; + targets: string[]; + unsupported: string | null; + output: unknown; + binding: { kind: string }; + }[]; + }; + const queries: unknown[] = [], + contracts: PublicQueryContract[] = [], + templates: InspectionTemplate[] = []; + for (const iface of plan.definition.workspace.interfaceImports) { + if (iface.template) continue; + const atoms = plan.definition.workspace.conformances + .filter((c) => c.interfaceRevisionId === iface.revisionId) + .map((c) => c.atomId) + .sort(); + if (!atoms.length) continue; + const members = iface.members + .filter((member) => (member.kind === "value" || member.kind === "relationship") && member.queryRead) + .sort((a, b) => (a.id < b.id ? -1 : 1)); + if (!members.length || members.length > 128) continue; + const template: InspectionTemplate = { interface: iface.revisionId, members: [] }; + for (const mode of ["local", "complete", "server"] as const) { + const lane = mode === "server" ? "server" : "local"; + const complete = mode === "complete"; + const fields: unknown[] = [], + selections: unknown[] = [], + inputFields: Record = {}, + outputFields: Record = {}; + for (const [index, member] of members.entries()) { + if (member.kind !== "value" && member.kind !== "relationship") continue; + const getter = member.operations.find((op) => op.displayName === (member.kind === "value" ? "get" : "resolve")); + if (!getter) continue; + const providers = atoms.map((atom) => + kernel.operations.find( + (op) => op.atom === atom && op.interface === iface.revisionId && op.operation === getter.id, + ), + ); + if (providers.some((op) => !op || op.unsupported || !["snapshot", "remote"].includes(op.effect))) continue; + const portable = providers.every( + (op) => op!.effect === "snapshot" && op!.targets.includes("target:org.quixos.execution.javascript@1"), + ); + if (lane === "local" && !portable) continue; + const key = "m" + index, + flag = "s" + index; + const many = member.kind === "relationship" && ["many", "many-unique"].includes(member.cardinality); + // Only native edges promise the relationship traversal used by the interpreter. + if (member.kind === "relationship" && providers.some((op) => op!.binding.kind !== "edge")) continue; + if (!complete && (lane === "local" || !portable)) + template.members.push({ + id: member.id, + name: member.displayName, + key, + flag, + lane, + many, + relationship: member.kind === "relationship", + }); + inputFields[flag] = scalar("bool"); + const field = selection(member.displayName, { + key, + interfaceRevisionId: iface.revisionId, + memberId: member.id, + conditions: [{ include: true, value: variable(flag) }], + }); + let output = portableValueSchema(getter.outputType); + if (member.kind === "relationship") { + const expectation = + member.target.kind === "atom" + ? { kind: "atom", id: member.target.atomId } + : { kind: "interface", id: member.target.interfaceRevisionId }; + const node = record({ _camino: record({ ref: { kind: "object-ref", expectation } }) }); + if (member.target.kind === "interface") + Object.assign(field, { targetInterfaceRevisionId: member.target.interfaceRevisionId }); + if (many) { + const bound = key + (complete ? "All" : "First"); + inputFields[bound] = scalar("int32"); + if (!complete) inputFields[key + "After"] = optional(scalar("string")); + const keyFields = { key: scalar("string"), ...(member.keyType ? { mapKey: scalar(member.keyType) } : {}) }; + const keySelections = [selection("key"), ...(member.keyType ? [selection("mapKey")] : [])]; + Object.assign(field, { + arguments: complete + ? { all: variable(bound) } + : { first: variable(bound), after: variable(key + "After") }, + selection: [ + selection("entries", { selection: [...keySelections, selection("node", { selection: [meta()] })] }), + ...(complete + ? [] + : [selection("pageInfo", { selection: [selection("hasNextPage"), selection("endCursor")] })]), + ], + }); + output = record({ + entries: { kind: "list", value: record({ ...keyFields, node }) }, + ...(complete + ? {} + : { pageInfo: record({ hasNextPage: scalar("bool"), endCursor: optional(scalar("string")) }) }), + }); + } else { + Object.assign(field, { selection: [meta()] }); + output = node; + } + } + outputFields[key] = optional(output); + selections.push(field); + for (const op of providers) + fields.push({ + atom: op!.atom, + interface: iface.revisionId, + member: member.id, + name: member.displayName, + operation: op!.key, + }); + } + if (!selections.length) continue; + const root = { kind: "object" as const, interface: iface.revisionId }; + const input = record(inputFields), + output = record({ root: record(outputFields) }); + const base = { + root, + atoms, + variables: input, + defaults: {}, + selection: [selection("root", { selection: selections })], + fields, + admittedOperations: [], + budgets: { + rows: 512, + depth: 8, + resultBytes: 8 * 1024 * 1024, + candidates: 1000, + rpcCalls: 128, + concurrency: 8, + deadlineMs: 10000, + }, + }; + for (const kind of lane === "local" ? (["inputs", "view"] as const) : (["server"] as const)) { + const templateKey = complete ? (kind === "inputs" ? "completeInputs" : "completeView") : kind; + const query = JSON.stringify([iface.revisionId, templateKey]); + const sourceQuery = JSON.stringify([iface.revisionId, complete ? "completeInputs" : "inputs"]); + const program = { + ...base, + id: JSON.stringify([inspectionPackage, query]), + placement: kind === "inputs" ? "replica" : kind === "view" ? "local" : "remote", + source: kind === "view" ? JSON.stringify([inspectionPackage, sourceQuery]) : null, + }; + const contract: PublicQueryContract = { + package: inspectionPackage, + query, + definitionDigest: hexDigest(program), + root, + placement: kind === "inputs" ? "replica" : kind === "view" ? "local" : "server", + ...(kind === "view" ? { sourceQuery } : {}), + input, + output, + }; + queries.push(program); + contracts.push(contract); + template[templateKey] = contract; + } + } + if (template.members.length) templates.push(template); + } + return { queries, contracts, templates }; +} diff --git a/src/execution-world/installation.ts b/src/execution-world/installation.ts new file mode 100644 index 0000000..61be4dc --- /dev/null +++ b/src/execution-world/installation.ts @@ -0,0 +1,304 @@ +import { compileInspectionTemplates, type InspectionTemplate } from "./inspection.js"; +import path from "node:path"; +import { loadCheckedArtifactWorld } from "./receipts.js"; +import { contentArtifactUrl, loadArtifactWorld, type LoadedArtifactWorld } from "./files.js"; +import { selectArtifactImplementation, type ArtifactImplementation, type ComponentArtifact } from "./artifacts.js"; +import { ExecutionPlanError, hexDigest, type ExecutionPlan } from "./plan.js"; +import { + publicOperationContract, + publicQueryContract, + type PublicOperationContract, + type PublicQueryContract, +} from "./client-contracts.js"; +import { resultInterfaces } from "./projections.js"; + +/** Activation is assigned by graph authority, never by a package artifact. */ +export interface ActiveExecutionIdentity { + readonly workspace: string; + readonly world: string; + readonly plan: string; + readonly epoch: string; +} +/** Host-owned execution selection. The lifecycle manager fills its checked inputs. */ +export interface ExecutionGroupIdentity { + readonly logicalId: string; + readonly executionDigest: string; +} +export interface ClientContractCatalog { + readonly inspections: readonly InspectionTemplate[]; + readonly resultInterfaces: Readonly>; + readonly operations: readonly PublicOperationContract[]; + readonly queries: readonly PublicQueryContract[]; + readonly components: readonly InstalledComponent[]; + readonly presentations: readonly { id: string; subject: string; packageRevision: string; component: string }[]; + readonly moduleBindings: readonly { + atom: string; + interface: string; + member: string; + packageRevision: string; + id: string; + }[]; +} +/** Explicit projection prevents accidental spreading of filesystem roots or private bindings. */ +export function clientContractCatalog( + plan: ExecutionPlan, + installation: ExecutionInstallation, + requestedPackages: readonly string[], +): ClientContractCatalog { + if (installation.plan !== plan.digest || hexDigest(plan.definition) !== plan.digest) + throw new ExecutionPlanError( + "CATALOG_PLAN_MISMATCH", + plan.digest, + "Installation and public catalog must use the same checked plan", + ); + const packages = requestedPackages.map((revision) => { + const pkg = plan.definition.workspace.packageImports.find((p) => p.revisionId === revision); + if (!pkg) + throw new ExecutionPlanError("UNINSTALLED_PACKAGE", revision, "Requested catalog package is not installed"); + return pkg; + }); + // Interface metadata contains no authority or private implementation configuration. + // A later admission optimization can narrow this set without changing operation identities. + const operations = plan.definition.workspace.interfaceImports + .filter((i) => !i.template) + .flatMap((iface) => + iface.members.flatMap((member) => + member.operations + .filter((o) => o.mode === "call") + .map((operation) => publicOperationContract(iface, operation)), + ), + ); + const inspection = compileInspectionTemplates(plan); + return { + inspections: inspection.templates, + resultInterfaces: Object.fromEntries( + plan.definition.operations.map((operation) => [operation.key, resultInterfaces(plan, operation.key)]), + ), + operations, + queries: [ + ...inspection.contracts, + ...packages.flatMap((pkg) => (pkg.checkedQueries ?? []).map((q) => publicQueryContract(pkg, q.declaration.id))), + ], + components: installation.components.filter((c) => requestedPackages.includes(c.packageRevision)), + moduleBindings: plan.definition.workspace.conformances.flatMap((c) => + (c.moduleBindings ?? []) + .filter((b) => requestedPackages.includes(b.packageRevisionId)) + .map((b) => ({ + atom: c.atomId, + interface: c.interfaceRevisionId, + member: b.memberId, + packageRevision: b.packageRevisionId, + id: b.moduleId, + })), + ), + presentations: (plan.definition.presentations ?? []) + .filter((p) => requestedPackages.includes(p.packageRevisionId)) + .map((p) => ({ id: p.id, subject: p.subject, packageRevision: p.packageRevisionId, component: p.componentId })), + }; +} + +export interface PackageInstallationSelection { + readonly browser?: { readonly id?: string }; + readonly service?: { readonly id?: string }; + /** Including a module is optional; executing it still requires host trust policy. */ + readonly module?: { readonly id?: string }; +} +export interface ExecutionInstallation { + readonly codec: "camino-execution-installation-v1"; + readonly plan: string; + readonly world: string; + readonly packages: readonly { + readonly revision: string; + readonly root: string; + readonly service?: ArtifactImplementation; + readonly module?: ArtifactImplementation; + readonly browserImplementation?: ArtifactImplementation; + readonly browserURL?: string; + readonly browserRegistry?: string; + }[]; + readonly routes: Readonly>; + readonly coreURL?: string; + readonly engineURL?: string; + readonly components: readonly InstalledComponent[]; +} +/** Safe to expose in a presentation catalog: no source paths, state slots, or host configuration. */ +export interface InstalledComponent { + readonly role: { + readonly interface: string; + readonly member: string; + readonly subject: string; + readonly contract: string; + readonly typeArguments?: readonly unknown[]; + readonly requiredInterfaces?: readonly string[]; + }; + readonly packageRevision: string; + readonly id: string; + readonly subject: string; + readonly url: string; + readonly exportName: string; + readonly styles: readonly string[]; + readonly compatibility: ComponentArtifact["compatibility"]; + /** Owned and interpreted by the React artifact consumer. */ + readonly preparation?: unknown; + readonly sharedModules: Readonly>; +} +/** Build-time validation uses the compiler selected by the Nix derivation. */ +export async function checkExecutionInstallation( + plan: ExecutionPlan, + roots: Readonly>, + generator: string, + selections: Readonly> = {}, +): Promise { + const loaded = await loadCheckedArtifactWorld(plan, roots, generator); + return assembleExecutionInstallation(plan, roots, loaded, selections); +} +/** Load immutable artifacts without regenerating bindings with the host compiler. + * Format, coverage, content and runtime ABI checks still apply. This grants no + * authority to activate a world or run optional modules in process. */ +export async function loadExecutionInstallation( + plan: ExecutionPlan, + roots: Readonly>, + selections: Readonly> = {}, +): Promise { + return assembleExecutionInstallation(plan, roots, await loadArtifactWorld(plan, roots), selections); +} +function assembleExecutionInstallation( + plan: ExecutionPlan, + roots: Readonly>, + loaded: LoadedArtifactWorld, + selections: Readonly>, +): ExecutionInstallation { + const fail = (where: string, message: string): never => { + throw new ExecutionPlanError("INSTALLATION_INVALID", where, message); + }; + for (const revision of Object.keys(selections)) + if (!loaded.manifests.some((manifest) => manifest.packageRevisionId === revision)) + fail(revision, "Selection names a package outside this checked world"); + const routes: Record = Object.create(null); + const packages: ExecutionInstallation["packages"][number][] = []; + const components: InstalledComponent[] = []; + const sharedComponentModules = new Map(); + let coreURL: string | undefined, engineURL: string | undefined; + for (const manifest of loaded.manifests) { + const revision = manifest.packageRevisionId; + const root = roots[revision]!; + const selection = selections[revision] ?? {}; + const exports = plan.definition.packages.find((pkg) => pkg.revision === revision)!.exports; + const service = + exports.length || selection.service + ? selectArtifactImplementation(manifest, { + ...selection.service, + target: "server", + compatibility: "quixos-service-v1", + exports: exports.map((entry) => entry.id), + }) + : undefined; + for (const component of manifest.components ?? []) { + const sharedModules = Object.fromEntries( + Object.entries(component.sharedModules).map(([name, filePath]) => { + const file = component.files.find((file) => file.path === filePath)!; + const url = contentArtifactUrl(file), + previous = sharedComponentModules.get(name); + if (previous && previous !== url) fail(component.id, `Components disagree on shared module ${name}`); + sharedComponentModules.set(name, url); + return [name, url]; + }), + ) as InstalledComponent["sharedModules"]; + for (const file of component.files) { + const url = contentArtifactUrl(file); + if (routes[url] && routes[url].mediaType !== file.mediaType) fail(url, "Conflicting artifact media types"); + routes[url] ??= { path: path.join(root, file.path), mediaType: file.mediaType }; + } + components.push({ + ...(() => { + const pkg = plan.definition.workspace.packageImports.find((entry) => entry.revisionId === revision); + const artifact = pkg?.buildArtifacts?.["org.quixos.react.preparation/1"] as + | { modules: { module: string }[] } + | undefined; + const preparation = artifact?.modules.find((entry) => entry.module === component.id); + return preparation ? { preparation } : {}; + })(), + role: (() => { + const module = plan.definition.packages + .find((p) => p.revision === revision)! + .modules!.find((m) => m.id === component.id)!; + return { + interface: module.interfaceRevisionId, + member: module.memberId, + subject: component.subject, + contract: module.contract, + ...(module.typeArguments ? { typeArguments: module.typeArguments } : {}), + ...(module.requiredInterfaces ? { requiredInterfaces: module.requiredInterfaces } : {}), + }; + })(), + packageRevision: revision, + id: component.id, + subject: component.subject, + compatibility: component.compatibility, + exportName: component.exportName, + styles: component.files.filter((file) => file.mediaType === "text/css").map(contentArtifactUrl), + sharedModules, + url: contentArtifactUrl(component.files.find((file) => file.path === component.path)!), + }); + } + const module = selection.module + ? selectArtifactImplementation(manifest, { + ...selection.module, + target: "server", + compatibility: "quixos-js-module-v1", + exports: exports.map((entry) => entry.id), + }) + : undefined; + const browserExports = exports.filter( + (entry) => + entry.execution.kind !== "remote" && + entry.execution.targets.includes("target:org.quixos.execution.javascript@1"), + ); + if (!browserExports.length) { + if (selection.browser) fail(revision, "Package has no browser exports to select"); + packages.push({ revision, root, ...(service ? { service } : {}), ...(module ? { module } : {}) }); + continue; + } + const browser = selectArtifactImplementation(manifest, { + ...selection.browser, + target: "target:org.quixos.execution.javascript@1", + compatibility: "quixos-js-module-v1", + exports: browserExports.map((entry) => entry.id), + }); + const entry = browser.files.find((file) => file.path === browser.path)!; + const shared = (name: string) => { + const files = browser.files.filter((file) => file.path.endsWith("/" + name)); + if (files.length !== 1) fail(revision, "Expected one shared " + name); + return contentArtifactUrl(files[0]!); + }; + const core = shared("core.mjs"), + engine = shared("engine.mjs"); + if ((coreURL && coreURL !== core) || (engineURL && engineURL !== engine)) + fail(revision, "Browser packages disagree on the shared runtime identity"); + coreURL = core; + engineURL = engine; + for (const file of browser.files) { + const url = contentArtifactUrl(file); + if (routes[url] && routes[url].mediaType !== file.mediaType) fail(url, "Conflicting artifact media types"); + routes[url] ??= { path: path.join(root, file.path), mediaType: file.mediaType }; + } + packages.push({ + revision, + root, + ...(service ? { service } : {}), + ...(module ? { module } : {}), + browserImplementation: browser, + browserURL: contentArtifactUrl(entry), + browserRegistry: browser.registryExport, + }); + } + return { + codec: "camino-execution-installation-v1", + plan: plan.digest, + world: loaded.digest, + packages, + components, + routes, + ...(coreURL ? { coreURL, engineURL } : {}), + }; +} diff --git a/src/execution-world/kernel.ts b/src/execution-world/kernel.ts new file mode 100644 index 0000000..47d9ff5 --- /dev/null +++ b/src/execution-world/kernel.ts @@ -0,0 +1,371 @@ +import { portableReflectionType, portableReflectionValue } from "./reflection.js"; +import type { + ValueType, + InitializationField, + PersistentAttachment, + DependencyBinding, + BoundDependency, + PackageExport, +} from "../capability-model/types.js"; +import { type ExecutionPlan, ExecutionPlanError, hexDigest } from "./plan.js"; +import { portableValueSchema } from "../bindings/portable.js"; +import { subscriptionContract } from "../capability-model/journals.js"; +type PreparedStep = string | null | { case: string; id: string }; +function preparedInputPaths(type: ValueType, path: PreparedStep[] = []): PreparedStep[][] { + if (type.kind === "change") return [path]; + if (type.kind === "optional") return preparedInputPaths(type.value, path); + if (type.kind === "list") return preparedInputPaths(type.value, [...path, null]); + if (type.kind === "variant") + return Object.entries(type.cases).flatMap(([label, entry]) => + preparedInputPaths(entry.value, [...path, { case: label, id: entry.id }]), + ); + if (type.kind === "record") + return Object.entries(type.fields).flatMap(([key, value]) => preparedInputPaths(value, [...path, key])); + return []; +} +/** Small data-only interpreter input; no compiler, parser, Node module or SQL dependency reaches the browser. */ +export function compileKernelProgram(plan: ExecutionPlan): unknown { + if (hexDigest(plan.definition) !== plan.digest) + throw new ExecutionPlanError("INVALID_PLAN", "kernel", "Plan digest mismatch"); + const workspace = plan.definition.workspace; + if (workspace.sharedAttachments.some((attachment) => attachment.kind !== "edge")) + throw new ExecutionPlanError( + "UNSUPPORTED_SHARED_STORAGE", + "kernel", + "Shared scalar storage is not implemented; use conformance-owned state", + ); + const schema = (type: ValueType): unknown | null => { + try { + return portableValueSchema(type); + } catch { + return null; + } + }; + const ownerBySlot = new Map(); + const edges: { id: string; owner: string | null; a: unknown; b: unknown }[] = []; + const lowerEdge = (storage: Extract, owner: string | null) => { + const endpoints = storage.endpoints.map((endpoint) => { + if (endpoint.ordered || (endpoint.keyType && endpoint.keyType !== "string") || endpoint.retainOther) + throw new ExecutionPlanError( + "UNSUPPORTED_GRAPH_FEATURE", + storage.id, + "Ordered relations, non-string keys and automatic retention are not implemented", + ); + return { + projection: endpoint.projectionId, + constraint: { + kind: endpoint.constraint.kind, + id: + endpoint.constraint.kind === "atom" ? endpoint.constraint.atomId : endpoint.constraint.interfaceRevisionId, + }, + cardinality: endpoint.cardinality, + ...(endpoint.keyType ? { keyType: endpoint.keyType } : {}), + ...(endpoint.onDelete ? { onDelete: endpoint.onDelete } : {}), + }; + }); + edges.push({ id: storage.id, owner, a: endpoints[0], b: endpoints[1] }); + }; + for (const storage of workspace.sharedAttachments) if (storage.kind === "edge") lowerEdge(storage, null); + const owners = plan.definition.owners.map((owner) => { + const slots: unknown[] = []; + for (const storage of owner.storage as PersistentAttachment[]) { + if (storage.kind === "state") { + const shape = schema(storage.valueType); + if (!shape) + throw new ExecutionPlanError("UNSUPPORTED_STORAGE_CODEC", storage.id, "Unsupported canonical value type"); + ownerBySlot.set(storage.id, owner.id); + const pinsFor = (payload: ValueType) => { + const expectations = new Map(); + const walk = (type: ValueType): void => { + if (type.kind === "object-ref") { + const expectation = { + kind: type.expectation.kind, + id: type.expectation.kind === "atom" ? type.expectation.atomId : type.expectation.interfaceRevisionId, + }; + expectations.set(JSON.stringify(expectation), expectation); + } else if (type.kind === "list" || type.kind === "optional") walk(type.value); + else if (type.kind === "record") Object.values(type.fields).forEach(walk); + else if (type.kind === "variant") Object.values(type.cases).forEach((entry) => walk(entry.value)); + }; + walk(payload); + const pins = [...expectations.values()].map((expectation) => { + const id = `journal-pin:${hexDigest([storage.id, expectation])}`; + const projection = `${id}:source`; + edges.push({ + id, + owner: owner.id, + a: { + projection, + constraint: { kind: "atom", id: owner.atom }, + cardinality: "many", + keyType: "string", + onDelete: "detach", + }, + b: { projection: `${id}:target`, constraint: expectation, cardinality: "many", onDelete: "restrict" }, + }); + return { edge: id, projection }; + }); + return pins; + }; + let journal: unknown, subscription: unknown; + if (storage.storagePolicy.kind === "journal") { + journal = { limits: storage.storagePolicy.limits, pins: pinsFor(storage.valueType) }; + } + if (storage.storagePolicy.kind === "subscription") { + const bindings = plan.definition.operations.filter( + (operation) => + operation.atomId === owner.atom && + operation.binding.kind === "subscription" && + operation.binding.slotId === storage.id && + operation.binding.primitive === "attach", + ); + if (bindings.length !== 1) + throw new ExecutionPlanError( + "SUBSCRIPTION_ATTACHMENT_REQUIRED", + storage.id, + "Native subscription storage requires one checked attachment operation", + ); + const binding = bindings[0]!; + const operation = workspace.interfaceImports + .find((iface) => iface.revisionId === binding.interfaceRevisionId)! + .members.flatMap((member) => member.operations) + .find((operation) => operation.id === binding.operationId)!; + const contract = subscriptionContract( + storage.storagePolicy.payload, + storage.valueType, + operation, + workspace.interfaceImports, + ); + const expectation = (value: typeof contract.source) => ({ + kind: value.kind, + id: value.kind === "atom" ? value.atomId : value.interfaceRevisionId, + }); + const source = expectation(contract.source), + handler = expectation(contract.handler); + const configuration: ValueType = { + kind: "record", + fields: { + source: { kind: "object-ref", expectation: contract.source }, + handler: { kind: "object-ref", expectation: contract.handler }, + parameters: storage.valueType, + }, + }; + const edge = `subscription-active:${hexDigest(storage.id)}`, + projection = `${edge}:subscription`; + edges.push({ + id: edge, + owner: owner.id, + a: { projection, constraint: { kind: "atom", id: owner.atom }, cardinality: "many", onDelete: "restrict" }, + b: { projection: `${edge}:stream`, constraint: source, cardinality: "many", onDelete: "restrict" }, + }); + subscription = { + payload: schema(storage.storagePolicy.payload), + source, + handler, + operation: contract.operation, + pins: pinsFor(configuration), + active: { edge, projection }, + }; + } + slots.push({ + id: storage.id, + schema: shape, + policy: storage.storagePolicy.kind === "crdt-document" ? "crdt" : storage.storagePolicy.kind, + ...(journal ? { journal } : {}), + ...(subscription ? { subscription } : {}), + ...(Object.hasOwn(storage, "defaultValue") ? { defaultLiteral: storage.defaultValue } : {}), + }); + } else lowerEdge(storage, owner.id); + } + return { + id: owner.id, + atom: owner.atom, + interface: owner.interface, + slots, + initialize: (owner.initialization as InitializationField[]).map((field) => ({ + input: field.input, + binding: + field.binding.kind === "state" + ? { kind: "state", slot: field.binding.slotId } + : { kind: "edge", edge: field.binding.edgeTypeId, projection: field.binding.projectionId }, + })), + }; + }); + const slotOwner = (slot: string) => { + const owner = ownerBySlot.get(slot); + if (!owner) throw new ExecutionPlanError("MISSING_STORAGE_OWNER", slot, "No checked owner"); + return owner; + }; + const via = (binding: DependencyBinding) => + "via" in binding && binding.via + ? { via: { edge: binding.via.edgeTypeId, projection: binding.via.projectionId } } + : {}; + const operations = plan.definition.operations.map((operation) => { + const binding = operation.binding; + let target: unknown; + let unsupported: string | null = null; + if (binding.kind === "lifecycle") target = { kind: "lifecycle", primitive: binding.primitive }; + else if (binding.kind === "journal" || binding.kind === "subscription") + target = { + kind: binding.kind, + owner: slotOwner(binding.slotId), + slot: binding.slotId, + primitive: binding.primitive, + }; + else if (binding.kind === "state") + target = { kind: "state", owner: slotOwner(binding.slotId), slot: binding.slotId, primitive: binding.primitive }; + else if (binding.kind === "edge") + target = { + kind: "edge", + edge: binding.edgeTypeId, + projection: binding.projectionId, + primitive: binding.primitive, + }; + else { + const pkg = workspace.packageImports.find((p) => p.revisionId === binding.packageRevisionId)!; + const entry = pkg.exports.find((e) => e.id === binding.exportId)!; + const compilePorts = (entry: PackageExport, dependencies: BoundDependency[]) => + dependencies.map((dependency) => { + const requirement = entry.dependencyPorts.find((p) => p.id === dependency.portId)!.requirement; + const bound = dependency.binding; + const common = { id: dependency.portId, ...via(bound) }; + if (bound.kind === "reflection" && requirement.kind === "reflection") + return { + ...common, + kind: "reflection", + values: Object.fromEntries( + bound.selectors.map((selector) => [ + selector, + { + schema: portableValueSchema(portableReflectionType(selector)), + value: portableReflectionValue(workspace, selector), + }, + ]), + ), + }; + if (bound.kind === "state" && requirement.kind === "state") + return { + ...common, + kind: "state", + owner: slotOwner(bound.slotId), + slot: bound.slotId, + primitives: requirement.primitives, + }; + if (bound.kind === "edge" && requirement.kind === "edge") + return { + ...common, + kind: "edge", + edge: bound.edgeTypeId, + projection: bound.projectionId, + primitives: requirement.primitives, + }; + if (bound.kind === "interface" && requirement.kind === "interface") { + const iface = workspace.interfaceImports.find((i) => i.revisionId === bound.interfaceRevisionId)!; + return { + ...common, + kind: "interface", + interface: bound.interfaceRevisionId, + ...(!bound.receiver && + !bound.via && + (requirement.operations ?? iface.members.flatMap((m) => m.operations.map((o) => o.id))).every( + (id) => iface.members.flatMap((m) => m.operations).find((o) => o.id === id)?.scope === "class", + ) + ? { staticAtom: operation.atomId } + : {}), + ...(bound.receiver ? { receiver: bound.receiver } : {}), + operations: requirement.operations ?? iface.members.flatMap((m) => m.operations.map((o) => o.id)), + }; + } + if (bound.kind === "creation" && requirement.kind === "creation") + return { + ...common, + kind: "creation", + atom: bound.atomId, + schema: portableValueSchema(requirement.inputType), + recipe: hexDigest({ + codec: "camino-creation-recipe-v1", + versions: plan.definition.versions, + operation: operation.key, + export: entry.id, + port: dependency.portId, + requirement, + binding: bound, + owners: owners.filter((owner) => owner.atom === bound.atomId), + edges: edges.filter((edge) => + owners.some((owner) => owner.atom === bound.atomId && owner.id === edge.owner), + ), + }), + owners: bound.owners.map((seed) => { + const owner = owners.find((o) => o.atom === bound.atomId && o.interface === seed.interfaceRevisionId); + if (!owner) + throw new ExecutionPlanError( + "MISSING_STORAGE_OWNER", + dependency.portId, + "Creation has no initialization owner", + ); + return { owner: owner.id, inputPath: seed.inputPath }; + }), + }; + unsupported = `UNSUPPORTED_EXECUTION_PORT:${bound.kind}`; + return { ...common, kind: "unavailable" }; + }); + const ports = compilePorts(entry, binding.dependencies); + const check = entry.acceptanceExportId + ? pkg.exports.find((candidate) => candidate.id === entry.acceptanceExportId)! + : undefined; + target = { + kind: "package", + package: binding.packageRevisionId, + export: binding.exportId, + ports, + ...(entry.preparation ? { preparation: entry.preparation } : {}), + ...(check + ? { acceptance: { export: check.id, ports: compilePorts(check, binding.acceptanceDependencies ?? []) } } + : {}), + }; + } + const inputChanges = preparedInputPaths(operation.input); + const input = schema(operation.input), + output = schema(operation.output); + return { + key: operation.key, + atom: operation.atomId, + interface: operation.interfaceRevisionId, + operation: operation.operationId, + scope: operation.scope, + effect: operation.execution.kind, + targets: operation.availableTargets, + input, + ...(inputChanges.length ? { inputChanges } : {}), + output, + unsupported: unsupported ?? (input && output ? null : "UNSUPPORTED_OPERATION_CODEC"), + binding: target, + }; + }); + const packages = workspace.packageImports.map((pkg) => ({ + revision: pkg.revisionId, + exports: pkg.exports.map((entry) => ({ + id: entry.id, + ...(entry.preparation ? { preparation: entry.preparation } : {}), + ...(entry.acceptanceExportId ? { acceptance: entry.acceptanceExportId } : {}), + effect: entry.execution?.kind ?? "remote", + targets: entry.execution && entry.execution.kind !== "remote" ? entry.execution.targets : [], + receiver: + entry.kind === "function" + ? null + : entry.kind === "operation" && entry.receiverRequirement.kind === "exact-atom" + ? { kind: "atom", id: entry.receiverRequirement.atomId } + : "object", + input: schema(entry.inputType), + output: schema(entry.outputType), + })), + })); + const body = { + codec: "camino-kernel-program-v1", + model: { workspace: workspace.workspaceId, atoms: workspace.atoms.map((atom) => atom.id), owners, edges }, + operations, + packages, + }; + // The interpreter's plan key covers its real input, not source/build evidence. + return { ...body, plan: hexDigest({ versions: plan.definition.versions, program: body }) }; +} diff --git a/src/execution-world/module-contracts.ts b/src/execution-world/module-contracts.ts new file mode 100644 index 0000000..2105713 --- /dev/null +++ b/src/execution-world/module-contracts.ts @@ -0,0 +1,17 @@ +import type { InterfaceRevision } from "../capability-model/types.js"; + +/** Public, checked type information for artifact consumers. It describes the + * nominal role, without exposing implementations or synthesizing any storage. */ +export function moduleTypeContext(iface: InterfaceRevision): { + typeArguments?: readonly unknown[]; + requiredInterfaces?: readonly string[]; +} { + return { + ...(iface.application?.arguments.length + ? { + typeArguments: structuredClone(iface.application.arguments), + } + : {}), + ...(iface.requiredInterfaces?.length ? { requiredInterfaces: [...iface.requiredInterfaces].sort() } : {}), + }; +} diff --git a/src/execution-world/native-backends.ts b/src/execution-world/native-backends.ts new file mode 100644 index 0000000..eb42914 --- /dev/null +++ b/src/execution-world/native-backends.ts @@ -0,0 +1,21 @@ +import type { Binding } from "../capability-model/types.js"; + +/** Nominal library identity. Backends implement primitives; target names carry no + * built-in semantics in the graph planner. The JS backend is installed by the + * current toolchain, alongside the authoritative server executor. */ +export const javascriptTarget = "target:org.quixos.execution.javascript@1"; +export interface NativeExecutionBackend { + readonly target: string; + readonly supports: (binding: Exclude) => boolean; +} +export const standardNativeBackends: readonly NativeExecutionBackend[] = [ + { target: "server", supports: () => true }, + { + target: javascriptTarget, + supports: (binding) => + binding.kind === "state" || + binding.kind === "edge" || + binding.kind === "journal" || + (binding.kind === "subscription" && binding.primitive === "status"), + }, +]; diff --git a/src/execution-world/plan.ts b/src/execution-world/plan.ts new file mode 100644 index 0000000..da65c4c --- /dev/null +++ b/src/execution-world/plan.ts @@ -0,0 +1,561 @@ +import { standardNativeBackends, type NativeExecutionBackend } from "./native-backends.js"; +import type { + WorkspaceRevision, + ExecutionContract, + ValueType, + Binding, + DependencyBinding, + EdgeTraversal, + InterfaceRevisionId, + AtomId, + OperationId, +} from "../capability-model/types.js"; +import { validateWorkspaceRevision } from "../capability-model/validation.js"; +import { canonicalJson, contentDigest } from "../capability-model/evolution.js"; +import { resolveForwardedBinding } from "../capability-model/forwarding.js"; +import { linkQueries, type LinkedQuery } from "../query/link.js"; +import { type BindingSchema } from "../bindings/index.js"; +import { unsupportedQueryOperator } from "./query-operators.js"; +import { packageBuildContexts } from "./build-contract.js"; + +export class ExecutionPlanError extends Error { + constructor( + readonly code: string, + readonly path: string, + message: string, + ) { + super(`${code} ${path}: ${message}`); + this.name = "ExecutionPlanError"; + } +} +export const hexDigest = (value: unknown): string => contentDigest(value).slice("sha256:".length); +const sorted = (values: readonly T[], key: (value: T) => string): T[] => + [...values].sort((a, b) => (key(a) < key(b) ? -1 : key(a) > key(b) ? 1 : 0)); +export const publicOperationKey = (atom: string, iface: string, operation: string): string => + JSON.stringify([atom, iface, operation]); +export type DataDependency = + | { kind: "state"; slot: string; via?: EdgeTraversal } + | { kind: "edge"; edge: string; projection: string; via?: EdgeTraversal } + | { kind: "operation"; operation: string; port: string; via?: EdgeTraversal } + | { kind: "query"; query: string; port: string }; +export interface PlannedOperation { + key: string; + atomId: string; + interfaceRevisionId: string; + operationId: string; + scope: "object" | "class"; + execution: ExecutionContract; + /** Targets available for this complete installed implementation/dependency closure. */ + availableTargets: string[]; + input: ValueType; + output: ValueType; + /** Concrete target after public forwarding. Never chosen by a runtime caller. */ + binding: Exclude; + dependencies: DataDependency[]; +} +export interface PlannedQuery { + id: string; + query: LinkedQuery; + /** Query-selected getter entrypoints; dependency traversal supplies canonical inputs. */ + operations: string[]; + /** Local query roots that admission must support, in addition to selected wire outputs. */ + admittedOperations: string[]; +} +export interface PlanPackage { + modules?: (NonNullable[number] & { + contract: string; + subject?: import("../capability-model/types.js").ObjectExpectation; + typeArguments?: readonly unknown[]; + requiredInterfaces?: readonly string[]; + })[]; + controllers?: NonNullable; + components?: { id: string; displayName: string; subject: InterfaceRevisionId; runtime: "react" }[]; + revision: string; + source: WorkspaceRevision["packageImports"][number]["source"]; + sourceDigest: string; + bindingDigest: string; + exports: { id: string; execution: ExecutionContract }[]; +} + +export interface ExecutionPlanDefinition { + presentations?: NonNullable; + codec: "camino-execution-plan-v1"; + workspace: WorkspaceRevision; + bindingSchema: BindingSchema; + packages: PlanPackage[]; + owners: { id: string; atom: string; interface: string; initialization: unknown; storage: unknown }[]; + operations: PlannedOperation[]; + queries: PlannedQuery[]; + profiles: { slot: string; profile: string }[]; + versions: { + value: "camino-value-v1"; + reference: "camino-ref-v1"; + ports: "quixos-transaction-ports-v1"; + crdt: "camino-automerge-3.4.1-structured-v1"; + }; +} +export interface ExecutionPlan { + definition: ExecutionPlanDefinition; + digest: string; +} +export const executionVersions: ExecutionPlanDefinition["versions"] = { + value: "camino-value-v1", + reference: "camino-ref-v1", + ports: "quixos-transaction-ports-v1", + crdt: "camino-automerge-3.4.1-structured-v1", +}; +/** Selection only: the coordinator separately verifies the caller's grant and receiver witness. */ +export function resolvePlannedOperation( + plan: ExecutionPlan, + atom: string, + iface: string, + operation: string, +): PlannedOperation { + const key = publicOperationKey(atom, iface, operation); + const selected = plan.definition.operations.find((entry) => entry.key === key); + if (!selected) throw new ExecutionPlanError("UNINSTALLED_OPERATION", key, "No installed public operation binding"); + return selected; +} + +/** Bound argument/state codec subset. A parsed type alone does not mean a runtime implements it. */ +function checkValue(type: ValueType, path: string, allowChange = false): void { + switch (type.kind) { + case "variant": + for (const [label, entry] of Object.entries(type.cases)) checkValue(entry.value, `${path}.${label}`, allowChange); + return; + case "scalar": + case "object-ref": + return; + case "builtin": + if (type.name === "unit") return; + break; + case "optional": + case "list": + checkValue(type.value, path, allowChange); + return; + case "record": + for (const [name, field] of Object.entries(type.fields)) checkValue(field, `${path}.${name}`, allowChange); + return; + case "change": + if (allowChange) { + checkValue(type.value, path); + return; + } + break; + } + throw new ExecutionPlanError("UNSUPPORTED_PORTABLE_CODEC", path, `Portable execution does not support ${type.kind}`); +} + +/** Compile declarations into a closed dispatch/dependency graph before artifact selection. */ +export function compileExecutionPlan( + input: WorkspaceRevision, + backends: readonly NativeExecutionBackend[] = standardNativeBackends, +): ExecutionPlan { + const workspace = structuredClone(input); + const issues = validateWorkspaceRevision(workspace); + if (issues.length) + throw new ExecutionPlanError( + "INVALID_WORKSPACE", + workspace.id, + issues.map((i) => `${i.path}: ${i.message}`).join("\n"), + ); + const interfaces = new Map(workspace.interfaceImports.map((i) => [i.revisionId, i])); + const packages = new Map(workspace.packageImports.map((p) => [p.revisionId, p])); + const attachments = [...workspace.sharedAttachments, ...workspace.conformances.flatMap((c) => c.privateAttachments)]; + for (const pkg of workspace.packageImports) + for (const entry of pkg.exports) + if (entry.execution && entry.execution.kind !== "remote") { + checkValue(entry.inputType, `${pkg.revisionId}/${entry.id}/input`, true); + checkValue(entry.outputType, `${pkg.revisionId}/${entry.id}/output`, true); + } + for (const attachment of attachments) + if (attachment.kind === "state" && attachment.storagePolicy.kind === "crdt-document") + checkValue(attachment.valueType, attachment.id); + const operationMap = new Map(); + const queries = linkQueries(workspace); + const queryKey = (pkg: string, id: string) => JSON.stringify([pkg, id]); + const queryMap = new Map(queries.map((q) => [queryKey(q.packageRevisionId, q.checked.declaration.id), q])); + const declaration = (iface: InterfaceRevisionId, operation: OperationId) => + interfaces + .get(iface) + ?.members.flatMap((m) => m.operations) + .find((o) => o.id === operation); + const queryOperations = (query: LinkedQuery) => + query.fields.map((f) => publicOperationKey(f.atomId, f.interfaceRevisionId, f.getter)); + const targetAtoms = (binding: DependencyBinding, atom: AtomId): AtomId[] => { + if (binding.kind === "interface" && binding.receiver === "input") + return workspace.atoms + .filter((candidate) => + workspace.conformances.some( + (conformance) => + conformance.atomId === candidate.id && conformance.interfaceRevisionId === binding.interfaceRevisionId, + ), + ) + .map((candidate) => candidate.id); + if (!("via" in binding) || !binding.via) return [atom]; + const edge = attachments.find((a) => a.id === binding.via!.edgeTypeId); + if (!edge || edge.kind !== "edge") throw new ExecutionPlanError("MISSING_EDGE", atom, "Missing traversal edge"); + const endpoint = edge.endpoints.find((e) => e.projectionId !== binding.via!.projectionId)!; + const constraint = endpoint.constraint; + return workspace.atoms + .filter((a) => + constraint.kind === "atom" + ? constraint.atomId === a.id + : workspace.conformances.some( + (c) => c.atomId === a.id && c.interfaceRevisionId === constraint.interfaceRevisionId, + ), + ) + .map((a) => a.id); + }; + const add = (atom: AtomId, iface: InterfaceRevisionId, operation: OperationId): PlannedOperation => { + const key = publicOperationKey(atom, iface, operation), + existing = operationMap.get(key); + if (existing) return existing; + const publicContract = declaration(iface, operation); + if (!publicContract) throw new ExecutionPlanError("MISSING_OPERATION", key, "Missing public contract"); + const { binding } = resolveForwardedBinding(workspace, atom, iface, operation); + const planned: PlannedOperation = { + key, + atomId: atom, + interfaceRevisionId: iface, + operationId: operation, + scope: publicContract.scope === "class" ? "class" : "object", + execution: publicContract.execution ?? { kind: "remote" }, + availableTargets: [], + input: publicContract.inputType, + output: publicContract.outputType, + binding, + dependencies: [], + }; + operationMap.set(key, planned); + const portable = planned.execution.kind !== "remote"; + if (portable) { + checkValue(planned.input, `${key}/input`, true); + checkValue(planned.output, `${key}/output`, true); + } + if (binding.kind === "state" || binding.kind === "journal" || binding.kind === "subscription") + planned.dependencies.push({ kind: "state", slot: binding.slotId }); + else if (binding.kind === "edge") + planned.dependencies.push({ kind: "edge", edge: binding.edgeTypeId, projection: binding.projectionId }); + else if (binding.kind === "package") { + const entry = packages.get(binding.packageRevisionId)?.exports.find((e) => e.id === binding.exportId); + if (!entry) throw new ExecutionPlanError("MISSING_EXPORT", key, "Unresolved package export"); + for (const dependency of binding.dependencies) { + const port = entry.dependencyPorts.find((p) => p.id === dependency.portId)!; + const target = dependency.binding; + const via = "via" in target && target.via ? { via: target.via } : {}; + if ("via" in target && target.via) + planned.dependencies.push({ kind: "edge", edge: target.via.edgeTypeId, projection: target.via.projectionId }); + if (target.kind === "state") planned.dependencies.push({ kind: "state", slot: target.slotId, ...via }); + else if (target.kind === "edge") + planned.dependencies.push({ kind: "edge", edge: target.edgeTypeId, projection: target.projectionId, ...via }); + else if (target.kind === "interface" && port.requirement.kind === "interface") { + const selected = + port.requirement.operations ?? + interfaces.get(target.interfaceRevisionId)!.members.flatMap((m) => m.operations.map((o) => o.id)); + for (const targetAtom of targetAtoms(target, atom)) + for (const selectedOperation of selected) { + const operation = add(targetAtom, target.interfaceRevisionId, selectedOperation); + planned.dependencies.push({ kind: "operation", operation: operation.key, port: port.id, ...via }); + } + } else if (target.kind === "query") { + const query = queryMap.get(queryKey(target.packageRevisionId, target.queryId)); + if (!query) throw new ExecutionPlanError("MISSING_QUERY", key, `Unresolved query port ${port.id}`); + planned.dependencies.push({ + kind: "query", + query: queryKey(target.packageRevisionId, target.queryId), + port: port.id, + }); + } + } + } + planned.dependencies = sorted(planned.dependencies, canonicalJson); + return planned; + }; + for (const conformance of workspace.conformances) + for (const entry of conformance.operationBindings) + add(conformance.atomId, conformance.interfaceRevisionId, entry.operationId); + // Check the transitive closure, retaining a dependency path for diagnostics. + for (const root of operationMap.values()) { + if (root.execution.kind === "remote") continue; + const requested = root.execution, + visited = new Set(); + const walk = (key: string, path: string[]) => { + if (visited.has(key)) return; + visited.add(key); + const node = operationMap.get(key)!; + if (node.execution.kind === "remote" || (requested.kind === "snapshot" && node.execution.kind !== "snapshot")) + throw new ExecutionPlanError( + "NONPORTABLE_DEPENDENCY", + path.join(" -> "), + "Execution effect is not allowed by the root contract", + ); + for (const dependency of node.dependencies) { + if (dependency.kind === "operation") + walk(dependency.operation, [...path, dependency.port, dependency.operation]); + if (dependency.kind === "query") { + // An admitted presentation window is not an authoritative mutation read set. + if (requested.kind === "transaction") + throw new ExecutionPlanError( + "QUERY_IN_TRANSACTION", + [...path, dependency.port].join(" -> "), + "Capture explicit object references as mutation inputs; local presentation queries cannot define transaction membership", + ); + const query = queryMap.get(dependency.query)!; + if (query.checked.declaration.placement?.kind !== "local") + throw new ExecutionPlanError( + "REMOTE_QUERY_DEPENDENCY", + path.join(" -> "), + "Portable code requires a local query", + ); + for (const operation of queryOperations(query)) walk(operation, [...path, dependency.port, operation]); + } + } + }; + walk(root.key, [root.key]); + } + // Additional implementation targets are opportunities, not new public requirements. + // Start with provider support and remove targets until the complete installed + // dependency closure agrees. This also handles recursive call graphs without + // mistaking an unfinished DFS for proof of availability. + for (const node of operationMap.values()) { + if (node.execution.kind === "remote") continue; + const binding = node.binding; + const provider = + binding.kind === "package" + ? packages.get(binding.packageRevisionId)!.exports.find((entry) => entry.id === binding.exportId)!.execution + : { + kind: node.execution.kind, + targets: backends.filter((backend) => backend.supports(binding)).map((backend) => backend.target), + }; + if (provider && provider.kind !== "remote") node.availableTargets = [...provider.targets].sort(); + } + let targetsChanged = true; + while (targetsChanged) { + targetsChanged = false; + for (const node of operationMap.values()) { + const available = node.availableTargets.filter((target) => + node.dependencies.every((dependency) => { + if (dependency.kind === "operation") + return operationMap.get(dependency.operation)!.availableTargets.includes(target); + if (dependency.kind === "query") { + const query = queryMap.get(dependency.query)!; + return ( + node.execution.kind === "snapshot" && + query.checked.declaration.placement?.kind === "local" && + queryOperations(query).every((key) => operationMap.get(key)!.availableTargets.includes(target)) + ); + } + return true; + }), + ); + if (available.length !== node.availableTargets.length) { + node.availableTargets = available; + targetsChanged = true; + } + } + } + for (const node of operationMap.values()) + if (node.execution.kind !== "remote" && !node.availableTargets.includes("server")) + throw new ExecutionPlanError( + "MISSING_SERVER_IMPLEMENTATION", + node.key, + "Every graph operation requires an authoritative implementation across its dependency closure", + ); + const plannedQueries: PlannedQuery[] = queries.map((query) => ({ + id: queryKey(query.packageRevisionId, query.checked.declaration.id), + query, + operations: queryOperations(query).sort(), + admittedOperations: [], + })); + for (const query of plannedQueries) { + for (const field of query.query.fields) { + const operation = operationMap.get(publicOperationKey(field.atomId, field.interfaceRevisionId, field.getter))!; + const effect = query.query.checked.effects.find( + (effect) => effect.interfaceRevisionId === field.interfaceRevisionId && effect.memberId === field.memberId, + ); + if ( + operation.execution.kind === "remote" && + (query.query.checked.declaration.placement?.kind === "replica" || effect?.uses.some((use) => use !== "select")) + ) + throw new ExecutionPlanError( + "QUERY_REMOTE_MEMBERSHIP_UNSUPPORTED", + query.id, + "Remote getters may enrich selected values after native snapshot closure; they cannot determine membership, order or canonical-input admission. Expose an accepted canonical value or split the service result into a separate query.", + ); + const member = interfaces.get(field.interfaceRevisionId)?.members.find((member) => member.id === field.memberId); + if (member?.kind === "relationship" && operation.binding.kind !== "edge") + throw new ExecutionPlanError( + "QUERY_NON_NATIVE_RELATION", + query.id, + "This query runtime traverses native graph edges. Expose an edge relationship, or return a computed service value through a value getter.", + ); + } + const unsupported = unsupportedQueryOperator(query.query.checked); + if (unsupported) + throw new ExecutionPlanError( + "QUERY_OPERATOR_UNSUPPORTED", + query.id, + `This execution world does not support ${unsupported}. Use a bounded portable getter over admitted inputs, or a server service returning the computed value. Query placement will not change automatically.`, + ); + const placement = query.query.checked.declaration.placement; + if (placement?.kind !== "local") continue; + const source = plannedQueries.find( + (q) => q.id === queryKey(query.query.packageRevisionId, placement.sourceQueryId), + ); + if (!source || source.query.checked.declaration.placement?.kind !== "replica") + throw new ExecutionPlanError("MISSING_REPLICA_SOURCE", query.id, "Local query source must be a replica query"); + const localRoot = query.query.checked.declaration.root, + sourceRoot = source.query.checked.declaration.root; + if (localRoot.kind !== sourceRoot.kind || localRoot.interface !== sourceRoot.interface) + throw new ExecutionPlanError( + "REPLICA_ROOT_MISMATCH", + query.id, + "Local and replica queries must use the same root contract", + ); + if (localRoot.kind === "workspace-interface") { + const sourceWindows = source.query.checked.selection + .flatMap((root) => root.selection) + .filter((s) => s.name === "objects"); + for (const selection of query.query.checked.selection + .flatMap((root) => root.selection) + .filter((s) => s.name === "objects")) { + if ( + !sourceWindows.some( + (window) => + canonicalJson({ arguments: window.arguments, conditions: window.conditions }) === + canonicalJson({ arguments: selection.arguments, conditions: selection.conditions }), + ) + ) + throw new ExecutionPlanError( + "REPLICA_WINDOW_MISMATCH", + query.id, + "Local objects must observe the identical confirmed source window; use availableObjects for a local candidate view", + ); + } + } + const traversals = (linked: LinkedQuery) => { + const paths = new Map }>(); + const entry = (key: string) => { + let value = paths.get(key); + if (!value) { + value = { complete: false, requiresComplete: false, windows: new Set() }; + paths.set(key, value); + } + return value; + }; + const visit = (selections: LinkedQuery["checked"]["selection"], parents: string[]) => { + for (const selection of selections) { + for (const stage of selection.relational?.stages ?? []) { + if (stage.operation.kind === "source") + entry(JSON.stringify([...parents, stage.operation.memberId])).requiresComplete = true; + } + const member = selection.interfaceRevisionId + ? interfaces.get(selection.interfaceRevisionId)?.members.find((m) => m.id === selection.memberId) + : undefined; + const path = member?.kind === "relationship" ? [...parents, member.id] : parents; + if (member?.kind === "relationship") { + const many = member.cardinality === "many" || member.cardinality === "many-unique"; + const complete = !selection.predicate && (!many || !!selection.arguments.all); + const key = JSON.stringify(path); + const value = entry(key); + value.complete ||= complete; + if (selection.arguments.first) + value.windows.add(canonicalJson({ arguments: selection.arguments, conditions: selection.conditions })); + else value.requiresComplete = true; + } + visit(selection.selection, path); + } + }; + visit(linked.checked.selection, []); + return paths; + }; + const admittedTraversals = traversals(source.query); + for (const [traversal, needed] of traversals(query.query)) { + if (!admittedTraversals.has(traversal)) + throw new ExecutionPlanError( + "REPLICA_TRAVERSAL_MISSING", + query.id, + `Replica source does not admit relationship path ${traversal}`, + ); + if ( + !admittedTraversals.get(traversal)!.complete && + (needed.requiresComplete || + !needed.windows.size || + [...needed.windows].some((window) => !admittedTraversals.get(traversal)!.windows.has(window))) + ) + throw new ExecutionPlanError( + "REPLICA_RELATION_INCOMPLETE", + query.id, + `Local relationship ${traversal} needs either complete source candidates or the identical confirmed first window; a window cannot supply unseen matches`, + ); + } + // The admission executor follows these canonical dependencies on the admitted roots. + // They do not change source candidate membership or make local take/filters authoritative. + source.admittedOperations.push(...query.operations); + for (const operation of query.operations) { + const node = operationMap.get(operation)!; + if (node.execution.kind !== "snapshot" || !node.availableTargets.some((target) => target !== "server")) + throw new ExecutionPlanError( + "NONPORTABLE_QUERY_FIELD", + `${query.id}/${operation}`, + "Local query getter needs an installed local snapshot implementation", + ); + } + } + for (const query of plannedQueries) query.admittedOperations = [...new Set(query.admittedOperations)].sort(); + const schema: BindingSchema = { + format: "quixos-bindings", + version: 1, + interfaces: sorted( + workspace.interfaceImports.filter((i) => !i.template), + (i) => i.revisionId, + ), + interfaceTemplates: sorted( + workspace.interfaceImports.filter((i) => i.template), + (i) => i.revisionId, + ), + packages: sorted(workspace.packageImports, (p) => p.revisionId), + }; + const normalizedSchema = JSON.parse(canonicalJson(schema)) as BindingSchema; + const buildContexts = packageBuildContexts(normalizedSchema); + const definition: ExecutionPlanDefinition = { + codec: "camino-execution-plan-v1", + workspace, + bindingSchema: normalizedSchema, + ...(workspace.presentations?.length ? { presentations: sorted(workspace.presentations, (p) => p.id) } : {}), + packages: sorted(workspace.packageImports, (p) => p.revisionId).map((p) => { + const contract = buildContexts[p.revisionId]!.contract; + return { + ...contract.definition.package, + source: p.source, + sourceDigest: hexDigest({ revision: p.revisionId, source: p.source }), + bindingDigest: contract.digest, + }; + }), + owners: sorted(workspace.conformances, (c) => c.id ?? "").map((c) => { + if (!c.id) + throw new ExecutionPlanError( + "OWNER_ID_REQUIRED", + `${c.atomId}/${c.interfaceRevisionId}`, + "Installed state owners require explicit stable identities", + ); + return { + id: c.id, + atom: c.atomId, + interface: c.interfaceRevisionId, + initialization: c.initialization ?? [], + storage: c.privateAttachments, + }; + }), + operations: sorted([...operationMap.values()], (o) => o.key), + queries: sorted(plannedQueries, (q) => q.id), + profiles: sorted( + attachments.filter((a) => a.kind === "state" && a.storagePolicy.kind === "crdt-document"), + (a) => a.id, + ).map((a) => ({ slot: a.id, profile: "camino-automerge-3.4.1-structured-v1" })), + versions: { ...executionVersions }, + }; + const normalized = JSON.parse(canonicalJson(definition)) as ExecutionPlanDefinition; + return { definition: normalized, digest: hexDigest(normalized) }; +} diff --git a/src/execution-world/ports.ts b/src/execution-world/ports.ts new file mode 100644 index 0000000..d26e5a3 --- /dev/null +++ b/src/execution-world/ports.ts @@ -0,0 +1,84 @@ +/** Process/module ABI shared by generated adapters. Values are decoded against the checked + * operation/port schema by the coordinator, not trusted because this header parsed. */ +export const transactionPortAbi = "quixos-transaction-ports-v1"; +export type PortAction = + | { kind: "reflection"; selector: string } + | { kind: "state"; primitive: "read" | "write" | "edit" } + | { kind: "edge"; primitive: "resolve" | "disconnect" } + | { kind: "edge"; primitive: "connect"; key: string } + | { kind: "interface"; operationId: string; key?: string } + | { kind: "query" } + | { kind: "creation"; key: string }; +export interface PortInvocation { + abi: typeof transactionPortAbi; + /** Coordinator-issued frame and invocation handles, not application object IDs. */ + frame: string; + invocation: string; + sequence: number; + portId: string; + action: PortAction; + input: unknown; +} +export interface PackageInvocation { + abi: typeof transactionPortAbi; + workspace: string; + frame: string; + invocation: string; + exportId: string; + effect: "snapshot" | "transaction" | "remote"; + /** Capability arguments/receiver use the versioned managed-reference codec. */ + input: unknown; + receiver: unknown | null; +} +export type InvocationReply = + | { abi: typeof transactionPortAbi; invocation: string; status: "returned"; output: unknown } + | { abi: typeof transactionPortAbi; invocation: string; status: "failed"; error: { code: string; message: string } }; +export type ServiceControl = + | { abi: typeof transactionPortAbi; kind: "ready" } + | { abi: typeof transactionPortAbi; kind: "cancel"; invocation: string } + | { abi: typeof transactionPortAbi; kind: "shutdown" }; +/** ABI-only header check. LF-08 resolves handles and enforces the frame/port authority. */ +export function checkPortInvocation(value: unknown): PortInvocation { + if (!value || typeof value !== "object" || Array.isArray(value) || Object.getPrototypeOf(value) !== Object.prototype) + throw new Error("Invalid port invocation"); + const fields = value as Record; + const keys = ["abi", "frame", "invocation", "sequence", "portId", "action", "input"]; + if ( + Object.keys(fields).length !== keys.length || + keys.some((key) => !Object.hasOwn(fields, key)) || + fields.abi !== transactionPortAbi + ) + throw new Error("Invalid port invocation fields or ABI"); + for (const key of ["frame", "invocation", "portId"]) + if (typeof fields[key] !== "string" || !fields[key] || fields[key].length > 4096) + throw new Error(`Invalid port invocation ${key}`); + if (!Number.isSafeInteger(fields.sequence) || (fields.sequence as number) < 1) + throw new Error("Invalid port invocation sequence"); + const action = fields.action as Record; + if ( + !action || + typeof action !== "object" || + Array.isArray(action) || + Object.getPrototypeOf(action) !== Object.prototype + ) + throw new Error("Invalid port action"); + const exact = (names: string[]) => + Object.keys(action).length === names.length && names.every((name) => Object.hasOwn(action, name)); + const identifier = (value: unknown) => typeof value === "string" && value.length > 0 && value.length <= 1024; + const valid = + action.kind === "state" + ? exact(["kind", "primitive"]) && ["read", "write", "edit"].includes(action.primitive as string) + : action.kind === "edge" + ? (exact(["kind", "primitive"]) && ["resolve", "disconnect"].includes(action.primitive as string)) || + (exact(["kind", "primitive", "key"]) && action.primitive === "connect" && identifier(action.key)) + : action.kind === "interface" + ? (exact(["kind", "operationId"]) || (exact(["kind", "operationId", "key"]) && identifier(action.key))) && + identifier(action.operationId) + : action.kind === "creation" + ? exact(["kind", "key"]) && identifier(action.key) + : action.kind === "reflection" + ? exact(["kind", "selector"]) && identifier(action.selector) + : action.kind === "query" && exact(["kind"]); + if (!valid) throw new Error("Invalid port action"); + return { ...fields, action: { ...action } } as unknown as PortInvocation; +} diff --git a/src/execution-world/projections.ts b/src/execution-world/projections.ts new file mode 100644 index 0000000..2eb24af --- /dev/null +++ b/src/execution-world/projections.ts @@ -0,0 +1,44 @@ +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(); + 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; +} diff --git a/src/execution-world/public-reflection.ts b/src/execution-world/public-reflection.ts new file mode 100644 index 0000000..d828670 --- /dev/null +++ b/src/execution-world/public-reflection.ts @@ -0,0 +1,104 @@ +import { compileInspectionTemplates } from "./inspection.js"; +import { compileKernelProgram } from "./kernel.js"; +import { hexDigest, type ExecutionPlan } from "./plan.js"; +import { clientContractCatalog, type ExecutionInstallation } from "./installation.js"; +import { publicOperationContract, publicQueryContract } from "./client-contracts.js"; + +/** Explicit public projection. Never serialize conformance storage or package ports. */ +export function publicDefinitionSnapshot(plan: ExecutionPlan, installation: ExecutionInstallation) { + const workspace = plan.definition.workspace; + const kernel = compileKernelProgram(plan) as { + operations: { + atom: string; + interface: string; + operation: string; + unsupported: string | null; + effect: string; + targets: string[]; + }[]; + }; + const catalog = clientContractCatalog( + plan, + installation, + installation.packages.map((pkg) => pkg.revision), + ); + const rows = { + atoms: workspace.atoms.map((atom) => ({ id: atom.id, name: atom.displayName, documentation: atom.documentation })), + interfaces: workspace.interfaceImports.map((iface) => ({ + id: iface.revisionId, + interface: iface.interfaceId, + name: iface.displayName, + source: iface.source, + template: iface.template, + application: iface.application, + requiredInterfaces: iface.requiredInterfaces ?? [], + argumentRequirements: iface.argumentRequirements ?? [], + members: iface.members.map((member) => ({ + ...member, + operations: member.operations.map((operation) => ({ + ...operation, + ...(operation.mode === "call" && !iface.template + ? { descriptor: publicOperationContract(iface, operation) } + : {}), + })), + })), + })), + conformances: workspace.conformances.map((c) => ({ + id: JSON.stringify([c.atomId, c.interfaceRevisionId]), + atom: c.atomId, + interface: c.interfaceRevisionId, + semanticMajor: c.semanticMajor ?? 0, + operations: ( + workspace.interfaceImports.find((iface) => iface.revisionId === c.interfaceRevisionId)?.members ?? [] + ).flatMap((member) => + member.operations.map((operation) => { + const installed = kernel.operations.find( + (op) => op.atom === c.atomId && op.interface === c.interfaceRevisionId && op.operation === operation.id, + ); + return { + operation: operation.id, + available: !!installed && !installed.unsupported, + unavailable: installed?.unsupported ?? (installed ? null : "NOT_CALLABLE"), + execution: installed ? { kind: installed.effect, targets: installed.targets } : null, + }; + }), + ), + modules: catalog.moduleBindings.filter( + (binding) => binding.atom === c.atomId && binding.interface === c.interfaceRevisionId, + ), + })), + queries: [ + ...compileInspectionTemplates(plan).contracts.map((descriptor) => ({ + id: JSON.stringify([descriptor.package, descriptor.query]), + name: "Public inspection", + package: descriptor.package, + descriptor, + system: true, + })), + ...workspace.packageImports.flatMap((pkg) => + (pkg.checkedQueries ?? []).map((query) => ({ + id: JSON.stringify([pkg.revisionId, query.declaration.id]), + name: query.declaration.displayName, + package: pkg.revisionId, + descriptor: publicQueryContract(pkg, query.declaration.id), + fragment: query.declaration.fragment, + limits: query.declaration.budgets, + watch: query.declaration.watch, + })), + ), + ], + presentations: catalog.components.map((component) => ({ + id: JSON.stringify([component.packageRevision, component.id]), + component, + registrations: catalog.presentations.filter( + (p) => p.packageRevision === component.packageRevision && p.component === component.id, + ), + })), + }; + const publicRows = JSON.parse(JSON.stringify(rows)) as Record< + keyof typeof rows, + { id: string; [key: string]: unknown }[] + >; + for (const entries of Object.values(publicRows)) entries.sort((a, b) => (a.id < b.id ? -1 : a.id > b.id ? 1 : 0)); + return { codec: "camino-definitions-v1" as const, id: hexDigest(publicRows), rows: publicRows }; +} diff --git a/src/execution-world/queries.ts b/src/execution-world/queries.ts new file mode 100644 index 0000000..26cb3c3 --- /dev/null +++ b/src/execution-world/queries.ts @@ -0,0 +1,110 @@ +import { compileKernelProgram } from "./kernel.js"; +import { compileInspectionTemplates } from "./inspection.js"; +import { portableValueSchema } from "../bindings/portable.js"; +import { ExecutionPlanError, hexDigest, type ExecutionPlan } from "./plan.js"; +import type { ValueType } from "../capability-model/types.js"; +import { compileQueryComposition } from "../query/composition.js"; + +const lowerTypes = (value: unknown): unknown => + JSON.parse( + JSON.stringify(value, (key, value: unknown) => { + if ( + key === "type" && + value && + typeof value === "object" && + "kind" in value && + ["scalar", "builtin", "optional", "list", "record", "object-ref"].includes(String(value.kind)) + ) + return portableValueSchema(value as ValueType); + return value; + }), + ); + +/** Checked query IR and installed getter identities, without GraphQL schemas/parsers or SQL adapters. + * Keep authored selection names only for output paths; field access uses resolved interface/member IDs. */ +export function compileQueryProgram(plan: ExecutionPlan): unknown { + if (hexDigest(plan.definition) !== plan.digest) + throw new ExecutionPlanError("INVALID_PLAN", "queries", "Plan digest mismatch"); + const allQueries = new Map(plan.definition.queries.map(({ id, query }) => [id, query.checked])); + const compositions = plan.definition.workspace.packageImports.flatMap((pkg) => + (pkg.queryCompositions ?? []).map((composition) => { + const scoped = new Map(allQueries); + for (const query of pkg.checkedQueries ?? []) scoped.set(query.declaration.id, query); + const checked = compileQueryComposition( + { + id: composition.id, + input: composition.input, + steps: composition.steps, + maxQueries: composition.maxQueries, + }, + scoped, + ); + if (checked.definitionDigest !== composition.definitionDigest) + throw new ExecutionPlanError("INVALID_QUERY_COMPOSITION", composition.id, "Checked dependencies changed"); + return { + id: JSON.stringify([pkg.revisionId, composition.id]), + input: portableValueSchema(checked.input), + output: portableValueSchema(checked.output), + maxQueries: checked.maxQueries, + steps: checked.steps.map((step) => ({ + ...step, + query: (pkg.checkedQueries ?? []).some((query) => query.declaration.id === step.query) + ? JSON.stringify([pkg.revisionId, step.query]) + : step.query, + })), + }; + }), + ); + return { + codec: "camino-query-program-v1", + plan: (compileKernelProgram(plan) as { plan: string }).plan, + ...(compositions.length ? { compositions } : {}), + queries: [ + ...compileInspectionTemplates(plan).queries, + ...plan.definition.queries.map((entry) => queryProgramEntry(plan, entry)), + ], + }; +} + +export function queryProgramEntry(plan: ExecutionPlan, entry: import("./plan.js").PlannedQuery) { + const { id, query, admittedOperations } = entry; + const interfaces = plan.definition.workspace.interfaceImports; + + const checked = query.checked; + return { + id, + ...(checked.declaration.fragment && !checked.declaration.fragment.admission + ? { + fragment: { + package: query.packageRevisionId, + id: checked.declaration.id, + digest: checked.definitionDigest, + }, + } + : {}), + placement: checked.declaration.placement?.kind ?? "remote", + source: + checked.declaration.placement?.kind === "local" + ? JSON.stringify([query.packageRevisionId, checked.declaration.placement.sourceQueryId]) + : null, + root: checked.declaration.root, + atoms: query.rootAtoms, + variables: portableValueSchema(checked.variables), + defaults: checked.variableDefaults, + selection: lowerTypes(checked.selection), + budgets: checked.declaration.budgets, + fields: query.fields.map((field) => { + const member = interfaces + .find((i) => i.revisionId === field.interfaceRevisionId)! + .members.find((m) => m.id === field.memberId)!; + return { + atom: field.atomId, + interface: field.interfaceRevisionId, + member: field.memberId, + name: member.displayName, + operation: JSON.stringify([field.atomId, field.interfaceRevisionId, field.getter]), + }; + }), + admittedOperations, + }; +} diff --git a/src/execution-world/query-document.ts b/src/execution-world/query-document.ts new file mode 100644 index 0000000..a897ab8 --- /dev/null +++ b/src/execution-world/query-document.ts @@ -0,0 +1,96 @@ +import { compileQuery } from "../query/compile.js"; +import { linkQueries } from "../query/link.js"; +import { capabilityId } from "../capability-model/types.js"; +import { portableValueSchema } from "../bindings/portable.js"; +import { hexDigest, type ExecutionPlan, type PlannedQuery } from "./plan.js"; +import { compileKernelProgram } from "./kernel.js"; +import { queryProgramEntry } from "./queries.js"; +import { unsupportedQueryOperator } from "./query-operators.js"; + +export interface QueryDocument { + readonly interface: string; + readonly document: string; + readonly operation: string; + readonly objectRoot: boolean; + readonly views?: readonly { readonly atom: string; readonly interface: string }[]; +} +/** Runtime query documents use the same language, nominal contracts, linking + * and interpreter as packaged queries. They introduce no resources or code. */ +export async function compilePublicQuery(plan: ExecutionPlan, request: QueryDocument) { + if (hexDigest(plan.definition) !== plan.digest) throw Error("INVALID_PLAN"); + if ( + typeof request.document !== "string" || + Buffer.byteLength(request.document) > 32768 || + typeof request.operation !== "string" || + !/^[A-Za-z_][A-Za-z0-9_]{0,127}$/.test(request.operation) || + typeof request.interface !== "string" || + request.interface.length > 2048 + ) + throw Error("QUERY_DOCUMENT_LIMIT"); + const id = "public-query:" + hexDigest(request); + const views = request.views ?? []; + if (!Array.isArray(views) || views.length > 16 || new Set(views.map((v) => v.atom)).size !== views.length) + throw Error("QUERY_VIEW_LIMIT"); + for (const view of views) { + if ( + !view || + Object.keys(view).sort().join(",") !== "atom,interface" || + typeof view.atom !== "string" || + typeof view.interface !== "string" || + view.atom.length > 2048 || + view.interface.length > 2048 || + !plan.definition.workspace.interfaceImports.some((i) => i.revisionId === view.interface) + ) + throw Error("QUERY_VIEW_INVALID"); + } + const checked = await compileQuery( + { + id, + displayName: request.operation, + root: { + kind: request.objectRoot ? "object" : "workspace-interface", + interface: capabilityId.interfaceRevision(request.interface), + }, + document: "request.graphql", + operation: request.operation, + fragments: [], + views: views.map((view) => ({ + atomId: capabilityId.atom(view.atom), + interfaceRevisionId: capabilityId.interfaceRevision(view.interface), + })), + allowances: [], + watch: false, + budgets: { + rows: 512, + depth: 8, + resultBytes: 256 * 1024, + candidates: 10000, + rpcCalls: 128, + concurrency: 8, + deadlineMs: 10000, + }, + }, + plan.definition.workspace.interfaceImports, + async () => request.document, + ); + const unsupported = unsupportedQueryOperator(checked); + if (unsupported) throw Error(`QUERY_OPERATOR_UNSUPPORTED: ${unsupported}`); + const linked = linkQueries(plan.definition.workspace, [ + { packageRevisionId: "org.quixos.public-query/1", checked }, + ]).find((q) => q.checked === checked)!; + const kernel = compileKernelProgram(plan) as { + operations: { key: string; effect: string; unsupported: string | null; binding: { kind: string } }[]; + }; + for (const field of linked.fields) { + const key = JSON.stringify([field.atomId, field.interfaceRevisionId, field.getter]); + const operation = kernel.operations.find((op) => op.key === key); + const member = plan.definition.workspace.interfaceImports + .find((i) => i.revisionId === field.interfaceRevisionId)! + .members.find((m) => m.id === field.memberId)!; + if (!operation || operation.unsupported || operation.effect !== "snapshot") + throw Error("QUERY_DOCUMENT_REQUIRES_SNAPSHOT: use an installed query for remote-effect enrichment"); + if (member.kind === "relationship" && operation.binding.kind !== "edge") throw Error("QUERY_NON_NATIVE_RELATION"); + } + const entry: PlannedQuery = { id, query: linked, operations: [], admittedOperations: [] }; + return { id, program: queryProgramEntry(plan, entry), output: portableValueSchema(checked.output) }; +} diff --git a/src/execution-world/query-operators.ts b/src/execution-world/query-operators.ts new file mode 100644 index 0000000..4682306 --- /dev/null +++ b/src/execution-world/query-operators.ts @@ -0,0 +1,57 @@ +import type { CheckedQuery, QuerySelection } from "../query/types.js"; +import type { QueryPredicate, QueryPathStep } from "../query/relational.js"; + +/** Supported by the fresh coordinator/replica interpreters. The language parser + * can describe more operators; that does not make them executable in this world. */ +export function unsupportedQueryOperator(query: CheckedQuery): string | undefined { + const path = (steps: QueryPathStep[]) => + steps.some((step) => step.kind !== "relation") ? "expanded source/target paths" : undefined; + const predicate = (value: QueryPredicate): string | undefined => { + if (value.kind === "reduce") return "aggregate predicates"; + if (value.kind === "and" || value.kind === "or") return value.children.map(predicate).find(Boolean); + if (value.kind === "not") return predicate(value.child); + if (value.kind === "as") return predicate(value.predicate); + if (value.kind === "compare") return path(value.expression.path); + if (value.kind === "relation") return path(value.path) ?? (value.predicate && predicate(value.predicate)); + return undefined; + }; + const conditional = (selections: readonly QuerySelection[]): boolean => + selections.some((selection) => selection.conditions.length > 0 || conditional(selection.selection)); + const walk = (selections: readonly QuerySelection[]): string | undefined => { + for (const selection of selections) { + if (selection.predicate) { + const reason = predicate(selection.predicate); + if (reason) return reason; + } + if (selection.relational) { + if (query.declaration.placement?.kind === "replica") return "aggregate selections in canonical-input scopes"; + const { stages, terminals } = selection.relational; + if (stages.length !== 1 || stages[0]!.operation.kind !== "source") + return "relational expansion, filtering or distinct stages"; + if (terminals.length !== 1) return "multiple aggregate terminals"; + const terminal = terminals[0]!; + if ( + terminal.kind !== "aggregate" || + terminal.stage !== stages[0]!.id || + terminal.keys.length || + terminal.order.length || + terminal.residual || + terminal.where || + terminal.having || + terminal.first || + terminal.all || + terminal.after || + terminal.reductions.some( + (reduction) => reduction.reduction.operator !== "count" || reduction.reduction.operand, + ) + ) + return "grouping or reductions other than a direct relationship count"; + if (conditional(selection.selection)) return "conditional aggregate projections"; + } + const reason = walk(selection.selection); + if (reason) return reason; + } + return undefined; + }; + return walk(query.selection); +} diff --git a/src/execution-world/receipts.ts b/src/execution-world/receipts.ts new file mode 100644 index 0000000..27f76d5 --- /dev/null +++ b/src/execution-world/receipts.ts @@ -0,0 +1,168 @@ +import { createHash } from "node:crypto"; +import { generatePortableTypeScriptBindings, generateRemoteTypeScriptBindings } from "../bindings/portable.js"; +import { type ExecutionPlan, ExecutionPlanError, hexDigest } from "./plan.js"; +import { type ArtifactReader, type PackageArtifacts, type ArtifactGroup } from "./artifacts.js"; +import { loadArtifactWorld, type LoadedArtifactWorld } from "./files.js"; +import { componentAssetDeclarations } from "./client-contracts.js"; + +import { checkPackageBuildContract, packageBuildContract, type PackageBuildContract } from "./build-contract.js"; + +export interface GroupReceipt { + schemaVersion: 1; + packageRevisionId: string; + bindingDigest: string; + manifestDigest: string; + generatorDigest: string; + generated: { target: string; path: string; digest: string; bytes: number }[]; +} +export interface ArtifactReceipt extends GroupReceipt { + sourceDigest: string; +} +const hashBytes = (bytes: Uint8Array) => createHash("sha256").update(bytes).digest("hex"); +/** Nominal identities never become filesystem paths. */ +export const generatedBindingPath = (target: string) => `share/quixos/generated/target-${hexDigest(target)}.ts`; +/** Deterministic expected receipt. A trusted build must also typecheck and bundle these exact sources. + * This is consistency evidence inside a trusted Nix build path, not remote build attestation. */ +export function expectedArtifactReceipt( + plan: ExecutionPlan, + manifest: PackageArtifacts, + generatorDigest: string, +): { receipt: ArtifactReceipt; sources: Map } { + const pkg = plan.definition.packages.find((p) => p.revision === manifest.packageRevisionId); + if (!pkg || manifest.sourceDigest !== pkg.sourceDigest) + throw new ExecutionPlanError("INVALID_RECEIPT", manifest.packageRevisionId, "Receipt package/source mismatch"); + return expectedPackageReceipt( + packageBuildContract(plan.definition.bindingSchema, pkg.revision), + manifest, + pkg.sourceDigest, + generatorDigest, + ); +} +export function expectedPackageReceipt( + contract: PackageBuildContract, + manifest: PackageArtifacts, + sourceDigest: string, + generatorDigest: string, +): { receipt: ArtifactReceipt; sources: Map } { + if (manifest.sourceDigest !== sourceDigest) + throw new ExecutionPlanError("INVALID_RECEIPT", manifest.packageRevisionId, "Receipt source mismatch"); + const result = expectedGroupReceipt(contract, manifest, generatorDigest); + return { ...result, receipt: { ...result.receipt, sourceDigest } }; +} +export function expectedGroupReceipt( + contract: PackageBuildContract, + manifest: ArtifactGroup, + generatorDigest: string, +): { receipt: GroupReceipt; sources: Map } { + checkPackageBuildContract(contract); + if (!/^[0-9a-f]{64}$/.test(generatorDigest)) throw new Error("Invalid trusted generator digest"); + const pkg = contract.definition.package; + const schemaPackage = contract.definition.bindings.packages.find((p) => p.revisionId === pkg.revision)!; + if ( + manifest.packageRevisionId !== pkg.revision || + manifest.bindingDigest !== contract.digest || + hexDigest(manifest.versions) !== hexDigest(contract.definition.versions) + ) + throw new ExecutionPlanError("INVALID_RECEIPT", manifest.packageRevisionId, "Receipt package/contract mismatch"); + // A process service may implement transactions in any language. Only the + // checked JS module path promises generated TypeScript adapter evidence. + const available = new Set( + manifest.implementations + .filter( + (i) => + i.compatibility === "quixos-js-module-v1" && + i.exports.some((id) => pkg.exports.some((entry) => entry.id === id && entry.execution.kind !== "remote")), + ) + .map((i) => i.target), + ); + const targets = [ + ...new Set( + schemaPackage.exports.flatMap((e) => (e.execution && e.execution.kind !== "remote" ? e.execution.targets : [])), + ), + ] + .filter((t) => available.has(t)) + .sort(); + const sources = new Map(); + const generated = targets.map((target) => { + const path = generatedBindingPath(target); + const bytes = new TextEncoder().encode( + generatePortableTypeScriptBindings(contract.definition.bindings, pkg.revision, { target }), + ); + sources.set(path, bytes); + return { target, path, digest: hashBytes(bytes), bytes: bytes.length }; + }); + if (manifest.components?.length) { + for (const [path, source] of [ + ["share/quixos/generated/client.ts", contract.definition.component!], + ["share/quixos/generated/component-assets.d.ts", componentAssetDeclarations()], + ] as const) { + const bytes = new TextEncoder().encode(source); + sources.set(path, bytes); + generated.push({ target: "window", path, digest: hashBytes(bytes), bytes: bytes.length }); + } + } + // Custom language services need no TypeScript adapter. A checked JS module + // exporting remote handlers does, and must carry the exact generated source. + if ( + manifest.implementations.some( + (implementation) => + implementation.compatibility === "quixos-js-module-v1" && + implementation.exports.some((id) => + pkg.exports.some((entry) => entry.id === id && entry.execution.kind === "remote"), + ), + ) + ) { + const path = "share/quixos/generated/remote.ts"; + const bytes = new TextEncoder().encode( + generateRemoteTypeScriptBindings(contract.definition.bindings, pkg.revision), + ); + sources.set(path, bytes); + generated.push({ target: "server", path, digest: hashBytes(bytes), bytes: bytes.length }); + } + return { + receipt: { + schemaVersion: 1, + packageRevisionId: pkg.revision, + bindingDigest: contract.digest, + manifestDigest: hexDigest(manifest), + generatorDigest, + generated, + }, + sources, + }; +} +export async function checkArtifactReceipt( + plan: ExecutionPlan, + manifest: PackageArtifacts, + receipt: unknown, + generatorDigest: string, + read: ArtifactReader, +): Promise { + const expected = expectedArtifactReceipt(plan, manifest, generatorDigest); + if (hexDigest(receipt) !== hexDigest(expected.receipt)) + throw new ExecutionPlanError( + "INVALID_RECEIPT", + manifest.packageRevisionId, + "Receipt differs from the trusted compiler/schema/artifact expectation", + ); + for (const file of expected.receipt.generated) { + const actual = await read(manifest.packageRevisionId, file.path, file.bytes); + if (actual.length !== file.bytes || hashBytes(actual) !== file.digest) + throw new ExecutionPlanError("INVALID_RECEIPT", file.path, "Generated bindings were replaced or omitted"); + } +} +/** Checked Nix builds validate generated sources with their selected compiler. + * Runtime loading uses the stored outputs instead of repeating compilation. */ +export async function loadCheckedArtifactWorld( + plan: ExecutionPlan, + roots: Readonly>, + generatorDigest: string, +): Promise { + const loaded = await loadArtifactWorld(plan, roots); + for (const manifest of loaded.manifests) { + const bytes = await loaded.read(manifest.packageRevisionId, "share/quixos/build-receipt.json", 16 * 1024 * 1024); + const receipt: unknown = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes)); + await checkArtifactReceipt(plan, manifest, receipt, generatorDigest, loaded.read); + } + return loaded; +} diff --git a/src/execution-world/reflection.ts b/src/execution-world/reflection.ts new file mode 100644 index 0000000..d513ebe --- /dev/null +++ b/src/execution-world/reflection.ts @@ -0,0 +1,28 @@ +import type { ValueType, WorkspaceRevision } from "../capability-model/types.js"; +import { ExecutionPlanError } from "./plan.js"; +const string: ValueType = { kind: "scalar", name: "string" }; +/** Narrow immutable installation metadata. No graph enumeration or live reflection. */ +export function portableReflectionType(selector: string): ValueType { + if (selector !== "presentations") + throw new ExecutionPlanError( + "UNSUPPORTED_PORTABLE_REFLECTION", + selector, + "Only checked presentation metadata is portable", + ); + return { + kind: "list", + value: { kind: "record", fields: { id: string, subject: string, atoms: { kind: "list", value: string } } }, + }; +} +export function portableReflectionValue(workspace: WorkspaceRevision, selector: string) { + portableReflectionType(selector); + return (workspace.presentations ?? []) + .map((p) => ({ + id: p.id, + subject: p.subject, + atoms: [ + ...new Set(workspace.conformances.filter((c) => c.interfaceRevisionId === p.subject).map((c) => c.atomId)), + ].sort(), + })) + .sort((a, b) => a.id.localeCompare(b.id)); +} diff --git a/src/gen/camino/api_pb.ts b/src/gen/camino/api_pb.ts index 3cf4ae5..deda82f 100644 --- a/src/gen/camino/api_pb.ts +++ b/src/gen/camino/api_pb.ts @@ -6,7 +6,7 @@ import type { GenFile, GenMessage, GenService } from "@bufbuild/protobuf/codegen import { fileDesc, messageDesc, serviceDesc } from "@bufbuild/protobuf/codegenv2"; import type { PersistencePlan, QueryRelationalPlan, QuerySelection } from "./schema_pb.js"; import { file_camino_schema } from "./schema_pb.js"; -import type { CapabilityRef, FieldEditing } from "../quixos/refs_pb.js"; +import type { CapabilityRef, FieldEditing, WorkspaceQueryScope } from "../quixos/refs_pb.js"; import { file_quixos_refs } from "../quixos/refs_pb.js"; import type { Message } from "@bufbuild/protobuf"; @@ -14,7 +14,7 @@ import type { Message } from "@bufbuild/protobuf"; * Describes the file camino/api.proto. */ export const file_camino_api: GenFile = /*@__PURE__*/ - fileDesc("ChBjYW1pbm8vYXBpLnByb3RvEgZjYW1pbm8iCwoJTnVsbFZhbHVlInwKC09iamVjdFZhbHVlEi8KBmZpZWxkcxgBIAMoCzIfLmNhbWluby5PYmplY3RWYWx1ZS5GaWVsZHNFbnRyeRo8CgtGaWVsZHNFbnRyeRILCgNrZXkYASABKAkSHAoFdmFsdWUYAiABKAsyDS5jYW1pbm8uVmFsdWU6AjgBIioKCUxpc3RWYWx1ZRIdCgZ2YWx1ZXMYASADKAsyDS5jYW1pbm8uVmFsdWUiHQoIUmVmVmFsdWUSEQoJb2JqZWN0X2lkGAEgASgJIjwKCUNyZHRWYWx1ZRIMCgR0eXBlGAEgASgJEhAKCGVuY29kaW5nGAIgASgJEg8KB3BheWxvYWQYAyABKAwiqAEKEFN0YXRlVmFsdWVTb3VyY2USEQoJb2JqZWN0X2lkGAEgASgJEg8KB3Nsb3RfaWQYAiABKAkSFwoPdmFsdWVfdHlwZV9qc29uGAMgASgJEhsKE3N0b3JhZ2VfcG9saWN5X2pzb24YBCABKAkSEAoIcmV2aXNpb24YBSABKAQSKAoNY3JkdF9zbmFwc2hvdBgGIAEoCzIRLmNhbWluby5DcmR0VmFsdWUiNgoLVmFsdWVTb3VyY2USJwoFc3RhdGUYASABKAsyGC5jYW1pbm8uU3RhdGVWYWx1ZVNvdXJjZSKpAwoFVmFsdWUSJwoKbnVsbF92YWx1ZRgBIAEoCzIRLmNhbWluby5OdWxsVmFsdWVIABIUCgpib29sX3ZhbHVlGAIgASgISAASFgoMbnVtYmVyX3ZhbHVlGAMgASgBSAASFgoMc3RyaW5nX3ZhbHVlGAQgASgJSAASFwoNaW50ZWdlcl92YWx1ZRgFIAEoCUgAEhUKC2J5dGVzX3ZhbHVlGAYgASgMSAASKwoMb2JqZWN0X3ZhbHVlGAcgASgLMhMuY2FtaW5vLk9iamVjdFZhbHVlSAASJwoKbGlzdF92YWx1ZRgIIAEoCzIRLmNhbWluby5MaXN0VmFsdWVIABIlCglyZWZfdmFsdWUYCSABKAsyEC5jYW1pbm8uUmVmVmFsdWVIABInCgpjcmR0X3ZhbHVlGAogASgLMhEuY2FtaW5vLkNyZHRWYWx1ZUgAEiMKBnNvdXJjZRgLIAEoCzITLmNhbWluby5WYWx1ZVNvdXJjZRIuCgxxdWVyeV9vcmlnaW4YDCABKAsyGC5jYW1pbm8uUXVlcnlGaWVsZE9yaWdpbkIGCgRraW5kIpUBChBRdWVyeUZpZWxkT3JpZ2luEhQKDHNlbGVjdGlvbl9pZBgBIAEoCRIPCgdhdG9tX2lkGAIgASgJEh0KFWludGVyZmFjZV9yZXZpc2lvbl9pZBgDIAEoCRIRCgltZW1iZXJfaWQYBCABKAkSKAoGc291cmNlGAUgASgLMhguY2FtaW5vLlN0YXRlVmFsdWVTb3VyY2UikwIKDlF1ZXJ5TGl2ZUZpZWxkEiMKBHBhdGgYASADKAsyFS5jYW1pbm8uUXVlcnlQYXRoUGFydBIUCgxzZWxlY3Rpb25faWQYAiABKAkSEQoJb2JqZWN0X2lkGAMgASgJEikKCmNhcGFiaWxpdHkYBCABKAsyFS5xdWl4b3MuQ2FwYWJpbGl0eVJlZhIaChJ3YXRjaF9vcGVyYXRpb25faWQYBSABKAkSGwoTc2V0dGVyX29wZXJhdGlvbl9pZBgGIAEoCRIlCgdlZGl0aW5nGAcgASgLMhQucXVpeG9zLkZpZWxkRWRpdGluZxIoCgZzb3VyY2UYCCABKAsyGC5jYW1pbm8uU3RhdGVWYWx1ZVNvdXJjZSJGCh1JbnN0YWxsUGVyc2lzdGVuY2VQbGFuUmVxdWVzdBIlCgRwbGFuGAEgASgLMhcuY2FtaW5vLlBlcnNpc3RlbmNlUGxhbiJHCh5JbnN0YWxsUGVyc2lzdGVuY2VQbGFuUmVzcG9uc2USJQoEcGxhbhgBIAEoCzIXLmNhbWluby5QZXJzaXN0ZW5jZVBsYW4iGwoZR2V0UGVyc2lzdGVuY2VQbGFuUmVxdWVzdCJDChpHZXRQZXJzaXN0ZW5jZVBsYW5SZXNwb25zZRIlCgRwbGFuGAEgASgLMhcuY2FtaW5vLlBlcnNpc3RlbmNlUGxhbiKwAQoTQ3JlYXRlT2JqZWN0UmVxdWVzdBIPCgdhdG9tX2lkGAEgASgJEkQKDWluaXRpYWxfc3RhdGUYAiADKAsyLS5jYW1pbm8uQ3JlYXRlT2JqZWN0UmVxdWVzdC5Jbml0aWFsU3RhdGVFbnRyeRpCChFJbml0aWFsU3RhdGVFbnRyeRILCgNrZXkYASABKAkSHAoFdmFsdWUYAiABKAsyDS5jYW1pbm8uVmFsdWU6AjgBIjwKFENyZWF0ZU9iamVjdFJlc3BvbnNlEiQKBm9iamVjdBgBIAEoCzIULmNhbWluby5DYW1pbm9PYmplY3QiJQoQR2V0T2JqZWN0UmVxdWVzdBIRCglvYmplY3RfaWQYASABKAkiOQoRR2V0T2JqZWN0UmVzcG9uc2USJAoGb2JqZWN0GAEgASgLMhQuY2FtaW5vLkNhbWlub09iamVjdCIlChJMaXN0T2JqZWN0c1JlcXVlc3QSDwoHYXRvbV9pZBgBIAEoCSI8ChNMaXN0T2JqZWN0c1Jlc3BvbnNlEiUKB29iamVjdHMYASADKAsyFC5jYW1pbm8uQ2FtaW5vT2JqZWN0IjYKEFJlYWRTdGF0ZVJlcXVlc3QSEQoJb2JqZWN0X2lkGAEgASgJEg8KB3Nsb3RfaWQYAiABKAkiMQoRUmVhZFN0YXRlUmVzcG9uc2USHAoFdmFsdWUYASABKAsyDS5jYW1pbm8uVmFsdWUicQoRV3JpdGVTdGF0ZVJlcXVlc3QSEQoJb2JqZWN0X2lkGAEgASgJEg8KB3Nsb3RfaWQYAiABKAkSHAoFdmFsdWUYAyABKAsyDS5jYW1pbm8uVmFsdWUSGgoSY2xpZW50X211dGF0aW9uX2lkGAQgASgJIlgKEldyaXRlU3RhdGVSZXNwb25zZRIcCgV2YWx1ZRgBIAEoCzINLmNhbWluby5WYWx1ZRIkCgZvYmplY3QYAiABKAsyFC5jYW1pbm8uQ2FtaW5vT2JqZWN0IsABChJDb25uZWN0RWRnZVJlcXVlc3QSFAoMZWRnZV90eXBlX2lkGAEgASgJEhUKDXByb2plY3Rpb25faWQYAiABKAkSEQoJb2JqZWN0X2lkGAMgASgJEhgKEHRhcmdldF9vYmplY3RfaWQYBCABKAkSFAoHb3JkaW5hbBgFIAEoBUgAiAEBEhsKDnRhcmdldF9vcmRpbmFsGAYgASgFSAGIAQFCCgoIX29yZGluYWxCEQoPX3RhcmdldF9vcmRpbmFsIjcKE0Nvbm5lY3RFZGdlUmVzcG9uc2USIAoEZWRnZRgBIAEoCzISLmNhbWluby5DYW1pbm9FZGdlIlQKElJlc29sdmVFZGdlUmVxdWVzdBIRCglvYmplY3RfaWQYASABKAkSFAoMZWRnZV90eXBlX2lkGAIgASgJEhUKDXByb2plY3Rpb25faWQYAyABKAkiOAoTUmVzb2x2ZUVkZ2VSZXNwb25zZRIhCgVlZGdlcxgBIAMoCzISLmNhbWluby5DYW1pbm9FZGdlIigKFURpc2Nvbm5lY3RFZGdlUmVxdWVzdBIPCgdlZGdlX2lkGAEgASgJIikKFkRpc2Nvbm5lY3RFZGdlUmVzcG9uc2USDwoHZWRnZV9pZBgBIAEoCSJYCg9Db2xsZWN0aW9uRW50cnkSDwoHZWRnZV9pZBgBIAEoCRIYChB0YXJnZXRfb2JqZWN0X2lkGAIgASgJEhoKA2tleRgDIAEoCzINLmNhbWluby5WYWx1ZSJUChZSZWFkQ29sbGVjdGlvblJlc3BvbnNlEhAKCHJldmlzaW9uGAEgASgEEigKB2VudHJpZXMYAiADKAsyFy5jYW1pbm8uQ29sbGVjdGlvbkVudHJ5Ip8BChhSZXBsYWNlQ29sbGVjdGlvblJlcXVlc3QSEQoJb2JqZWN0X2lkGAEgASgJEhQKDGVkZ2VfdHlwZV9pZBgCIAEoCRIVCg1wcm9qZWN0aW9uX2lkGAMgASgJEhkKEWV4cGVjdGVkX3JldmlzaW9uGAQgASgEEigKB2VudHJpZXMYBSADKAsyFy5jYW1pbm8uQ29sbGVjdGlvbkVudHJ5IiMKDkxpc3RPcHNSZXF1ZXN0EhEKCW9iamVjdF9pZBgBIAEoCSIwCg9MaXN0T3BzUmVzcG9uc2USHQoDb3BzGAEgAygLMhAuY2FtaW5vLkNhbWlub09wIm4KEldhdGNoT2JqZWN0UmVxdWVzdBIRCglvYmplY3RfaWQYASABKAkSEwoLYWZ0ZXJfb3BfaWQYAiABKAkSGAoQaW5jbHVkZV9zbmFwc2hvdBgDIAEoCBIWCg5hdHRhY2htZW50X2lkcxgEIAMoCSJrChBXYXRjaE9iamVjdEV2ZW50EhEKCW9iamVjdF9pZBgBIAEoCRImCghzbmFwc2hvdBgCIAEoCzIULmNhbWluby5DYW1pbm9PYmplY3QSHAoCb3AYAyABKAsyEC5jYW1pbm8uQ2FtaW5vT3Ai2QIKDENhbWlub09iamVjdBIKCgJpZBgBIAEoCRIPCgdhdG9tX2lkGAIgASgJEh0KFXdvcmtzcGFjZV9yZXZpc2lvbl9pZBgDIAEoCRISCgpjcmVhdGVkX2F0GAQgASgJEhIKCnVwZGF0ZWRfYXQYBSABKAkSLgoFc3RhdGUYBiADKAsyHy5jYW1pbm8uQ2FtaW5vT2JqZWN0LlN0YXRlRW50cnkSQQoPc3RhdGVfcmV2aXNpb25zGAcgAygLMiguY2FtaW5vLkNhbWlub09iamVjdC5TdGF0ZVJldmlzaW9uc0VudHJ5GjsKClN0YXRlRW50cnkSCwoDa2V5GAEgASgJEhwKBXZhbHVlGAIgASgLMg0uY2FtaW5vLlZhbHVlOgI4ARo1ChNTdGF0ZVJldmlzaW9uc0VudHJ5EgsKA2tleRgBIAEoCRINCgV2YWx1ZRgCIAEoBDoCOAEivwIKCkNhbWlub0VkZ2USCgoCaWQYASABKAkSFAoMZWRnZV90eXBlX2lkGAIgASgJEhcKD2ZpcnN0X29iamVjdF9pZBgDIAEoCRIYChBzZWNvbmRfb2JqZWN0X2lkGAQgASgJEhsKE2ZpcnN0X3Byb2plY3Rpb25faWQYBSABKAkSHAoUc2Vjb25kX3Byb2plY3Rpb25faWQYBiABKAkSGgoNZmlyc3Rfb3JkaW5hbBgHIAEoBUgAiAEBEhsKDnNlY29uZF9vcmRpbmFsGAggASgFSAGIAQESEgoKY3JlYXRlZF9hdBgJIAEoCRIWCg5maXJzdF9rZXlfanNvbhgKIAEoCRIXCg9zZWNvbmRfa2V5X2pzb24YCyABKAlCEAoOX2ZpcnN0X29yZGluYWxCEQoPX3NlY29uZF9vcmRpbmFsIpkBCghDYW1pbm9PcBIKCgJpZBgBIAEoCRIRCglvYmplY3RfaWQYAiABKAkSDwoHb3Bfa2luZBgDIAEoCRIeCgdwYXlsb2FkGAQgASgLMg0uY2FtaW5vLlZhbHVlEg0KBWFjdG9yGAUgASgJEhIKCmNyZWF0ZWRfYXQYBiABKAkSGgoSY2xpZW50X211dGF0aW9uX2lkGAcgASgJItABCgxRdWVyeVJlcXVlc3QSEAoIcXVlcnlfaWQYASABKAkSEQoJb2JqZWN0X2lkGAIgASgJEjYKCXZhcmlhYmxlcxgDIAMoCzIjLmNhbWluby5RdWVyeVJlcXVlc3QuVmFyaWFibGVzRW50cnkSIgoaZXhwZWN0ZWRfZGVmaW5pdGlvbl9kaWdlc3QYBCABKAkaPwoOVmFyaWFibGVzRW50cnkSCwoDa2V5GAEgASgJEhwKBXZhbHVlGAIgASgLMg0uY2FtaW5vLlZhbHVlOgI4ASI5Cg1RdWVyeVBhdGhQYXJ0Eg8KBWZpZWxkGAEgASgJSAASDwoFaW5kZXgYAiABKA1IAEIGCgRwYXJ0It0CChFRdWVyeVBlbmRpbmdGaWVsZBIjCgRwYXRoGAEgAygLMhUuY2FtaW5vLlF1ZXJ5UGF0aFBhcnQSEQoJb2JqZWN0X2lkGAIgASgJEh0KFWludGVyZmFjZV9yZXZpc2lvbl9pZBgDIAEoCRIRCgltZW1iZXJfaWQYBCABKAkSFAoMb3BlcmF0aW9uX2lkGAUgASgJEhcKD3ZhbHVlX3R5cGVfanNvbhgGIAEoCRIcCg9yZXNpZHVhbF93aW5kb3cYByABKA1IAIgBARIUCgxyZXNpZHVhbF9yb3cYCCABKA0SFgoOcmVzaWR1YWxfZmllbGQYCSABKAkSHwoScmVsYXRpb25hbF9jYXB0dXJlGAogASgNSAGIAQESFwoPY2FwdHVyZWRfb2JqZWN0GAsgASgNQhIKEF9yZXNpZHVhbF93aW5kb3dCFQoTX3JlbGF0aW9uYWxfY2FwdHVyZSL2AQoKUXVlcnlTdGF0cxIRCglzcWxfY291bnQYASABKA0SDgoGc3FsX21zGAIgASgBEhEKCXJwY19jb3VudBgDIAEoDRIOCgZycGNfbXMYBCABKAESFAoMcmVzdWx0X2J5dGVzGAUgASgNEhYKDnByZXBhcmF0aW9uX21zGAYgASgBEhAKCHRvdGFsX21zGAcgASgBEhkKEXJlbGF0aW9uYWxfc3RhZ2VzGAggASgNEhsKE2NhcHR1cmVkX2NhbmRpZGF0ZXMYCSABKA0SFQoNcmVsYXRpb25hbF9tcxgKIAEoARITCgtyZXNpZHVhbF9tcxgLIAEoASKmAwoNUXVlcnlSZXNwb25zZRIcCgV2YWx1ZRgBIAEoCzINLmNhbWluby5WYWx1ZRIUCgxkYXRhX3ZlcnNpb24YAiABKAkSFgoOYmluZGluZ19kaWdlc3QYAyABKAkSKgoHcGVuZGluZxgEIAMoCzIZLmNhbWluby5RdWVyeVBlbmRpbmdGaWVsZBIhCgVzdGF0cxgFIAEoCzISLmNhbWluby5RdWVyeVN0YXRzEhkKEXByZXBhcmF0aW9uX3Rva2VuGAYgASgJEikKBmVycm9ycxgHIAMoCzIZLmNhbWluby5RdWVyeUZpZWxkRmFpbHVyZRI1ChByZXNpZHVhbF93aW5kb3dzGAggAygLMhsuY2FtaW5vLlF1ZXJ5UmVzaWR1YWxXaW5kb3cSEwoLY29uc2lzdGVuY3kYCSABKAkSOwoTcmVsYXRpb25hbF9jYXB0dXJlcxgKIAMoCzIeLmNhbWluby5RdWVyeVJlbGF0aW9uYWxDYXB0dXJlEisKC2xpdmVfZmllbGRzGAsgAygLMhYuY2FtaW5vLlF1ZXJ5TGl2ZUZpZWxkIloKE1F1ZXJ5Q2FwdHVyZWRNZW1iZXISEQoJb2JqZWN0X2lkGAEgASgJEhAKCGVudHJ5X2lkGAIgASgJEh4KB21hcF9rZXkYAyABKAsyDS5jYW1pbm8uVmFsdWUiRAoUUXVlcnlDYXB0dXJlZE1lbWJlcnMSLAoHZW50cmllcxgBIAMoCzIbLmNhbWluby5RdWVyeUNhcHR1cmVkTWVtYmVyIq8DChNRdWVyeUNhcHR1cmVkT2JqZWN0EhEKCW9iamVjdF9pZBgBIAEoCRI3CgZmaWVsZHMYAiADKAsyJy5jYW1pbm8uUXVlcnlDYXB0dXJlZE9iamVjdC5GaWVsZHNFbnRyeRJACgtmaWVsZF90eXBlcxgDIAMoCzIrLmNhbWluby5RdWVyeUNhcHR1cmVkT2JqZWN0LkZpZWxkVHlwZXNFbnRyeRJFCg1yZWxhdGlvbnNoaXBzGAQgAygLMi4uY2FtaW5vLlF1ZXJ5Q2FwdHVyZWRPYmplY3QuUmVsYXRpb25zaGlwc0VudHJ5GjwKC0ZpZWxkc0VudHJ5EgsKA2tleRgBIAEoCRIcCgV2YWx1ZRgCIAEoCzINLmNhbWluby5WYWx1ZToCOAEaMQoPRmllbGRUeXBlc0VudHJ5EgsKA2tleRgBIAEoCRINCgV2YWx1ZRgCIAEoCToCOAEaUgoSUmVsYXRpb25zaGlwc0VudHJ5EgsKA2tleRgBIAEoCRIrCgV2YWx1ZRgCIAEoCzIcLmNhbWluby5RdWVyeUNhcHR1cmVkTWVtYmVyczoCOAEiuQIKFlF1ZXJ5UmVsYXRpb25hbENhcHR1cmUSIwoEcGF0aBgBIAMoCzIVLmNhbWluby5RdWVyeVBhdGhQYXJ0EikKBHBsYW4YAiABKAsyGy5jYW1pbm8uUXVlcnlSZWxhdGlvbmFsUGxhbhIWCg5yb290X29iamVjdF9pZBgDIAEoCRIsCgdvYmplY3RzGAQgAygLMhsuY2FtaW5vLlF1ZXJ5Q2FwdHVyZWRPYmplY3QSFgoOdmFyaWFibGVzX2pzb24YBSABKAkSEQoJcm93X2xpbWl0GAYgASgNEhcKD2NhbmRpZGF0ZV9saW1pdBgHIAEoDRIcCg9yZXNpZHVhbF93aW5kb3cYCCABKA1IAIgBARITCgtyZXN1bHRfcGF0aBgJIAMoCUISChBfcmVzaWR1YWxfd2luZG93IpgBChBRdWVyeVJlc2lkdWFsUm93EhAKCGVudHJ5X2lkGAEgASgJEjQKBmZpZWxkcxgCIAMoCzIkLmNhbWluby5RdWVyeVJlc2lkdWFsUm93LkZpZWxkc0VudHJ5GjwKC0ZpZWxkc0VudHJ5EgsKA2tleRgBIAEoCRIcCgV2YWx1ZRgCIAEoCzINLmNhbWluby5WYWx1ZToCOAEiNwoSUXVlcnlSZXNpZHVhbE9yZGVyEg0KBWZpZWxkGAEgASgJEhIKCmRlc2NlbmRpbmcYAiABKAgilgMKE1F1ZXJ5UmVzaWR1YWxXaW5kb3cSIwoEcGF0aBgBIAMoCzIVLmNhbWluby5RdWVyeVBhdGhQYXJ0EiYKBHJvd3MYAiADKAsyGC5jYW1pbm8uUXVlcnlSZXNpZHVhbFJvdxIWCg5wcmVkaWNhdGVfanNvbhgDIAEoCRIpCgVvcmRlchgEIAMoCzIaLmNhbWluby5RdWVyeVJlc2lkdWFsT3JkZXISDQoFbGltaXQYBSABKA0SEwoLYm91bmRlZF9hbGwYBiABKAgSKQoJc2VsZWN0aW9uGAcgAygLMhYuY2FtaW5vLlF1ZXJ5U2VsZWN0aW9uEkAKC2ZpZWxkX3R5cGVzGAggAygLMisuY2FtaW5vLlF1ZXJ5UmVzaWR1YWxXaW5kb3cuRmllbGRUeXBlc0VudHJ5EhIKCnJlbGF0aW9uYWwYCSABKAgSFwoPbWF0Y2hlZF9lbnRyaWVzGAogAygJGjEKD0ZpZWxkVHlwZXNFbnRyeRILCgNrZXkYASABKAkSDQoFdmFsdWUYAiABKAk6AjgBIkcKEVF1ZXJ5RmllbGRGYWlsdXJlEiMKBHBhdGgYASADKAsyFS5jYW1pbm8uUXVlcnlQYXRoUGFydBINCgVlcnJvchgCIAEoCSJQChNRdWVyeUNoYW5nZXNSZXF1ZXN0EhAKCHF1ZXJ5X2lkGAEgASgJEhEKCW9iamVjdF9pZBgCIAEoCRIUCgxkYXRhX3ZlcnNpb24YAyABKAkiJwoUUXVlcnlDaGFuZ2VzUmVzcG9uc2USDwoHY2hhbmdlZBgBIAEoCDLCCQoNQ2FtaW5vU2VydmljZRI7CgxFeGVjdXRlUXVlcnkSFC5jYW1pbm8uUXVlcnlSZXF1ZXN0GhUuY2FtaW5vLlF1ZXJ5UmVzcG9uc2USSQoMUXVlcnlDaGFuZ2VzEhsuY2FtaW5vLlF1ZXJ5Q2hhbmdlc1JlcXVlc3QaHC5jYW1pbm8uUXVlcnlDaGFuZ2VzUmVzcG9uc2USZwoWSW5zdGFsbFBlcnNpc3RlbmNlUGxhbhIlLmNhbWluby5JbnN0YWxsUGVyc2lzdGVuY2VQbGFuUmVxdWVzdBomLmNhbWluby5JbnN0YWxsUGVyc2lzdGVuY2VQbGFuUmVzcG9uc2USWwoSR2V0UGVyc2lzdGVuY2VQbGFuEiEuY2FtaW5vLkdldFBlcnNpc3RlbmNlUGxhblJlcXVlc3QaIi5jYW1pbm8uR2V0UGVyc2lzdGVuY2VQbGFuUmVzcG9uc2USSQoMQ3JlYXRlT2JqZWN0EhsuY2FtaW5vLkNyZWF0ZU9iamVjdFJlcXVlc3QaHC5jYW1pbm8uQ3JlYXRlT2JqZWN0UmVzcG9uc2USQAoJR2V0T2JqZWN0EhguY2FtaW5vLkdldE9iamVjdFJlcXVlc3QaGS5jYW1pbm8uR2V0T2JqZWN0UmVzcG9uc2USRgoLTGlzdE9iamVjdHMSGi5jYW1pbm8uTGlzdE9iamVjdHNSZXF1ZXN0GhsuY2FtaW5vLkxpc3RPYmplY3RzUmVzcG9uc2USQAoJUmVhZFN0YXRlEhguY2FtaW5vLlJlYWRTdGF0ZVJlcXVlc3QaGS5jYW1pbm8uUmVhZFN0YXRlUmVzcG9uc2USQwoKV3JpdGVTdGF0ZRIZLmNhbWluby5Xcml0ZVN0YXRlUmVxdWVzdBoaLmNhbWluby5Xcml0ZVN0YXRlUmVzcG9uc2USRgoLQ29ubmVjdEVkZ2USGi5jYW1pbm8uQ29ubmVjdEVkZ2VSZXF1ZXN0GhsuY2FtaW5vLkNvbm5lY3RFZGdlUmVzcG9uc2USRgoLUmVzb2x2ZUVkZ2USGi5jYW1pbm8uUmVzb2x2ZUVkZ2VSZXF1ZXN0GhsuY2FtaW5vLlJlc29sdmVFZGdlUmVzcG9uc2USTwoORGlzY29ubmVjdEVkZ2USHS5jYW1pbm8uRGlzY29ubmVjdEVkZ2VSZXF1ZXN0Gh4uY2FtaW5vLkRpc2Nvbm5lY3RFZGdlUmVzcG9uc2USTAoOUmVhZENvbGxlY3Rpb24SGi5jYW1pbm8uUmVzb2x2ZUVkZ2VSZXF1ZXN0Gh4uY2FtaW5vLlJlYWRDb2xsZWN0aW9uUmVzcG9uc2USVQoRUmVwbGFjZUNvbGxlY3Rpb24SIC5jYW1pbm8uUmVwbGFjZUNvbGxlY3Rpb25SZXF1ZXN0Gh4uY2FtaW5vLlJlYWRDb2xsZWN0aW9uUmVzcG9uc2USOgoHTGlzdE9wcxIWLmNhbWluby5MaXN0T3BzUmVxdWVzdBoXLmNhbWluby5MaXN0T3BzUmVzcG9uc2USRQoLV2F0Y2hPYmplY3QSGi5jYW1pbm8uV2F0Y2hPYmplY3RSZXF1ZXN0GhguY2FtaW5vLldhdGNoT2JqZWN0RXZlbnQwAWIGcHJvdG8z", [file_camino_schema, file_quixos_refs]); + fileDesc("ChBjYW1pbm8vYXBpLnByb3RvEgZjYW1pbm8iCwoJTnVsbFZhbHVlInwKC09iamVjdFZhbHVlEi8KBmZpZWxkcxgBIAMoCzIfLmNhbWluby5PYmplY3RWYWx1ZS5GaWVsZHNFbnRyeRo8CgtGaWVsZHNFbnRyeRILCgNrZXkYASABKAkSHAoFdmFsdWUYAiABKAsyDS5jYW1pbm8uVmFsdWU6AjgBIioKCUxpc3RWYWx1ZRIdCgZ2YWx1ZXMYASADKAsyDS5jYW1pbm8uVmFsdWUiHQoIUmVmVmFsdWUSEQoJb2JqZWN0X2lkGAEgASgJIjwKCUNyZHRWYWx1ZRIMCgR0eXBlGAEgASgJEhAKCGVuY29kaW5nGAIgASgJEg8KB3BheWxvYWQYAyABKAwiqAEKEFN0YXRlVmFsdWVTb3VyY2USEQoJb2JqZWN0X2lkGAEgASgJEg8KB3Nsb3RfaWQYAiABKAkSFwoPdmFsdWVfdHlwZV9qc29uGAMgASgJEhsKE3N0b3JhZ2VfcG9saWN5X2pzb24YBCABKAkSEAoIcmV2aXNpb24YBSABKAQSKAoNY3JkdF9zbmFwc2hvdBgGIAEoCzIRLmNhbWluby5DcmR0VmFsdWUiNgoLVmFsdWVTb3VyY2USJwoFc3RhdGUYASABKAsyGC5jYW1pbm8uU3RhdGVWYWx1ZVNvdXJjZSKpAwoFVmFsdWUSJwoKbnVsbF92YWx1ZRgBIAEoCzIRLmNhbWluby5OdWxsVmFsdWVIABIUCgpib29sX3ZhbHVlGAIgASgISAASFgoMbnVtYmVyX3ZhbHVlGAMgASgBSAASFgoMc3RyaW5nX3ZhbHVlGAQgASgJSAASFwoNaW50ZWdlcl92YWx1ZRgFIAEoCUgAEhUKC2J5dGVzX3ZhbHVlGAYgASgMSAASKwoMb2JqZWN0X3ZhbHVlGAcgASgLMhMuY2FtaW5vLk9iamVjdFZhbHVlSAASJwoKbGlzdF92YWx1ZRgIIAEoCzIRLmNhbWluby5MaXN0VmFsdWVIABIlCglyZWZfdmFsdWUYCSABKAsyEC5jYW1pbm8uUmVmVmFsdWVIABInCgpjcmR0X3ZhbHVlGAogASgLMhEuY2FtaW5vLkNyZHRWYWx1ZUgAEiMKBnNvdXJjZRgLIAEoCzITLmNhbWluby5WYWx1ZVNvdXJjZRIuCgxxdWVyeV9vcmlnaW4YDCABKAsyGC5jYW1pbm8uUXVlcnlGaWVsZE9yaWdpbkIGCgRraW5kIpUBChBRdWVyeUZpZWxkT3JpZ2luEhQKDHNlbGVjdGlvbl9pZBgBIAEoCRIPCgdhdG9tX2lkGAIgASgJEh0KFWludGVyZmFjZV9yZXZpc2lvbl9pZBgDIAEoCRIRCgltZW1iZXJfaWQYBCABKAkSKAoGc291cmNlGAUgASgLMhguY2FtaW5vLlN0YXRlVmFsdWVTb3VyY2UikwIKDlF1ZXJ5TGl2ZUZpZWxkEiMKBHBhdGgYASADKAsyFS5jYW1pbm8uUXVlcnlQYXRoUGFydBIUCgxzZWxlY3Rpb25faWQYAiABKAkSEQoJb2JqZWN0X2lkGAMgASgJEikKCmNhcGFiaWxpdHkYBCABKAsyFS5xdWl4b3MuQ2FwYWJpbGl0eVJlZhIaChJ3YXRjaF9vcGVyYXRpb25faWQYBSABKAkSGwoTc2V0dGVyX29wZXJhdGlvbl9pZBgGIAEoCRIlCgdlZGl0aW5nGAcgASgLMhQucXVpeG9zLkZpZWxkRWRpdGluZxIoCgZzb3VyY2UYCCABKAsyGC5jYW1pbm8uU3RhdGVWYWx1ZVNvdXJjZSJGCh1JbnN0YWxsUGVyc2lzdGVuY2VQbGFuUmVxdWVzdBIlCgRwbGFuGAEgASgLMhcuY2FtaW5vLlBlcnNpc3RlbmNlUGxhbiJHCh5JbnN0YWxsUGVyc2lzdGVuY2VQbGFuUmVzcG9uc2USJQoEcGxhbhgBIAEoCzIXLmNhbWluby5QZXJzaXN0ZW5jZVBsYW4iGwoZR2V0UGVyc2lzdGVuY2VQbGFuUmVxdWVzdCJDChpHZXRQZXJzaXN0ZW5jZVBsYW5SZXNwb25zZRIlCgRwbGFuGAEgASgLMhcuY2FtaW5vLlBlcnNpc3RlbmNlUGxhbiKwAQoTQ3JlYXRlT2JqZWN0UmVxdWVzdBIPCgdhdG9tX2lkGAEgASgJEkQKDWluaXRpYWxfc3RhdGUYAiADKAsyLS5jYW1pbm8uQ3JlYXRlT2JqZWN0UmVxdWVzdC5Jbml0aWFsU3RhdGVFbnRyeRpCChFJbml0aWFsU3RhdGVFbnRyeRILCgNrZXkYASABKAkSHAoFdmFsdWUYAiABKAsyDS5jYW1pbm8uVmFsdWU6AjgBIjwKFENyZWF0ZU9iamVjdFJlc3BvbnNlEiQKBm9iamVjdBgBIAEoCzIULmNhbWluby5DYW1pbm9PYmplY3QiJQoQR2V0T2JqZWN0UmVxdWVzdBIRCglvYmplY3RfaWQYASABKAkiOQoRR2V0T2JqZWN0UmVzcG9uc2USJAoGb2JqZWN0GAEgASgLMhQuY2FtaW5vLkNhbWlub09iamVjdCIlChJMaXN0T2JqZWN0c1JlcXVlc3QSDwoHYXRvbV9pZBgBIAEoCSI8ChNMaXN0T2JqZWN0c1Jlc3BvbnNlEiUKB29iamVjdHMYASADKAsyFC5jYW1pbm8uQ2FtaW5vT2JqZWN0IjYKEFJlYWRTdGF0ZVJlcXVlc3QSEQoJb2JqZWN0X2lkGAEgASgJEg8KB3Nsb3RfaWQYAiABKAkiMQoRUmVhZFN0YXRlUmVzcG9uc2USHAoFdmFsdWUYASABKAsyDS5jYW1pbm8uVmFsdWUicQoRV3JpdGVTdGF0ZVJlcXVlc3QSEQoJb2JqZWN0X2lkGAEgASgJEg8KB3Nsb3RfaWQYAiABKAkSHAoFdmFsdWUYAyABKAsyDS5jYW1pbm8uVmFsdWUSGgoSY2xpZW50X211dGF0aW9uX2lkGAQgASgJIlgKEldyaXRlU3RhdGVSZXNwb25zZRIcCgV2YWx1ZRgBIAEoCzINLmNhbWluby5WYWx1ZRIkCgZvYmplY3QYAiABKAsyFC5jYW1pbm8uQ2FtaW5vT2JqZWN0IsABChJDb25uZWN0RWRnZVJlcXVlc3QSFAoMZWRnZV90eXBlX2lkGAEgASgJEhUKDXByb2plY3Rpb25faWQYAiABKAkSEQoJb2JqZWN0X2lkGAMgASgJEhgKEHRhcmdldF9vYmplY3RfaWQYBCABKAkSFAoHb3JkaW5hbBgFIAEoBUgAiAEBEhsKDnRhcmdldF9vcmRpbmFsGAYgASgFSAGIAQFCCgoIX29yZGluYWxCEQoPX3RhcmdldF9vcmRpbmFsIjcKE0Nvbm5lY3RFZGdlUmVzcG9uc2USIAoEZWRnZRgBIAEoCzISLmNhbWluby5DYW1pbm9FZGdlIlQKElJlc29sdmVFZGdlUmVxdWVzdBIRCglvYmplY3RfaWQYASABKAkSFAoMZWRnZV90eXBlX2lkGAIgASgJEhUKDXByb2plY3Rpb25faWQYAyABKAkiOAoTUmVzb2x2ZUVkZ2VSZXNwb25zZRIhCgVlZGdlcxgBIAMoCzISLmNhbWluby5DYW1pbm9FZGdlIigKFURpc2Nvbm5lY3RFZGdlUmVxdWVzdBIPCgdlZGdlX2lkGAEgASgJIikKFkRpc2Nvbm5lY3RFZGdlUmVzcG9uc2USDwoHZWRnZV9pZBgBIAEoCSJYCg9Db2xsZWN0aW9uRW50cnkSDwoHZWRnZV9pZBgBIAEoCRIYChB0YXJnZXRfb2JqZWN0X2lkGAIgASgJEhoKA2tleRgDIAEoCzINLmNhbWluby5WYWx1ZSJUChZSZWFkQ29sbGVjdGlvblJlc3BvbnNlEhAKCHJldmlzaW9uGAEgASgEEigKB2VudHJpZXMYAiADKAsyFy5jYW1pbm8uQ29sbGVjdGlvbkVudHJ5Ip8BChhSZXBsYWNlQ29sbGVjdGlvblJlcXVlc3QSEQoJb2JqZWN0X2lkGAEgASgJEhQKDGVkZ2VfdHlwZV9pZBgCIAEoCRIVCg1wcm9qZWN0aW9uX2lkGAMgASgJEhkKEWV4cGVjdGVkX3JldmlzaW9uGAQgASgEEigKB2VudHJpZXMYBSADKAsyFy5jYW1pbm8uQ29sbGVjdGlvbkVudHJ5IiMKDkxpc3RPcHNSZXF1ZXN0EhEKCW9iamVjdF9pZBgBIAEoCSIwCg9MaXN0T3BzUmVzcG9uc2USHQoDb3BzGAEgAygLMhAuY2FtaW5vLkNhbWlub09wIm4KEldhdGNoT2JqZWN0UmVxdWVzdBIRCglvYmplY3RfaWQYASABKAkSEwoLYWZ0ZXJfb3BfaWQYAiABKAkSGAoQaW5jbHVkZV9zbmFwc2hvdBgDIAEoCBIWCg5hdHRhY2htZW50X2lkcxgEIAMoCSJrChBXYXRjaE9iamVjdEV2ZW50EhEKCW9iamVjdF9pZBgBIAEoCRImCghzbmFwc2hvdBgCIAEoCzIULmNhbWluby5DYW1pbm9PYmplY3QSHAoCb3AYAyABKAsyEC5jYW1pbm8uQ2FtaW5vT3Ai2QIKDENhbWlub09iamVjdBIKCgJpZBgBIAEoCRIPCgdhdG9tX2lkGAIgASgJEh0KFXdvcmtzcGFjZV9yZXZpc2lvbl9pZBgDIAEoCRISCgpjcmVhdGVkX2F0GAQgASgJEhIKCnVwZGF0ZWRfYXQYBSABKAkSLgoFc3RhdGUYBiADKAsyHy5jYW1pbm8uQ2FtaW5vT2JqZWN0LlN0YXRlRW50cnkSQQoPc3RhdGVfcmV2aXNpb25zGAcgAygLMiguY2FtaW5vLkNhbWlub09iamVjdC5TdGF0ZVJldmlzaW9uc0VudHJ5GjsKClN0YXRlRW50cnkSCwoDa2V5GAEgASgJEhwKBXZhbHVlGAIgASgLMg0uY2FtaW5vLlZhbHVlOgI4ARo1ChNTdGF0ZVJldmlzaW9uc0VudHJ5EgsKA2tleRgBIAEoCRINCgV2YWx1ZRgCIAEoBDoCOAEivwIKCkNhbWlub0VkZ2USCgoCaWQYASABKAkSFAoMZWRnZV90eXBlX2lkGAIgASgJEhcKD2ZpcnN0X29iamVjdF9pZBgDIAEoCRIYChBzZWNvbmRfb2JqZWN0X2lkGAQgASgJEhsKE2ZpcnN0X3Byb2plY3Rpb25faWQYBSABKAkSHAoUc2Vjb25kX3Byb2plY3Rpb25faWQYBiABKAkSGgoNZmlyc3Rfb3JkaW5hbBgHIAEoBUgAiAEBEhsKDnNlY29uZF9vcmRpbmFsGAggASgFSAGIAQESEgoKY3JlYXRlZF9hdBgJIAEoCRIWCg5maXJzdF9rZXlfanNvbhgKIAEoCRIXCg9zZWNvbmRfa2V5X2pzb24YCyABKAlCEAoOX2ZpcnN0X29yZGluYWxCEQoPX3NlY29uZF9vcmRpbmFsIpkBCghDYW1pbm9PcBIKCgJpZBgBIAEoCRIRCglvYmplY3RfaWQYAiABKAkSDwoHb3Bfa2luZBgDIAEoCRIeCgdwYXlsb2FkGAQgASgLMg0uY2FtaW5vLlZhbHVlEg0KBWFjdG9yGAUgASgJEhIKCmNyZWF0ZWRfYXQYBiABKAkSGgoSY2xpZW50X211dGF0aW9uX2lkGAcgASgJIpICCgxRdWVyeVJlcXVlc3QSEAoIcXVlcnlfaWQYASABKAkSEwoJb2JqZWN0X2lkGAIgASgJSAASNgoPd29ya3NwYWNlX3Njb3BlGAUgASgLMhsucXVpeG9zLldvcmtzcGFjZVF1ZXJ5U2NvcGVIABI2Cgl2YXJpYWJsZXMYAyADKAsyIy5jYW1pbm8uUXVlcnlSZXF1ZXN0LlZhcmlhYmxlc0VudHJ5EiIKGmV4cGVjdGVkX2RlZmluaXRpb25fZGlnZXN0GAQgASgJGj8KDlZhcmlhYmxlc0VudHJ5EgsKA2tleRgBIAEoCRIcCgV2YWx1ZRgCIAEoCzINLmNhbWluby5WYWx1ZToCOAFCBgoEcm9vdCI5Cg1RdWVyeVBhdGhQYXJ0Eg8KBWZpZWxkGAEgASgJSAASDwoFaW5kZXgYAiABKA1IAEIGCgRwYXJ0It0CChFRdWVyeVBlbmRpbmdGaWVsZBIjCgRwYXRoGAEgAygLMhUuY2FtaW5vLlF1ZXJ5UGF0aFBhcnQSEQoJb2JqZWN0X2lkGAIgASgJEh0KFWludGVyZmFjZV9yZXZpc2lvbl9pZBgDIAEoCRIRCgltZW1iZXJfaWQYBCABKAkSFAoMb3BlcmF0aW9uX2lkGAUgASgJEhcKD3ZhbHVlX3R5cGVfanNvbhgGIAEoCRIcCg9yZXNpZHVhbF93aW5kb3cYByABKA1IAIgBARIUCgxyZXNpZHVhbF9yb3cYCCABKA0SFgoOcmVzaWR1YWxfZmllbGQYCSABKAkSHwoScmVsYXRpb25hbF9jYXB0dXJlGAogASgNSAGIAQESFwoPY2FwdHVyZWRfb2JqZWN0GAsgASgNQhIKEF9yZXNpZHVhbF93aW5kb3dCFQoTX3JlbGF0aW9uYWxfY2FwdHVyZSL2AQoKUXVlcnlTdGF0cxIRCglzcWxfY291bnQYASABKA0SDgoGc3FsX21zGAIgASgBEhEKCXJwY19jb3VudBgDIAEoDRIOCgZycGNfbXMYBCABKAESFAoMcmVzdWx0X2J5dGVzGAUgASgNEhYKDnByZXBhcmF0aW9uX21zGAYgASgBEhAKCHRvdGFsX21zGAcgASgBEhkKEXJlbGF0aW9uYWxfc3RhZ2VzGAggASgNEhsKE2NhcHR1cmVkX2NhbmRpZGF0ZXMYCSABKA0SFQoNcmVsYXRpb25hbF9tcxgKIAEoARITCgtyZXNpZHVhbF9tcxgLIAEoASKmAwoNUXVlcnlSZXNwb25zZRIcCgV2YWx1ZRgBIAEoCzINLmNhbWluby5WYWx1ZRIUCgxkYXRhX3ZlcnNpb24YAiABKAkSFgoOYmluZGluZ19kaWdlc3QYAyABKAkSKgoHcGVuZGluZxgEIAMoCzIZLmNhbWluby5RdWVyeVBlbmRpbmdGaWVsZBIhCgVzdGF0cxgFIAEoCzISLmNhbWluby5RdWVyeVN0YXRzEhkKEXByZXBhcmF0aW9uX3Rva2VuGAYgASgJEikKBmVycm9ycxgHIAMoCzIZLmNhbWluby5RdWVyeUZpZWxkRmFpbHVyZRI1ChByZXNpZHVhbF93aW5kb3dzGAggAygLMhsuY2FtaW5vLlF1ZXJ5UmVzaWR1YWxXaW5kb3cSEwoLY29uc2lzdGVuY3kYCSABKAkSOwoTcmVsYXRpb25hbF9jYXB0dXJlcxgKIAMoCzIeLmNhbWluby5RdWVyeVJlbGF0aW9uYWxDYXB0dXJlEisKC2xpdmVfZmllbGRzGAsgAygLMhYuY2FtaW5vLlF1ZXJ5TGl2ZUZpZWxkIloKE1F1ZXJ5Q2FwdHVyZWRNZW1iZXISEQoJb2JqZWN0X2lkGAEgASgJEhAKCGVudHJ5X2lkGAIgASgJEh4KB21hcF9rZXkYAyABKAsyDS5jYW1pbm8uVmFsdWUiRAoUUXVlcnlDYXB0dXJlZE1lbWJlcnMSLAoHZW50cmllcxgBIAMoCzIbLmNhbWluby5RdWVyeUNhcHR1cmVkTWVtYmVyIq8DChNRdWVyeUNhcHR1cmVkT2JqZWN0EhEKCW9iamVjdF9pZBgBIAEoCRI3CgZmaWVsZHMYAiADKAsyJy5jYW1pbm8uUXVlcnlDYXB0dXJlZE9iamVjdC5GaWVsZHNFbnRyeRJACgtmaWVsZF90eXBlcxgDIAMoCzIrLmNhbWluby5RdWVyeUNhcHR1cmVkT2JqZWN0LkZpZWxkVHlwZXNFbnRyeRJFCg1yZWxhdGlvbnNoaXBzGAQgAygLMi4uY2FtaW5vLlF1ZXJ5Q2FwdHVyZWRPYmplY3QuUmVsYXRpb25zaGlwc0VudHJ5GjwKC0ZpZWxkc0VudHJ5EgsKA2tleRgBIAEoCRIcCgV2YWx1ZRgCIAEoCzINLmNhbWluby5WYWx1ZToCOAEaMQoPRmllbGRUeXBlc0VudHJ5EgsKA2tleRgBIAEoCRINCgV2YWx1ZRgCIAEoCToCOAEaUgoSUmVsYXRpb25zaGlwc0VudHJ5EgsKA2tleRgBIAEoCRIrCgV2YWx1ZRgCIAEoCzIcLmNhbWluby5RdWVyeUNhcHR1cmVkTWVtYmVyczoCOAEiuQIKFlF1ZXJ5UmVsYXRpb25hbENhcHR1cmUSIwoEcGF0aBgBIAMoCzIVLmNhbWluby5RdWVyeVBhdGhQYXJ0EikKBHBsYW4YAiABKAsyGy5jYW1pbm8uUXVlcnlSZWxhdGlvbmFsUGxhbhIWCg5yb290X29iamVjdF9pZBgDIAEoCRIsCgdvYmplY3RzGAQgAygLMhsuY2FtaW5vLlF1ZXJ5Q2FwdHVyZWRPYmplY3QSFgoOdmFyaWFibGVzX2pzb24YBSABKAkSEQoJcm93X2xpbWl0GAYgASgNEhcKD2NhbmRpZGF0ZV9saW1pdBgHIAEoDRIcCg9yZXNpZHVhbF93aW5kb3cYCCABKA1IAIgBARITCgtyZXN1bHRfcGF0aBgJIAMoCUISChBfcmVzaWR1YWxfd2luZG93IpgBChBRdWVyeVJlc2lkdWFsUm93EhAKCGVudHJ5X2lkGAEgASgJEjQKBmZpZWxkcxgCIAMoCzIkLmNhbWluby5RdWVyeVJlc2lkdWFsUm93LkZpZWxkc0VudHJ5GjwKC0ZpZWxkc0VudHJ5EgsKA2tleRgBIAEoCRIcCgV2YWx1ZRgCIAEoCzINLmNhbWluby5WYWx1ZToCOAEiNwoSUXVlcnlSZXNpZHVhbE9yZGVyEg0KBWZpZWxkGAEgASgJEhIKCmRlc2NlbmRpbmcYAiABKAgilgMKE1F1ZXJ5UmVzaWR1YWxXaW5kb3cSIwoEcGF0aBgBIAMoCzIVLmNhbWluby5RdWVyeVBhdGhQYXJ0EiYKBHJvd3MYAiADKAsyGC5jYW1pbm8uUXVlcnlSZXNpZHVhbFJvdxIWCg5wcmVkaWNhdGVfanNvbhgDIAEoCRIpCgVvcmRlchgEIAMoCzIaLmNhbWluby5RdWVyeVJlc2lkdWFsT3JkZXISDQoFbGltaXQYBSABKA0SEwoLYm91bmRlZF9hbGwYBiABKAgSKQoJc2VsZWN0aW9uGAcgAygLMhYuY2FtaW5vLlF1ZXJ5U2VsZWN0aW9uEkAKC2ZpZWxkX3R5cGVzGAggAygLMisuY2FtaW5vLlF1ZXJ5UmVzaWR1YWxXaW5kb3cuRmllbGRUeXBlc0VudHJ5EhIKCnJlbGF0aW9uYWwYCSABKAgSFwoPbWF0Y2hlZF9lbnRyaWVzGAogAygJGjEKD0ZpZWxkVHlwZXNFbnRyeRILCgNrZXkYASABKAkSDQoFdmFsdWUYAiABKAk6AjgBIkcKEVF1ZXJ5RmllbGRGYWlsdXJlEiMKBHBhdGgYASADKAsyFS5jYW1pbm8uUXVlcnlQYXRoUGFydBINCgVlcnJvchgCIAEoCSKSAQoTUXVlcnlDaGFuZ2VzUmVxdWVzdBIQCghxdWVyeV9pZBgBIAEoCRITCglvYmplY3RfaWQYAiABKAlIABI2Cg93b3Jrc3BhY2Vfc2NvcGUYBCABKAsyGy5xdWl4b3MuV29ya3NwYWNlUXVlcnlTY29wZUgAEhQKDGRhdGFfdmVyc2lvbhgDIAEoCUIGCgRyb290IicKFFF1ZXJ5Q2hhbmdlc1Jlc3BvbnNlEg8KB2NoYW5nZWQYASABKAgywgkKDUNhbWlub1NlcnZpY2USOwoMRXhlY3V0ZVF1ZXJ5EhQuY2FtaW5vLlF1ZXJ5UmVxdWVzdBoVLmNhbWluby5RdWVyeVJlc3BvbnNlEkkKDFF1ZXJ5Q2hhbmdlcxIbLmNhbWluby5RdWVyeUNoYW5nZXNSZXF1ZXN0GhwuY2FtaW5vLlF1ZXJ5Q2hhbmdlc1Jlc3BvbnNlEmcKFkluc3RhbGxQZXJzaXN0ZW5jZVBsYW4SJS5jYW1pbm8uSW5zdGFsbFBlcnNpc3RlbmNlUGxhblJlcXVlc3QaJi5jYW1pbm8uSW5zdGFsbFBlcnNpc3RlbmNlUGxhblJlc3BvbnNlElsKEkdldFBlcnNpc3RlbmNlUGxhbhIhLmNhbWluby5HZXRQZXJzaXN0ZW5jZVBsYW5SZXF1ZXN0GiIuY2FtaW5vLkdldFBlcnNpc3RlbmNlUGxhblJlc3BvbnNlEkkKDENyZWF0ZU9iamVjdBIbLmNhbWluby5DcmVhdGVPYmplY3RSZXF1ZXN0GhwuY2FtaW5vLkNyZWF0ZU9iamVjdFJlc3BvbnNlEkAKCUdldE9iamVjdBIYLmNhbWluby5HZXRPYmplY3RSZXF1ZXN0GhkuY2FtaW5vLkdldE9iamVjdFJlc3BvbnNlEkYKC0xpc3RPYmplY3RzEhouY2FtaW5vLkxpc3RPYmplY3RzUmVxdWVzdBobLmNhbWluby5MaXN0T2JqZWN0c1Jlc3BvbnNlEkAKCVJlYWRTdGF0ZRIYLmNhbWluby5SZWFkU3RhdGVSZXF1ZXN0GhkuY2FtaW5vLlJlYWRTdGF0ZVJlc3BvbnNlEkMKCldyaXRlU3RhdGUSGS5jYW1pbm8uV3JpdGVTdGF0ZVJlcXVlc3QaGi5jYW1pbm8uV3JpdGVTdGF0ZVJlc3BvbnNlEkYKC0Nvbm5lY3RFZGdlEhouY2FtaW5vLkNvbm5lY3RFZGdlUmVxdWVzdBobLmNhbWluby5Db25uZWN0RWRnZVJlc3BvbnNlEkYKC1Jlc29sdmVFZGdlEhouY2FtaW5vLlJlc29sdmVFZGdlUmVxdWVzdBobLmNhbWluby5SZXNvbHZlRWRnZVJlc3BvbnNlEk8KDkRpc2Nvbm5lY3RFZGdlEh0uY2FtaW5vLkRpc2Nvbm5lY3RFZGdlUmVxdWVzdBoeLmNhbWluby5EaXNjb25uZWN0RWRnZVJlc3BvbnNlEkwKDlJlYWRDb2xsZWN0aW9uEhouY2FtaW5vLlJlc29sdmVFZGdlUmVxdWVzdBoeLmNhbWluby5SZWFkQ29sbGVjdGlvblJlc3BvbnNlElUKEVJlcGxhY2VDb2xsZWN0aW9uEiAuY2FtaW5vLlJlcGxhY2VDb2xsZWN0aW9uUmVxdWVzdBoeLmNhbWluby5SZWFkQ29sbGVjdGlvblJlc3BvbnNlEjoKB0xpc3RPcHMSFi5jYW1pbm8uTGlzdE9wc1JlcXVlc3QaFy5jYW1pbm8uTGlzdE9wc1Jlc3BvbnNlEkUKC1dhdGNoT2JqZWN0EhouY2FtaW5vLldhdGNoT2JqZWN0UmVxdWVzdBoYLmNhbWluby5XYXRjaE9iamVjdEV2ZW50MAFiBnByb3RvMw", [file_camino_schema, file_quixos_refs]); /** * @generated from message camino.NullValue @@ -1104,9 +1104,21 @@ export type QueryRequest = Message<"camino.QueryRequest"> & { queryId: string; /** - * @generated from field: string object_id = 2; + * @generated from oneof camino.QueryRequest.root */ - objectId: string; + root: { + /** + * @generated from field: string object_id = 2; + */ + value: string; + case: "objectId"; + } | { + /** + * @generated from field: quixos.WorkspaceQueryScope workspace_scope = 5; + */ + value: WorkspaceQueryScope; + case: "workspaceScope"; + } | { case: undefined; value?: undefined }; /** * @generated from field: map variables = 3; @@ -1641,9 +1653,21 @@ export type QueryChangesRequest = Message<"camino.QueryChangesRequest"> & { queryId: string; /** - * @generated from field: string object_id = 2; + * @generated from oneof camino.QueryChangesRequest.root */ - objectId: string; + root: { + /** + * @generated from field: string object_id = 2; + */ + value: string; + case: "objectId"; + } | { + /** + * @generated from field: quixos.WorkspaceQueryScope workspace_scope = 4; + */ + value: WorkspaceQueryScope; + case: "workspaceScope"; + } | { case: undefined; value?: undefined }; /** * @generated from field: string data_version = 3; diff --git a/src/gen/camino/schema_pb.ts b/src/gen/camino/schema_pb.ts index 644a900..c239c35 100644 --- a/src/gen/camino/schema_pb.ts +++ b/src/gen/camino/schema_pb.ts @@ -10,7 +10,7 @@ import type { Message } from "@bufbuild/protobuf"; * Describes the file camino/schema.proto. */ export const file_camino_schema: GenFile = /*@__PURE__*/ - fileDesc("ChNjYW1pbm8vc2NoZW1hLnByb3RvEgZjYW1pbm8iNwoOQXRvbURlZmluaXRpb24SDwoHYXRvbV9pZBgBIAEoCRIUCgxkaXNwbGF5X25hbWUYAiABKAkiWQoPQXRvbUNvbmZvcm1hbmNlEg8KB2F0b21faWQYASABKAkSHQoVaW50ZXJmYWNlX3JldmlzaW9uX2lkGAIgASgJEhYKDmNvbmZvcm1hbmNlX2lkGAMgASgJIsIBCg9TdGF0ZUF0dGFjaG1lbnQSDwoHc2xvdF9pZBgBIAEoCRIYChBhdHRhY2hlZF9hdG9tX2lkGAIgASgJEhQKDGRpc3BsYXlfbmFtZRgDIAEoCRIXCg92YWx1ZV90eXBlX2pzb24YBCABKAkSGwoTc3RvcmFnZV9wb2xpY3lfanNvbhgFIAEoCRIaChJkZWZhdWx0X3ZhbHVlX2pzb24YBiABKAkSHAoUb3duZXJfY29uZm9ybWFuY2VfaWQYByABKAkiUAoSRW5kcG9pbnRDb25zdHJhaW50EhEKB2F0b21faWQYASABKAlIABIfChVpbnRlcmZhY2VfcmV2aXNpb25faWQYAiABKAlIAEIGCgRraW5kIvsBCgxFZGdlRW5kcG9pbnQSFQoNcHJvamVjdGlvbl9pZBgBIAEoCRIUCgxkaXNwbGF5X25hbWUYAiABKAkSLgoKY29uc3RyYWludBgDIAEoCzIaLmNhbWluby5FbmRwb2ludENvbnN0cmFpbnQSKAoLY2FyZGluYWxpdHkYBCABKA4yEy5jYW1pbm8uQ2FyZGluYWxpdHkSDwoHb3JkZXJlZBgFIAEoCBIRCglvbl9kZWxldGUYBiABKAkSFAoMcmV0YWluX290aGVyGAcgASgIEhAKCGtleV90eXBlGAggASgJEhgKEHB1YmxpY190cmF2ZXJzYWwYCSABKAgipQEKDkVkZ2VBdHRhY2htZW50EhQKDGVkZ2VfdHlwZV9pZBgBIAEoCRIUCgxkaXNwbGF5X25hbWUYAiABKAkSIwoFZmlyc3QYAyABKAsyFC5jYW1pbm8uRWRnZUVuZHBvaW50EiQKBnNlY29uZBgEIAEoCzIULmNhbWluby5FZGdlRW5kcG9pbnQSHAoUb3duZXJfY29uZm9ybWFuY2VfaWQYBSABKAkilQIKD1BlcnNpc3RlbmNlUGxhbhIUCgx3b3Jrc3BhY2VfaWQYASABKAkSHQoVd29ya3NwYWNlX3JldmlzaW9uX2lkGAIgASgJEiUKBWF0b21zGAMgAygLMhYuY2FtaW5vLkF0b21EZWZpbml0aW9uEi0KDGNvbmZvcm1hbmNlcxgEIAMoCzIXLmNhbWluby5BdG9tQ29uZm9ybWFuY2USJwoGc3RhdGVzGAUgAygLMhcuY2FtaW5vLlN0YXRlQXR0YWNobWVudBIlCgVlZGdlcxgGIAMoCzIWLmNhbWluby5FZGdlQXR0YWNobWVudBInCgdxdWVyaWVzGAcgAygLMhYuY2FtaW5vLkluc3RhbGxlZFF1ZXJ5Ip4BCg1RdWVyeUFyZ3VtZW50EhIKCHZhcmlhYmxlGAEgASgJSAASFgoMbGl0ZXJhbF9qc29uGAIgASgJSAASKQoEbGlzdBgDIAEoCzIZLmNhbWluby5RdWVyeUFyZ3VtZW50TGlzdEgAEi0KBm9iamVjdBgEIAEoCzIbLmNhbWluby5RdWVyeUFyZ3VtZW50T2JqZWN0SABCBwoFdmFsdWUiOgoRUXVlcnlBcmd1bWVudExpc3QSJQoGdmFsdWVzGAEgAygLMhUuY2FtaW5vLlF1ZXJ5QXJndW1lbnQilAEKE1F1ZXJ5QXJndW1lbnRPYmplY3QSNwoGZmllbGRzGAEgAygLMicuY2FtaW5vLlF1ZXJ5QXJndW1lbnRPYmplY3QuRmllbGRzRW50cnkaRAoLRmllbGRzRW50cnkSCwoDa2V5GAEgASgJEiQKBXZhbHVlGAIgASgLMhUuY2FtaW5vLlF1ZXJ5QXJndW1lbnQ6AjgBIkcKDlF1ZXJ5Q29uZGl0aW9uEg8KB2luY2x1ZGUYASABKAgSJAoFdmFsdWUYAiABKAsyFS5jYW1pbm8uUXVlcnlBcmd1bWVudCLUAwoOUXVlcnlTZWxlY3Rpb24SDAoEbmFtZRgBIAEoCRILCgNrZXkYAiABKAkSHQoVaW50ZXJmYWNlX3JldmlzaW9uX2lkGAMgASgJEhEKCW1lbWJlcl9pZBgEIAEoCRIkChx0YXJnZXRfaW50ZXJmYWNlX3JldmlzaW9uX2lkGAUgASgJEioKCmNvbmRpdGlvbnMYBiADKAsyFi5jYW1pbm8uUXVlcnlDb25kaXRpb24SOAoJYXJndW1lbnRzGAcgAygLMiUuY2FtaW5vLlF1ZXJ5U2VsZWN0aW9uLkFyZ3VtZW50c0VudHJ5EikKCXNlbGVjdGlvbhgIIAMoCzIWLmNhbWluby5RdWVyeVNlbGVjdGlvbhIvCgpyZWxhdGlvbmFsGAkgASgLMhsuY2FtaW5vLlF1ZXJ5UmVsYXRpb25hbFBsYW4SKQoJcHJlZGljYXRlGAogASgLMhYuY2FtaW5vLlF1ZXJ5UHJlZGljYXRlEhkKEWxpdmVfc2VsZWN0aW9uX2lkGAsgASgJGkcKDkFyZ3VtZW50c0VudHJ5EgsKA2tleRgBIAEoCRIkCgV2YWx1ZRgCIAEoCzIVLmNhbWluby5RdWVyeUFyZ3VtZW50OgI4ASJqCg1RdWVyeVBhdGhTdGVwEhAKBnNvdXJjZRgBIAEoCEgAEhAKBnRhcmdldBgCIAEoCEgAEi0KCHJlbGF0aW9uGAMgASgLMhkuY2FtaW5vLlF1ZXJ5UmVsYXRpb25QYXRoSABCBgoEc3RlcCJ9ChFRdWVyeVJlbGF0aW9uUGF0aBIdChVpbnRlcmZhY2VfcmV2aXNpb25faWQYASABKAkSEQoJbWVtYmVyX2lkGAIgASgJEiQKHHRhcmdldF9pbnRlcmZhY2VfcmV2aXNpb25faWQYAyABKAkSEAoIb3B0aW9uYWwYBCABKAgiRQoRUXVlcnlGaWVsZE9wZXJhbmQSHQoVaW50ZXJmYWNlX3JldmlzaW9uX2lkGAEgASgJEhEKCW1lbWJlcl9pZBgCIAEoCSK2AQoPUXVlcnlFeHByZXNzaW9uEiMKBHBhdGgYASADKAsyFS5jYW1pbm8uUXVlcnlQYXRoU3RlcBIqCgVmaWVsZBgCIAEoCzIZLmNhbWluby5RdWVyeUZpZWxkT3BlcmFuZEgAEg0KA3JlZhgDIAEoCUgAEg8KBWVudHJ5GAQgASgISAASEQoHbWFwX2tleRgFIAEoCEgAEhcKD3ZhbHVlX3R5cGVfanNvbhgGIAEoCUIGCgRsZWFmIoUBCg5RdWVyeVJlZHVjdGlvbhIwCghvcGVyYXRvchgBIAEoDjIeLmNhbWluby5RdWVyeUFnZ3JlZ2F0ZU9wZXJhdG9yEigKB29wZXJhbmQYAiABKAsyFy5jYW1pbm8uUXVlcnlFeHByZXNzaW9uEhcKD3ZhbHVlX3R5cGVfanNvbhgDIAEoCSJ1CgxRdWVyeU9wZXJhbmQSLQoKZXhwcmVzc2lvbhgBIAEoCzIXLmNhbWluby5RdWVyeUV4cHJlc3Npb25IABIrCglyZWR1Y3Rpb24YAiABKAsyFi5jYW1pbm8uUXVlcnlSZWR1Y3Rpb25IAEIJCgdvcGVyYW5kIpEBCg9RdWVyeUNvbXBhcmlzb24SJQoHb3BlcmFuZBgBIAEoCzIULmNhbWluby5RdWVyeU9wZXJhbmQSMQoIb3BlcmF0b3IYAiABKA4yHy5jYW1pbm8uUXVlcnlDb21wYXJpc29uT3BlcmF0b3ISJAoFdmFsdWUYAyABKAsyFS5jYW1pbm8uUXVlcnlBcmd1bWVudCI+ChJRdWVyeVByZWRpY2F0ZUxpc3QSKAoIY2hpbGRyZW4YASADKAsyFi5jYW1pbm8uUXVlcnlQcmVkaWNhdGUi9QEKFlF1ZXJ5UmVsYXRpb25QcmVkaWNhdGUSIwoEcGF0aBgBIAMoCzIVLmNhbWluby5RdWVyeVBhdGhTdGVwEisKCHJlbGF0aW9uGAIgASgLMhkuY2FtaW5vLlF1ZXJ5UmVsYXRpb25QYXRoEjgKCG9wZXJhdG9yGAMgASgOMiYuY2FtaW5vLlF1ZXJ5UmVsYXRpb25QcmVkaWNhdGVPcGVyYXRvchIpCglwcmVkaWNhdGUYBCABKAsyFi5jYW1pbm8uUXVlcnlQcmVkaWNhdGUSJAoFdmFsdWUYBSABKAsyFS5jYW1pbm8uUXVlcnlBcmd1bWVudCK6AQoXUXVlcnlSZWR1Y3Rpb25QcmVkaWNhdGUSIwoEcGF0aBgBIAMoCzIVLmNhbWluby5RdWVyeVBhdGhTdGVwEisKCHJlbGF0aW9uGAIgASgLMhkuY2FtaW5vLlF1ZXJ5UmVsYXRpb25QYXRoEiUKBXdoZXJlGAMgASgLMhYuY2FtaW5vLlF1ZXJ5UHJlZGljYXRlEiYKBmhhdmluZxgEIAEoCzIWLmNhbWluby5RdWVyeVByZWRpY2F0ZSKsAgoOUXVlcnlQcmVkaWNhdGUSKQoDYW5kGAEgASgLMhouY2FtaW5vLlF1ZXJ5UHJlZGljYXRlTGlzdEgAEigKAm9yGAIgASgLMhouY2FtaW5vLlF1ZXJ5UHJlZGljYXRlTGlzdEgAEiUKA25vdBgDIAEoCzIWLmNhbWluby5RdWVyeVByZWRpY2F0ZUgAEioKB2NvbXBhcmUYBCABKAsyFy5jYW1pbm8uUXVlcnlDb21wYXJpc29uSAASMgoIcmVsYXRpb24YBSABKAsyHi5jYW1pbm8uUXVlcnlSZWxhdGlvblByZWRpY2F0ZUgAEjEKBnJlZHVjZRgGIAEoCzIfLmNhbWluby5RdWVyeVJlZHVjdGlvblByZWRpY2F0ZUgAQgsKCXByZWRpY2F0ZSJhCghRdWVyeVJvdxIoCgZvYmplY3QYASABKAsyFi5jYW1pbm8uUXVlcnlPYmplY3RSb3dIABIkCgRwYWlyGAIgASgLMhQuY2FtaW5vLlF1ZXJ5UGFpclJvd0gAQgUKA3JvdyJVCg5RdWVyeU9iamVjdFJvdxIdChVpbnRlcmZhY2VfcmV2aXNpb25faWQYASABKAkSEgoKbWVtYmVyc2hpcBgCIAEoCBIQCghrZXlfdHlwZRgDIAEoCSJSCgxRdWVyeVBhaXJSb3cSIAoGc291cmNlGAEgASgLMhAuY2FtaW5vLlF1ZXJ5Um93EiAKBnRhcmdldBgCIAEoCzIQLmNhbWluby5RdWVyeVJvdyJFCghRdWVyeUtleRIMCgRwYXRoGAEgAygJEisKCmV4cHJlc3Npb24YAiABKAsyFy5jYW1pbm8uUXVlcnlFeHByZXNzaW9uIi8KDVF1ZXJ5RGlzdGluY3QSHgoEa2V5cxgBIAMoCzIQLmNhbWluby5RdWVyeUtleSJiCg5RdWVyeUV4cGFuc2lvbhIjCgRwYXRoGAEgAygLMhUuY2FtaW5vLlF1ZXJ5UGF0aFN0ZXASKwoIcmVsYXRpb24YAiABKAsyGS5jYW1pbm8uUXVlcnlSZWxhdGlvblBhdGgimAIKFFF1ZXJ5UmVsYXRpb25hbFN0YWdlEgoKAmlkGAEgASgNEhIKBWlucHV0GAIgASgNSAGIAQESHQoDcm93GAMgASgLMhAuY2FtaW5vLlF1ZXJ5Um93EisKBnNvdXJjZRgEIAEoCzIZLmNhbWluby5RdWVyeVJlbGF0aW9uUGF0aEgAEigKBmZpbHRlchgFIAEoCzIWLmNhbWluby5RdWVyeVByZWRpY2F0ZUgAEigKBmV4cGFuZBgGIAEoCzIWLmNhbWluby5RdWVyeUV4cGFuc2lvbkgAEikKCGRpc3RpbmN0GAcgASgLMhUuY2FtaW5vLlF1ZXJ5RGlzdGluY3RIAEILCglvcGVyYXRpb25CCAoGX2lucHV0IlMKGFF1ZXJ5UmVkdWN0aW9uUHJvamVjdGlvbhIMCgRwYXRoGAEgAygJEikKCXJlZHVjdGlvbhgCIAEoCzIWLmNhbWluby5RdWVyeVJlZHVjdGlvbiJQChNRdWVyeUFnZ3JlZ2F0ZU9yZGVyEiUKB29wZXJhbmQYASABKAsyFC5jYW1pbm8uUXVlcnlPcGVyYW5kEhIKCmRlc2NlbmRpbmcYAiABKAgi0gMKF1F1ZXJ5UmVsYXRpb25hbFRlcm1pbmFsEg0KBXN0YWdlGAEgASgNEgwKBHBhdGgYAiADKAkSDgoGZ3JvdXBzGAMgASgIEh4KBGtleXMYBCADKAsyEC5jYW1pbm8uUXVlcnlLZXkSNAoKcmVkdWN0aW9ucxgFIAMoCzIgLmNhbWluby5RdWVyeVJlZHVjdGlvblByb2plY3Rpb24SJQoFd2hlcmUYBiABKAsyFi5jYW1pbm8uUXVlcnlQcmVkaWNhdGUSJgoGaGF2aW5nGAcgASgLMhYuY2FtaW5vLlF1ZXJ5UHJlZGljYXRlEioKBW9yZGVyGAggAygLMhsuY2FtaW5vLlF1ZXJ5QWdncmVnYXRlT3JkZXISJAoFZmlyc3QYCSABKAsyFS5jYW1pbm8uUXVlcnlBcmd1bWVudBIiCgNhbGwYCiABKAsyFS5jYW1pbm8uUXVlcnlBcmd1bWVudBIkCgVhZnRlchgLIAEoCzIVLmNhbWluby5RdWVyeUFyZ3VtZW50EikKCXNlbGVjdGlvbhgMIAMoCzIWLmNhbWluby5RdWVyeVNlbGVjdGlvbhIQCghyZXNpZHVhbBgNIAEoCBIMCgRyb3dzGA4gASgIIncKE1F1ZXJ5UmVsYXRpb25hbFBsYW4SLAoGc3RhZ2VzGAEgAygLMhwuY2FtaW5vLlF1ZXJ5UmVsYXRpb25hbFN0YWdlEjIKCXRlcm1pbmFscxgCIAMoCzIfLmNhbWluby5RdWVyeVJlbGF0aW9uYWxUZXJtaW5hbCLpAgoQUXVlcnlSZWFkQmluZGluZxIPCgdhdG9tX2lkGAEgASgJEh0KFWludGVyZmFjZV9yZXZpc2lvbl9pZBgCIAEoCRIRCgltZW1iZXJfaWQYAyABKAkSGwoTZ2V0dGVyX29wZXJhdGlvbl9pZBgEIAEoCRIPCgdzbG90X2lkGAUgASgJEhQKDGVkZ2VfdHlwZV9pZBgGIAEoCRIVCg1wcm9qZWN0aW9uX2lkGAcgASgJEgsKA3JwYxgIIAEoCBIgChh3YXRjaF9zdGFydF9vcGVyYXRpb25faWQYCSABKAkSHwoXd2F0Y2hfc3RvcF9vcGVyYXRpb25faWQYCiABKAkSEgoKZmllbGRfbmFtZRgLIAEoCRIXCg92YWx1ZV90eXBlX2pzb24YDCABKAkSKAoLY2FyZGluYWxpdHkYDSABKA4yEy5jYW1pbm8uQ2FyZGluYWxpdHkSEAoIa2V5X3R5cGUYDiABKAkikgEKDFF1ZXJ5QnVkZ2V0cxIMCgRyb3dzGAEgASgNEg0KBWRlcHRoGAIgASgNEhQKDHJlc3VsdF9ieXRlcxgDIAEoDRISCgpjYW5kaWRhdGVzGAQgASgNEhEKCXJwY19jYWxscxgFIAEoDRITCgtjb25jdXJyZW5jeRgGIAEoDRITCgtkZWFkbGluZV9tcxgHIAEoDSKNBAoOSW5zdGFsbGVkUXVlcnkSCgoCaWQYASABKAkSGQoRZGVmaW5pdGlvbl9kaWdlc3QYAiABKAkSFgoOYmluZGluZ19kaWdlc3QYAyABKAkSIgoacm9vdF9pbnRlcmZhY2VfcmV2aXNpb25faWQYBCABKAkSKQoJc2VsZWN0aW9uGAUgAygLMhYuY2FtaW5vLlF1ZXJ5U2VsZWN0aW9uEioKCGJpbmRpbmdzGAYgAygLMhguY2FtaW5vLlF1ZXJ5UmVhZEJpbmRpbmcSJQoHYnVkZ2V0cxgHIAEoCzIULmNhbWluby5RdWVyeUJ1ZGdldHMSGwoTdmFyaWFibGVzX3R5cGVfanNvbhgIIAEoCRIYChBvdXRwdXRfdHlwZV9qc29uGAkgASgJEkcKEXZhcmlhYmxlX2RlZmF1bHRzGAogAygLMiwuY2FtaW5vLkluc3RhbGxlZFF1ZXJ5LlZhcmlhYmxlRGVmYXVsdHNFbnRyeRINCgV3YXRjaBgLIAEoCBIbChNwb2xsaW5nX2ludGVydmFsX21zGAwgASgNEh4KFnJwY19wcmVkaWNhdGVfb3Jfb3JkZXIYDSABKAgaTgoVVmFyaWFibGVEZWZhdWx0c0VudHJ5EgsKA2tleRgBIAEoCRIkCgV2YWx1ZRgCIAEoCzIVLmNhbWluby5RdWVyeUFyZ3VtZW50OgI4ASpoCgtDYXJkaW5hbGl0eRIbChdDQVJESU5BTElUWV9VTlNQRUNJRklFRBAAEhAKDE9QVElPTkFMX09ORRABEg8KC0VYQUNUTFlfT05FEAISCAoETUFOWRADEg8KC01BTllfVU5JUVVFEAQqmgIKFlF1ZXJ5QWdncmVnYXRlT3BlcmF0b3ISKAokUVVFUllfQUdHUkVHQVRFX09QRVJBVE9SX1VOU1BFQ0lGSUVEEAASIgoeUVVFUllfQUdHUkVHQVRFX09QRVJBVE9SX0NPVU5UEAESKgomUVVFUllfQUdHUkVHQVRFX09QRVJBVE9SX0NPVU5UX1BSRVNFTlQQAhIgChxRVUVSWV9BR0dSRUdBVEVfT1BFUkFUT1JfU1VNEAMSIAocUVVFUllfQUdHUkVHQVRFX09QRVJBVE9SX0FWRxAEEiAKHFFVRVJZX0FHR1JFR0FURV9PUEVSQVRPUl9NSU4QBRIgChxRVUVSWV9BR0dSRUdBVEVfT1BFUkFUT1JfTUFYEAYquQIKF1F1ZXJ5Q29tcGFyaXNvbk9wZXJhdG9yEikKJVFVRVJZX0NPTVBBUklTT05fT1BFUkFUT1JfVU5TUEVDSUZJRUQQABIgChxRVUVSWV9DT01QQVJJU09OX09QRVJBVE9SX0VREAESIAocUVVFUllfQ09NUEFSSVNPTl9PUEVSQVRPUl9JThACEiUKIVFVRVJZX0NPTVBBUklTT05fT1BFUkFUT1JfSVNfTlVMTBADEiAKHFFVRVJZX0NPTVBBUklTT05fT1BFUkFUT1JfTFQQBBIhCh1RVUVSWV9DT01QQVJJU09OX09QRVJBVE9SX0xURRAFEiAKHFFVRVJZX0NPTVBBUklTT05fT1BFUkFUT1JfR1QQBhIhCh1RVUVSWV9DT01QQVJJU09OX09QRVJBVE9SX0dURRAHKoQCCh5RdWVyeVJlbGF0aW9uUHJlZGljYXRlT3BlcmF0b3ISMQotUVVFUllfUkVMQVRJT05fUFJFRElDQVRFX09QRVJBVE9SX1VOU1BFQ0lGSUVEEAASKgomUVVFUllfUkVMQVRJT05fUFJFRElDQVRFX09QRVJBVE9SX1NPTUUQARIqCiZRVUVSWV9SRUxBVElPTl9QUkVESUNBVEVfT1BFUkFUT1JfTk9ORRACEigKJFFVRVJZX1JFTEFUSU9OX1BSRURJQ0FURV9PUEVSQVRPUl9JUxADEi0KKVFVRVJZX1JFTEFUSU9OX1BSRURJQ0FURV9PUEVSQVRPUl9JU19OVUxMEARiBnByb3RvMw"); + fileDesc("ChNjYW1pbm8vc2NoZW1hLnByb3RvEgZjYW1pbm8iNwoOQXRvbURlZmluaXRpb24SDwoHYXRvbV9pZBgBIAEoCRIUCgxkaXNwbGF5X25hbWUYAiABKAkiWQoPQXRvbUNvbmZvcm1hbmNlEg8KB2F0b21faWQYASABKAkSHQoVaW50ZXJmYWNlX3JldmlzaW9uX2lkGAIgASgJEhYKDmNvbmZvcm1hbmNlX2lkGAMgASgJIsIBCg9TdGF0ZUF0dGFjaG1lbnQSDwoHc2xvdF9pZBgBIAEoCRIYChBhdHRhY2hlZF9hdG9tX2lkGAIgASgJEhQKDGRpc3BsYXlfbmFtZRgDIAEoCRIXCg92YWx1ZV90eXBlX2pzb24YBCABKAkSGwoTc3RvcmFnZV9wb2xpY3lfanNvbhgFIAEoCRIaChJkZWZhdWx0X3ZhbHVlX2pzb24YBiABKAkSHAoUb3duZXJfY29uZm9ybWFuY2VfaWQYByABKAkiUAoSRW5kcG9pbnRDb25zdHJhaW50EhEKB2F0b21faWQYASABKAlIABIfChVpbnRlcmZhY2VfcmV2aXNpb25faWQYAiABKAlIAEIGCgRraW5kIvsBCgxFZGdlRW5kcG9pbnQSFQoNcHJvamVjdGlvbl9pZBgBIAEoCRIUCgxkaXNwbGF5X25hbWUYAiABKAkSLgoKY29uc3RyYWludBgDIAEoCzIaLmNhbWluby5FbmRwb2ludENvbnN0cmFpbnQSKAoLY2FyZGluYWxpdHkYBCABKA4yEy5jYW1pbm8uQ2FyZGluYWxpdHkSDwoHb3JkZXJlZBgFIAEoCBIRCglvbl9kZWxldGUYBiABKAkSFAoMcmV0YWluX290aGVyGAcgASgIEhAKCGtleV90eXBlGAggASgJEhgKEHB1YmxpY190cmF2ZXJzYWwYCSABKAgipQEKDkVkZ2VBdHRhY2htZW50EhQKDGVkZ2VfdHlwZV9pZBgBIAEoCRIUCgxkaXNwbGF5X25hbWUYAiABKAkSIwoFZmlyc3QYAyABKAsyFC5jYW1pbm8uRWRnZUVuZHBvaW50EiQKBnNlY29uZBgEIAEoCzIULmNhbWluby5FZGdlRW5kcG9pbnQSHAoUb3duZXJfY29uZm9ybWFuY2VfaWQYBSABKAkilQIKD1BlcnNpc3RlbmNlUGxhbhIUCgx3b3Jrc3BhY2VfaWQYASABKAkSHQoVd29ya3NwYWNlX3JldmlzaW9uX2lkGAIgASgJEiUKBWF0b21zGAMgAygLMhYuY2FtaW5vLkF0b21EZWZpbml0aW9uEi0KDGNvbmZvcm1hbmNlcxgEIAMoCzIXLmNhbWluby5BdG9tQ29uZm9ybWFuY2USJwoGc3RhdGVzGAUgAygLMhcuY2FtaW5vLlN0YXRlQXR0YWNobWVudBIlCgVlZGdlcxgGIAMoCzIWLmNhbWluby5FZGdlQXR0YWNobWVudBInCgdxdWVyaWVzGAcgAygLMhYuY2FtaW5vLkluc3RhbGxlZFF1ZXJ5Ip4BCg1RdWVyeUFyZ3VtZW50EhIKCHZhcmlhYmxlGAEgASgJSAASFgoMbGl0ZXJhbF9qc29uGAIgASgJSAASKQoEbGlzdBgDIAEoCzIZLmNhbWluby5RdWVyeUFyZ3VtZW50TGlzdEgAEi0KBm9iamVjdBgEIAEoCzIbLmNhbWluby5RdWVyeUFyZ3VtZW50T2JqZWN0SABCBwoFdmFsdWUiOgoRUXVlcnlBcmd1bWVudExpc3QSJQoGdmFsdWVzGAEgAygLMhUuY2FtaW5vLlF1ZXJ5QXJndW1lbnQilAEKE1F1ZXJ5QXJndW1lbnRPYmplY3QSNwoGZmllbGRzGAEgAygLMicuY2FtaW5vLlF1ZXJ5QXJndW1lbnRPYmplY3QuRmllbGRzRW50cnkaRAoLRmllbGRzRW50cnkSCwoDa2V5GAEgASgJEiQKBXZhbHVlGAIgASgLMhUuY2FtaW5vLlF1ZXJ5QXJndW1lbnQ6AjgBIkcKDlF1ZXJ5Q29uZGl0aW9uEg8KB2luY2x1ZGUYASABKAgSJAoFdmFsdWUYAiABKAsyFS5jYW1pbm8uUXVlcnlBcmd1bWVudCLUAwoOUXVlcnlTZWxlY3Rpb24SDAoEbmFtZRgBIAEoCRILCgNrZXkYAiABKAkSHQoVaW50ZXJmYWNlX3JldmlzaW9uX2lkGAMgASgJEhEKCW1lbWJlcl9pZBgEIAEoCRIkChx0YXJnZXRfaW50ZXJmYWNlX3JldmlzaW9uX2lkGAUgASgJEioKCmNvbmRpdGlvbnMYBiADKAsyFi5jYW1pbm8uUXVlcnlDb25kaXRpb24SOAoJYXJndW1lbnRzGAcgAygLMiUuY2FtaW5vLlF1ZXJ5U2VsZWN0aW9uLkFyZ3VtZW50c0VudHJ5EikKCXNlbGVjdGlvbhgIIAMoCzIWLmNhbWluby5RdWVyeVNlbGVjdGlvbhIvCgpyZWxhdGlvbmFsGAkgASgLMhsuY2FtaW5vLlF1ZXJ5UmVsYXRpb25hbFBsYW4SKQoJcHJlZGljYXRlGAogASgLMhYuY2FtaW5vLlF1ZXJ5UHJlZGljYXRlEhkKEWxpdmVfc2VsZWN0aW9uX2lkGAsgASgJGkcKDkFyZ3VtZW50c0VudHJ5EgsKA2tleRgBIAEoCRIkCgV2YWx1ZRgCIAEoCzIVLmNhbWluby5RdWVyeUFyZ3VtZW50OgI4ASJqCg1RdWVyeVBhdGhTdGVwEhAKBnNvdXJjZRgBIAEoCEgAEhAKBnRhcmdldBgCIAEoCEgAEi0KCHJlbGF0aW9uGAMgASgLMhkuY2FtaW5vLlF1ZXJ5UmVsYXRpb25QYXRoSABCBgoEc3RlcCJ9ChFRdWVyeVJlbGF0aW9uUGF0aBIdChVpbnRlcmZhY2VfcmV2aXNpb25faWQYASABKAkSEQoJbWVtYmVyX2lkGAIgASgJEiQKHHRhcmdldF9pbnRlcmZhY2VfcmV2aXNpb25faWQYAyABKAkSEAoIb3B0aW9uYWwYBCABKAgiWQoRUXVlcnlGaWVsZE9wZXJhbmQSHQoVaW50ZXJmYWNlX3JldmlzaW9uX2lkGAEgASgJEhEKCW1lbWJlcl9pZBgCIAEoCRISCgp2YWx1ZV9wYXRoGAMgAygJIrYBCg9RdWVyeUV4cHJlc3Npb24SIwoEcGF0aBgBIAMoCzIVLmNhbWluby5RdWVyeVBhdGhTdGVwEioKBWZpZWxkGAIgASgLMhkuY2FtaW5vLlF1ZXJ5RmllbGRPcGVyYW5kSAASDQoDcmVmGAMgASgJSAASDwoFZW50cnkYBCABKAhIABIRCgdtYXBfa2V5GAUgASgISAASFwoPdmFsdWVfdHlwZV9qc29uGAYgASgJQgYKBGxlYWYihQEKDlF1ZXJ5UmVkdWN0aW9uEjAKCG9wZXJhdG9yGAEgASgOMh4uY2FtaW5vLlF1ZXJ5QWdncmVnYXRlT3BlcmF0b3ISKAoHb3BlcmFuZBgCIAEoCzIXLmNhbWluby5RdWVyeUV4cHJlc3Npb24SFwoPdmFsdWVfdHlwZV9qc29uGAMgASgJInUKDFF1ZXJ5T3BlcmFuZBItCgpleHByZXNzaW9uGAEgASgLMhcuY2FtaW5vLlF1ZXJ5RXhwcmVzc2lvbkgAEisKCXJlZHVjdGlvbhgCIAEoCzIWLmNhbWluby5RdWVyeVJlZHVjdGlvbkgAQgkKB29wZXJhbmQikQEKD1F1ZXJ5Q29tcGFyaXNvbhIlCgdvcGVyYW5kGAEgASgLMhQuY2FtaW5vLlF1ZXJ5T3BlcmFuZBIxCghvcGVyYXRvchgCIAEoDjIfLmNhbWluby5RdWVyeUNvbXBhcmlzb25PcGVyYXRvchIkCgV2YWx1ZRgDIAEoCzIVLmNhbWluby5RdWVyeUFyZ3VtZW50Ij4KElF1ZXJ5UHJlZGljYXRlTGlzdBIoCghjaGlsZHJlbhgBIAMoCzIWLmNhbWluby5RdWVyeVByZWRpY2F0ZSL1AQoWUXVlcnlSZWxhdGlvblByZWRpY2F0ZRIjCgRwYXRoGAEgAygLMhUuY2FtaW5vLlF1ZXJ5UGF0aFN0ZXASKwoIcmVsYXRpb24YAiABKAsyGS5jYW1pbm8uUXVlcnlSZWxhdGlvblBhdGgSOAoIb3BlcmF0b3IYAyABKA4yJi5jYW1pbm8uUXVlcnlSZWxhdGlvblByZWRpY2F0ZU9wZXJhdG9yEikKCXByZWRpY2F0ZRgEIAEoCzIWLmNhbWluby5RdWVyeVByZWRpY2F0ZRIkCgV2YWx1ZRgFIAEoCzIVLmNhbWluby5RdWVyeUFyZ3VtZW50IroBChdRdWVyeVJlZHVjdGlvblByZWRpY2F0ZRIjCgRwYXRoGAEgAygLMhUuY2FtaW5vLlF1ZXJ5UGF0aFN0ZXASKwoIcmVsYXRpb24YAiABKAsyGS5jYW1pbm8uUXVlcnlSZWxhdGlvblBhdGgSJQoFd2hlcmUYAyABKAsyFi5jYW1pbm8uUXVlcnlQcmVkaWNhdGUSJgoGaGF2aW5nGAQgASgLMhYuY2FtaW5vLlF1ZXJ5UHJlZGljYXRlImMKF1F1ZXJ5SW50ZXJmYWNlUHJlZGljYXRlEh0KFWludGVyZmFjZV9yZXZpc2lvbl9pZBgBIAEoCRIpCglwcmVkaWNhdGUYAiABKAsyFi5jYW1pbm8uUXVlcnlQcmVkaWNhdGUi2wIKDlF1ZXJ5UHJlZGljYXRlEikKA2FuZBgBIAEoCzIaLmNhbWluby5RdWVyeVByZWRpY2F0ZUxpc3RIABIoCgJvchgCIAEoCzIaLmNhbWluby5RdWVyeVByZWRpY2F0ZUxpc3RIABIlCgNub3QYAyABKAsyFi5jYW1pbm8uUXVlcnlQcmVkaWNhdGVIABIqCgdjb21wYXJlGAQgASgLMhcuY2FtaW5vLlF1ZXJ5Q29tcGFyaXNvbkgAEjIKCHJlbGF0aW9uGAUgASgLMh4uY2FtaW5vLlF1ZXJ5UmVsYXRpb25QcmVkaWNhdGVIABIxCgZyZWR1Y2UYBiABKAsyHy5jYW1pbm8uUXVlcnlSZWR1Y3Rpb25QcmVkaWNhdGVIABItCgJhcxgHIAEoCzIfLmNhbWluby5RdWVyeUludGVyZmFjZVByZWRpY2F0ZUgAQgsKCXByZWRpY2F0ZSJhCghRdWVyeVJvdxIoCgZvYmplY3QYASABKAsyFi5jYW1pbm8uUXVlcnlPYmplY3RSb3dIABIkCgRwYWlyGAIgASgLMhQuY2FtaW5vLlF1ZXJ5UGFpclJvd0gAQgUKA3JvdyJVCg5RdWVyeU9iamVjdFJvdxIdChVpbnRlcmZhY2VfcmV2aXNpb25faWQYASABKAkSEgoKbWVtYmVyc2hpcBgCIAEoCBIQCghrZXlfdHlwZRgDIAEoCSJSCgxRdWVyeVBhaXJSb3cSIAoGc291cmNlGAEgASgLMhAuY2FtaW5vLlF1ZXJ5Um93EiAKBnRhcmdldBgCIAEoCzIQLmNhbWluby5RdWVyeVJvdyJFCghRdWVyeUtleRIMCgRwYXRoGAEgAygJEisKCmV4cHJlc3Npb24YAiABKAsyFy5jYW1pbm8uUXVlcnlFeHByZXNzaW9uIi8KDVF1ZXJ5RGlzdGluY3QSHgoEa2V5cxgBIAMoCzIQLmNhbWluby5RdWVyeUtleSJiCg5RdWVyeUV4cGFuc2lvbhIjCgRwYXRoGAEgAygLMhUuY2FtaW5vLlF1ZXJ5UGF0aFN0ZXASKwoIcmVsYXRpb24YAiABKAsyGS5jYW1pbm8uUXVlcnlSZWxhdGlvblBhdGgimAIKFFF1ZXJ5UmVsYXRpb25hbFN0YWdlEgoKAmlkGAEgASgNEhIKBWlucHV0GAIgASgNSAGIAQESHQoDcm93GAMgASgLMhAuY2FtaW5vLlF1ZXJ5Um93EisKBnNvdXJjZRgEIAEoCzIZLmNhbWluby5RdWVyeVJlbGF0aW9uUGF0aEgAEigKBmZpbHRlchgFIAEoCzIWLmNhbWluby5RdWVyeVByZWRpY2F0ZUgAEigKBmV4cGFuZBgGIAEoCzIWLmNhbWluby5RdWVyeUV4cGFuc2lvbkgAEikKCGRpc3RpbmN0GAcgASgLMhUuY2FtaW5vLlF1ZXJ5RGlzdGluY3RIAEILCglvcGVyYXRpb25CCAoGX2lucHV0IlMKGFF1ZXJ5UmVkdWN0aW9uUHJvamVjdGlvbhIMCgRwYXRoGAEgAygJEikKCXJlZHVjdGlvbhgCIAEoCzIWLmNhbWluby5RdWVyeVJlZHVjdGlvbiJQChNRdWVyeUFnZ3JlZ2F0ZU9yZGVyEiUKB29wZXJhbmQYASABKAsyFC5jYW1pbm8uUXVlcnlPcGVyYW5kEhIKCmRlc2NlbmRpbmcYAiABKAgi0gMKF1F1ZXJ5UmVsYXRpb25hbFRlcm1pbmFsEg0KBXN0YWdlGAEgASgNEgwKBHBhdGgYAiADKAkSDgoGZ3JvdXBzGAMgASgIEh4KBGtleXMYBCADKAsyEC5jYW1pbm8uUXVlcnlLZXkSNAoKcmVkdWN0aW9ucxgFIAMoCzIgLmNhbWluby5RdWVyeVJlZHVjdGlvblByb2plY3Rpb24SJQoFd2hlcmUYBiABKAsyFi5jYW1pbm8uUXVlcnlQcmVkaWNhdGUSJgoGaGF2aW5nGAcgASgLMhYuY2FtaW5vLlF1ZXJ5UHJlZGljYXRlEioKBW9yZGVyGAggAygLMhsuY2FtaW5vLlF1ZXJ5QWdncmVnYXRlT3JkZXISJAoFZmlyc3QYCSABKAsyFS5jYW1pbm8uUXVlcnlBcmd1bWVudBIiCgNhbGwYCiABKAsyFS5jYW1pbm8uUXVlcnlBcmd1bWVudBIkCgVhZnRlchgLIAEoCzIVLmNhbWluby5RdWVyeUFyZ3VtZW50EikKCXNlbGVjdGlvbhgMIAMoCzIWLmNhbWluby5RdWVyeVNlbGVjdGlvbhIQCghyZXNpZHVhbBgNIAEoCBIMCgRyb3dzGA4gASgIIncKE1F1ZXJ5UmVsYXRpb25hbFBsYW4SLAoGc3RhZ2VzGAEgAygLMhwuY2FtaW5vLlF1ZXJ5UmVsYXRpb25hbFN0YWdlEjIKCXRlcm1pbmFscxgCIAMoCzIfLmNhbWluby5RdWVyeVJlbGF0aW9uYWxUZXJtaW5hbCLpAgoQUXVlcnlSZWFkQmluZGluZxIPCgdhdG9tX2lkGAEgASgJEh0KFWludGVyZmFjZV9yZXZpc2lvbl9pZBgCIAEoCRIRCgltZW1iZXJfaWQYAyABKAkSGwoTZ2V0dGVyX29wZXJhdGlvbl9pZBgEIAEoCRIPCgdzbG90X2lkGAUgASgJEhQKDGVkZ2VfdHlwZV9pZBgGIAEoCRIVCg1wcm9qZWN0aW9uX2lkGAcgASgJEgsKA3JwYxgIIAEoCBIgChh3YXRjaF9zdGFydF9vcGVyYXRpb25faWQYCSABKAkSHwoXd2F0Y2hfc3RvcF9vcGVyYXRpb25faWQYCiABKAkSEgoKZmllbGRfbmFtZRgLIAEoCRIXCg92YWx1ZV90eXBlX2pzb24YDCABKAkSKAoLY2FyZGluYWxpdHkYDSABKA4yEy5jYW1pbm8uQ2FyZGluYWxpdHkSEAoIa2V5X3R5cGUYDiABKAkirwEKElF1ZXJ5RmllbGRDb250cmFjdBIdChVpbnRlcmZhY2VfcmV2aXNpb25faWQYASABKAkSEQoJbWVtYmVyX2lkGAIgASgJEhIKCmZpZWxkX25hbWUYAyABKAkSFwoPdmFsdWVfdHlwZV9qc29uGAQgASgJEigKC2NhcmRpbmFsaXR5GAUgASgOMhMuY2FtaW5vLkNhcmRpbmFsaXR5EhAKCGtleV90eXBlGAYgASgJIpIBCgxRdWVyeUJ1ZGdldHMSDAoEcm93cxgBIAEoDRINCgVkZXB0aBgCIAEoDRIUCgxyZXN1bHRfYnl0ZXMYAyABKA0SEgoKY2FuZGlkYXRlcxgEIAEoDRIRCglycGNfY2FsbHMYBSABKA0SEwoLY29uY3VycmVuY3kYBiABKA0SEwoLZGVhZGxpbmVfbXMYByABKA0iTAoOUXVlcnlQbGFjZW1lbnQSEQoHcmVwbGljYRgBIAEoCEgAEh8KFWxvY2FsX3NvdXJjZV9xdWVyeV9pZBgCIAEoCUgAQgYKBGtpbmQimgUKDkluc3RhbGxlZFF1ZXJ5EgoKAmlkGAEgASgJEhkKEWRlZmluaXRpb25fZGlnZXN0GAIgASgJEhYKDmJpbmRpbmdfZGlnZXN0GAMgASgJEhoKEG9iamVjdF9pbnRlcmZhY2UYBCABKAlIABIdChN3b3Jrc3BhY2VfaW50ZXJmYWNlGA4gASgJSAASKQoJc2VsZWN0aW9uGAUgAygLMhYuY2FtaW5vLlF1ZXJ5U2VsZWN0aW9uEioKCGJpbmRpbmdzGAYgAygLMhguY2FtaW5vLlF1ZXJ5UmVhZEJpbmRpbmcSJQoHYnVkZ2V0cxgHIAEoCzIULmNhbWluby5RdWVyeUJ1ZGdldHMSGwoTdmFyaWFibGVzX3R5cGVfanNvbhgIIAEoCRIYChBvdXRwdXRfdHlwZV9qc29uGAkgASgJEkcKEXZhcmlhYmxlX2RlZmF1bHRzGAogAygLMiwuY2FtaW5vLkluc3RhbGxlZFF1ZXJ5LlZhcmlhYmxlRGVmYXVsdHNFbnRyeRINCgV3YXRjaBgLIAEoCBIbChNwb2xsaW5nX2ludGVydmFsX21zGAwgASgNEh4KFnJwY19wcmVkaWNhdGVfb3Jfb3JkZXIYDSABKAgSFQoNcm9vdF9hdG9tX2lkcxgPIAMoCRIqCgZmaWVsZHMYECADKAsyGi5jYW1pbm8uUXVlcnlGaWVsZENvbnRyYWN0EikKCXBsYWNlbWVudBgRIAEoCzIWLmNhbWluby5RdWVyeVBsYWNlbWVudBpOChVWYXJpYWJsZURlZmF1bHRzRW50cnkSCwoDa2V5GAEgASgJEiQKBXZhbHVlGAIgASgLMhUuY2FtaW5vLlF1ZXJ5QXJndW1lbnQ6AjgBQgYKBHJvb3QqaAoLQ2FyZGluYWxpdHkSGwoXQ0FSRElOQUxJVFlfVU5TUEVDSUZJRUQQABIQCgxPUFRJT05BTF9PTkUQARIPCgtFWEFDVExZX09ORRACEggKBE1BTlkQAxIPCgtNQU5ZX1VOSVFVRRAEKpoCChZRdWVyeUFnZ3JlZ2F0ZU9wZXJhdG9yEigKJFFVRVJZX0FHR1JFR0FURV9PUEVSQVRPUl9VTlNQRUNJRklFRBAAEiIKHlFVRVJZX0FHR1JFR0FURV9PUEVSQVRPUl9DT1VOVBABEioKJlFVRVJZX0FHR1JFR0FURV9PUEVSQVRPUl9DT1VOVF9QUkVTRU5UEAISIAocUVVFUllfQUdHUkVHQVRFX09QRVJBVE9SX1NVTRADEiAKHFFVRVJZX0FHR1JFR0FURV9PUEVSQVRPUl9BVkcQBBIgChxRVUVSWV9BR0dSRUdBVEVfT1BFUkFUT1JfTUlOEAUSIAocUVVFUllfQUdHUkVHQVRFX09QRVJBVE9SX01BWBAGKu0CChdRdWVyeUNvbXBhcmlzb25PcGVyYXRvchIpCiVRVUVSWV9DT01QQVJJU09OX09QRVJBVE9SX1VOU1BFQ0lGSUVEEAASIAocUVVFUllfQ09NUEFSSVNPTl9PUEVSQVRPUl9FURABEiAKHFFVRVJZX0NPTVBBUklTT05fT1BFUkFUT1JfSU4QAhIlCiFRVUVSWV9DT01QQVJJU09OX09QRVJBVE9SX0lTX05VTEwQAxIgChxRVUVSWV9DT01QQVJJU09OX09QRVJBVE9SX0xUEAQSIQodUVVFUllfQ09NUEFSSVNPTl9PUEVSQVRPUl9MVEUQBRIgChxRVUVSWV9DT01QQVJJU09OX09QRVJBVE9SX0dUEAYSIQodUVVFUllfQ09NUEFSSVNPTl9PUEVSQVRPUl9HVEUQBxIyCi5RVUVSWV9DT01QQVJJU09OX09QRVJBVE9SX0NPTlRBSU5TX0lOU0VOU0lUSVZFEAgqhAIKHlF1ZXJ5UmVsYXRpb25QcmVkaWNhdGVPcGVyYXRvchIxCi1RVUVSWV9SRUxBVElPTl9QUkVESUNBVEVfT1BFUkFUT1JfVU5TUEVDSUZJRUQQABIqCiZRVUVSWV9SRUxBVElPTl9QUkVESUNBVEVfT1BFUkFUT1JfU09NRRABEioKJlFVRVJZX1JFTEFUSU9OX1BSRURJQ0FURV9PUEVSQVRPUl9OT05FEAISKAokUVVFUllfUkVMQVRJT05fUFJFRElDQVRFX09QRVJBVE9SX0lTEAMSLQopUVVFUllfUkVMQVRJT05fUFJFRElDQVRFX09QRVJBVE9SX0lTX05VTEwQBGIGcHJvdG8z"); /** * @generated from message camino.AtomDefinition @@ -539,6 +539,11 @@ export type QueryFieldOperand = Message<"camino.QueryFieldOperand"> & { * @generated from field: string member_id = 2; */ memberId: string; + + /** + * @generated from field: repeated string value_path = 3; + */ + valuePath: string[]; }; /** @@ -768,6 +773,28 @@ export type QueryReductionPredicate = Message<"camino.QueryReductionPredicate"> export const QueryReductionPredicateSchema: GenMessage = /*@__PURE__*/ messageDesc(file_camino_schema, 21); +/** + * @generated from message camino.QueryInterfacePredicate + */ +export type QueryInterfacePredicate = Message<"camino.QueryInterfacePredicate"> & { + /** + * @generated from field: string interface_revision_id = 1; + */ + interfaceRevisionId: string; + + /** + * @generated from field: camino.QueryPredicate predicate = 2; + */ + predicate?: QueryPredicate | undefined; +}; + +/** + * Describes the message camino.QueryInterfacePredicate. + * Use `create(QueryInterfacePredicateSchema)` to create a new message. + */ +export const QueryInterfacePredicateSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_camino_schema, 22); + /** * @generated from message camino.QueryPredicate */ @@ -811,6 +838,12 @@ export type QueryPredicate = Message<"camino.QueryPredicate"> & { */ value: QueryReductionPredicate; case: "reduce"; + } | { + /** + * @generated from field: camino.QueryInterfacePredicate as = 7; + */ + value: QueryInterfacePredicate; + case: "as"; } | { case: undefined; value?: undefined }; }; @@ -819,7 +852,7 @@ export type QueryPredicate = Message<"camino.QueryPredicate"> & { * Use `create(QueryPredicateSchema)` to create a new message. */ export const QueryPredicateSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_camino_schema, 22); + messageDesc(file_camino_schema, 23); /** * @generated from message camino.QueryRow @@ -848,7 +881,7 @@ export type QueryRow = Message<"camino.QueryRow"> & { * Use `create(QueryRowSchema)` to create a new message. */ export const QueryRowSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_camino_schema, 23); + messageDesc(file_camino_schema, 24); /** * @generated from message camino.QueryObjectRow @@ -875,7 +908,7 @@ export type QueryObjectRow = Message<"camino.QueryObjectRow"> & { * Use `create(QueryObjectRowSchema)` to create a new message. */ export const QueryObjectRowSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_camino_schema, 24); + messageDesc(file_camino_schema, 25); /** * @generated from message camino.QueryPairRow @@ -897,7 +930,7 @@ export type QueryPairRow = Message<"camino.QueryPairRow"> & { * Use `create(QueryPairRowSchema)` to create a new message. */ export const QueryPairRowSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_camino_schema, 25); + messageDesc(file_camino_schema, 26); /** * @generated from message camino.QueryKey @@ -919,7 +952,7 @@ export type QueryKey = Message<"camino.QueryKey"> & { * Use `create(QueryKeySchema)` to create a new message. */ export const QueryKeySchema: GenMessage = /*@__PURE__*/ - messageDesc(file_camino_schema, 26); + messageDesc(file_camino_schema, 27); /** * @generated from message camino.QueryDistinct @@ -936,7 +969,7 @@ export type QueryDistinct = Message<"camino.QueryDistinct"> & { * Use `create(QueryDistinctSchema)` to create a new message. */ export const QueryDistinctSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_camino_schema, 27); + messageDesc(file_camino_schema, 28); /** * @generated from message camino.QueryExpansion @@ -958,7 +991,7 @@ export type QueryExpansion = Message<"camino.QueryExpansion"> & { * Use `create(QueryExpansionSchema)` to create a new message. */ export const QueryExpansionSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_camino_schema, 28); + messageDesc(file_camino_schema, 29); /** * @generated from message camino.QueryRelationalStage @@ -1014,7 +1047,7 @@ export type QueryRelationalStage = Message<"camino.QueryRelationalStage"> & { * Use `create(QueryRelationalStageSchema)` to create a new message. */ export const QueryRelationalStageSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_camino_schema, 29); + messageDesc(file_camino_schema, 30); /** * @generated from message camino.QueryReductionProjection @@ -1036,7 +1069,7 @@ export type QueryReductionProjection = Message<"camino.QueryReductionProjection" * Use `create(QueryReductionProjectionSchema)` to create a new message. */ export const QueryReductionProjectionSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_camino_schema, 30); + messageDesc(file_camino_schema, 31); /** * @generated from message camino.QueryAggregateOrder @@ -1058,7 +1091,7 @@ export type QueryAggregateOrder = Message<"camino.QueryAggregateOrder"> & { * Use `create(QueryAggregateOrderSchema)` to create a new message. */ export const QueryAggregateOrderSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_camino_schema, 31); + messageDesc(file_camino_schema, 32); /** * @generated from message camino.QueryRelationalTerminal @@ -1142,7 +1175,7 @@ export type QueryRelationalTerminal = Message<"camino.QueryRelationalTerminal"> * Use `create(QueryRelationalTerminalSchema)` to create a new message. */ export const QueryRelationalTerminalSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_camino_schema, 32); + messageDesc(file_camino_schema, 33); /** * @generated from message camino.QueryRelationalPlan @@ -1164,7 +1197,7 @@ export type QueryRelationalPlan = Message<"camino.QueryRelationalPlan"> & { * Use `create(QueryRelationalPlanSchema)` to create a new message. */ export const QueryRelationalPlanSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_camino_schema, 33); + messageDesc(file_camino_schema, 34); /** * @generated from message camino.QueryReadBinding @@ -1246,7 +1279,51 @@ export type QueryReadBinding = Message<"camino.QueryReadBinding"> & { * Use `create(QueryReadBindingSchema)` to create a new message. */ export const QueryReadBindingSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_camino_schema, 34); + messageDesc(file_camino_schema, 35); + +/** + * Checked schema survives even when no atom currently implements an interface. + * + * @generated from message camino.QueryFieldContract + */ +export type QueryFieldContract = Message<"camino.QueryFieldContract"> & { + /** + * @generated from field: string interface_revision_id = 1; + */ + interfaceRevisionId: string; + + /** + * @generated from field: string member_id = 2; + */ + memberId: string; + + /** + * @generated from field: string field_name = 3; + */ + fieldName: string; + + /** + * @generated from field: string value_type_json = 4; + */ + valueTypeJson: string; + + /** + * @generated from field: camino.Cardinality cardinality = 5; + */ + cardinality: Cardinality; + + /** + * @generated from field: string key_type = 6; + */ + keyType: string; +}; + +/** + * Describes the message camino.QueryFieldContract. + * Use `create(QueryFieldContractSchema)` to create a new message. + */ +export const QueryFieldContractSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_camino_schema, 36); /** * @generated from message camino.QueryBudgets @@ -1293,7 +1370,36 @@ export type QueryBudgets = Message<"camino.QueryBudgets"> & { * Use `create(QueryBudgetsSchema)` to create a new message. */ export const QueryBudgetsSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_camino_schema, 35); + messageDesc(file_camino_schema, 37); + +/** + * @generated from message camino.QueryPlacement + */ +export type QueryPlacement = Message<"camino.QueryPlacement"> & { + /** + * @generated from oneof camino.QueryPlacement.kind + */ + kind: { + /** + * @generated from field: bool replica = 1; + */ + value: boolean; + case: "replica"; + } | { + /** + * @generated from field: string local_source_query_id = 2; + */ + value: string; + case: "localSourceQueryId"; + } | { case: undefined; value?: undefined }; +}; + +/** + * Describes the message camino.QueryPlacement. + * Use `create(QueryPlacementSchema)` to create a new message. + */ +export const QueryPlacementSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_camino_schema, 38); /** * @generated from message camino.InstalledQuery @@ -1315,9 +1421,21 @@ export type InstalledQuery = Message<"camino.InstalledQuery"> & { bindingDigest: string; /** - * @generated from field: string root_interface_revision_id = 4; + * @generated from oneof camino.InstalledQuery.root */ - rootInterfaceRevisionId: string; + root: { + /** + * @generated from field: string object_interface = 4; + */ + value: string; + case: "objectInterface"; + } | { + /** + * @generated from field: string workspace_interface = 14; + */ + value: string; + case: "workspaceInterface"; + } | { case: undefined; value?: undefined }; /** * @generated from field: repeated camino.QuerySelection selection = 5; @@ -1363,6 +1481,21 @@ export type InstalledQuery = Message<"camino.InstalledQuery"> & { * @generated from field: bool rpc_predicate_or_order = 13; */ rpcPredicateOrOrder: boolean; + + /** + * @generated from field: repeated string root_atom_ids = 15; + */ + rootAtomIds: string[]; + + /** + * @generated from field: repeated camino.QueryFieldContract fields = 16; + */ + fields: QueryFieldContract[]; + + /** + * @generated from field: camino.QueryPlacement placement = 17; + */ + placement?: QueryPlacement | undefined; }; /** @@ -1370,7 +1503,7 @@ export type InstalledQuery = Message<"camino.InstalledQuery"> & { * Use `create(InstalledQuerySchema)` to create a new message. */ export const InstalledQuerySchema: GenMessage = /*@__PURE__*/ - messageDesc(file_camino_schema, 36); + messageDesc(file_camino_schema, 39); /** * @generated from enum camino.Cardinality @@ -1497,6 +1630,11 @@ export enum QueryComparisonOperator { * @generated from enum value: QUERY_COMPARISON_OPERATOR_GTE = 7; */ GTE = 7, + + /** + * @generated from enum value: QUERY_COMPARISON_OPERATOR_CONTAINS_INSENSITIVE = 8; + */ + CONTAINS_INSENSITIVE = 8, } /** diff --git a/src/gen/quixos/orch_pb.ts b/src/gen/quixos/orch_pb.ts index 80285fd..f513e4c 100644 --- a/src/gen/quixos/orch_pb.ts +++ b/src/gen/quixos/orch_pb.ts @@ -10,7 +10,7 @@ import type { InstalledQuery } from "../camino/schema_pb.js"; import { file_camino_schema } from "../camino/schema_pb.js"; import type { PackageDescriptor } from "./package_pb.js"; import { file_quixos_package } from "./package_pb.js"; -import type { CapabilityRef, ConformanceWitness, FieldEditing, PackageExportRef } from "./refs_pb.js"; +import type { CapabilityRef, ConformanceWitness, FieldEditing, PackageExportRef, WorkspaceQueryScope } from "./refs_pb.js"; import { file_quixos_refs } from "./refs_pb.js"; import type { DerivedDependency } from "./runtime_pb.js"; import { file_quixos_runtime } from "./runtime_pb.js"; @@ -20,7 +20,412 @@ import type { Message } from "@bufbuild/protobuf"; * Describes the file quixos/orch.proto. */ export const file_quixos_orch: GenFile = /*@__PURE__*/ - fileDesc("ChFxdWl4b3Mvb3JjaC5wcm90bxILcXVpeG9zLm9yY2gitwEKClF1ZXJ5RXZlbnQSDgoGcnVuX2lkGAEgASgJEhAKCHNlcXVlbmNlGAIgASgEEgwKBGtpbmQYAyABKAkSJwoIc25hcHNob3QYBCABKAsyFS5jYW1pbm8uUXVlcnlSZXNwb25zZRIjCgRwYXRoGAUgAygLMhUuY2FtaW5vLlF1ZXJ5UGF0aFBhcnQSHAoFdmFsdWUYBiABKAsyDS5jYW1pbm8uVmFsdWUSDQoFZXJyb3IYByABKAkiRQoRVHJ5Q29uZm9ybVJlcXVlc3QSEQoJb2JqZWN0X2lkGAEgASgJEh0KFWludGVyZmFjZV9yZXZpc2lvbl9pZBgCIAEoCSJFChJUcnlDb25mb3JtUmVzcG9uc2USLwoLY29uZm9ybWFuY2UYASABKAsyGi5xdWl4b3MuQ29uZm9ybWFuY2VXaXRuZXNzIqUBChZDb25zdHJ1Y3RPYmplY3RSZXF1ZXN0Eg8KB2F0b21faWQYASABKAkSPQoFaW5wdXQYAiADKAsyLi5xdWl4b3Mub3JjaC5Db25zdHJ1Y3RPYmplY3RSZXF1ZXN0LklucHV0RW50cnkaOwoKSW5wdXRFbnRyeRILCgNrZXkYASABKAkSHAoFdmFsdWUYAiABKAsyDS5jYW1pbm8uVmFsdWU6AjgBIj8KF0NvbnN0cnVjdE9iamVjdFJlc3BvbnNlEiQKBm9iamVjdBgBIAEoCzIULmNhbWluby5DYW1pbm9PYmplY3QibQomUmVzb2x2ZU9yQ29uc3RydWN0UmVsYXRlZE9iamVjdFJlcXVlc3QSEQoJb2JqZWN0X2lkGAEgASgJEh0KFWludGVyZmFjZV9yZXZpc2lvbl9pZBgCIAEoCRIRCgltZW1iZXJfaWQYAyABKAkiZAonUmVzb2x2ZU9yQ29uc3RydWN0UmVsYXRlZE9iamVjdFJlc3BvbnNlEiQKBm9iamVjdBgBIAEoCzIULmNhbWluby5DYW1pbm9PYmplY3QSEwoLY29uc3RydWN0ZWQYAiABKAgi8AEKF0ludm9rZUNhcGFiaWxpdHlSZXF1ZXN0EikKCmNhcGFiaWxpdHkYASABKAsyFS5xdWl4b3MuQ2FwYWJpbGl0eVJlZhIRCglvYmplY3RfaWQYAiABKAkSPgoFaW5wdXQYAyADKAsyLy5xdWl4b3Mub3JjaC5JbnZva2VDYXBhYmlsaXR5UmVxdWVzdC5JbnB1dEVudHJ5EhoKEmNsaWVudF9tdXRhdGlvbl9pZBgEIAEoCRo7CgpJbnB1dEVudHJ5EgsKA2tleRgBIAEoCRIcCgV2YWx1ZRgCIAEoCzINLmNhbWluby5WYWx1ZToCOAEizgEKHEludm9rZUNsYXNzQ2FwYWJpbGl0eVJlcXVlc3QSFgoOY29uZm9ybWFuY2VfaWQYASABKAkSFAoMb3BlcmF0aW9uX2lkGAIgASgJEkMKBWlucHV0GAMgAygLMjQucXVpeG9zLm9yY2guSW52b2tlQ2xhc3NDYXBhYmlsaXR5UmVxdWVzdC5JbnB1dEVudHJ5GjsKCklucHV0RW50cnkSCwoDa2V5GAEgASgJEhwKBXZhbHVlGAIgASgLMg0uY2FtaW5vLlZhbHVlOgI4ASL+AQoYSW52b2tlQ2FwYWJpbGl0eVJlc3BvbnNlEhUKDWludm9jYXRpb25faWQYASABKAkSKwoKYWN0aXZhdGlvbhgCIAEoCzIXLnF1aXhvcy5vcmNoLkFjdGl2YXRpb24SCgoCb2sYAyABKAgSHQoGcmVzdWx0GAQgASgLMg0uY2FtaW5vLlZhbHVlEg0KBWVycm9yGAUgASgJEjcKDGRlcGVuZGVuY2llcxgGIAMoCzIhLnF1aXhvcy5ydW50aW1lLkRlcml2ZWREZXBlbmRlbmN5EisKDWZpZWxkX2VkaXRpbmcYByABKAsyFC5xdWl4b3MuRmllbGRFZGl0aW5nIv4BChpFZGl0Q2FwYWJpbGl0eUZpZWxkUmVxdWVzdBIpCgpjYXBhYmlsaXR5GAEgASgLMhUucXVpeG9zLkNhcGFiaWxpdHlSZWYSEQoJb2JqZWN0X2lkGAIgASgJEhsKE3NldHRlcl9vcGVyYXRpb25faWQYAyABKAkSFgoOYmluZGluZ19kaWdlc3QYBCABKAkSIwoGdXBkYXRlGAUgASgLMhEuY2FtaW5vLkNyZHRWYWx1ZUgAEiQKC3JlcGxhY2VtZW50GAcgASgLMg0uY2FtaW5vLlZhbHVlSAASGgoSY2xpZW50X211dGF0aW9uX2lkGAYgASgJQgYKBGVkaXQi0gEKFldhdGNoQ2FwYWJpbGl0eVJlcXVlc3QSKQoKY2FwYWJpbGl0eRgBIAEoCzIVLnF1aXhvcy5DYXBhYmlsaXR5UmVmEhEKCW9iamVjdF9pZBgCIAEoCRI9CgVpbnB1dBgDIAMoCzIuLnF1aXhvcy5vcmNoLldhdGNoQ2FwYWJpbGl0eVJlcXVlc3QuSW5wdXRFbnRyeRo7CgpJbnB1dEVudHJ5EgsKA2tleRgBIAEoCRIcCgV2YWx1ZRgCIAEoCzINLmNhbWluby5WYWx1ZToCOAEikAIKFFdhdGNoQ2FwYWJpbGl0eUV2ZW50EhUKDWludm9jYXRpb25faWQYASABKAkSKwoKYWN0aXZhdGlvbhgCIAEoCzIXLnF1aXhvcy5vcmNoLkFjdGl2YXRpb24SEAoId2F0Y2hfaWQYAyABKAkSHAoFdmFsdWUYBCABKAsyDS5jYW1pbm8uVmFsdWUSNwoMZGVwZW5kZW5jaWVzGAUgAygLMiEucXVpeG9zLnJ1bnRpbWUuRGVyaXZlZERlcGVuZGVuY3kSDQoFZXJyb3IYBiABKAkSDwoHaW5pdGlhbBgHIAEoCBIrCg1maWVsZF9lZGl0aW5nGAggASgLMhQucXVpeG9zLkZpZWxkRWRpdGluZyJbChNHZXRXb3Jrc3BhY2VSZXF1ZXN0EiMKG2luY2x1ZGVfaW50ZXJmYWNlX2NvbnRyYWN0cxgBIAEoCBIfChdpbmNsdWRlX3F1ZXJ5X2NvbnRyYWN0cxgCIAEoCCK7AwoUR2V0V29ya3NwYWNlUmVzcG9uc2USFAoMd29ya3NwYWNlX2lkGAEgASgJEh0KFXdvcmtzcGFjZV9yZXZpc2lvbl9pZBgCIAEoCRIaChJzb3VyY2Vfcm9vdF9jb21taXQYAyABKAkSKgoiZW1wdHlfaW5wdXRfY29uc3RydWN0aWJsZV9hdG9tX2lkcxgEIAMoCRI/ChFjYXBhYmlsaXR5X2lucHV0cxgFIAMoCzIkLnF1aXhvcy5vcmNoLkNhcGFiaWxpdHlJbnB1dENvbnRyYWN0EkEKEmNvbnN0cnVjdG9yX2lucHV0cxgGIAMoCzIlLnF1aXhvcy5vcmNoLkNvbnN0cnVjdG9ySW5wdXRDb250cmFjdBIXCg9pbnRlcmZhY2VzX2pzb24YByABKAkSOAoSY2xhc3NfY2FwYWJpbGl0aWVzGAggAygLMhwucXVpeG9zLm9yY2guQ2xhc3NDYXBhYmlsaXR5Ei4KB3F1ZXJpZXMYCSADKAsyHS5xdWl4b3Mub3JjaC5RdWVyeURlc2NyaXB0aW9uEh8KF2FjdGl2ZV9xdWVyeV9leGVjdXRpb25zGAogASgNIr0BChBRdWVyeURlc2NyaXB0aW9uEgoKAmlkGAEgASgJEgwKBG5hbWUYAiABKAkSGwoTcGFja2FnZV9yZXZpc2lvbl9pZBgDIAEoCRIQCghkb2N1bWVudBgEIAEoCRIOCgZzY2hlbWEYBSABKAkSFAoMc291cmNlX2ZpbGVzGAYgAygJEhQKDGVmZmVjdHNfanNvbhgHIAEoCRIkCgRwbGFuGAggASgLMhYuY2FtaW5vLkluc3RhbGxlZFF1ZXJ5IrkBCg9DbGFzc0NhcGFiaWxpdHkSFgoOY29uZm9ybWFuY2VfaWQYASABKAkSDwoHYXRvbV9pZBgCIAEoCRIdChVpbnRlcmZhY2VfcmV2aXNpb25faWQYAyABKAkSFQoNZGVmaW5pdGlvbl9pZBgEIAEoCRIUCgxvcGVyYXRpb25faWQYBSABKAkSFwoPaW5wdXRfdHlwZV9qc29uGAYgASgJEhgKEG91dHB1dF90eXBlX2pzb24YByABKAkiYQoXQ2FwYWJpbGl0eUlucHV0Q29udHJhY3QSHQoVaW50ZXJmYWNlX3JldmlzaW9uX2lkGAEgASgJEhQKDG9wZXJhdGlvbl9pZBgCIAEoCRIRCgl0eXBlX2pzb24YAyABKAkiPgoYQ29uc3RydWN0b3JJbnB1dENvbnRyYWN0Eg8KB2F0b21faWQYASABKAkSEQoJdHlwZV9qc29uGAIgASgJIhgKFkxpc3RBY3RpdmF0aW9uc1JlcXVlc3QiHwodTGlzdFBhY2thZ2VEZXNjcmlwdG9yc1JlcXVlc3QiUAoeTGlzdFBhY2thZ2VEZXNjcmlwdG9yc1Jlc3BvbnNlEi4KC2Rlc2NyaXB0b3JzGAEgAygLMhkucXVpeG9zLlBhY2thZ2VEZXNjcmlwdG9yIhwKGkxpc3RQYWNrYWdlUnVudGltZXNSZXF1ZXN0IlIKG0xpc3RQYWNrYWdlUnVudGltZXNSZXNwb25zZRIzCghydW50aW1lcxgBIAMoCzIhLnF1aXhvcy5vcmNoLlBhY2thZ2VSdW50aW1lU3RhdHVzIkcKF0xpc3RBY3RpdmF0aW9uc1Jlc3BvbnNlEiwKC2FjdGl2YXRpb25zGAEgAygLMhcucXVpeG9zLm9yY2guQWN0aXZhdGlvbiI/ChZDbG9zZUFjdGl2YXRpb25SZXF1ZXN0EhUKDWFjdGl2YXRpb25faWQYASABKAkSDgoGcmVhc29uGAIgASgJIkYKF0Nsb3NlQWN0aXZhdGlvblJlc3BvbnNlEisKCmFjdGl2YXRpb24YASABKAsyFy5xdWl4b3Mub3JjaC5BY3RpdmF0aW9uIusBCgpBY3RpdmF0aW9uEhUKDWFjdGl2YXRpb25faWQYASABKAkSKAoGZXhwb3J0GAIgASgLMhgucXVpeG9zLlBhY2thZ2VFeHBvcnRSZWYSEQoJb2JqZWN0X2lkGAMgASgJEg0KBXN0YXRlGAQgASgJEg4KBmRlbWFuZBgFIAEoDRIRCglvcGVuZWRfYXQYBiABKAkSFAoMbGFzdF91c2VkX2F0GAcgASgJEhgKEGlkbGVfZGVhZGxpbmVfYXQYCCABKAkSEQoJY2xvc2VkX2F0GAkgASgJEhQKDGNsb3NlX3JlYXNvbhgKIAEoCSKzAgoUUGFja2FnZVJ1bnRpbWVTdGF0dXMSEwoLcnVudGltZV9rZXkYASABKAkSGwoTcGFja2FnZV9yZXZpc2lvbl9pZBgCIAEoCRIZChFzb3VyY2VfcmVwb3NpdG9yeRgDIAEoCRIVCg1zb3VyY2VfY29tbWl0GAQgASgJEhQKDGJ1aWxkX3RhcmdldBgFIAEoCRITCgtzZXJ2ZXJfcGF0aBgGIAEoCRILCgNwaWQYByABKA0SDQoFc3RhdGUYCCABKAkSEgoKc3RhcnRlZF9hdBgJIAEoCRIZChFsYXN0X2hhbmRzaGFrZV9hdBgKIAEoCRIgChhydW50aW1lX3Byb3RvY29sX3ZlcnNpb24YCyABKAkSHwoXYWR2ZXJ0aXNlZF9leHBvcnRfY291bnQYDCABKA0yywoKE09yY2hlc3RyYXRvclJ1bnRpbWUSOwoMRXhlY3V0ZVF1ZXJ5EhQuY2FtaW5vLlF1ZXJ5UmVxdWVzdBoVLmNhbWluby5RdWVyeVJlc3BvbnNlEj0KCldhdGNoUXVlcnkSFC5jYW1pbm8uUXVlcnlSZXF1ZXN0GhcucXVpeG9zLm9yY2guUXVlcnlFdmVudDABEk0KClRyeUNvbmZvcm0SHi5xdWl4b3Mub3JjaC5UcnlDb25mb3JtUmVxdWVzdBofLnF1aXhvcy5vcmNoLlRyeUNvbmZvcm1SZXNwb25zZRJfChBJbnZva2VDYXBhYmlsaXR5EiQucXVpeG9zLm9yY2guSW52b2tlQ2FwYWJpbGl0eVJlcXVlc3QaJS5xdWl4b3Mub3JjaC5JbnZva2VDYXBhYmlsaXR5UmVzcG9uc2USZQoTRWRpdENhcGFiaWxpdHlGaWVsZBInLnF1aXhvcy5vcmNoLkVkaXRDYXBhYmlsaXR5RmllbGRSZXF1ZXN0GiUucXVpeG9zLm9yY2guSW52b2tlQ2FwYWJpbGl0eVJlc3BvbnNlEmkKFUludm9rZUNsYXNzQ2FwYWJpbGl0eRIpLnF1aXhvcy5vcmNoLkludm9rZUNsYXNzQ2FwYWJpbGl0eVJlcXVlc3QaJS5xdWl4b3Mub3JjaC5JbnZva2VDYXBhYmlsaXR5UmVzcG9uc2USWwoPV2F0Y2hDYXBhYmlsaXR5EiMucXVpeG9zLm9yY2guV2F0Y2hDYXBhYmlsaXR5UmVxdWVzdBohLnF1aXhvcy5vcmNoLldhdGNoQ2FwYWJpbGl0eUV2ZW50MAESXAoPQ29uc3RydWN0T2JqZWN0EiMucXVpeG9zLm9yY2guQ29uc3RydWN0T2JqZWN0UmVxdWVzdBokLnF1aXhvcy5vcmNoLkNvbnN0cnVjdE9iamVjdFJlc3BvbnNlEowBCh9SZXNvbHZlT3JDb25zdHJ1Y3RSZWxhdGVkT2JqZWN0EjMucXVpeG9zLm9yY2guUmVzb2x2ZU9yQ29uc3RydWN0UmVsYXRlZE9iamVjdFJlcXVlc3QaNC5xdWl4b3Mub3JjaC5SZXNvbHZlT3JDb25zdHJ1Y3RSZWxhdGVkT2JqZWN0UmVzcG9uc2USUwoMR2V0V29ya3NwYWNlEiAucXVpeG9zLm9yY2guR2V0V29ya3NwYWNlUmVxdWVzdBohLnF1aXhvcy5vcmNoLkdldFdvcmtzcGFjZVJlc3BvbnNlEnEKFkxpc3RQYWNrYWdlRGVzY3JpcHRvcnMSKi5xdWl4b3Mub3JjaC5MaXN0UGFja2FnZURlc2NyaXB0b3JzUmVxdWVzdBorLnF1aXhvcy5vcmNoLkxpc3RQYWNrYWdlRGVzY3JpcHRvcnNSZXNwb25zZRJoChNMaXN0UGFja2FnZVJ1bnRpbWVzEicucXVpeG9zLm9yY2guTGlzdFBhY2thZ2VSdW50aW1lc1JlcXVlc3QaKC5xdWl4b3Mub3JjaC5MaXN0UGFja2FnZVJ1bnRpbWVzUmVzcG9uc2USXAoPTGlzdEFjdGl2YXRpb25zEiMucXVpeG9zLm9yY2guTGlzdEFjdGl2YXRpb25zUmVxdWVzdBokLnF1aXhvcy5vcmNoLkxpc3RBY3RpdmF0aW9uc1Jlc3BvbnNlElwKD0Nsb3NlQWN0aXZhdGlvbhIjLnF1aXhvcy5vcmNoLkNsb3NlQWN0aXZhdGlvblJlcXVlc3QaJC5xdWl4b3Mub3JjaC5DbG9zZUFjdGl2YXRpb25SZXNwb25zZWIGcHJvdG8z", [file_camino_api, file_camino_schema, file_quixos_package, file_quixos_refs, file_quixos_runtime]); + fileDesc("ChFxdWl4b3Mvb3JjaC5wcm90bxILcXVpeG9zLm9yY2gifAoSRGVmaW5pdGlvblNlbGVjdG9yEgwKBGtpbmQYASABKAkSCwoDaWRzGAIgAygJEg8KB2F0b21faWQYAyABKAkSHQoVaW50ZXJmYWNlX3JldmlzaW9uX2lkGAQgASgJEhsKE3BhY2thZ2VfcmV2aXNpb25faWQYBSABKAkimQEKEkRlZmluaXRpb25JZGVudGl0eRIUCgx3b3Jrc3BhY2VfaWQYASABKAkSHQoVd29ya3NwYWNlX3JldmlzaW9uX2lkGAIgASgJEhoKEnNvdXJjZV9yb290X2NvbW1pdBgDIAEoCRIXCg93b3Jrc3BhY2VfZXBvY2gYBCABKAkSGQoRZGVmaW5pdGlvbl9kaWdlc3QYBSABKAkipQEKEURlZmluaXRpb25SZXF1ZXN0EjEKCHNlbGVjdG9yGAEgASgLMh8ucXVpeG9zLm9yY2guRGVmaW5pdGlvblNlbGVjdG9yEg0KBWZpcnN0GAIgASgNEg0KBWFmdGVyGAMgASgJEhAKCHNuYXBzaG90GAQgASgJEhQKDHdvcmtzcGFjZV9pZBgFIAEoCRIXCg93b3Jrc3BhY2VfZXBvY2gYBiABKAkiVgoNUmVmbGVjdGVkQXRvbRIKCgJpZBgBIAEoCRIUCgxkaXNwbGF5X25hbWUYAiABKAkSIwobY29uc3RydWN0b3JfaW5wdXRfdHlwZV9qc29uGAMgASgJImMKElJlZmxlY3RlZEludGVyZmFjZRIKCgJpZBgBIAEoCRIUCgxpbnRlcmZhY2VfaWQYAiABKAkSFAoMZGlzcGxheV9uYW1lGAMgASgJEhUKDWNvbnRyYWN0X2pzb24YBCABKAkizAEKFFJlZmxlY3RlZENvbmZvcm1hbmNlEgoKAmlkGAEgASgJEg8KB2F0b21faWQYAiABKAkSHQoVaW50ZXJmYWNlX3JldmlzaW9uX2lkGAMgASgJEhUKDWRlZmluaXRpb25faWQYBCABKAkSFQoNb3BlcmF0aW9uX2lkcxgFIAMoCRI4ChJjbGFzc19jYXBhYmlsaXRpZXMYBiADKAsyHC5xdWl4b3Mub3JjaC5DbGFzc0NhcGFiaWxpdHkSEAoIaW5zdGFuY2UYByABKAgivgIKDlJlZmxlY3RlZFF1ZXJ5EgoKAmlkGAEgASgJEgwKBG5hbWUYAiABKAkSGwoTcGFja2FnZV9yZXZpc2lvbl9pZBgDIAEoCRIZChFkZWZpbml0aW9uX2RpZ2VzdBgEIAEoCRIWCg5iaW5kaW5nX2RpZ2VzdBgFIAEoCRIRCglyb290X2tpbmQYBiABKAkSHQoVaW50ZXJmYWNlX3JldmlzaW9uX2lkGAcgASgJEhsKE3ZhcmlhYmxlc190eXBlX2pzb24YCCABKAkSGAoQb3V0cHV0X3R5cGVfanNvbhgJIAEoCRIUCgxlZmZlY3RzX2pzb24YCiABKAkSDQoFd2F0Y2gYCyABKAgSNAoPd29ya3NwYWNlX3Njb3BlGAwgASgLMhsucXVpeG9zLldvcmtzcGFjZVF1ZXJ5U2NvcGUiMwoOUmVmbGVjdGVkSW5kZXgSCgoCaWQYASABKAkSFQoNY29udHJhY3RfanNvbhgCIAEoCSKYAgoQRGVmaW5pdGlvblJlY29yZBIqCgRhdG9tGAEgASgLMhoucXVpeG9zLm9yY2guUmVmbGVjdGVkQXRvbUgAEjQKCWludGVyZmFjZRgCIAEoCzIfLnF1aXhvcy5vcmNoLlJlZmxlY3RlZEludGVyZmFjZUgAEjgKC2NvbmZvcm1hbmNlGAMgASgLMiEucXVpeG9zLm9yY2guUmVmbGVjdGVkQ29uZm9ybWFuY2VIABIsCgVxdWVyeRgEIAEoCzIbLnF1aXhvcy5vcmNoLlJlZmxlY3RlZFF1ZXJ5SAASLAoFaW5kZXgYBSABKAsyGy5xdWl4b3Mub3JjaC5SZWZsZWN0ZWRJbmRleEgAQgwKCmRlZmluaXRpb24iiAEKDkRlZmluaXRpb25QYWdlEjEKCGlkZW50aXR5GAEgASgLMh8ucXVpeG9zLm9yY2guRGVmaW5pdGlvbklkZW50aXR5Ei4KB3JlY29yZHMYAiADKAsyHS5xdWl4b3Mub3JjaC5EZWZpbml0aW9uUmVjb3JkEhMKC25leHRfY3Vyc29yGAMgASgJIrcBCgpRdWVyeUV2ZW50Eg4KBnJ1bl9pZBgBIAEoCRIQCghzZXF1ZW5jZRgCIAEoBBIMCgRraW5kGAMgASgJEicKCHNuYXBzaG90GAQgASgLMhUuY2FtaW5vLlF1ZXJ5UmVzcG9uc2USIwoEcGF0aBgFIAMoCzIVLmNhbWluby5RdWVyeVBhdGhQYXJ0EhwKBXZhbHVlGAYgASgLMg0uY2FtaW5vLlZhbHVlEg0KBWVycm9yGAcgASgJIkUKEVRyeUNvbmZvcm1SZXF1ZXN0EhEKCW9iamVjdF9pZBgBIAEoCRIdChVpbnRlcmZhY2VfcmV2aXNpb25faWQYAiABKAkiRQoSVHJ5Q29uZm9ybVJlc3BvbnNlEi8KC2NvbmZvcm1hbmNlGAEgASgLMhoucXVpeG9zLkNvbmZvcm1hbmNlV2l0bmVzcyKlAQoWQ29uc3RydWN0T2JqZWN0UmVxdWVzdBIPCgdhdG9tX2lkGAEgASgJEj0KBWlucHV0GAIgAygLMi4ucXVpeG9zLm9yY2guQ29uc3RydWN0T2JqZWN0UmVxdWVzdC5JbnB1dEVudHJ5GjsKCklucHV0RW50cnkSCwoDa2V5GAEgASgJEhwKBXZhbHVlGAIgASgLMg0uY2FtaW5vLlZhbHVlOgI4ASI/ChdDb25zdHJ1Y3RPYmplY3RSZXNwb25zZRIkCgZvYmplY3QYASABKAsyFC5jYW1pbm8uQ2FtaW5vT2JqZWN0Im0KJlJlc29sdmVPckNvbnN0cnVjdFJlbGF0ZWRPYmplY3RSZXF1ZXN0EhEKCW9iamVjdF9pZBgBIAEoCRIdChVpbnRlcmZhY2VfcmV2aXNpb25faWQYAiABKAkSEQoJbWVtYmVyX2lkGAMgASgJImQKJ1Jlc29sdmVPckNvbnN0cnVjdFJlbGF0ZWRPYmplY3RSZXNwb25zZRIkCgZvYmplY3QYASABKAsyFC5jYW1pbm8uQ2FtaW5vT2JqZWN0EhMKC2NvbnN0cnVjdGVkGAIgASgIIvABChdJbnZva2VDYXBhYmlsaXR5UmVxdWVzdBIpCgpjYXBhYmlsaXR5GAEgASgLMhUucXVpeG9zLkNhcGFiaWxpdHlSZWYSEQoJb2JqZWN0X2lkGAIgASgJEj4KBWlucHV0GAMgAygLMi8ucXVpeG9zLm9yY2guSW52b2tlQ2FwYWJpbGl0eVJlcXVlc3QuSW5wdXRFbnRyeRIaChJjbGllbnRfbXV0YXRpb25faWQYBCABKAkaOwoKSW5wdXRFbnRyeRILCgNrZXkYASABKAkSHAoFdmFsdWUYAiABKAsyDS5jYW1pbm8uVmFsdWU6AjgBIs4BChxJbnZva2VDbGFzc0NhcGFiaWxpdHlSZXF1ZXN0EhYKDmNvbmZvcm1hbmNlX2lkGAEgASgJEhQKDG9wZXJhdGlvbl9pZBgCIAEoCRJDCgVpbnB1dBgDIAMoCzI0LnF1aXhvcy5vcmNoLkludm9rZUNsYXNzQ2FwYWJpbGl0eVJlcXVlc3QuSW5wdXRFbnRyeRo7CgpJbnB1dEVudHJ5EgsKA2tleRgBIAEoCRIcCgV2YWx1ZRgCIAEoCzINLmNhbWluby5WYWx1ZToCOAEi/gEKGEludm9rZUNhcGFiaWxpdHlSZXNwb25zZRIVCg1pbnZvY2F0aW9uX2lkGAEgASgJEisKCmFjdGl2YXRpb24YAiABKAsyFy5xdWl4b3Mub3JjaC5BY3RpdmF0aW9uEgoKAm9rGAMgASgIEh0KBnJlc3VsdBgEIAEoCzINLmNhbWluby5WYWx1ZRINCgVlcnJvchgFIAEoCRI3CgxkZXBlbmRlbmNpZXMYBiADKAsyIS5xdWl4b3MucnVudGltZS5EZXJpdmVkRGVwZW5kZW5jeRIrCg1maWVsZF9lZGl0aW5nGAcgASgLMhQucXVpeG9zLkZpZWxkRWRpdGluZyL+AQoaRWRpdENhcGFiaWxpdHlGaWVsZFJlcXVlc3QSKQoKY2FwYWJpbGl0eRgBIAEoCzIVLnF1aXhvcy5DYXBhYmlsaXR5UmVmEhEKCW9iamVjdF9pZBgCIAEoCRIbChNzZXR0ZXJfb3BlcmF0aW9uX2lkGAMgASgJEhYKDmJpbmRpbmdfZGlnZXN0GAQgASgJEiMKBnVwZGF0ZRgFIAEoCzIRLmNhbWluby5DcmR0VmFsdWVIABIkCgtyZXBsYWNlbWVudBgHIAEoCzINLmNhbWluby5WYWx1ZUgAEhoKEmNsaWVudF9tdXRhdGlvbl9pZBgGIAEoCUIGCgRlZGl0ItIBChZXYXRjaENhcGFiaWxpdHlSZXF1ZXN0EikKCmNhcGFiaWxpdHkYASABKAsyFS5xdWl4b3MuQ2FwYWJpbGl0eVJlZhIRCglvYmplY3RfaWQYAiABKAkSPQoFaW5wdXQYAyADKAsyLi5xdWl4b3Mub3JjaC5XYXRjaENhcGFiaWxpdHlSZXF1ZXN0LklucHV0RW50cnkaOwoKSW5wdXRFbnRyeRILCgNrZXkYASABKAkSHAoFdmFsdWUYAiABKAsyDS5jYW1pbm8uVmFsdWU6AjgBIpACChRXYXRjaENhcGFiaWxpdHlFdmVudBIVCg1pbnZvY2F0aW9uX2lkGAEgASgJEisKCmFjdGl2YXRpb24YAiABKAsyFy5xdWl4b3Mub3JjaC5BY3RpdmF0aW9uEhAKCHdhdGNoX2lkGAMgASgJEhwKBXZhbHVlGAQgASgLMg0uY2FtaW5vLlZhbHVlEjcKDGRlcGVuZGVuY2llcxgFIAMoCzIhLnF1aXhvcy5ydW50aW1lLkRlcml2ZWREZXBlbmRlbmN5Eg0KBWVycm9yGAYgASgJEg8KB2luaXRpYWwYByABKAgSKwoNZmllbGRfZWRpdGluZxgIIAEoCzIULnF1aXhvcy5GaWVsZEVkaXRpbmciWwoTR2V0V29ya3NwYWNlUmVxdWVzdBIjChtpbmNsdWRlX2ludGVyZmFjZV9jb250cmFjdHMYASABKAgSHwoXaW5jbHVkZV9xdWVyeV9jb250cmFjdHMYAiABKAgiuwMKFEdldFdvcmtzcGFjZVJlc3BvbnNlEhQKDHdvcmtzcGFjZV9pZBgBIAEoCRIdChV3b3Jrc3BhY2VfcmV2aXNpb25faWQYAiABKAkSGgoSc291cmNlX3Jvb3RfY29tbWl0GAMgASgJEioKImVtcHR5X2lucHV0X2NvbnN0cnVjdGlibGVfYXRvbV9pZHMYBCADKAkSPwoRY2FwYWJpbGl0eV9pbnB1dHMYBSADKAsyJC5xdWl4b3Mub3JjaC5DYXBhYmlsaXR5SW5wdXRDb250cmFjdBJBChJjb25zdHJ1Y3Rvcl9pbnB1dHMYBiADKAsyJS5xdWl4b3Mub3JjaC5Db25zdHJ1Y3RvcklucHV0Q29udHJhY3QSFwoPaW50ZXJmYWNlc19qc29uGAcgASgJEjgKEmNsYXNzX2NhcGFiaWxpdGllcxgIIAMoCzIcLnF1aXhvcy5vcmNoLkNsYXNzQ2FwYWJpbGl0eRIuCgdxdWVyaWVzGAkgAygLMh0ucXVpeG9zLm9yY2guUXVlcnlEZXNjcmlwdGlvbhIfChdhY3RpdmVfcXVlcnlfZXhlY3V0aW9ucxgKIAEoDSLzAQoQUXVlcnlEZXNjcmlwdGlvbhIKCgJpZBgBIAEoCRIMCgRuYW1lGAIgASgJEhsKE3BhY2thZ2VfcmV2aXNpb25faWQYAyABKAkSEAoIZG9jdW1lbnQYBCABKAkSDgoGc2NoZW1hGAUgASgJEhQKDHNvdXJjZV9maWxlcxgGIAMoCRIUCgxlZmZlY3RzX2pzb24YByABKAkSJAoEcGxhbhgIIAEoCzIWLmNhbWluby5JbnN0YWxsZWRRdWVyeRI0Cg93b3Jrc3BhY2Vfc2NvcGUYCSABKAsyGy5xdWl4b3MuV29ya3NwYWNlUXVlcnlTY29wZSK5AQoPQ2xhc3NDYXBhYmlsaXR5EhYKDmNvbmZvcm1hbmNlX2lkGAEgASgJEg8KB2F0b21faWQYAiABKAkSHQoVaW50ZXJmYWNlX3JldmlzaW9uX2lkGAMgASgJEhUKDWRlZmluaXRpb25faWQYBCABKAkSFAoMb3BlcmF0aW9uX2lkGAUgASgJEhcKD2lucHV0X3R5cGVfanNvbhgGIAEoCRIYChBvdXRwdXRfdHlwZV9qc29uGAcgASgJImEKF0NhcGFiaWxpdHlJbnB1dENvbnRyYWN0Eh0KFWludGVyZmFjZV9yZXZpc2lvbl9pZBgBIAEoCRIUCgxvcGVyYXRpb25faWQYAiABKAkSEQoJdHlwZV9qc29uGAMgASgJIj4KGENvbnN0cnVjdG9ySW5wdXRDb250cmFjdBIPCgdhdG9tX2lkGAEgASgJEhEKCXR5cGVfanNvbhgCIAEoCSIYChZMaXN0QWN0aXZhdGlvbnNSZXF1ZXN0Ih8KHUxpc3RQYWNrYWdlRGVzY3JpcHRvcnNSZXF1ZXN0IlAKHkxpc3RQYWNrYWdlRGVzY3JpcHRvcnNSZXNwb25zZRIuCgtkZXNjcmlwdG9ycxgBIAMoCzIZLnF1aXhvcy5QYWNrYWdlRGVzY3JpcHRvciIcChpMaXN0UGFja2FnZVJ1bnRpbWVzUmVxdWVzdCJSChtMaXN0UGFja2FnZVJ1bnRpbWVzUmVzcG9uc2USMwoIcnVudGltZXMYASADKAsyIS5xdWl4b3Mub3JjaC5QYWNrYWdlUnVudGltZVN0YXR1cyJHChdMaXN0QWN0aXZhdGlvbnNSZXNwb25zZRIsCgthY3RpdmF0aW9ucxgBIAMoCzIXLnF1aXhvcy5vcmNoLkFjdGl2YXRpb24iPwoWQ2xvc2VBY3RpdmF0aW9uUmVxdWVzdBIVCg1hY3RpdmF0aW9uX2lkGAEgASgJEg4KBnJlYXNvbhgCIAEoCSJGChdDbG9zZUFjdGl2YXRpb25SZXNwb25zZRIrCgphY3RpdmF0aW9uGAEgASgLMhcucXVpeG9zLm9yY2guQWN0aXZhdGlvbiLrAQoKQWN0aXZhdGlvbhIVCg1hY3RpdmF0aW9uX2lkGAEgASgJEigKBmV4cG9ydBgCIAEoCzIYLnF1aXhvcy5QYWNrYWdlRXhwb3J0UmVmEhEKCW9iamVjdF9pZBgDIAEoCRINCgVzdGF0ZRgEIAEoCRIOCgZkZW1hbmQYBSABKA0SEQoJb3BlbmVkX2F0GAYgASgJEhQKDGxhc3RfdXNlZF9hdBgHIAEoCRIYChBpZGxlX2RlYWRsaW5lX2F0GAggASgJEhEKCWNsb3NlZF9hdBgJIAEoCRIUCgxjbG9zZV9yZWFzb24YCiABKAkiswIKFFBhY2thZ2VSdW50aW1lU3RhdHVzEhMKC3J1bnRpbWVfa2V5GAEgASgJEhsKE3BhY2thZ2VfcmV2aXNpb25faWQYAiABKAkSGQoRc291cmNlX3JlcG9zaXRvcnkYAyABKAkSFQoNc291cmNlX2NvbW1pdBgEIAEoCRIUCgxidWlsZF90YXJnZXQYBSABKAkSEwoLc2VydmVyX3BhdGgYBiABKAkSCwoDcGlkGAcgASgNEg0KBXN0YXRlGAggASgJEhIKCnN0YXJ0ZWRfYXQYCSABKAkSGQoRbGFzdF9oYW5kc2hha2VfYXQYCiABKAkSIAoYcnVudGltZV9wcm90b2NvbF92ZXJzaW9uGAsgASgJEh8KF2FkdmVydGlzZWRfZXhwb3J0X2NvdW50GAwgASgNMu4LChNPcmNoZXN0cmF0b3JSdW50aW1lEk4KD1JlYWREZWZpbml0aW9ucxIeLnF1aXhvcy5vcmNoLkRlZmluaXRpb25SZXF1ZXN0GhsucXVpeG9zLm9yY2guRGVmaW5pdGlvblBhZ2USUQoQV2F0Y2hEZWZpbml0aW9ucxIeLnF1aXhvcy5vcmNoLkRlZmluaXRpb25SZXF1ZXN0GhsucXVpeG9zLm9yY2guRGVmaW5pdGlvblBhZ2UwARI7CgxFeGVjdXRlUXVlcnkSFC5jYW1pbm8uUXVlcnlSZXF1ZXN0GhUuY2FtaW5vLlF1ZXJ5UmVzcG9uc2USPQoKV2F0Y2hRdWVyeRIULmNhbWluby5RdWVyeVJlcXVlc3QaFy5xdWl4b3Mub3JjaC5RdWVyeUV2ZW50MAESTQoKVHJ5Q29uZm9ybRIeLnF1aXhvcy5vcmNoLlRyeUNvbmZvcm1SZXF1ZXN0Gh8ucXVpeG9zLm9yY2guVHJ5Q29uZm9ybVJlc3BvbnNlEl8KEEludm9rZUNhcGFiaWxpdHkSJC5xdWl4b3Mub3JjaC5JbnZva2VDYXBhYmlsaXR5UmVxdWVzdBolLnF1aXhvcy5vcmNoLkludm9rZUNhcGFiaWxpdHlSZXNwb25zZRJlChNFZGl0Q2FwYWJpbGl0eUZpZWxkEicucXVpeG9zLm9yY2guRWRpdENhcGFiaWxpdHlGaWVsZFJlcXVlc3QaJS5xdWl4b3Mub3JjaC5JbnZva2VDYXBhYmlsaXR5UmVzcG9uc2USaQoVSW52b2tlQ2xhc3NDYXBhYmlsaXR5EikucXVpeG9zLm9yY2guSW52b2tlQ2xhc3NDYXBhYmlsaXR5UmVxdWVzdBolLnF1aXhvcy5vcmNoLkludm9rZUNhcGFiaWxpdHlSZXNwb25zZRJbCg9XYXRjaENhcGFiaWxpdHkSIy5xdWl4b3Mub3JjaC5XYXRjaENhcGFiaWxpdHlSZXF1ZXN0GiEucXVpeG9zLm9yY2guV2F0Y2hDYXBhYmlsaXR5RXZlbnQwARJcCg9Db25zdHJ1Y3RPYmplY3QSIy5xdWl4b3Mub3JjaC5Db25zdHJ1Y3RPYmplY3RSZXF1ZXN0GiQucXVpeG9zLm9yY2guQ29uc3RydWN0T2JqZWN0UmVzcG9uc2USjAEKH1Jlc29sdmVPckNvbnN0cnVjdFJlbGF0ZWRPYmplY3QSMy5xdWl4b3Mub3JjaC5SZXNvbHZlT3JDb25zdHJ1Y3RSZWxhdGVkT2JqZWN0UmVxdWVzdBo0LnF1aXhvcy5vcmNoLlJlc29sdmVPckNvbnN0cnVjdFJlbGF0ZWRPYmplY3RSZXNwb25zZRJTCgxHZXRXb3Jrc3BhY2USIC5xdWl4b3Mub3JjaC5HZXRXb3Jrc3BhY2VSZXF1ZXN0GiEucXVpeG9zLm9yY2guR2V0V29ya3NwYWNlUmVzcG9uc2UScQoWTGlzdFBhY2thZ2VEZXNjcmlwdG9ycxIqLnF1aXhvcy5vcmNoLkxpc3RQYWNrYWdlRGVzY3JpcHRvcnNSZXF1ZXN0GisucXVpeG9zLm9yY2guTGlzdFBhY2thZ2VEZXNjcmlwdG9yc1Jlc3BvbnNlEmgKE0xpc3RQYWNrYWdlUnVudGltZXMSJy5xdWl4b3Mub3JjaC5MaXN0UGFja2FnZVJ1bnRpbWVzUmVxdWVzdBooLnF1aXhvcy5vcmNoLkxpc3RQYWNrYWdlUnVudGltZXNSZXNwb25zZRJcCg9MaXN0QWN0aXZhdGlvbnMSIy5xdWl4b3Mub3JjaC5MaXN0QWN0aXZhdGlvbnNSZXF1ZXN0GiQucXVpeG9zLm9yY2guTGlzdEFjdGl2YXRpb25zUmVzcG9uc2USXAoPQ2xvc2VBY3RpdmF0aW9uEiMucXVpeG9zLm9yY2guQ2xvc2VBY3RpdmF0aW9uUmVxdWVzdBokLnF1aXhvcy5vcmNoLkNsb3NlQWN0aXZhdGlvblJlc3BvbnNlYgZwcm90bzM", [file_camino_api, file_camino_schema, file_quixos_package, file_quixos_refs, file_quixos_runtime]); + +/** + * Activated definitions, not runtime metrics or object data. Nested type JSON + * uses the compiler's canonical ValueType/InterfaceRevision public contracts. + * + * @generated from message quixos.orch.DefinitionSelector + */ +export type DefinitionSelector = Message<"quixos.orch.DefinitionSelector"> & { + /** + * active | atoms | interfaces | conformances | queries | indexes + * + * @generated from field: string kind = 1; + */ + kind: string; + + /** + * @generated from field: repeated string ids = 2; + */ + ids: string[]; + + /** + * @generated from field: string atom_id = 3; + */ + atomId: string; + + /** + * @generated from field: string interface_revision_id = 4; + */ + interfaceRevisionId: string; + + /** + * @generated from field: string package_revision_id = 5; + */ + packageRevisionId: string; +}; + +/** + * Describes the message quixos.orch.DefinitionSelector. + * Use `create(DefinitionSelectorSchema)` to create a new message. + */ +export const DefinitionSelectorSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_quixos_orch, 0); + +/** + * @generated from message quixos.orch.DefinitionIdentity + */ +export type DefinitionIdentity = Message<"quixos.orch.DefinitionIdentity"> & { + /** + * @generated from field: string workspace_id = 1; + */ + workspaceId: string; + + /** + * @generated from field: string workspace_revision_id = 2; + */ + workspaceRevisionId: string; + + /** + * @generated from field: string source_root_commit = 3; + */ + sourceRootCommit: string; + + /** + * @generated from field: string workspace_epoch = 4; + */ + workspaceEpoch: string; + + /** + * @generated from field: string definition_digest = 5; + */ + definitionDigest: string; +}; + +/** + * Describes the message quixos.orch.DefinitionIdentity. + * Use `create(DefinitionIdentitySchema)` to create a new message. + */ +export const DefinitionIdentitySchema: GenMessage = /*@__PURE__*/ + messageDesc(file_quixos_orch, 1); + +/** + * @generated from message quixos.orch.DefinitionRequest + */ +export type DefinitionRequest = Message<"quixos.orch.DefinitionRequest"> & { + /** + * @generated from field: quixos.orch.DefinitionSelector selector = 1; + */ + selector?: DefinitionSelector | undefined; + + /** + * 1..100; 0 uses 50 + * + * @generated from field: uint32 first = 2; + */ + first: number; + + /** + * @generated from field: string after = 3; + */ + after: string; + + /** + * issued definition_digest; current serving snapshot only + * + * @generated from field: string snapshot = 4; + */ + snapshot: string; + + /** + * required for injected package authority + * + * @generated from field: string workspace_id = 5; + */ + workspaceId: string; + + /** + * required for invocation-bound package calls + * + * @generated from field: string workspace_epoch = 6; + */ + workspaceEpoch: string; +}; + +/** + * Describes the message quixos.orch.DefinitionRequest. + * Use `create(DefinitionRequestSchema)` to create a new message. + */ +export const DefinitionRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_quixos_orch, 2); + +/** + * @generated from message quixos.orch.ReflectedAtom + */ +export type ReflectedAtom = Message<"quixos.orch.ReflectedAtom"> & { + /** + * @generated from field: string id = 1; + */ + id: string; + + /** + * @generated from field: string display_name = 2; + */ + displayName: string; + + /** + * @generated from field: string constructor_input_type_json = 3; + */ + constructorInputTypeJson: string; +}; + +/** + * Describes the message quixos.orch.ReflectedAtom. + * Use `create(ReflectedAtomSchema)` to create a new message. + */ +export const ReflectedAtomSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_quixos_orch, 3); + +/** + * @generated from message quixos.orch.ReflectedInterface + */ +export type ReflectedInterface = Message<"quixos.orch.ReflectedInterface"> & { + /** + * @generated from field: string id = 1; + */ + id: string; + + /** + * @generated from field: string interface_id = 2; + */ + interfaceId: string; + + /** + * @generated from field: string display_name = 3; + */ + displayName: string; + + /** + * canonical interface contract, excluding source location + * + * @generated from field: string contract_json = 4; + */ + contractJson: string; +}; + +/** + * Describes the message quixos.orch.ReflectedInterface. + * Use `create(ReflectedInterfaceSchema)` to create a new message. + */ +export const ReflectedInterfaceSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_quixos_orch, 4); + +/** + * @generated from message quixos.orch.ReflectedConformance + */ +export type ReflectedConformance = Message<"quixos.orch.ReflectedConformance"> & { + /** + * @generated from field: string id = 1; + */ + id: string; + + /** + * @generated from field: string atom_id = 2; + */ + atomId: string; + + /** + * @generated from field: string interface_revision_id = 3; + */ + interfaceRevisionId: string; + + /** + * @generated from field: string definition_id = 4; + */ + definitionId: string; + + /** + * @generated from field: repeated string operation_ids = 5; + */ + operationIds: string[]; + + /** + * @generated from field: repeated quixos.orch.ClassCapability class_capabilities = 6; + */ + classCapabilities: ClassCapability[]; + + /** + * @generated from field: bool instance = 7; + */ + instance: boolean; +}; + +/** + * Describes the message quixos.orch.ReflectedConformance. + * Use `create(ReflectedConformanceSchema)` to create a new message. + */ +export const ReflectedConformanceSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_quixos_orch, 5); + +/** + * @generated from message quixos.orch.ReflectedQuery + */ +export type ReflectedQuery = Message<"quixos.orch.ReflectedQuery"> & { + /** + * @generated from field: string id = 1; + */ + id: string; + + /** + * @generated from field: string name = 2; + */ + name: string; + + /** + * @generated from field: string package_revision_id = 3; + */ + packageRevisionId: string; + + /** + * @generated from field: string definition_digest = 4; + */ + definitionDigest: string; + + /** + * @generated from field: string binding_digest = 5; + */ + bindingDigest: string; + + /** + * @generated from field: string root_kind = 6; + */ + rootKind: string; + + /** + * @generated from field: string interface_revision_id = 7; + */ + interfaceRevisionId: string; + + /** + * @generated from field: string variables_type_json = 8; + */ + variablesTypeJson: string; + + /** + * @generated from field: string output_type_json = 9; + */ + outputTypeJson: string; + + /** + * @generated from field: string effects_json = 10; + */ + effectsJson: string; + + /** + * @generated from field: bool watch = 11; + */ + watch: boolean; + + /** + * @generated from field: quixos.WorkspaceQueryScope workspace_scope = 12; + */ + workspaceScope?: WorkspaceQueryScope | undefined; +}; + +/** + * Describes the message quixos.orch.ReflectedQuery. + * Use `create(ReflectedQuerySchema)` to create a new message. + */ +export const ReflectedQuerySchema: GenMessage = /*@__PURE__*/ + messageDesc(file_quixos_orch, 6); + +/** + * @generated from message quixos.orch.ReflectedIndex + */ +export type ReflectedIndex = Message<"quixos.orch.ReflectedIndex"> & { + /** + * @generated from field: string id = 1; + */ + id: string; + + /** + * @generated from field: string contract_json = 2; + */ + contractJson: string; +}; + +/** + * Describes the message quixos.orch.ReflectedIndex. + * Use `create(ReflectedIndexSchema)` to create a new message. + */ +export const ReflectedIndexSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_quixos_orch, 7); + +/** + * @generated from message quixos.orch.DefinitionRecord + */ +export type DefinitionRecord = Message<"quixos.orch.DefinitionRecord"> & { + /** + * @generated from oneof quixos.orch.DefinitionRecord.definition + */ + definition: { + /** + * @generated from field: quixos.orch.ReflectedAtom atom = 1; + */ + value: ReflectedAtom; + case: "atom"; + } | { + /** + * @generated from field: quixos.orch.ReflectedInterface interface = 2; + */ + value: ReflectedInterface; + case: "interface"; + } | { + /** + * @generated from field: quixos.orch.ReflectedConformance conformance = 3; + */ + value: ReflectedConformance; + case: "conformance"; + } | { + /** + * @generated from field: quixos.orch.ReflectedQuery query = 4; + */ + value: ReflectedQuery; + case: "query"; + } | { + /** + * @generated from field: quixos.orch.ReflectedIndex index = 5; + */ + value: ReflectedIndex; + case: "index"; + } | { case: undefined; value?: undefined }; +}; + +/** + * Describes the message quixos.orch.DefinitionRecord. + * Use `create(DefinitionRecordSchema)` to create a new message. + */ +export const DefinitionRecordSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_quixos_orch, 8); + +/** + * @generated from message quixos.orch.DefinitionPage + */ +export type DefinitionPage = Message<"quixos.orch.DefinitionPage"> & { + /** + * @generated from field: quixos.orch.DefinitionIdentity identity = 1; + */ + identity?: DefinitionIdentity | undefined; + + /** + * @generated from field: repeated quixos.orch.DefinitionRecord records = 2; + */ + records: DefinitionRecord[]; + + /** + * @generated from field: string next_cursor = 3; + */ + nextCursor: string; +}; + +/** + * Describes the message quixos.orch.DefinitionPage. + * Use `create(DefinitionPageSchema)` to create a new message. + */ +export const DefinitionPageSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_quixos_orch, 9); /** * @generated from message quixos.orch.QueryEvent @@ -67,7 +472,7 @@ export type QueryEvent = Message<"quixos.orch.QueryEvent"> & { * Use `create(QueryEventSchema)` to create a new message. */ export const QueryEventSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_quixos_orch, 0); + messageDesc(file_quixos_orch, 10); /** * Exact closed interface lookup; no policy selection or competing conformances. @@ -91,7 +496,7 @@ export type TryConformRequest = Message<"quixos.orch.TryConformRequest"> & { * Use `create(TryConformRequestSchema)` to create a new message. */ export const TryConformRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_quixos_orch, 1); + messageDesc(file_quixos_orch, 11); /** * @generated from message quixos.orch.TryConformResponse @@ -110,7 +515,7 @@ export type TryConformResponse = Message<"quixos.orch.TryConformResponse"> & { * Use `create(TryConformResponseSchema)` to create a new message. */ export const TryConformResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_quixos_orch, 2); + messageDesc(file_quixos_orch, 12); /** * @generated from message quixos.orch.ConstructObjectRequest @@ -132,7 +537,7 @@ export type ConstructObjectRequest = Message<"quixos.orch.ConstructObjectRequest * Use `create(ConstructObjectRequestSchema)` to create a new message. */ export const ConstructObjectRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_quixos_orch, 3); + messageDesc(file_quixos_orch, 13); /** * @generated from message quixos.orch.ConstructObjectResponse @@ -149,7 +554,7 @@ export type ConstructObjectResponse = Message<"quixos.orch.ConstructObjectRespon * Use `create(ConstructObjectResponseSchema)` to create a new message. */ export const ConstructObjectResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_quixos_orch, 4); + messageDesc(file_quixos_orch, 14); /** * @generated from message quixos.orch.ResolveOrConstructRelatedObjectRequest @@ -176,7 +581,7 @@ export type ResolveOrConstructRelatedObjectRequest = Message<"quixos.orch.Resolv * Use `create(ResolveOrConstructRelatedObjectRequestSchema)` to create a new message. */ export const ResolveOrConstructRelatedObjectRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_quixos_orch, 5); + messageDesc(file_quixos_orch, 15); /** * @generated from message quixos.orch.ResolveOrConstructRelatedObjectResponse @@ -198,7 +603,7 @@ export type ResolveOrConstructRelatedObjectResponse = Message<"quixos.orch.Resol * Use `create(ResolveOrConstructRelatedObjectResponseSchema)` to create a new message. */ export const ResolveOrConstructRelatedObjectResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_quixos_orch, 6); + messageDesc(file_quixos_orch, 16); /** * @generated from message quixos.orch.InvokeCapabilityRequest @@ -233,7 +638,7 @@ export type InvokeCapabilityRequest = Message<"quixos.orch.InvokeCapabilityReque * Use `create(InvokeCapabilityRequestSchema)` to create a new message. */ export const InvokeCapabilityRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_quixos_orch, 7); + messageDesc(file_quixos_orch, 17); /** * @generated from message quixos.orch.InvokeClassCapabilityRequest @@ -260,7 +665,7 @@ export type InvokeClassCapabilityRequest = Message<"quixos.orch.InvokeClassCapab * Use `create(InvokeClassCapabilityRequestSchema)` to create a new message. */ export const InvokeClassCapabilityRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_quixos_orch, 8); + messageDesc(file_quixos_orch, 18); /** * @generated from message quixos.orch.InvokeCapabilityResponse @@ -307,7 +712,7 @@ export type InvokeCapabilityResponse = Message<"quixos.orch.InvokeCapabilityResp * Use `create(InvokeCapabilityResponseSchema)` to create a new message. */ export const InvokeCapabilityResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_quixos_orch, 9); + messageDesc(file_quixos_orch, 19); /** * @generated from message quixos.orch.EditCapabilityFieldRequest @@ -363,7 +768,7 @@ export type EditCapabilityFieldRequest = Message<"quixos.orch.EditCapabilityFiel * Use `create(EditCapabilityFieldRequestSchema)` to create a new message. */ export const EditCapabilityFieldRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_quixos_orch, 10); + messageDesc(file_quixos_orch, 20); /** * @generated from message quixos.orch.WatchCapabilityRequest @@ -390,7 +795,7 @@ export type WatchCapabilityRequest = Message<"quixos.orch.WatchCapabilityRequest * Use `create(WatchCapabilityRequestSchema)` to create a new message. */ export const WatchCapabilityRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_quixos_orch, 11); + messageDesc(file_quixos_orch, 21); /** * @generated from message quixos.orch.WatchCapabilityEvent @@ -442,7 +847,7 @@ export type WatchCapabilityEvent = Message<"quixos.orch.WatchCapabilityEvent"> & * Use `create(WatchCapabilityEventSchema)` to create a new message. */ export const WatchCapabilityEventSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_quixos_orch, 12); + messageDesc(file_quixos_orch, 22); /** * @generated from message quixos.orch.GetWorkspaceRequest @@ -466,7 +871,7 @@ export type GetWorkspaceRequest = Message<"quixos.orch.GetWorkspaceRequest"> & { * Use `create(GetWorkspaceRequestSchema)` to create a new message. */ export const GetWorkspaceRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_quixos_orch, 13); + messageDesc(file_quixos_orch, 23); /** * @generated from message quixos.orch.GetWorkspaceResponse @@ -533,7 +938,7 @@ export type GetWorkspaceResponse = Message<"quixos.orch.GetWorkspaceResponse"> & * Use `create(GetWorkspaceResponseSchema)` to create a new message. */ export const GetWorkspaceResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_quixos_orch, 14); + messageDesc(file_quixos_orch, 24); /** * @generated from message quixos.orch.QueryDescription @@ -578,6 +983,11 @@ export type QueryDescription = Message<"quixos.orch.QueryDescription"> & { * @generated from field: camino.InstalledQuery plan = 8; */ plan?: InstalledQuery | undefined; + + /** + * @generated from field: quixos.WorkspaceQueryScope workspace_scope = 9; + */ + workspaceScope?: WorkspaceQueryScope | undefined; }; /** @@ -585,7 +995,7 @@ export type QueryDescription = Message<"quixos.orch.QueryDescription"> & { * Use `create(QueryDescriptionSchema)` to create a new message. */ export const QueryDescriptionSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_quixos_orch, 15); + messageDesc(file_quixos_orch, 25); /** * @generated from message quixos.orch.ClassCapability @@ -632,7 +1042,7 @@ export type ClassCapability = Message<"quixos.orch.ClassCapability"> & { * Use `create(ClassCapabilitySchema)` to create a new message. */ export const ClassCapabilitySchema: GenMessage = /*@__PURE__*/ - messageDesc(file_quixos_orch, 16); + messageDesc(file_quixos_orch, 26); /** * @generated from message quixos.orch.CapabilityInputContract @@ -659,7 +1069,7 @@ export type CapabilityInputContract = Message<"quixos.orch.CapabilityInputContra * Use `create(CapabilityInputContractSchema)` to create a new message. */ export const CapabilityInputContractSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_quixos_orch, 17); + messageDesc(file_quixos_orch, 27); /** * @generated from message quixos.orch.ConstructorInputContract @@ -681,7 +1091,7 @@ export type ConstructorInputContract = Message<"quixos.orch.ConstructorInputCont * Use `create(ConstructorInputContractSchema)` to create a new message. */ export const ConstructorInputContractSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_quixos_orch, 18); + messageDesc(file_quixos_orch, 28); /** * @generated from message quixos.orch.ListActivationsRequest @@ -694,7 +1104,7 @@ export type ListActivationsRequest = Message<"quixos.orch.ListActivationsRequest * Use `create(ListActivationsRequestSchema)` to create a new message. */ export const ListActivationsRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_quixos_orch, 19); + messageDesc(file_quixos_orch, 29); /** * @generated from message quixos.orch.ListPackageDescriptorsRequest @@ -707,7 +1117,7 @@ export type ListPackageDescriptorsRequest = Message<"quixos.orch.ListPackageDesc * Use `create(ListPackageDescriptorsRequestSchema)` to create a new message. */ export const ListPackageDescriptorsRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_quixos_orch, 20); + messageDesc(file_quixos_orch, 30); /** * @generated from message quixos.orch.ListPackageDescriptorsResponse @@ -724,7 +1134,7 @@ export type ListPackageDescriptorsResponse = Message<"quixos.orch.ListPackageDes * Use `create(ListPackageDescriptorsResponseSchema)` to create a new message. */ export const ListPackageDescriptorsResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_quixos_orch, 21); + messageDesc(file_quixos_orch, 31); /** * @generated from message quixos.orch.ListPackageRuntimesRequest @@ -737,7 +1147,7 @@ export type ListPackageRuntimesRequest = Message<"quixos.orch.ListPackageRuntime * Use `create(ListPackageRuntimesRequestSchema)` to create a new message. */ export const ListPackageRuntimesRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_quixos_orch, 22); + messageDesc(file_quixos_orch, 32); /** * @generated from message quixos.orch.ListPackageRuntimesResponse @@ -754,7 +1164,7 @@ export type ListPackageRuntimesResponse = Message<"quixos.orch.ListPackageRuntim * Use `create(ListPackageRuntimesResponseSchema)` to create a new message. */ export const ListPackageRuntimesResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_quixos_orch, 23); + messageDesc(file_quixos_orch, 33); /** * @generated from message quixos.orch.ListActivationsResponse @@ -771,7 +1181,7 @@ export type ListActivationsResponse = Message<"quixos.orch.ListActivationsRespon * Use `create(ListActivationsResponseSchema)` to create a new message. */ export const ListActivationsResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_quixos_orch, 24); + messageDesc(file_quixos_orch, 34); /** * @generated from message quixos.orch.CloseActivationRequest @@ -793,7 +1203,7 @@ export type CloseActivationRequest = Message<"quixos.orch.CloseActivationRequest * Use `create(CloseActivationRequestSchema)` to create a new message. */ export const CloseActivationRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_quixos_orch, 25); + messageDesc(file_quixos_orch, 35); /** * @generated from message quixos.orch.CloseActivationResponse @@ -810,7 +1220,7 @@ export type CloseActivationResponse = Message<"quixos.orch.CloseActivationRespon * Use `create(CloseActivationResponseSchema)` to create a new message. */ export const CloseActivationResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_quixos_orch, 26); + messageDesc(file_quixos_orch, 36); /** * @generated from message quixos.orch.Activation @@ -872,7 +1282,7 @@ export type Activation = Message<"quixos.orch.Activation"> & { * Use `create(ActivationSchema)` to create a new message. */ export const ActivationSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_quixos_orch, 27); + messageDesc(file_quixos_orch, 37); /** * @generated from message quixos.orch.PackageRuntimeStatus @@ -944,12 +1354,28 @@ export type PackageRuntimeStatus = Message<"quixos.orch.PackageRuntimeStatus"> & * Use `create(PackageRuntimeStatusSchema)` to create a new message. */ export const PackageRuntimeStatusSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_quixos_orch, 28); + messageDesc(file_quixos_orch, 38); /** * @generated from service quixos.orch.OrchestratorRuntime */ export const OrchestratorRuntime: GenService<{ + /** + * @generated from rpc quixos.orch.OrchestratorRuntime.ReadDefinitions + */ + readDefinitions: { + methodKind: "unary"; + input: typeof DefinitionRequestSchema; + output: typeof DefinitionPageSchema; + }, + /** + * @generated from rpc quixos.orch.OrchestratorRuntime.WatchDefinitions + */ + watchDefinitions: { + methodKind: "server_streaming"; + input: typeof DefinitionRequestSchema; + output: typeof DefinitionPageSchema; + }, /** * @generated from rpc quixos.orch.OrchestratorRuntime.ExecuteQuery */ diff --git a/src/gen/quixos/refs_pb.ts b/src/gen/quixos/refs_pb.ts index 7e54117..12d7c24 100644 --- a/src/gen/quixos/refs_pb.ts +++ b/src/gen/quixos/refs_pb.ts @@ -10,7 +10,7 @@ import type { Message } from "@bufbuild/protobuf"; * Describes the file quixos/refs.proto. */ export const file_quixos_refs: GenFile = /*@__PURE__*/ - fileDesc("ChFxdWl4b3MvcmVmcy5wcm90bxIGcXVpeG9zInUKDUNhcGFiaWxpdHlSZWYSHQoVaW50ZXJmYWNlX3JldmlzaW9uX2lkGAEgASgJEhQKDG9wZXJhdGlvbl9pZBgCIAEoCRIvCgtjb25mb3JtYW5jZRgDIAEoCzIaLnF1aXhvcy5Db25mb3JtYW5jZVdpdG5lc3Mi0QEKDEZpZWxkRWRpdGluZxIbChNnZXR0ZXJfb3BlcmF0aW9uX2lkGAEgASgJEhsKE3NldHRlcl9vcGVyYXRpb25faWQYAiABKAkSFQoNZG9jdW1lbnRfdHlwZRgDIAEoCRIWCg5iaW5kaW5nX2RpZ2VzdBgEIAEoCRInCgRtb2RlGAUgASgOMhkucXVpeG9zLkZpZWxkRWRpdGluZy5Nb2RlIi8KBE1vZGUSDwoLVU5TUEVDSUZJRUQQABIMCghSRUdJU1RFUhABEggKBENSRFQQAiKWAQoSQ29uZm9ybWFuY2VXaXRuZXNzEhEKCW9iamVjdF9pZBgBIAEoCRIdChVpbnRlcmZhY2VfcmV2aXNpb25faWQYAiABKAkSFgoOY29uZm9ybWFuY2VfaWQYAyABKAkSHQoVd29ya3NwYWNlX3JldmlzaW9uX2lkGAQgASgJEhcKD3dvcmtzcGFjZV9lcG9jaBgFIAEoCSJCChBQYWNrYWdlRXhwb3J0UmVmEhsKE3BhY2thZ2VfcmV2aXNpb25faWQYASABKAkSEQoJZXhwb3J0X2lkGAIgASgJItgBChJJbmplY3RlZERlcGVuZGVuY3kSDwoHcG9ydF9pZBgBIAEoCRIXCg1zdGF0ZV9zbG90X2lkGAIgASgJSAASJgoEZWRnZRgDIAEoCzIWLnF1aXhvcy5FZGdlRGVwZW5kZW5jeUgAEh8KFWludGVyZmFjZV9yZXZpc2lvbl9pZBgEIAEoCUgAEh0KE2NvbnN0cnVjdG9yX2F0b21faWQYBSABKAlIABISCghxdWVyeV9pZBgHIAEoCUgAEhEKCW9iamVjdF9pZBgGIAEoCUIJCgdiaW5kaW5nIj0KDkVkZ2VEZXBlbmRlbmN5EhQKDGVkZ2VfdHlwZV9pZBgBIAEoCRIVCg1wcm9qZWN0aW9uX2lkGAIgASgJYgZwcm90bzM"); + fileDesc("ChFxdWl4b3MvcmVmcy5wcm90bxIGcXVpeG9zInUKDUNhcGFiaWxpdHlSZWYSHQoVaW50ZXJmYWNlX3JldmlzaW9uX2lkGAEgASgJEhQKDG9wZXJhdGlvbl9pZBgCIAEoCRIvCgtjb25mb3JtYW5jZRgDIAEoCzIaLnF1aXhvcy5Db25mb3JtYW5jZVdpdG5lc3Mi0QEKDEZpZWxkRWRpdGluZxIbChNnZXR0ZXJfb3BlcmF0aW9uX2lkGAEgASgJEhsKE3NldHRlcl9vcGVyYXRpb25faWQYAiABKAkSFQoNZG9jdW1lbnRfdHlwZRgDIAEoCRIWCg5iaW5kaW5nX2RpZ2VzdBgEIAEoCRInCgRtb2RlGAUgASgOMhkucXVpeG9zLkZpZWxkRWRpdGluZy5Nb2RlIi8KBE1vZGUSDwoLVU5TUEVDSUZJRUQQABIMCghSRUdJU1RFUhABEggKBENSRFQQAiKWAQoSQ29uZm9ybWFuY2VXaXRuZXNzEhEKCW9iamVjdF9pZBgBIAEoCRIdChVpbnRlcmZhY2VfcmV2aXNpb25faWQYAiABKAkSFgoOY29uZm9ybWFuY2VfaWQYAyABKAkSHQoVd29ya3NwYWNlX3JldmlzaW9uX2lkGAQgASgJEhcKD3dvcmtzcGFjZV9lcG9jaBgFIAEoCSJCChBQYWNrYWdlRXhwb3J0UmVmEhsKE3BhY2thZ2VfcmV2aXNpb25faWQYASABKAkSEQoJZXhwb3J0X2lkGAIgASgJIskCChJJbmplY3RlZERlcGVuZGVuY3kSDwoHcG9ydF9pZBgBIAEoCRIXCg1zdGF0ZV9zbG90X2lkGAIgASgJSAASJgoEZWRnZRgDIAEoCzIWLnF1aXhvcy5FZGdlRGVwZW5kZW5jeUgAEh8KFWludGVyZmFjZV9yZXZpc2lvbl9pZBgEIAEoCUgAEh0KE2NvbnN0cnVjdG9yX2F0b21faWQYBSABKAlIABISCghxdWVyeV9pZBgHIAEoCUgAEjsKD3dvcmtzcGFjZV9xdWVyeRgIIAEoCzIgLnF1aXhvcy5Xb3Jrc3BhY2VRdWVyeURlcGVuZGVuY3lIABIyCgpyZWZsZWN0aW9uGAkgASgLMhwucXVpeG9zLlJlZmxlY3Rpb25EZXBlbmRlbmN5SAASEQoJb2JqZWN0X2lkGAYgASgJQgkKB2JpbmRpbmciWAoUUmVmbGVjdGlvbkRlcGVuZGVuY3kSFAoMd29ya3NwYWNlX2lkGAEgASgJEhcKD3dvcmtzcGFjZV9lcG9jaBgCIAEoCRIRCglzZWxlY3RvcnMYAyADKAkimgEKE1dvcmtzcGFjZVF1ZXJ5U2NvcGUSFAoMd29ya3NwYWNlX2lkGAEgASgJEh0KFXdvcmtzcGFjZV9yZXZpc2lvbl9pZBgCIAEoCRIXCg93b3Jrc3BhY2VfZXBvY2gYAyABKAkSHQoVaW50ZXJmYWNlX3JldmlzaW9uX2lkGAQgASgJEhYKDmJpbmRpbmdfZGlnZXN0GAUgASgJIlgKGFdvcmtzcGFjZVF1ZXJ5RGVwZW5kZW5jeRIQCghxdWVyeV9pZBgBIAEoCRIqCgVzY29wZRgCIAEoCzIbLnF1aXhvcy5Xb3Jrc3BhY2VRdWVyeVNjb3BlIj0KDkVkZ2VEZXBlbmRlbmN5EhQKDGVkZ2VfdHlwZV9pZBgBIAEoCRIVCg1wcm9qZWN0aW9uX2lkGAIgASgJYgZwcm90bzM"); /** * @generated from message quixos.CapabilityRef @@ -207,6 +207,18 @@ export type InjectedDependency = Message<"quixos.InjectedDependency"> & { */ value: string; case: "queryId"; + } | { + /** + * @generated from field: quixos.WorkspaceQueryDependency workspace_query = 8; + */ + value: WorkspaceQueryDependency; + case: "workspaceQuery"; + } | { + /** + * @generated from field: quixos.ReflectionDependency reflection = 9; + */ + value: ReflectionDependency; + case: "reflection"; } | { case: undefined; value?: undefined }; /** @@ -225,6 +237,95 @@ export type InjectedDependency = Message<"quixos.InjectedDependency"> & { export const InjectedDependencySchema: GenMessage = /*@__PURE__*/ messageDesc(file_quixos_refs, 4); +/** + * @generated from message quixos.ReflectionDependency + */ +export type ReflectionDependency = Message<"quixos.ReflectionDependency"> & { + /** + * @generated from field: string workspace_id = 1; + */ + workspaceId: string; + + /** + * @generated from field: string workspace_epoch = 2; + */ + workspaceEpoch: string; + + /** + * @generated from field: repeated string selectors = 3; + */ + selectors: string[]; +}; + +/** + * Describes the message quixos.ReflectionDependency. + * Use `create(ReflectionDependencySchema)` to create a new message. + */ +export const ReflectionDependencySchema: GenMessage = /*@__PURE__*/ + messageDesc(file_quixos_refs, 5); + +/** + * Scope evidence is not authority by itself. The caller must also hold the + * matching workspace-query grant (or the authenticated workspace user grant). + * + * @generated from message quixos.WorkspaceQueryScope + */ +export type WorkspaceQueryScope = Message<"quixos.WorkspaceQueryScope"> & { + /** + * @generated from field: string workspace_id = 1; + */ + workspaceId: string; + + /** + * @generated from field: string workspace_revision_id = 2; + */ + workspaceRevisionId: string; + + /** + * @generated from field: string workspace_epoch = 3; + */ + workspaceEpoch: string; + + /** + * @generated from field: string interface_revision_id = 4; + */ + interfaceRevisionId: string; + + /** + * @generated from field: string binding_digest = 5; + */ + bindingDigest: string; +}; + +/** + * Describes the message quixos.WorkspaceQueryScope. + * Use `create(WorkspaceQueryScopeSchema)` to create a new message. + */ +export const WorkspaceQueryScopeSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_quixos_refs, 6); + +/** + * @generated from message quixos.WorkspaceQueryDependency + */ +export type WorkspaceQueryDependency = Message<"quixos.WorkspaceQueryDependency"> & { + /** + * @generated from field: string query_id = 1; + */ + queryId: string; + + /** + * @generated from field: quixos.WorkspaceQueryScope scope = 2; + */ + scope?: WorkspaceQueryScope | undefined; +}; + +/** + * Describes the message quixos.WorkspaceQueryDependency. + * Use `create(WorkspaceQueryDependencySchema)` to create a new message. + */ +export const WorkspaceQueryDependencySchema: GenMessage = /*@__PURE__*/ + messageDesc(file_quixos_refs, 7); + /** * @generated from message quixos.EdgeDependency */ @@ -245,5 +346,5 @@ export type EdgeDependency = Message<"quixos.EdgeDependency"> & { * Use `create(EdgeDependencySchema)` to create a new message. */ export const EdgeDependencySchema: GenMessage = /*@__PURE__*/ - messageDesc(file_quixos_refs, 5); + messageDesc(file_quixos_refs, 8); diff --git a/src/query/compile.ts b/src/query/compile.ts index c899715..6711e72 100644 --- a/src/query/compile.ts +++ b/src/query/compile.ts @@ -3,6 +3,9 @@ import { Source, validate, specifiedRules, + PossibleFragmentSpreadsRule, + NoUnusedVariablesRule, + GraphQLError, print, printSchema, visit, @@ -35,7 +38,9 @@ import { } from "../capability-model/types.js"; import { querySchema } from "./schema.js"; import { objectRow } from "./relational-schema.js"; +import { workspaceQueryRootId } from "./root.js"; import { relationalCompiler } from "./relational-compile.js"; +import { fragmentSources, type FragmentResolver } from "./fragments.js"; import { QueryCompileError, type QueryDeclaration, @@ -61,11 +66,13 @@ const fail = (code: string, message: string, node?: ASTNode): never => { ); }; -/** Only immutable repository sources call this. Runtime requests never compile documents. */ +/** Shared checker for authored queries and bounded public query documents. + * Runtime execution receives its checked IR, never an unchecked document. */ export async function compileQuery( declaration: QueryDeclaration, interfaces: readonly InterfaceRevision[], read: (name: string) => Promise, + resolveFragment?: FragmentResolver, ): Promise { const paths = [declaration.document, ...declaration.fragments]; if (paths.length > 128 || new Set(paths).size !== paths.length) @@ -85,9 +92,59 @@ export async function compileQuery( const document = parse(new Source(text, path), { maxTokens: 10000 }); definitions.push(...document.definitions); } - const document: DocumentNode = { kind: "Document" as DocumentNode["kind"], definitions }; + const fragmentContracts = await fragmentSources(declaration, definitions, resolveFragment); + let document: DocumentNode = { kind: "Document" as DocumentNode["kind"], definitions }; + if (definitions.length > 256 || Buffer.byteLength(print(document)) > 262144) + fail("QUERY_SOURCE_LIMIT", "Expanded fragment sources exceed the query source budget"); + visit(document, { + Directive(node) { + if (node.name.value === "live") + fail( + "QUERY_RETIRED_DIRECTIVE", + "@live is retired. Observations watch ordinary query values; use checked operations for mutations.", + node, + ); + }, + }); const generated = querySchema(declaration, interfaces, document); - const operations = definitions.filter((entry) => entry.kind === "OperationDefinition"); + // Identity belongs to the checked object result, including fragment results. + // Schema membership deliberately excludes structured values and query wrappers. + const identity = parse("query Identity { _camino { ref } }", { noLocation: true }).definitions[0]!; + if (identity.kind !== "OperationDefinition") throw new Error("Invalid identity selection"); + const identitySelection = identity.selectionSet.selections[0]!; + const identityTypes = new TypeInfo(generated.schema); + document = visit( + document, + visitWithTypeInfo(identityTypes, { + SelectionSet: { + leave(node, _key, parentNode) { + // An inline projection is part of its containing object. Its identity + // remains that object's base interface, not the projected interface. + if (parentNode && !Array.isArray(parentNode) && "kind" in parentNode && parentNode.kind === "InlineFragment") + return; + const parent = identityTypes.getParentType(); + const contract = parent && generated.byName.get(parent.name); + if (!contract || contract.revisionId === workspaceQueryRootId) return; + const hasIdentity = node.selections.some( + (field) => + field.kind === "Field" && + field.name.value === "_camino" && + !field.alias && + !field.directives?.length && + field.selectionSet?.selections.some( + (child) => + child.kind === "Field" && child.name.value === "ref" && !child.alias && !child.directives?.length, + ), + ); + // GraphQL validation caches comparisons by AST identity. Each nominal + // object must get its own metadata subtree, even though the spelling is + // identical (its reference scalar can be different). + if (!hasIdentity) return { ...node, selections: [...node.selections, structuredClone(identitySelection)] }; + }, + }, + }), + ); + const operations = document.definitions.filter((entry) => entry.kind === "OperationDefinition"); if ( operations.length !== 1 || operations[0]!.operation !== "query" || @@ -101,29 +158,112 @@ export async function compileQuery( if (node.name.value.startsWith("__")) fail("QUERY_UNSUPPORTED_FEATURE", "Introspection is not supported", node); }, Directive(node) { - if (!["include", "skip", "live"].includes(node.name.value)) + if (!["include", "skip"].includes(node.name.value)) fail("QUERY_UNSUPPORTED_FEATURE", `Unsupported directive ${node.name.value}`, node); }, - InlineFragment(node) { - fail("QUERY_UNSUPPORTED_FEATURE", "Use named fragments on the exact selected type", node); - }, }); - const errors = validate(generated.schema, document, specifiedRules, { maxErrors: 20 }); + // Capability interfaces are nominal views of one graph object, not mutually + // exclusive GraphQL object variants. Check overlap using declared requirements. + const projectionTypes = new TypeInfo(generated.schema); + const requires = (from: InterfaceRevisionId, to: InterfaceRevisionId, seen = new Set()): boolean => { + if (from === to) return true; + if (seen.has(from)) return false; + seen.add(from); + return generated.contracts.get(from)?.requiredInterfaces?.some((id) => requires(id, to, seen)) ?? false; + }; + visit( + document, + visitWithTypeInfo(projectionTypes, { + Field(node) { + const parent = projectionTypes.getParentType(); + const member = + parent && generated.byName.get(parent.name)?.members.find((member) => member.displayName === node.name.value); + if (member?.kind === "relationship") generated.target(member); + }, + InlineFragment(node) { + if (!node.typeCondition) return; + const from = projectionTypes.getParentType(); + const a = from && generated.byName.get(from.name); + const b = generated.byName.get(node.typeCondition.name.value); + if (!a || !b || !requires(a.revisionId, b.revisionId)) + fail( + "QUERY_UNSUPPORTED_FEATURE", + "Inline interface projections currently require a declared interface requirement", + node, + ); + }, + FragmentSpread(node) { + const fragment = document.definitions.find( + (entry) => entry.kind === "FragmentDefinition" && entry.name.value === node.name.value, + ); + if ( + fragment?.kind === "FragmentDefinition" && + fragment.typeCondition.name.value !== projectionTypes.getParentType()?.name + ) + fail("QUERY_UNSUPPORTED_FEATURE", "Named fragments currently require the exact nominal root interface", node); + }, + }), + ); + const bindingVariables = new Set( + [...fragmentContracts.values()] + .filter((entry) => entry.mode === "reference") + .flatMap((entry) => Object.values(entry.variables)), + ); + const errors = validate( + generated.schema, + document, + [ + ...specifiedRules.filter((rule) => rule !== PossibleFragmentSpreadsRule && rule !== NoUnusedVariablesRule), + (context) => ({ + OperationDefinition: { + leave(operation) { + const used = new Set(context.getRecursiveVariableUsages(operation).map(({ node }) => node.name.value)); + for (const variable of operation.variableDefinitions ?? []) { + const name = variable.variable.name.value; + if (!used.has(name) && !bindingVariables.has(name)) + context.reportError(new GraphQLError(`Variable "$${name}" is never used.`, { nodes: variable })); + } + }, + }, + }), + ], + { maxErrors: 20 }, + ); if (errors.length) { const error = errors[0]!; fail("QUERY_VALIDATION", error.message, error.nodes?.[0]); } const effects = new Map(); const fragments = new Map( - definitions + document.definitions .filter((entry): entry is FragmentDefinitionNode => entry.kind === "FragmentDefinition") .map((entry) => [entry.name.value, entry]), ); const mark = (id: InterfaceRevisionId, name: string, use: QueryUse, node: ASTNode) => { + // The virtual extent has no provider/getter effect. Its membership is + // linked separately, including when the query only selects references. + if (id === workspaceQueryRootId) return; const contract = generated.contracts.get(id)!; const member = contract.members.find((entry) => entry.displayName === name); - if (!member || member.kind === "operation" || !member.queryRead) + if (!member || member.kind === "operation" || member.kind === "module" || !member.queryRead) return fail("QUERY_FIELD_NOT_QUERYABLE", `${contract.displayName}.${name} is not queryable`, node); + if (declaration.placement?.kind === "local") { + const getter = member.operations.find( + (operation) => operation.displayName === (member.kind === "value" ? "get" : "resolve"), + ); + if (getter?.execution?.kind !== "snapshot") + fail( + "QUERY_LOCAL_DEPENDENCY", + `Local query requires a graph snapshot getter for ${contract.displayName}.${name}; split the remote field or move the query to the server`, + node, + ); + if (member.queryRead.execution === "rpc-permitted") + fail( + "QUERY_LOCAL_DEPENDENCY", + `Local query cannot include remote-compute field ${contract.displayName}.${name}`, + node, + ); + } const key = `${id}\0${member.id}`; const effect = effects.get(key) ?? { interfaceRevisionId: id, @@ -180,16 +320,7 @@ export async function compileQuery( Field(node) { const parent = info.getParentType(); const contract = parent && generated.byName.get(parent.name); - if (node.directives?.some((directive) => directive.name.value === "live")) { - const member = contract?.members.find((entry) => entry.displayName === node.name.value); - if (!member || member.kind !== "value" || member.queryRead?.execution !== "native") - fail( - "QUERY_LIVE_UNSUPPORTED", - "@live requires a direct native-queryable value field, not RPC enrichment or a synthetic result", - node, - ); - } - if (!contract || node.name.value === "_qx") return; + if (!contract || node.name.value === "_camino") return; mark(contract.revisionId, node.name.value, "select", node); const member = contract.members.find((entry) => entry.displayName === node.name.value)!; if (member.kind !== "relationship" || (member.cardinality !== "many" && member.cardinality !== "many-unique")) @@ -203,7 +334,13 @@ export async function compileQuery( fail("QUERY_UNSUPPORTED_FEATURE", "Bounded-all does not accept a continuation", node); for (const argument of node.arguments ?? []) { if (argument.name.value === "where") - compiledPredicates.set(node, relational.predicate(objectRow(target), argument.value)); + compiledPredicates.set( + node, + relational.predicate( + objectRow(target, member.keyType ? { keyType: member.keyType } : undefined), + argument.value, + ), + ); if (argument.name.value === "orderBy") inputEffects(target, argument.value, "order"); if ( ["first", "all"].includes(argument.name.value) && @@ -280,16 +417,29 @@ export async function compileQuery( if (isListType(type)) return valueType.list(output(type.ofType, selections, depth, conditional)); if (isObjectType(type)) { const fields: Record = {}; - const add = (set: SelectionSetNode, conditional = false) => { + const add = (set: SelectionSetNode, conditional = false, selectedType = type) => { for (const selection of set.selections) { if (++expandedFields > 10000) fail("QUERY_WORK_LIMIT", "Expanded query exceeds 10000 fields", selection); if (selection.kind === "FragmentSpread") { - add(fragments.get(selection.name.value)!.selectionSet, conditional || !!selection.directives?.length); + if (fragmentContracts.get(selection.name.value)?.mode === "reference") continue; + add( + fragments.get(selection.name.value)!.selectionSet, + conditional || !!selection.directives?.length, + selectedType, + ); + continue; + } + if (selection.kind === "InlineFragment") { + const projected = selection.typeCondition + ? generated.schema.getType(selection.typeCondition.name.value) + : selectedType; + if (!isObjectType(projected)) return fail("QUERY_TYPE", "Expected an object projection", selection); + add(selection.selectionSet, conditional || !!selection.directives?.length, projected); continue; } if (selection.kind !== "Field") continue; const key = selection.alias?.value ?? selection.name.value; - const field = type.getFields()[selection.name.value]!; + const field = selectedType.getFields()[selection.name.value]!; const previous = fields[key]; fields[key] = output( field.type, @@ -305,7 +455,9 @@ export async function compileQuery( if (previous) fields[key] = mergeOutput(previous, fields[key]!); } }; - if (selections) add(selections, conditional); + // A condition on this object makes the object optional, not every child + // of the object when it is present. Fragment conditions are handled by add. + if (selections) add(selections); return { kind: "record", fields }; } return ( @@ -376,17 +528,41 @@ export async function compileQuery( set: SelectionSetNode, parent: import("graphql").GraphQLObjectType, inherited: QuerySelection["conditions"] = [], - ): QuerySelection[] => - set.selections.flatMap((node): QuerySelection[] => { - if (node.kind === "FragmentSpread") - return selection(fragments.get(node.name.value)!.selectionSet, parent, [ - ...inherited, - ...conditions(node.directives), - ]); + ): QuerySelection[] => { + const selections = set.selections.flatMap((node): QuerySelection[] => { + if (node.kind === "InlineFragment") { + const projected = node.typeCondition ? generated.schema.getType(node.typeCondition.name.value) : parent; + if (!isObjectType(projected)) return fail("QUERY_TYPE", "Expected an object projection", node); + return selection(node.selectionSet, projected, [...inherited, ...conditions(node.directives)]); + } + if (node.kind === "FragmentSpread") { + const inheritedConditions = [...inherited, ...conditions(node.directives)]; + const contract = fragmentContracts.get(node.name.value); + if (contract && generated.byName.get(parent.name)?.revisionId !== contract.root) + fail("FRAGMENT_ROOT_MISMATCH", "Imported fragment requires its exact nominal root interface", node); + return [ + ...(contract + ? [ + { + name: "$fragment", + key: "", + selection: [], + arguments: {}, + conditions: inheritedConditions, + fragmentSpread: contract, + }, + ] + : []), + ...(contract?.mode === "reference" + ? [] + : selection(fragments.get(node.name.value)!.selectionSet, parent, inheritedConditions)), + ]; + } if (node.kind !== "Field") return []; const contract = generated.byName.get(parent.name); const member = contract?.members.find((entry) => entry.displayName === node.name.value); const type = getNamedType(parent.getFields()[node.name.value]!.type); + const referenceType = type && generated.scalarTypes.get(type.name); const role = generated.relational.roles.get(parent.name); const relationship = role?.kind === "relations" @@ -404,15 +580,13 @@ export async function compileQuery( { name: node.name.value, key: node.alias?.value ?? node.name.value, - ...(node.directives?.some((directive) => directive.name.value === "live") - ? { - liveSelectionId: createHash("sha256") - .update(JSON.stringify([node.loc?.source.name, node.loc?.start, contract?.revisionId, member?.id])) - .digest("hex"), - } - : {}), ...(member && contract ? { interfaceRevisionId: contract.revisionId, memberId: member.id } : {}), ...(member?.kind === "relationship" ? { targetInterfaceRevisionId: generated.target(member) } : {}), + ...(node.name.value === "ref" && + referenceType?.kind === "object-ref" && + referenceType.expectation.kind === "interface" + ? { targetInterfaceRevisionId: referenceType.expectation.interfaceRevisionId } + : {}), conditions: [...inherited, ...conditions(node.directives)], arguments: Object.fromEntries( (node.arguments ?? []).map((entry) => [entry.name.value, argument(entry.value)]), @@ -423,30 +597,65 @@ export async function compileQuery( }, ]; }); + const contract = generated.byName.get(parent.name); + if (!contract || contract.revisionId === workspaceQueryRootId) return selections; + // Fragment expansion may repeat metadata. Combine its children so a later + // implicit reference cannot overwrite an explicitly selected aggregate. + const metadata = selections.filter((field) => field.key === "_camino" && field.name === "_camino"); + const responseFields = new Map(); + for (const field of selections.filter((field) => field.memberId)) { + const previous = responseFields.get(field.key); + if ( + previous && + (previous.interfaceRevisionId !== field.interfaceRevisionId || previous.memberId !== field.memberId) + ) + fail("QUERY_PROJECTION_CONFLICT", `Alias distinct interface fields selected as ${field.key}`); + responseFields.set(field.key, field); + } + if (!metadata.length) return selections; + const children = metadata.flatMap((field) => + field.selection.map((child) => ({ + ...child, + conditions: [...field.conditions, ...child.conditions], + })), + ); + const ref = children.find((child) => child.key === "ref" && child.name === "ref")!; + return [ + ...selections.filter((field) => !metadata.includes(field)), + { + ...metadata[0]!, + conditions: [], + selection: [...children.filter((child) => child.key !== "ref"), { ...ref, conditions: [] }], + }, + ]; + }; const normalized = print(document), schema = printSchema(generated.schema); const selections = selection(operations[0]!.selectionSet, generated.schema.getQueryType()!); - const checkPresentation = (entries: QuerySelection[]) => { - for (const key of new Set(entries.map((entry) => entry.key))) { - const siblings = entries.filter((entry) => entry.key === key); - if (siblings.some((entry) => entry.liveSelectionId) && siblings.some((entry) => !entry.liveSelectionId)) - throw new QueryCompileError( - "QUERY_LIVE_CONFLICT", - `${key} mixes live and plain selections; use separate aliases`, - ); - checkPresentation(siblings.flatMap((entry) => entry.selection)); - } - }; - checkPresentation(selections); const definitionDigest = createHash("sha256") .update( canonicalJson({ - semantics: 2, + semantics: 6, declaration, normalized, - interfaces: [...generated.byName.values()].sort((a, b) => - a.revisionId < b.revisionId ? -1 : a.revisionId > b.revisionId ? 1 : 0, - ), + fragmentContracts: Object.fromEntries(fragmentContracts), + // Source provenance belongs to the checked installation. Identical public + // contracts keep their query identity when retained in another repository. + // Closed generic identity remains explicit in revisionId/application; + // its template's retention source is provenance too. byName also holds + // names probed while discovering nominal projections, so only include + // interfaces actually present in the generated query schema. + interfaces: Object.values(generated.schema.getTypeMap()) + .filter(isObjectType) + .flatMap((type) => { + const revision = generated.byName.get(type.name); + if (!revision) return []; + const { source: _source, application, ...contract } = revision; + if (!application) return [contract]; + const { source: _templateSource, ...identity } = application; + return [{ ...contract, application: identity }]; + }) + .sort((a, b) => (a.revisionId < b.revisionId ? -1 : a.revisionId > b.revisionId ? 1 : 0)), }), ) .digest("hex"); @@ -465,6 +674,7 @@ export async function compileQuery( .map((entry) => [entry.variable.name.value, argument(entry.defaultValue!)]), ), sourceFiles: paths, + ...(fragmentContracts.size ? { fragmentContracts: Object.fromEntries(fragmentContracts) } : {}), }; } @@ -472,10 +682,11 @@ export const compileRepositoryQuery = async ( root: string, declaration: QueryDeclaration, interfaces: readonly InterfaceRevision[], + resolveFragment?: FragmentResolver, ) => { const started = performance.now(); try { - return await compileQuery(declaration, interfaces, (name) => readRepositorySource(root, name)); + return await compileQuery(declaration, interfaces, (name) => readRepositorySource(root, name), resolveFragment); } finally { // Timing belongs in check logs, never in immutable artifact identities. console.error( diff --git a/src/query/composition.ts b/src/query/composition.ts new file mode 100644 index 0000000..5c472a7 --- /dev/null +++ b/src/query/composition.ts @@ -0,0 +1,157 @@ +import { createHash } from "node:crypto"; +import type { ValueType } from "../capability-model/types.js"; +import { canonicalJson } from "../capability-model/evolution.js"; +import { QueryCompileError, type CheckedQuery } from "./types.js"; + +/** Bindings refer to checked values, never executable callbacks. A path cannot + * silently cross a nullable value or expand an unbounded collection. */ +export type QueryBinding = + | { flatMap: { source: QueryBinding; path: string[] } } + | { literal: string | boolean } + | { equal: [QueryBinding, QueryBinding] } + | { from: "input"; path: string[] } + | { from: "item"; path: string[] } + | { from: "result"; name: string; path: string[] } + | { record: Record }; + +export interface QueryCompositionStep { + name: string; + query: string; + root: QueryBinding | null; + arguments: QueryBinding; + /** Null roots skip this step and produce null, rather than querying an + * arbitrary object or treating unavailable query inputs as absence. */ + optional?: boolean; + when?: QueryBinding; + /** An optional object is unwrapped as item; absence skips the query. */ + with?: QueryBinding; + each?: { source: QueryBinding; limit: number }; +} + +export interface QueryComposition { + id: string; + input: ValueType; + steps: QueryCompositionStep[]; + maxQueries: number; +} + +export interface CheckedQueryComposition extends QueryComposition { + output: ValueType; + definitionDigest: string; + dependencies: { query: string; digest: string }[]; +} + +/** Checks a finite dependency graph. Query identities are resolved by the + * caller's package/workspace scope; no presentation concepts participate. */ +export function compileQueryComposition( + source: QueryComposition, + queries: ReadonlyMap, +): CheckedQueryComposition { + const fail = (message: string): never => { + throw new QueryCompileError("QUERY_COMPOSITION", `${source.id}: ${message}`); + }; + if (!Number.isSafeInteger(source.maxQueries) || source.maxQueries < 1) fail("maxQueries must be a positive integer"); + if (source.input.kind !== "record") fail("input must be a record"); + const outputs = new Map(); + const dependencies = new Map(); + let count = 0; + const binding = (expression: QueryBinding, item?: ValueType) => + queryBindingType(expression, source.input, outputs, item); + const equal = (a: ValueType, b: ValueType) => canonicalJson(a) === canonicalJson(b); + for (const step of source.steps) { + if (!step.name || outputs.has(step.name)) fail(`duplicate or empty step name ${step.name}`); + const query = queries.get(step.query); + if (!query) return fail(`unknown query ${step.query}`); + let item: ValueType | undefined; + if (step.with) { + if (step.each) fail(`${step.name} cannot combine with and each`); + const value = binding(step.with); + item = value.kind === "optional" ? value.value : value; + } + if (step.each) { + const collection = binding(step.each.source); + if (collection.kind !== "list") return fail(`${step.name}.each requires a list`); + if (!Number.isSafeInteger(step.each.limit) || step.each.limit < 1) + fail(`${step.name}.each.limit must be a positive integer`); + item = collection.value; + } + count += step.each?.limit ?? 1; + if (!Number.isSafeInteger(count) || count > source.maxQueries) fail("declared steps exceed maxQueries"); + if (step.when && !equal(binding(step.when, item), { kind: "scalar", name: "bool" })) + fail(`${step.name}.when requires a boolean`); + if (query.declaration.root.kind === "workspace-interface") { + if (step.root !== null) fail(`${step.name} workspace query takes no object root`); + } else { + if (step.root === null) return fail(`${step.name} requires an object root`); + let root = binding(step.root, item); + if (root.kind === "optional" && step.optional) root = root.value; + const expected: ValueType = { + kind: "object-ref", + expectation: { + kind: "interface", + interfaceRevisionId: query.declaration.root.interface, + }, + }; + if (!equal(root, expected)) fail(`${step.name} root must be a reference to ${query.declaration.root.interface}`); + } + if (!equal(binding(step.arguments, item), query.variables)) + fail(`${step.name} arguments do not match ${step.query}`); + let output = query.output; + if (step.optional || step.with || step.when) output = { kind: "optional", value: output }; + if (step.each) output = { kind: "list", value: output }; + outputs.set(step.name, output); + dependencies.set(step.query, query.definitionDigest); + } + const result = { + ...structuredClone(source), + output: { kind: "record" as const, fields: Object.fromEntries(outputs) }, + dependencies: [...dependencies].map(([query, digest]) => ({ query, digest })), + }; + return { ...result, definitionDigest: createHash("sha256").update(canonicalJson(result)).digest("hex") }; +} + +export function queryBindingType( + expression: QueryBinding, + input: ValueType, + outputs: ReadonlyMap, + item?: ValueType, +): ValueType { + const fail = (message: string): never => { + throw new QueryCompileError("QUERY_COMPOSITION", message); + }; + const binding = (expression: QueryBinding, item?: ValueType) => queryBindingType(expression, input, outputs, item); + const equal = (a: ValueType, b: ValueType) => canonicalJson(a) === canonicalJson(b); + if ("flatMap" in expression) { + const rows = binding(expression.flatMap.source, item); + if (rows.kind !== "list") return fail("flatMap requires a list"); + const children = binding({ from: "item", path: expression.flatMap.path }, rows.value); + if (children.kind !== "list") return fail("flatMap path requires a list"); + return children; + } + if ("literal" in expression) + return { kind: "scalar", name: typeof expression.literal === "string" ? "string" : "bool" }; + if ("equal" in expression) { + const left = binding(expression.equal[0], item); + const right = binding(expression.equal[1], item); + if (left.kind !== "scalar" || !equal(left, right)) fail("equal requires matching scalar operands"); + return { kind: "scalar", name: "bool" }; + } + if ("record" in expression) + return { + kind: "record", + fields: Object.fromEntries(Object.entries(expression.record).map(([key, value]) => [key, binding(value, item)])), + }; + let type = expression.from === "input" ? input : expression.from === "item" ? item : outputs.get(expression.name); + if (!type) + return fail( + expression.from === "result" + ? `result ${expression.name} must name an earlier step (forward references and cycles are unsupported)` + : "item is only available within a bounded each step", + ); + for (const key of expression.path) { + if (type.kind !== "record" || !Object.hasOwn(type.fields, key)) + return fail(`cannot select ${key} from ${type.kind}; nulls and collections need an explicit boundary`); + type = type.fields[key]!; + } + return type; +} diff --git a/src/query/fragments.ts b/src/query/fragments.ts new file mode 100644 index 0000000..3fe3867 --- /dev/null +++ b/src/query/fragments.ts @@ -0,0 +1,134 @@ +import { parse, print, visit, type DocumentNode, type FragmentDefinitionNode } from "graphql"; +import { QueryCompileError, type CheckedQuery, type QueryDeclaration, type QuerySelection } from "./types.js"; + +export type FragmentResolver = (packageRevisionId: string, fragmentId: string) => Promise; +/** Expand only explicitly imported exports. Each variable mapping is checked by + * GraphQL against the consuming operation, rather than silently inheriting names. */ +export async function fragmentSources( + declaration: QueryDeclaration, + definitions: DocumentNode["definitions"][number][], + resolve?: FragmentResolver, +) { + const markers = new Map>(); + for (const imported of declaration.importedFragments ?? []) { + if (imported.mode !== undefined && imported.mode !== "reference") + throw new QueryCompileError("FRAGMENT_IMPORT_MODE", "Unknown fragment import mode"); + if (!resolve) + throw new QueryCompileError( + "FRAGMENT_RESOLVER_REQUIRED", + "Resolve imported fragments through package dependencies", + ); + const checked = await resolve(imported.packageRevisionId, imported.fragmentId); + const exported = checked.declaration.fragment; + if (!exported || exported.admission) throw new QueryCompileError("NOT_EXPORTED_FRAGMENT", imported.fragmentId); + const variables = checked.variables.kind === "record" ? Object.keys(checked.variables.fields) : []; + if (JSON.stringify(Object.keys(imported.variables).sort()) !== JSON.stringify(variables.sort())) + throw new QueryCompileError( + "FRAGMENT_VARIABLE_MAPPING", + `Map every variable of ${checked.declaration.displayName} explicitly`, + ); + const source = parse(checked.document); + const sourceOperation = source.definitions.find( + (node) => node.kind === "OperationDefinition" && node.name?.value === checked.declaration.operation, + ); + const consumingOperation = definitions.find( + (node) => node.kind === "OperationDefinition" && node.name?.value === declaration.operation, + ); + for (const variable of sourceOperation?.kind === "OperationDefinition" + ? (sourceOperation.variableDefinitions ?? []) + : []) { + const name = imported.variables[variable.variable.name.value]; + const target = + consumingOperation?.kind === "OperationDefinition" + ? consumingOperation.variableDefinitions?.find((v) => v.variable.name.value === name) + : undefined; + if (!target || print(target.type) !== print(variable.type)) + throw new QueryCompileError( + "FRAGMENT_VARIABLE_TYPE", + `Mapped variable ${name} must have type ${print(variable.type)}`, + ); + } + const fragments = source.definitions.filter( + (node): node is FragmentDefinitionNode => node.kind === "FragmentDefinition", + ); + if (definitions.some((node) => "name" in node && node.name?.value === exported.name)) + throw new QueryCompileError("FRAGMENT_NAME_COLLISION", exported.name); + const privateNames = new Map( + fragments.map((node) => [ + node.name.value, + node.name.value === exported.name ? exported.name : `${exported.name}__${node.name.value}`, + ]), + ); + // A reference import binds the checked export, rather than spreading its + // fields into this operation. Keep a nominal stub for GraphQL root checking; + // the independently checked export supplies the actual projection contract. + const referenced = parse("fragment Binding on Placeholder { _camino { ref } }") + .definitions[0] as FragmentDefinitionNode; + for (const sourceFragment of imported.mode === "reference" + ? fragments.filter((entry) => entry.name.value === exported.name) + : fragments) { + const fragment = + imported.mode === "reference" ? { ...sourceFragment, selectionSet: referenced.selectionSet } : sourceFragment; + definitions.push( + visit(fragment, { + FragmentDefinition(node) { + return { ...node, name: { ...node.name, value: privateNames.get(node.name.value)! } }; + }, + FragmentSpread(node) { + return { ...node, name: { ...node.name, value: privateNames.get(node.name.value) ?? node.name.value } }; + }, + Variable(node) { + return { ...node, name: { ...node.name, value: imported.variables[node.name.value] ?? node.name.value } }; + }, + }), + ); + } + markers.set(exported.name, { + ...(imported.mode ? { mode: imported.mode } : {}), + package: imported.packageRevisionId, + fragment: imported.fragmentId, + digest: checked.definitionDigest, + root: checked.declaration.root.interface, + variables: imported.variables, + }); + for (const [name, contract] of Object.entries( + imported.mode === "reference" ? {} : (checked.fragmentContracts ?? {}), + )) { + markers.set(privateNames.get(name) ?? name, { + ...contract, + variables: Object.fromEntries( + Object.entries(contract.variables).map(([inner, outer]) => [inner, imported.variables[outer] ?? outer]), + ), + }); + } + } + if (declaration.fragment) { + const exported = declaration.fragment; + if (!definitions.some((node) => node.kind === "FragmentDefinition" && node.name.value === exported.name)) + throw new QueryCompileError("FRAGMENT_MISSING", exported.name); + const operations = definitions.filter((node) => node.kind === "OperationDefinition"); + if (!exported.operation) { + if (operations.length) + throw new QueryCompileError( + "FRAGMENT_OPERATION_REQUIRED", + "Name the standalone operation explicitly when a fragment document includes variables or an operation", + ); + definitions.push(...parse(`query ${declaration.operation} { root { ...${exported.name} } }`).definitions); + } else { + const operation = operations.find( + (node) => node.kind === "OperationDefinition" && node.name?.value === exported.operation, + ); + if (!operation || operation.kind !== "OperationDefinition") + throw new QueryCompileError("FRAGMENT_OPERATION_MISSING", exported.operation); + // The standalone operation specifies variables only. Its result must be the + // exact exported fragment, not additional fields or a different window. + const expected = parse(`query ${declaration.operation} { root { ...${exported.name} } }`).definitions[0]!; + if (expected.kind !== "OperationDefinition" || print(operation.selectionSet) !== print(expected.selectionSet)) + throw new QueryCompileError( + "FRAGMENT_STANDALONE_SHAPE", + "Standalone operation must select root { ...ExportedFragment }", + ); + } + } + return markers; +} diff --git a/src/query/link.ts b/src/query/link.ts index e11da31..f6684ca 100644 --- a/src/query/link.ts +++ b/src/query/link.ts @@ -10,6 +10,7 @@ import type { import { QueryCompileError, type CheckedQuery } from "./types.js"; import { queryRuntimePlan } from "./proto.js"; import { canonicalJson } from "../capability-model/evolution.js"; +import { resolveForwardedBinding } from "../capability-model/forwarding.js"; export interface LinkedQueryField { atomId: AtomId; @@ -27,17 +28,32 @@ export interface LinkedQuery { bindingDigest: string; fields: LinkedQueryField[]; attachments: PersistentAttachment[]; + rootAtoms: AtomId[]; } /** Link every possible implementation, including atoms absent from today's data. */ -export function linkQueries(workspace: WorkspaceRevision): LinkedQuery[] { +export function linkQueries( + workspace: WorkspaceRevision, + additional: readonly { + packageRevisionId: string; + checked: CheckedQuery; + }[] = [], +): LinkedQuery[] { const interfaces = new Map(workspace.interfaceImports.map((entry) => [entry.revisionId, entry])); const attachments = [ ...workspace.sharedAttachments, ...workspace.conformances.flatMap((entry) => entry.privateAttachments), ]; const linked: LinkedQuery[] = []; - for (const pkg of workspace.packageImports) + for (const pkg of [ + ...workspace.packageImports, + ...additional.map(({ packageRevisionId, checked }) => ({ + revisionId: packageRevisionId, + displayName: "Public query", + queries: [checked.declaration], + checkedQueries: [checked], + })), + ]) for (const declaration of pkg.queries ?? []) { const checked = pkg.checkedQueries?.find((query) => query.declaration.id === declaration.id); if (!checked || JSON.stringify(checked.declaration) !== JSON.stringify(declaration)) @@ -60,7 +76,12 @@ export function linkQueries(workspace: WorkspaceRevision): LinkedQuery[] { for (const effect of checked.effects) { const contract = interfaces.get(effect.interfaceRevisionId); const member = contract?.members.find((entry) => entry.id === effect.memberId); - if (!member || member.kind === "operation" || member.queryRead?.execution !== effect.execution) + if ( + !member || + member.kind === "operation" || + member.kind === "module" || + member.queryRead?.execution !== effect.execution + ) throw new QueryCompileError( "QUERY_STALE_CONTRACT", `Query field ${effect.interfaceRevisionId}.${effect.memberId} changed`, @@ -73,19 +94,29 @@ export function linkQueries(workspace: WorkspaceRevision): LinkedQuery[] { const provider = conformance.operationBindings.find((entry) => entry.operationId === getter.id); if (!provider) throw new QueryCompileError("QUERY_CONTRACT", `Missing binding ${conformance.atomId}.${getter.id}`); - const binding = provider.binding; + const binding = resolveForwardedBinding( + workspace, + conformance.atomId, + conformance.interfaceRevisionId, + getter.id, + ).binding; if (binding.kind === "package") { - if (effect.execution !== "rpc-permitted") + if (effect.execution === "native") throw new QueryCompileError( "QUERY_NATIVE_BINDING_REQUIRED", `Native query field ${effect.memberId} binds package code`, ); - if (!provider.queryReason?.trim()) + if (effect.execution === "rpc-permitted" && !provider.queryReason?.trim()) throw new QueryCompileError( "QUERY_RPC_PROVIDER_REASON", `Package query field ${effect.memberId} needs query-reason`, ); } else if (binding.kind === "state" && binding.primitive === "read") needed.add(binding.slotId); + else if ( + (binding.kind === "journal" && binding.primitive === "head") || + (binding.kind === "subscription" && binding.primitive === "status") + ) + needed.add(binding.slotId); else if (binding.kind === "edge" && binding.primitive === "resolve") needed.add(binding.edgeTypeId); else throw new QueryCompileError( @@ -94,7 +125,10 @@ export function linkQueries(workspace: WorkspaceRevision): LinkedQuery[] { ); const start = member.operations.find((op) => op.displayName === "watch-start"); const stop = member.operations.find((op) => op.displayName === "watch-stop"); - const watch = start && stop && conformance.operationBindings.find((entry) => entry.operationId === start.id); + const watch = + start && stop && conformance.operationBindings.some((entry) => entry.operationId === start.id) + ? resolveForwardedBinding(workspace, conformance.atomId, conformance.interfaceRevisionId, start.id) + : undefined; if ( binding.kind === "state" && watch && @@ -128,10 +162,29 @@ export function linkQueries(workspace: WorkspaceRevision): LinkedQuery[] { } } const storage = attachments.filter((entry) => needed.has(entry.id)); + const rootConformances = workspace.conformances.filter( + (entry) => entry.interfaceRevisionId === declaration.root.interface, + ); + const rootAtoms = [...new Set(rootConformances.map((entry) => entry.atomId))].sort(); + const qualifiedInterfaces = new Set(); + const qualifications = (node: unknown): void => { + if (!node || typeof node !== "object") return; + if ("kind" in node && node.kind === "as" && "interfaceRevisionId" in node) + qualifiedInterfaces.add(String(node.interfaceRevisionId)); + for (const child of Object.values(node)) qualifications(child); + }; + qualifications(checked.selection); const bindingDigest = createHash("sha256") .update( canonicalJson({ definition: checked.definitionDigest, + qualifiedConformances: workspace.conformances + .filter((entry) => qualifiedInterfaces.has(entry.interfaceRevisionId)) + .map((entry) => ({ atomId: entry.atomId, id: entry.id, interfaceRevisionId: entry.interfaceRevisionId })) + .sort((a, b) => canonicalJson(a).localeCompare(canonicalJson(b))), + rootConformances: rootConformances + .map((entry) => ({ atomId: entry.atomId, id: entry.id, interfaceRevisionId: entry.interfaceRevisionId })) + .sort((a, b) => canonicalJson(a).localeCompare(canonicalJson(b))), fields: [...fields].sort((a, b) => canonicalJson(a).localeCompare(canonicalJson(b))), storage: [...storage].sort((a, b) => a.id.localeCompare(b.id)), }), @@ -144,6 +197,7 @@ export function linkQueries(workspace: WorkspaceRevision): LinkedQuery[] { bindingDigest, fields, attachments: storage, + rootAtoms, }); } for (const entry of linked) entry.runtime = queryRuntimePlan(entry, workspace); diff --git a/src/query/presentation.ts b/src/query/presentation.ts deleted file mode 100644 index 2e9ddb5..0000000 --- a/src/query/presentation.ts +++ /dev/null @@ -1,58 +0,0 @@ -import type { InterfaceRevision, ValueType } from "../capability-model/types.js"; -import type { CheckedQuery, QuerySelection } from "./types.js"; - -/** Browser presentation is separate from the portable query value contract. */ -export function queryPresentation(query: CheckedQuery, interfaces: readonly InterfaceRevision[]) { - const result: { - path: string[]; - selectionId: string; - interfaceRevisionId: string; - getOperationId: string; - setOperationId?: string; - watchOperationId?: string; - inputKind: "value" | "fields"; - valueType: ValueType; - conditions: (QuerySelection["conditions"][number] & { defaultValue?: boolean })[]; - }[] = []; - const visit = (type: ValueType, selections: QuerySelection[], path: string[]) => { - if (type.kind === "optional") return visit(type.value, selections, path); - if (type.kind === "list") return visit(type.value, selections, [...path, "*"]); - if (type.kind !== "record") return; - for (const [name, field] of Object.entries(type.fields)) { - const matches = selections.filter((entry) => entry.key === name); - if (matches.some((entry) => entry.liveSelectionId) && matches.some((entry) => !entry.liveSelectionId)) - throw new Error(`QUERY_LIVE_CONFLICT: ${[...path, name].join(".")} mixes live and plain selections`); - for (const entry of matches) { - if (entry.liveSelectionId) { - const iface = interfaces.find((iface) => iface.revisionId === entry.interfaceRevisionId)!; - const member = iface.members.find((member) => member.id === entry.memberId)!; - if (member.kind !== "value") throw new Error("QUERY_LIVE_UNSUPPORTED"); - const get = member.operations.find((op) => op.displayName === "get")!; - const set = member.operations.find((op) => op.displayName === "set"); - result.push({ - path: [...path, name], - selectionId: entry.liveSelectionId, - interfaceRevisionId: iface.revisionId, - getOperationId: get.id, - setOperationId: set?.id, - watchOperationId: member.operations.find((op) => op.displayName === "watch-start")?.id, - inputKind: set?.inputType.kind === "record" ? "fields" : "value", - valueType: get.outputType, - conditions: entry.conditions.map((condition) => { - const fallback = - condition.value.kind === "variable" ? query.variableDefaults[condition.value.name] : undefined; - return { - ...condition, - ...(fallback?.kind === "literal" && typeof fallback.value === "boolean" - ? { defaultValue: fallback.value } - : {}), - }; - }), - }); - } else visit(field, entry.selection, [...path, name]); - } - } - }; - visit(query.output, query.selection, []); - return result; -} diff --git a/src/query/proto.ts b/src/query/proto.ts index 62ced89..4fc895f 100644 --- a/src/query/proto.ts +++ b/src/query/proto.ts @@ -46,9 +46,43 @@ export const queryRuntimePlan = (linked: LinkedQuery, workspace: WorkspaceRevisi InstalledQuerySchema, create(InstalledQuerySchema, { id: linked.id, + placement: linked.checked.declaration.placement && { + kind: + linked.checked.declaration.placement.kind === "replica" + ? { case: "replica", value: true } + : { + case: "localSourceQueryId", + value: `${linked.packageRevisionId}:${linked.checked.declaration.placement.sourceQueryId}`, + }, + }, definitionDigest: linked.checked.definitionDigest, bindingDigest: linked.bindingDigest, - rootInterfaceRevisionId: linked.checked.declaration.root, + root: { + case: linked.checked.declaration.root.kind === "object" ? "objectInterface" : "workspaceInterface", + value: linked.checked.declaration.root.interface, + }, + rootAtomIds: linked.rootAtoms, + fields: linked.checked.effects.map((effect) => { + const member = workspace.interfaceImports + .find((entry) => entry.revisionId === effect.interfaceRevisionId)! + .members.find((entry) => entry.id === effect.memberId)!; + return { + interfaceRevisionId: effect.interfaceRevisionId, + memberId: effect.memberId, + fieldName: member.displayName, + valueTypeJson: member.kind === "value" ? JSON.stringify(member.valueType) : "", + keyType: member.kind === "relationship" ? member.keyType : undefined, + cardinality: + member.kind === "relationship" + ? { + "optional-one": Cardinality.OPTIONAL_ONE, + "exactly-one": Cardinality.EXACTLY_ONE, + many: Cardinality.MANY, + "many-unique": Cardinality.MANY_UNIQUE, + }[member.cardinality] + : undefined, + }; + }), selection: linked.checked.selection.map(selection), budgets: linked.checked.declaration.budgets, variablesTypeJson: JSON.stringify(linked.checked.variables), @@ -82,7 +116,10 @@ export const queryRuntimePlan = (linked: LinkedQuery, workspace: WorkspaceRevisi "many-unique": Cardinality.MANY_UNIQUE, }[member.cardinality] : undefined, - slotId: field.binding.kind === "state" ? field.binding.slotId : "", + slotId: + ["state", "journal", "subscription"].includes(field.binding.kind) && "slotId" in field.binding + ? field.binding.slotId + : "", edgeTypeId: field.binding.kind === "edge" ? field.binding.edgeTypeId : "", projectionId: field.binding.kind === "edge" ? field.binding.projectionId : "", rpc: field.binding.kind === "package", diff --git a/src/query/relational-compile.ts b/src/query/relational-compile.ts index c9723bf..3860b58 100644 --- a/src/query/relational-compile.ts +++ b/src/query/relational-compile.ts @@ -53,7 +53,7 @@ const pairs = (node: ValueNode): readonly import("graphql").ObjectFieldNode[] => node.kind === "ObjectValue" ? node.fields : fail("QUERY_UNSUPPORTED_FEATURE", "Query structure must be a literal object", node); -const ops = new Set(["eq", "in", "isNull", "lt", "lte", "gt", "gte"]); +const ops = new Set(["eq", "containsInsensitive", "in", "isNull", "lt", "lte", "gt", "gte"]); const prefix = (a: readonly string[], b: readonly string[]) => a.every((part, i) => part === b[i]); export function relationalCompiler( @@ -94,7 +94,8 @@ export function relationalCompiler( if (!prefix(base, path)) return false; // Object identity does not determine which incoming membership carried it. return ( - last === "entry" || !(path[base.length] === "_qx" && ["entry", "mapKey"].includes(path[base.length + 1] ?? "")) + last === "entry" || + !(path[base.length] === "_camino" && ["entry", "mapKey"].includes(path[base.length + 1] ?? "")) ); }); if (!ok) @@ -153,9 +154,20 @@ export function relationalCompiler( available?: Availability, ): QueryExpression => { requireAvailable(names, available, node); - const meta = names.at(-2) === "_qx"; - const { row, path, nullable } = walk(root, names.slice(0, meta ? -2 : -1), uses, node); - const name = names.at(-1)!; + const meta = names.at(-2) === "_camino"; + let fieldIndex = names.length - 1; + if (!meta) { + let cursor = root; + for (let index = 0; index < names.length; index++) { + if (cursor.kind === "object" && rowFields(cursor).get(names[index]!)?.kind === "leaf") { + fieldIndex = index; + break; + } + if (index < names.length - 1) cursor = walk(root, names.slice(0, index + 1), uses, node).row; + } + } + const { row, path, nullable } = walk(root, names.slice(0, meta ? -2 : fieldIndex), uses, node); + const name = names[meta ? names.length - 1 : fieldIndex]!; let type: ValueType, leaf: QueryExpression["leaf"]; if (meta) { if (name === "ref" && row.kind === "object") { @@ -185,7 +197,24 @@ export function relationalCompiler( for (const use of uses) mark(row.interfaceRevisionId, name, use, node); const member = env.contracts.get(row.interfaceRevisionId)!.members.find((m) => m.displayName === name)!; type = field.type; - leaf = { kind: "field", interfaceRevisionId: row.interfaceRevisionId, memberId: member.id }; + const valuePath = names.slice(fieldIndex + 1); + let valueNullable = false; + for (const component of valuePath) { + if (type.kind === "optional") { + valueNullable = true; + type = type.value; + } + if (type.kind !== "record" || !Object.hasOwn(type.fields, component)) + return fail("QUERY_FIELD_NOT_QUERYABLE", `Unknown scalar value path ${names.join(".")}`, node); + type = type.fields[component]!; + } + if (valueNullable && type.kind !== "optional") type = valueType.optional(type); + leaf = { + kind: "field", + interfaceRevisionId: row.interfaceRevisionId, + memberId: member.id, + ...(valuePath.length ? { valuePath } : {}), + }; } return { path, leaf, type: nullable && type.kind !== "optional" ? valueType.optional(type) : type }; }; @@ -306,10 +335,27 @@ export function relationalCompiler( children.push({ kind: "not", child: { kind: "and", children: children.splice(before) } }); } else if (current.kind === "pair" && (name === "source" || name === "target")) visit(current[name], field.value, [...base, name]); - else if (name === "_qx") { + else if (name === "_camino") { for (const meta of pairs(field.value)) { + if (meta.name.value === "as") { + if (current.kind !== "object" || base.length || available) + fail( + "QUERY_UNSUPPORTED_FEATURE", + "Interface predicates require an object row before distinct/grouping", + meta, + ); + for (const branch of pairs(meta.value)) { + const contract = env.byName.get(branch.name.value)!; + children.push({ + kind: "as", + interfaceRevisionId: contract.revisionId, + predicate: predicate(objectRow(contract.revisionId), branch.value), + }); + } + continue; + } if (meta.name.value !== "relations") { - const expr = expression(row, [...base, "_qx", meta.name.value], ["predicate"], meta, available); + const expr = expression(row, [...base, "_camino", meta.name.value], ["predicate"], meta, available); children.push( ...(comparisons(meta.value, (operator, value) => ({ kind: "compare", @@ -356,15 +402,35 @@ export function relationalCompiler( } } } else { - const expr = expression(row, [...base, name], ["predicate"], field, available); - children.push( - ...(comparisons(field.value, (operator, value) => ({ - kind: "compare", - expression: expr, - operator, - value, - })) as QueryPredicate[]), - ); + const member = current.kind === "object" && relationships(current).find((m) => m.displayName === name); + if (member) { + const expr = expression(row, [...base, name, "_camino", "ref"], ["predicate"], field, available); + children.push( + ...(comparisons(field.value, (operator, value) => ({ + kind: "compare", + expression: expr, + operator, + value, + })) as QueryPredicate[]), + ); + continue; + } + const valuePredicate = (names: string[], value: ValueNode) => { + const expr = expression(row, names, ["predicate"], field, available); + const type = expr.type.kind === "optional" ? expr.type.value : expr.type; + if (type.kind === "record") { + for (const child of pairs(value)) valuePredicate([...names, child.name.value], child.value); + } else + children.push( + ...(comparisons(value, (operator, value) => ({ + kind: "compare", + expression: expr, + operator, + value, + })) as QueryPredicate[]), + ); + }; + valuePredicate([...base, name], field.value); } } }; @@ -496,7 +562,7 @@ export function relationalCompiler( expression: { ...k.expression, path: [{ kind: "source" as const }, ...k.expression.path] }, })), { - path: ["target", "_qx", "entry"], + path: ["target", "_camino", "entry"], expression: { path: [{ kind: "target" as const }], leaf: { kind: "entry" as const }, diff --git a/src/query/relational-proto.ts b/src/query/relational-proto.ts index 9f5d6a3..5cd12f8 100644 --- a/src/query/relational-proto.ts +++ b/src/query/relational-proto.ts @@ -50,6 +50,13 @@ export function relationalWire( }); const predicate = (p: QueryPredicate | QueryAggregatePredicate): wire.QueryPredicate => { switch (p.kind) { + case "as": + return create(wire.QueryPredicateSchema, { + predicate: { + case: "as", + value: { interfaceRevisionId: p.interfaceRevisionId, predicate: predicate(p.predicate) }, + }, + }); case "and": case "or": return create(wire.QueryPredicateSchema, { @@ -66,6 +73,7 @@ export function relationalWire( value: argument(p.value), operator: { eq: wire.QueryComparisonOperator.EQ, + containsInsensitive: wire.QueryComparisonOperator.CONTAINS_INSENSITIVE, in: wire.QueryComparisonOperator.IN, isNull: wire.QueryComparisonOperator.IS_NULL, lt: wire.QueryComparisonOperator.LT, diff --git a/src/query/relational-schema.ts b/src/query/relational-schema.ts index b26050b..8cc0435 100644 --- a/src/query/relational-schema.ts +++ b/src/query/relational-schema.ts @@ -1,3 +1,4 @@ +import { comparableValue } from "./structured.js"; import { GraphQLBoolean, GraphQLString, @@ -7,6 +8,7 @@ import { GraphQLInputObjectType, GraphQLList, GraphQLNonNull, + visit, type GraphQLInputType, type GraphQLOutputType, type GraphQLInputFieldConfigMap, @@ -42,9 +44,11 @@ export type SchemaRowField = { kind: "leaf"; type: ValueType } | { kind: "row"; export interface RelationalSchemaEnvironment { contracts: Map; target(member: RelationshipInterfaceMember): InterfaceRevisionId; + viewable(member: RelationshipInterfaceMember): boolean; name(id: InterfaceRevisionId): string; scalar(type: ValueType): GraphQLInputType & GraphQLOutputType; comparison(type: ValueType): GraphQLInputObjectType; + valueOrder(type: ValueType): GraphQLInputType; scalarTypes: Map; direction: GraphQLEnumType; cursor: GraphQLScalarType; @@ -52,6 +56,16 @@ export interface RelationalSchemaEnvironment { } export function relationalSchema(env: RelationalSchemaEnvironment, document?: DocumentNode) { + const requestedInterfaces = new Set(); + if (document) + visit(document, { + ObjectField(node) { + if (node.name.value !== "_camino" || node.value.kind !== "ObjectValue") return; + const qualified = node.value.fields.find((field) => field.name.value === "as"); + if (qualified?.value.kind === "ObjectValue") + for (const field of qualified.value.fields) requestedInterfaces.add(field.name.value); + }, + }); const outputCache = new Map(); const inputCache = new Map(); const referenceCache = new Map(); @@ -70,7 +84,8 @@ export function relationalSchema(env: RelationalSchemaEnvironment, document?: Do ? env.contracts .get(row.interfaceRevisionId)! .members.filter( - (member): member is RelationshipInterfaceMember => member.kind === "relationship" && !!member.queryRead, + (member): member is RelationshipInterfaceMember => + member.kind === "relationship" && !!member.queryRead && env.viewable(member), ) : []; const fields = (row: QueryRow): Map => { @@ -81,8 +96,9 @@ export function relationalSchema(env: RelationalSchemaEnvironment, document?: Do ]); return new Map( env.contracts.get(row.interfaceRevisionId)!.members.flatMap((member): [string, SchemaRowField][] => { - if (member.kind === "operation" || !member.queryRead) return []; + if (member.kind === "operation" || member.kind === "module" || !member.queryRead) return []; if (member.kind === "value") return [[member.displayName, { kind: "leaf", type: member.valueType }]]; + if (!env.viewable(member)) return []; if (many(member)) return []; return [ [ @@ -203,7 +219,9 @@ export function relationalSchema(env: RelationalSchemaEnvironment, document?: Do metadata[name] = { type: !operator && nullable ? t : new GraphQLNonNull(t) }; } if (Object.keys(metadata).length) - result._qx = { type: new GraphQLNonNull(out("ColumnsMeta", row, () => metadata, `${operator}:${nullable}`)) }; + result._camino = { + type: new GraphQLNonNull(out("ColumnsMeta", row, () => metadata, `${operator}:${nullable}`)), + }; // A type may have no matching scalar at this node; a private schema sentinel // keeps it valid while compiler validation forbids selecting that sentinel. if (!Object.keys(result).length) result._unavailable = { type: GraphQLBoolean }; @@ -231,7 +249,7 @@ export function relationalSchema(env: RelationalSchemaEnvironment, document?: Do else if (queryKeyType(field.type)) result[name] = { type: GraphQLBoolean }; } const meta = Object.fromEntries([...leafFields(row)].map(([name]) => [name, { type: GraphQLBoolean }])); - if (Object.keys(meta).length) result._qx = { type: inp("KeyMeta", row, () => meta) }; + if (Object.keys(meta).length) result._camino = { type: inp("KeyMeta", row, () => meta) }; return result; }); const comparisons = ( @@ -255,7 +273,8 @@ export function relationalSchema(env: RelationalSchemaEnvironment, document?: Do continue; } } - result[name] = { type: ordering ? env.direction : compare(type) }; + if (!comparableValue(type, ordering)) continue; + result[name] = { type: ordering ? env.valueOrder(type) : compare(type) }; } } const meta: GraphQLInputFieldConfigMap = {}; @@ -263,7 +282,7 @@ export function relationalSchema(env: RelationalSchemaEnvironment, document?: Do if (!operator || operator === "countPresent") meta[name] = { type: ordering ? env.direction : compare(operator ? aggregateType(operator, type) : type) }; if (Object.keys(meta).length) - result._qx = { type: inp("ExpressionMeta", row, () => meta, `${operator}:${ordering}`) }; + result._camino = { type: inp("ExpressionMeta", row, () => meta, `${operator}:${ordering}`) }; if (!Object.keys(result).length) result._unavailable = { type: GraphQLBoolean }; return result; }, @@ -298,13 +317,40 @@ export function relationalSchema(env: RelationalSchemaEnvironment, document?: Do not: { type: where(row) }, }; for (const [name, field] of fields(row)) { - if (row.kind === "object" && field.kind === "row") continue; if (name in result) throw new QueryCompileError("QUERY_SCHEMA_NAME", `Reserved predicate name ${name}`); + if (row.kind === "object" && field.kind === "row") { + result[name] = { + type: compare( + valueType.interfaceRef( + field.row.kind === "object" + ? field.row.interfaceRevisionId + : (() => { + throw new Error("Expected relationship object"); + })(), + ), + ), + }; + continue; + } + if (field.kind === "leaf" && !comparableValue(field.type)) continue; result[name] = { type: field.kind === "row" ? where(field.row) : compare(field.type) }; } - result._qx = { + result._camino = { type: inp("WhereMeta", row, () => { const meta: GraphQLInputFieldConfigMap = {}; + if (row.kind === "object" && requestedInterfaces.size) + meta.as = { + type: inp("Interfaces", row, () => + Object.fromEntries( + [...env.contracts.values()] + .filter((contract) => !contract.template && requestedInterfaces.has(env.name(contract.revisionId))) + .map((contract) => [ + env.name(contract.revisionId), + { type: where(objectRow(contract.revisionId)) }, + ]), + ), + ), + }; for (const [name, type] of leafFields(row)) meta[name] = { type: compare(type) }; const edges = relations(row); if (edges.length) @@ -393,7 +439,7 @@ export function relationalSchema(env: RelationalSchemaEnvironment, document?: Do const discoverObject = (row: QueryRow, set: SelectionSetNode) => { for (const node of selections(set)) if (node.selectionSet) { - if (node.name.value === "_qx") { + if (node.name.value === "_camino") { for (const meta of selections(node.selectionSet)) if (meta.name.value === "relations" && meta.selectionSet) for (const edge of selections(meta.selectionSet)) { diff --git a/src/query/relational.ts b/src/query/relational.ts index fee63c0..0e7b2a8 100644 --- a/src/query/relational.ts +++ b/src/query/relational.ts @@ -49,7 +49,7 @@ export type QueryPathStep = export interface QueryExpression { path: QueryPathStep[]; leaf: - | { kind: "field"; interfaceRevisionId: InterfaceRevisionId; memberId: MemberId } + | { kind: "field"; interfaceRevisionId: InterfaceRevisionId; memberId: MemberId; valuePath?: string[] } | { kind: "ref"; interfaceRevisionId: InterfaceRevisionId } | { kind: "entry" | "mapKey" }; type: ValueType; @@ -60,12 +60,13 @@ export interface QueryReduction { type: ValueType; } export type QueryPredicate = + | { kind: "as"; interfaceRevisionId: InterfaceRevisionId; predicate: QueryPredicate } | { kind: "and" | "or"; children: QueryPredicate[] } | { kind: "not"; child: QueryPredicate } | { kind: "compare"; expression: QueryExpression; - operator: "eq" | "in" | "isNull" | "lt" | "lte" | "gt" | "gte"; + operator: "eq" | "containsInsensitive" | "in" | "isNull" | "lt" | "lte" | "gt" | "gte"; value: QueryArgument; } | { @@ -93,7 +94,7 @@ export type QueryAggregatePredicate = | { kind: "compare"; expression: QueryExpression | QueryReduction; - operator: "eq" | "in" | "isNull" | "lt" | "lte" | "gt" | "gte"; + operator: "eq" | "containsInsensitive" | "in" | "isNull" | "lt" | "lte" | "gt" | "gte"; value: QueryArgument; }; export interface QueryKey { diff --git a/src/query/root.ts b/src/query/root.ts new file mode 100644 index 0000000..5a1b604 --- /dev/null +++ b/src/query/root.ts @@ -0,0 +1,43 @@ +import { + capabilityId, + type InterfaceRevision, + type InterfaceRevisionId, + type MemberId, +} from "../capability-model/types.js"; +import { QueryCompileError, type QueryRoot } from "./types.js"; + +/** Compiler-only row-source schema; never installed as an atom, edge or capability. */ +export const workspaceQueryRootId: InterfaceRevisionId = capabilityId.interfaceRevision( + "qx-internal-workspace-query-root", +); +export const workspaceObjectsMemberId: MemberId = capabilityId.member("qx-internal-workspace-objects"); +export function queryRootSchema(root: QueryRoot, interfaces: readonly InterfaceRevision[], local = false) { + if (interfaces.some((i) => i.revisionId === workspaceQueryRootId)) + throw new QueryCompileError("QUERY_RESERVED_ID", "The workspace query root identity is compiler-owned"); + if (root.kind === "object") return { interface: root.interface, interfaces }; + const contract = interfaces.find((i) => i.revisionId === root.interface); + if (!contract || contract.template) + throw new QueryCompileError("QUERY_CONTRACT", `Expected closed interface ${root.interface}`); + const synthetic: InterfaceRevision = { + interfaceId: capabilityId.interface("qx-internal-workspace-query-root"), + revisionId: workspaceQueryRootId, + displayName: "WorkspaceQueryRoot", + source: contract.source, + members: [ + { + kind: "relationship", + id: workspaceObjectsMemberId, + displayName: "objects", + target: { kind: "interface", interfaceRevisionId: root.interface }, + cardinality: "many-unique", + ordered: false, + operations: [], + queryRead: { execution: "native" }, + }, + ], + }; + // Local objects observes the confirmed server window. availableObjects is the + // separate opt-in view that also includes pending local candidates. + if (local) synthetic.members.push({ ...synthetic.members[0]!, displayName: "availableObjects" }); + return { interface: synthetic.revisionId, interfaces: [...interfaces, synthetic] }; +} diff --git a/src/query/schema.ts b/src/query/schema.ts index d8cbee2..a4a91e3 100644 --- a/src/query/schema.ts +++ b/src/query/schema.ts @@ -1,3 +1,4 @@ +import { structuredQueryTypes, comparableValue } from "./structured.js"; import { GraphQLBoolean, GraphQLString, @@ -10,9 +11,8 @@ import { GraphQLList, GraphQLNonNull, GraphQLSchema, - GraphQLDirective, - DirectiveLocation, specifiedDirectives, + visit, type GraphQLOutputType, type GraphQLInputType, type GraphQLFieldConfigMap, @@ -28,6 +28,7 @@ import type { } from "../capability-model/types.js"; import { QueryCompileError, type QueryDeclaration } from "./types.js"; import { relationalSchema, objectRow } from "./relational-schema.js"; +import { queryRootSchema, workspaceQueryRootId } from "./root.js"; // Validate losslessly; callers carry decimal strings across JSON transports. function integerParser(name: string, min: bigint, max: bigint, value: unknown): string { @@ -78,7 +79,8 @@ export function querySchema( interfaces: readonly InterfaceRevision[], document?: DocumentNode, ) { - const contracts = new Map(interfaces.map((entry) => [entry.revisionId, entry])); + const root = queryRootSchema(declaration.root, interfaces, declaration.placement?.kind === "local"); + const contracts = new Map(root.interfaces.map((entry) => [entry.revisionId, entry])); const objects = new Map(); const orders = new Map(); const byName = new Map(); @@ -124,19 +126,41 @@ export function querySchema( ); return views[0]!.interfaceRevisionId; }; + const variants = new Map(); const scalar = (type: ValueType): GraphQLOutputType & GraphQLInputType => { if (type.kind === "optional") return scalar(type.value); + if (type.kind === "variant") { + const name = `QxVariant_${createHash("sha256").update(JSON.stringify(type)).digest("hex").slice(0, 16)}`; + let result = variants.get(name); + if (!result) { + result = new GraphQLScalarType({ + name, + description: "Whole checked Camino variant value. Branch selection and predicates are not supported.", + serialize: (value) => value, + parseValue() { + throw new QueryCompileError("QUERY_TYPE", "Variant query inputs are not supported"); + }, + parseLiteral() { + throw new QueryCompileError("QUERY_TYPE", "Variant query inputs are not supported"); + }, + }); + variants.set(name, result); + scalarTypes.set(name, type); + } + return result; + } if (type.kind !== "scalar") throw new QueryCompileError("QUERY_TYPE", "Only scalar/optional scalar query fields are supported"); return queryScalars[type.name]; }; - const comparison = (type: ValueType) => { + const scalarComparison = (type: ValueType) => { const base = type.kind === "optional" ? type.value : type; const value = scalar(base); const key = String(value); let result = comparisons.get(key); if (!result) { const fields: GraphQLInputFieldConfigMap = { eq: { type: value }, isNull: { type: GraphQLBoolean } }; + if (base.kind === "scalar" && base.name === "string") fields.containsInsensitive = { type: value }; if (base.kind === "scalar" && base.name !== "bytes") fields.in = { type: new GraphQLList(new GraphQLNonNull(value)) }; if (base.kind === "scalar" && base.name !== "bool" && base.name !== "bytes") @@ -146,11 +170,28 @@ export function querySchema( } return result; }; + const structured = structuredQueryTypes(scalar, scalarComparison, direction); + const viewable = (member: RelationshipInterfaceMember) => + member.target.kind === "interface" || + declaration.views.some((view) => member.target.kind === "atom" && view.atomId === member.target.atomId); + const comparison = (type: ValueType) => structured.input(type) as GraphQLInputObjectType; const relational = relationalSchema( - { contracts, target, name, scalar, comparison, scalarTypes, direction, cursor: cursorScalar, pageInfo }, + { + contracts, + target, + viewable, + name, + scalar, + comparison, + valueOrder: (type) => structured.input(type, true), + scalarTypes, + direction, + cursor: cursorScalar, + pageInfo, + }, document, ); - relational.discover(declaration.root); + relational.discover(root.interface); const filter = (id: InterfaceRevisionId) => relational.where(objectRow(id)); const order = (id: InterfaceRevisionId) => { let result = orders.get(id); @@ -159,7 +200,7 @@ export function querySchema( for (const member of contract(id).members) { if (member.kind !== "value" || !member.queryRead) continue; const t = member.valueType.kind === "optional" ? member.valueType.value : member.valueType; - if (t.kind === "scalar" && t.name !== "bytes") fields[member.displayName] = { type: direction }; + if (comparableValue(t, true)) fields[member.displayName] = { type: structured.input(t, true) }; } if (Object.keys(fields).length === 0) return undefined; result = new GraphQLInputObjectType({ name: `${name(id)}Order`, fields }); @@ -177,21 +218,26 @@ export function querySchema( const metadata = new GraphQLObjectType({ name: `QxMetadata_${name(id)}`, fields: { - ref: { type: new GraphQLNonNull(relational.refs(id)) }, + ...(id === workspaceQueryRootId + ? {} + : { + ref: { type: new GraphQLNonNull(relational.refs(id)) }, + atom: { type: new GraphQLNonNull(GraphQLString) }, + }), ...(helpers ? { relations: { type: new GraphQLNonNull(helpers) } } : {}), }, }); - const fields: GraphQLFieldConfigMap = { _qx: { type: new GraphQLNonNull(metadata) } }; + const fields: GraphQLFieldConfigMap = { _camino: { type: new GraphQLNonNull(metadata) } }; for (const member of contract(id).members) { - if (member.kind === "operation" || !member.queryRead) continue; + if (member.kind === "operation" || member.kind === "module" || !member.queryRead) continue; if (!/^[_A-Za-z][_0-9A-Za-z]*$/.test(member.displayName) || member.displayName.startsWith("_")) throw new QueryCompileError("QUERY_SCHEMA_NAME", `Unsupported query field name ${member.displayName}`); if (member.kind === "value") { - const type = scalar(member.valueType); - fields[member.displayName] = { - type: member.valueType.kind === "optional" ? type : new GraphQLNonNull(type), - }; + fields[member.displayName] = { type: structured.output(member.valueType) }; } else { + // Reference-only projections need no views for unselected outgoing + // atom relationships. Selecting one still requires its explicit view. + if (!viewable(member)) continue; const targetId = target(member), node = object(targetId); if (member.cardinality === "exactly-one" || member.cardinality === "optional-one") @@ -230,7 +276,11 @@ export function querySchema( first: { type: GraphQLInt }, all: { type: GraphQLInt }, after: { type: cursorScalar }, - where: { type: filter(targetId) }, + where: { + type: member.keyType + ? relational.where(objectRow(targetId, { keyType: member.keyType })) + : filter(targetId), + }, ...(ordering ? { orderBy: { type: new GraphQLList(new GraphQLNonNull(ordering)) } } : {}), }, }; @@ -244,10 +294,23 @@ export function querySchema( return result; }; const schema = new GraphQLSchema({ - directives: [...specifiedDirectives, new GraphQLDirective({ name: "live", locations: [DirectiveLocation.FIELD] })], + directives: specifiedDirectives, + // Nominal projections can mention an interface not reached by a relation. + types: (() => { + const requested = new Set(); + if (document) + visit(document, { + NamedType(node) { + requested.add(node.name.value); + }, + }); + return [...contracts.values()] + .filter((contract) => !contract.template && requested.has(name(contract.revisionId))) + .map((contract) => object(contract.revisionId)); + })(), query: new GraphQLObjectType({ name: "QxQuery", - fields: { root: { type: new GraphQLNonNull(object(declaration.root)) } }, + fields: { root: { type: new GraphQLNonNull(object(root.interface)) } }, }), }); return { schema, byName, target, contracts, relational, scalarTypes }; diff --git a/src/query/structured.ts b/src/query/structured.ts new file mode 100644 index 0000000..f628cb3 --- /dev/null +++ b/src/query/structured.ts @@ -0,0 +1,76 @@ +import { createHash } from "node:crypto"; +import { + GraphQLObjectType, + GraphQLInputObjectType, + GraphQLList, + GraphQLNonNull, + type GraphQLNullableType, + type GraphQLOutputType, + type GraphQLInputType, + type GraphQLEnumType, +} from "graphql"; +import type { ValueType } from "../capability-model/types.js"; +import { QueryCompileError } from "./types.js"; + +export function comparableValue(type: ValueType, ordering = false): boolean { + if (type.kind === "optional") return comparableValue(type.value, ordering); + if (type.kind === "record") return Object.values(type.fields).some((t) => comparableValue(t, ordering)); + return type.kind === "scalar" && (!ordering || type.name !== "bytes"); +} + +/** Structured values retain one field identity; their children are value projections. */ +export function structuredQueryTypes( + scalar: (type: ValueType) => GraphQLInputType & GraphQLOutputType, + scalarComparison: (type: ValueType) => GraphQLInputObjectType, + direction: GraphQLEnumType, +) { + const objects = new Map(); + const inputs = new Map(); + const name = (role: string, type: ValueType) => + `QxValue${role}_${createHash("sha256").update(JSON.stringify(type)).digest("hex").slice(0, 16)}`; + const output = (type: ValueType, depth = 0): GraphQLOutputType => { + if (depth > 128) throw new QueryCompileError("QUERY_TYPE", "Structured value exceeds type depth limit"); + if (type.kind === "optional") return required(type.value, depth + 1); + return new GraphQLNonNull(required(type, depth)); + }; + const required = (type: ValueType, depth: number): GraphQLNullableType & GraphQLOutputType => { + if (type.kind === "optional") return required(type.value, depth + 1); + if (type.kind === "list") return new GraphQLList(output(type.value, depth + 1)); + if (type.kind !== "record") return scalar(type) as GraphQLNullableType & GraphQLOutputType; + const key = name("Record", type); + let result = objects.get(key); + if (!result) { + if (!Object.keys(type.fields).length) + throw new QueryCompileError("QUERY_TYPE", "Queryable records must have at least one field"); + result = new GraphQLObjectType({ + name: key, + fields: () => + Object.fromEntries( + Object.entries(type.fields).map(([key, value]) => [key, { type: output(value, depth + 1) }]), + ), + }); + objects.set(key, result); + } + return result; + }; + const input = (type: ValueType, ordering = false): GraphQLInputType => { + if (type.kind === "optional") return input(type.value, ordering); + if (type.kind !== "record") return ordering ? direction : scalarComparison(type); + const key = name(ordering ? "Order" : "Compare", type); + let result = inputs.get(key); + if (!result) { + result = new GraphQLInputObjectType({ + name: key, + fields: () => + Object.fromEntries( + Object.entries(type.fields) + .filter(([, value]) => comparableValue(value, ordering)) + .map(([key, value]) => [key, { type: input(value, ordering) }]), + ), + }); + inputs.set(key, result); + } + return result; + }; + return { output, input }; +} diff --git a/src/query/templates.ts b/src/query/templates.ts index f37cafa..80988fa 100644 --- a/src/query/templates.ts +++ b/src/query/templates.ts @@ -34,7 +34,7 @@ export function specializeQueryTemplate( return { ...structuredClone(template.declaration), ...identity, - root: substitution.application(template.root), + root: { kind: template.root.kind, interface: substitution.application(template.root.interface) }, views: template.views .map((view) => { const target = substitution.object(view.target); diff --git a/src/query/types.ts b/src/query/types.ts index 07aff34..cfa096c 100644 --- a/src/query/types.ts +++ b/src/query/types.ts @@ -31,10 +31,25 @@ export const defaultQueryBudgets: Readonly = Object.freeze({ concurrency: 8, deadlineMs: 10000, }); +export type QueryRoot = + | { kind: "object"; interface: Interface } + | { kind: "workspace-interface"; interface: Interface }; export interface QueryDeclaration { + /** Exported local projection; compilation also creates its direct admission. */ + fragment?: { name: string; operation?: string; admission?: boolean }; + importedFragments?: { + /** Bind an independently observed fragment without including its fields in + * this projection. Its acquisition is still an explicit replica source. */ + mode?: "reference"; + packageRevisionId: string; + packageAlias?: string; + fragmentId: string; + variables: Record; + }[]; + placement?: { kind: "replica" } | { kind: "local"; sourceQueryId: string }; id: string; displayName: string; - root: InterfaceRevisionId; + root: QueryRoot; document: string; operation: string; fragments: string[]; @@ -53,7 +68,7 @@ export interface QueryDeclaration { export interface QueryTemplate { declaration: Omit; parameters: TypeParameter[]; - root: InterfaceApplicationExpression; + root: QueryRoot; views: { target: ObjectTypeExpression; interface: InterfaceApplicationExpression }[]; allowances: { interface: InterfaceApplicationExpression; memberName: string; uses: QueryUse[]; reason: string }[]; } @@ -61,7 +76,7 @@ export interface QueryFieldEffect { interfaceRevisionId: InterfaceRevisionId; memberId: MemberId; uses: QueryUse[]; - execution: "native" | "rpc-permitted"; + execution: "native" | "rpc-permitted" | "portable"; } export interface QuerySourceLocation { file: string; @@ -79,9 +94,11 @@ export class QueryCompileError extends Error { } } export interface CheckedQuery { + fragmentContracts?: Record>; declaration: QueryDeclaration; definitionDigest: string; - /** Fixed, validated document: never supplied by runtime callers. */ + /** Fixed, validated document. Execution always uses checked IR; public runtime + * documents pass through this same checker before they are executed. */ document: string; schema: string; variables: ValueType; @@ -98,7 +115,15 @@ export type QueryArgument = | { kind: "list"; values: QueryArgument[] } | { kind: "object"; fields: Record }; export interface QuerySelection { - liveSelectionId?: string; + /** A checked occurrence marker, not an output field or storage capability. */ + fragmentSpread?: { + mode?: "reference"; + package: string; + fragment: string; + digest: string; + root: InterfaceRevisionId; + variables: Record; + }; name: string; key: string; interfaceRevisionId?: InterfaceRevisionId; diff --git a/src/react-build/declarations.ts b/src/react-build/declarations.ts new file mode 100644 index 0000000..429d5f4 --- /dev/null +++ b/src/react-build/declarations.ts @@ -0,0 +1,145 @@ +import type { InterfaceRevision, PackageRevision, ValueType } from "../capability-model/types.js"; +import type { QueryBinding, QueryComposition, QueryCompositionStep } from "../query/composition.js"; + +/** This is an artifact of the explicitly selected React frontend, not QX IR. */ +export const preparationArtifact = "org.quixos.react.preparation/1"; +export interface ReactChildDeclaration { + name: string; + subject: string; + root: QueryBinding; + slot: string; + with?: QueryBinding; + each?: { source: QueryBinding; limit: number }; + when?: QueryBinding; + role?: { interface: string; member: string }; + provider?: { package: string; module: string }; +} +export interface ReactModulePreparation { + module: string; + composition: string; + /** Public input type before workspace query lowering adds private projections. */ + input?: ValueType; + children: ReactChildDeclaration[]; +} +export interface ReactPreparationArtifact { + /** Frontend queries belong to installed preparation, not the authored client API. */ + internalQueries?: string[]; + modules: ReactModulePreparation[]; +} + +/** Names are package imports/module declarations. All query bindings are passed + * to Camino's ordinary composition checker after generated queries are checked. */ +export function presentationDeclarations( + text: string, + pkg: PackageRevision, + interfaces: readonly InterfaceRevision[], +): { compositions: QueryComposition[]; artifact: ReactPreparationArtifact } { + const source: unknown = JSON.parse(text); + const object = (value: unknown, label: string): Record => { + if (!value || typeof value !== "object" || Array.isArray(value)) throw Error(`${label}: expected an object`); + return value as Record; + }; + const top = object(source, "quixos.react.json"); + if (Object.keys(top).some((key) => key !== "modules")) throw Error("quixos.react.json: unknown field"); + const modules = object(top.modules, "modules"); + const compositions: QueryComposition[] = []; + const artifact: ReactPreparationArtifact = { modules: [] }; + const imported = (name: string) => { + const id = pkg.clientInterfaces?.[name]; + if (!id) throw Error(`React declaration: unknown imported interface ${name}`); + return id; + }; + for (const [name, value] of Object.entries(modules)) { + const declaration = object(value, `module ${name}`); + if (Object.keys(declaration).some((key) => !["inputs", "children"].includes(key))) + throw Error(`${name}: unknown preparation field`); + const module = pkg.modules?.find((entry) => entry.displayName === name); + if (!module) throw Error(`React declaration: unknown module ${name}`); + const contract = interfaces.find((entry) => entry.revisionId === module.interfaceRevisionId); + const member = contract?.members.find((entry) => entry.id === module.memberId); + if ( + member?.kind !== "module" || + member.subject?.kind !== "interface" || + !["org.quixos.react.component/1", "org.quixos.react.component/2", "org.quixos.react.subject-only/1"].includes( + member.contract, + ) + ) + throw Error(`${name}: preparation requires a React module contract`); + const fields: Record = { + subject: { + kind: "object-ref", + expectation: { kind: "interface", interfaceRevisionId: member.subject.interfaceRevisionId }, + }, + }; + if (member.contract === "org.quixos.react.component/2") { + const state = contract?.application?.arguments[1]; + if (state?.kind !== "object" || state.target.kind !== "interface") + throw Error(`${name}: expected a closed state interface`); + fields.navigation = { + kind: "object-ref", + expectation: { kind: "interface", interfaceRevisionId: state.target.interfaceRevisionId }, + }; + } + if (!Array.isArray(declaration.inputs)) throw Error(`${name}: inputs must be an array of query bindings`); + const steps = declaration.inputs.map((value) => { + const step = object(value, `${name} input`) as unknown as QueryCompositionStep; + if (step.name === "children") throw Error(`${name}: children is reserved for prepared child inputs`); + const query = pkg.queries?.find((entry) => entry.displayName === step.query); + if (!query) throw Error(`${name}.${step.name}: unknown query ${step.query}`); + if (query.placement?.kind !== "local" && !query.fragment) + throw Error(`${name}.${step.name}: requires a local projection or fragment`); + return { ...step, query: query.id }; + }); + const composition = `react:inputs:${module.id}`; + compositions.push({ + id: composition, + input: { kind: "record", fields }, + steps, + maxQueries: steps.reduce((count, step) => count + (step.each?.limit ?? 1), 0) || 1, + }); + const children = declaration.children ?? []; + if (!Array.isArray(children)) throw Error(`${name}: children must be an array`); + const names = new Set(); + const parsed = children.map((value) => { + const child = object(value, `${name} child`) as unknown as ReactChildDeclaration; + if (!child.name || names.has(child.name) || !child.slot || !child.root) + throw Error(`${name}: child requires a unique name, slot and root`); + names.add(child.name); + // A local module identifies a closed role, including its generic arguments. + // This avoids inventing an import alias for a generic application solely + // to select that role in the React preparation manifest. + let role: ReactChildDeclaration["role"]; + if (child.role) { + const authored = object(child.role, `${name}.${child.name} role`); + if (typeof authored.module === "string") { + const target = pkg.modules?.find((entry) => entry.displayName === authored.module); + if (!target || Object.keys(authored).length !== 1) + throw Error(`${name}.${child.name}: expected a local module role`); + role = { interface: target.interfaceRevisionId, member: target.memberId }; + } else { + role = { ...child.role, interface: imported(child.role.interface) }; + } + } + if (child.each && child.with) throw Error(`${name}.${child.name}: cannot combine each and with`); + if (child.each && (!Number.isSafeInteger(child.each.limit) || child.each.limit < 1)) + throw Error(`${name}.${child.name}: finite positive collection limit required`); + return { + ...child, + subject: imported(child.subject), + ...(role ? { role } : {}), + }; + }); + artifact.modules.push({ module: module.id, composition, children: parsed }); + } + for (const module of pkg.modules ?? []) { + const contract = interfaces.find((entry) => entry.revisionId === module.interfaceRevisionId); + const member = contract?.members.find((entry) => entry.id === module.memberId); + if ( + member?.kind === "module" && + member.contract.startsWith("org.quixos.react.") && + !artifact.modules.some((entry) => entry.module === module.id) + ) + throw Error(`React module ${module.displayName} has no preparation declaration`); + } + return { compositions, artifact }; +} diff --git a/src/react-build/expansion.ts b/src/react-build/expansion.ts new file mode 100644 index 0000000..4776f4c --- /dev/null +++ b/src/react-build/expansion.ts @@ -0,0 +1,431 @@ +import { createHash } from "node:crypto"; +import { + parse, + print, + visit, + Kind, + type SelectionSetNode, + type FragmentDefinitionNode, + type SelectionNode, + type DocumentNode, +} from "graphql"; +import type { PackageRevision, WorkspaceRevision } from "../capability-model/types.js"; +import { compileQuery } from "../query/compile.js"; +import { compileQueryComposition, type QueryBinding } from "../query/composition.js"; +import type { CheckedQuery, QueryDeclaration } from "../query/types.js"; +import { preparationArtifact, type ReactModulePreparation, type ReactPreparationArtifact } from "./declarations.js"; + +interface Branch { + packageRevision: string; + id: string; +} +type Child = ReactModulePreparation["children"][number] & { branches: Branch[] }; +interface Module { + pkg: PackageRevision; + preparation: ReactModulePreparation; +} +const identity = (pkg: string, id: string) => JSON.stringify([pkg, id]); +const suffix = (value: string) => createHash("sha256").update(value).digest("hex").slice(0, 20); + +/** Lower bounded, statically bound subject projections into ordinary fragments. + * The generated query still passes the normal GraphQL/QX checker. Stateful + * lookups remain independent ordinary root-scoped queries owned by the adapter. */ +export async function expandPresentationQueries(workspace: WorkspaceRevision): Promise { + const modules = new Map(); + for (const pkg of workspace.packageImports) { + const artifact = pkg.buildArtifacts?.[preparationArtifact] as ReactPreparationArtifact | undefined; + for (const preparation of artifact?.modules ?? []) + modules.set(identity(pkg.revisionId, preparation.module), { pkg, preparation }); + } + const query = (pkg: string, id: string) => { + const result = workspace.packageImports + .find((entry) => entry.revisionId === pkg) + ?.checkedQueries?.find((entry) => entry.declaration.id === id); + if (!result) throw Error(`Missing checked preparation query ${pkg}/${id}`); + return result; + }; + const resolver = async (pkg: string, id: string) => query(pkg, id); + const install = async (pkg: PackageRevision, declaration: QueryDeclaration, document: string) => { + const views = new Map(declaration.views.map((view) => [view.atomId, view])); + const allowances = [...declaration.allowances]; + for (const imported of declaration.importedFragments ?? []) { + const dependency = query(imported.packageRevisionId, imported.fragmentId).declaration; + for (const view of dependency.views) { + const previous = views.get(view.atomId); + if (previous && previous.interfaceRevisionId !== view.interfaceRevisionId) + throw Error(`${declaration.displayName}: conflicting interface views for ${view.atomId}`); + views.set(view.atomId, view); + } + for (const allowance of dependency.allowances) + if (!allowances.some((entry) => JSON.stringify(entry) === JSON.stringify(allowance))) + allowances.push(allowance); + } + declaration = { ...declaration, views: [...views.values()], allowances }; + const artifact = pkg.buildArtifacts?.[preparationArtifact] as ReactPreparationArtifact | undefined; + if (artifact) artifact.internalQueries = [...new Set([...(artifact.internalQueries ?? []), declaration.id])].sort(); + const checked = await compileQuery(declaration, workspace.interfaceImports, async () => document, resolver); + pkg.queries = [...(pkg.queries ?? []).filter((entry) => entry.id !== declaration.id), declaration]; + pkg.checkedQueries = [ + ...(pkg.checkedQueries ?? []).filter((entry) => entry.declaration.id !== declaration.id), + checked, + ]; + return checked; + }; + function expanded(source: CheckedQuery): SelectionSetNode { + const document = parse(source.document); + const fragments = new Map( + document.definitions + .filter((entry): entry is FragmentDefinitionNode => entry.kind === Kind.FRAGMENT_DEFINITION) + .map((entry) => [entry.name.value, entry]), + ); + const operation = document.definitions.find((entry) => entry.kind === Kind.OPERATION_DEFINITION); + if (!operation || operation.kind !== Kind.OPERATION_DEFINITION) + throw Error(`Missing query operation ${source.declaration.id}`); + const expand = (selection: SelectionSetNode, parents: string[] = []): SelectionSetNode => ({ + ...selection, + selections: selection.selections.flatMap((entry): SelectionNode[] => { + if (entry.kind === Kind.FRAGMENT_SPREAD) { + if (parents.includes(entry.name.value)) throw Error("Cyclic query fragment"); + const fragment = fragments.get(entry.name.value); + if (!fragment) throw Error(`Missing query fragment ${entry.name.value}`); + return [ + { + kind: Kind.INLINE_FRAGMENT, + typeCondition: fragment.typeCondition, + directives: entry.directives ?? [], + selectionSet: expand(fragment.selectionSet, [...parents, entry.name.value]), + }, + ]; + } + return [{ ...entry, ...(entry.selectionSet ? { selectionSet: expand(entry.selectionSet, parents) } : {}) }]; + }), + }); + return expand(operation.selectionSet); + } + function insert(selection: SelectionSetNode, path: readonly string[], spread: string): SelectionSetNode { + if (!path.length) + return { + ...selection, + selections: [...selection.selections, { kind: Kind.FRAGMENT_SPREAD, name: { kind: Kind.NAME, value: spread } }], + }; + let found = false; + const selections = selection.selections.map((entry): SelectionNode => { + if (entry.kind === Kind.INLINE_FRAGMENT) { + try { + const nested = insert(entry.selectionSet, path, spread); + found = true; + return { ...entry, selectionSet: nested }; + } catch (error) { + if (!(error instanceof Error) || error.message !== "REACT_SELECTION_PATH") throw error; + } + } + if (entry.kind === Kind.FIELD && (entry.alias?.value ?? entry.name.value) === path[0] && entry.selectionSet) { + found = true; + return { ...entry, selectionSet: insert(entry.selectionSet, path.slice(1), spread) }; + } + return entry; + }); + if (!found) throw Error("REACT_SELECTION_PATH"); + return { ...selection, selections }; + } + function location(binding: QueryBinding): { name: string; path: string[] } | undefined { + if ("from" in binding && binding.from === "result") return { name: binding.name, path: [...binding.path] }; + if ("flatMap" in binding) { + const root = location(binding.flatMap.source); + return root ? { name: root.name, path: [...root.path, ...binding.flatMap.path] } : undefined; + } + return undefined; + } + const done = new Set(); + const eager = new Map(); + const admissionFragments = new Map(); + const stateInputs = new Map(); + async function expandModule(key: string): Promise { + if (done.has(key)) return; + const module = modules.get(key); + if (!module) throw Error(`Missing preparation module ${key}`); + const { pkg, preparation } = module; + const children = preparation.children as Child[]; + for (const child of children) + for (const branch of child.branches) await expandModule(identity(branch.packageRevision, branch.id)); + const composition = pkg.queryCompositions!.find((entry) => entry.id === preparation.composition)!; + const steps = structuredClone(composition.steps); + const exported: { step: string; query: CheckedQuery }[] = []; + for (const step of steps) { + const original = query(pkg.revisionId, step.query); + // Variable roots/arguments remain checked dependent steps. Constants can + // be substituted without requesting new runtime parameters from a parent. + if (!("record" in step.arguments) || Object.values(step.arguments.record).some((value) => !("literal" in value))) + continue; + const constants = step.arguments.record; + let selection = expanded(original); + if (original.declaration.placement?.kind !== "local") + throw Error(key + "." + step.name + ": expected checked local input"); + const source = query(pkg.revisionId, original.declaration.placement.sourceQueryId); + let admissionSelection = expanded(source); + const admissionImports: NonNullable = []; + const imports: NonNullable = []; + for (const child of children) { + const selected = child.with + ? location(child.with) + : child.each + ? location(child.each.source) + : location(child.root); + if (!selected || selected.name !== step.name) continue; + const rootPath = "from" in child.root && child.root.from === "item" ? child.root.path : []; + const path = [...selected.path, ...rootPath]; + if (path.slice(-2).join(".") !== "_camino.ref") { + if (!rootPath.length && (child.with || child.each)) + throw Error(`${key}.${child.name}: child root must select the item's reference`); + } else path.splice(-2); + for (const branch of child.branches) { + for (const input of eager.get(identity(branch.packageRevision, branch.id)) ?? []) { + const declaration = input.query.declaration; + if ( + !imports.some( + (entry) => entry.packageRevisionId === branch.packageRevision && entry.fragmentId === declaration.id, + ) + ) { + imports.push({ + packageRevisionId: branch.packageRevision, + fragmentId: declaration.id, + variables: {}, + mode: "reference", + }); + } + selection = insert(selection, path, declaration.fragment!.name); + const admission = admissionFragments.get(identity(branch.packageRevision, declaration.id))!; + if ( + !admissionImports.some( + (entry) => + entry.packageRevisionId === branch.packageRevision && entry.fragmentId === admission.declaration.id, + ) + ) + admissionImports.push({ + packageRevisionId: branch.packageRevision, + fragmentId: admission.declaration.id, + variables: {}, + }); + admissionSelection = insert(admissionSelection, path, admission.declaration.fragment!.name); + if ( + !admissionImports.some( + (entry) => entry.packageRevisionId === branch.packageRevision && entry.fragmentId === declaration.id, + ) + ) + admissionImports.push({ + packageRevisionId: branch.packageRevision, + fragmentId: declaration.id, + variables: {}, + mode: "reference", + }); + admissionSelection = insert(admissionSelection, path, declaration.fragment!.name); + } + } + } + const token = suffix(identity(pkg.revisionId, `${preparation.module}/${step.name}`)); + const name = `ReactInput${token}`, + id = `react:input:${preparation.module}:${step.name}`; + const base: QueryDeclaration = { + ...original.declaration, + id, + displayName: name, + document: `generated/react/${name}.graphql`, + operation: name, + fragments: [], + importedFragments: imports, + watch: true, + budgets: { + ...original.declaration.budgets, + depth: Math.max(original.declaration.budgets.depth, 32), + rows: Math.max(original.declaration.budgets.rows, 5000), + }, + }; + delete base.fragment; + function documentFor(selection: SelectionSetNode, fragmentName: string): DocumentNode { + const substituted = visit(selection, { + Variable(node) { + const constant = constants[node.name.value]; + if (!constant || !("literal" in constant)) + throw Error(key + "." + step.name + ": unbound query variable " + node.name.value); + return typeof constant.literal === "boolean" + ? { kind: Kind.BOOLEAN, value: constant.literal } + : { kind: Kind.STRING, value: constant.literal }; + }, + }); + if (original.declaration.root.kind === "object") { + const root = substituted.selections.find((entry) => entry.kind === Kind.FIELD && entry.name.value === "root"); + if (!root || root.kind !== Kind.FIELD || !root.selectionSet) + throw Error(key + ": expected object-rooted query"); + const contract = workspace.interfaceImports.find( + (entry) => entry.revisionId === original.declaration.root.interface, + )!; + return { + kind: Kind.DOCUMENT, + definitions: [ + { + kind: Kind.FRAGMENT_DEFINITION, + name: { kind: Kind.NAME, value: fragmentName }, + typeCondition: { kind: Kind.NAMED_TYPE, name: { kind: Kind.NAME, value: contract.displayName } }, + selectionSet: root.selectionSet, + }, + ], + }; + } + return { + kind: Kind.DOCUMENT, + definitions: [ + { + kind: Kind.OPERATION_DEFINITION, + operation: "query" as import("graphql").OperationTypeNode, + name: { kind: Kind.NAME, value: name }, + selectionSet: substituted, + }, + ], + }; + } + if (original.declaration.root.kind === "object") base.fragment = { name }; + const sourceId = id + ":fragment-inputs"; + const admissionName = name + "Admission"; + const admissionBase: QueryDeclaration = { + ...base, + views: source.declaration.views, + allowances: source.declaration.allowances, + importedFragments: admissionImports, + ...(base.fragment ? { fragment: { name: admissionName } } : {}), + }; + const admissionDocument = documentFor(admissionSelection, admissionName); + await install( + pkg, + { + ...admissionBase, + id: sourceId, + displayName: name + "$inputs", + placement: { kind: "replica" }, + watch: false, + ...(base.fragment ? { fragment: { name: admissionName, admission: true } } : {}), + }, + print(admissionDocument), + ); + const admissionFragment = await install( + pkg, + { + ...admissionBase, + id: id + ":admission-projection", + displayName: name + "$admission", + placement: { kind: "local", sourceQueryId: sourceId }, + }, + print(admissionDocument), + ); + const local = await install( + pkg, + { ...base, placement: { kind: "local", sourceQueryId: sourceId } }, + print(documentFor(selection, name)), + ); + admissionFragments.set(identity(pkg.revisionId, local.declaration.id), admissionFragment); + step.query = local.declaration.id; + step.arguments = { record: {} }; + if ( + local.declaration.fragment && + step.root && + "from" in step.root && + step.root.from === "input" && + step.root.path.join(".") === "subject" + ) + exported.push({ step: step.name, query: local }); + if ( + local.declaration.fragment && + step.root && + "from" in step.root && + step.root.from === "input" && + step.root.path.join(".") === "navigation" + ) { + const iface = local.declaration.root.interface; + stateInputs.set(iface, [...(stateInputs.get(iface) ?? []), { pkg, query: local }]); + } + } + const available = new Map( + workspace.packageImports.flatMap((pkg) => + (pkg.checkedQueries ?? []).map((query) => [identity(pkg.revisionId, query.declaration.id), query] as const), + ), + ); + for (const query of pkg.checkedQueries ?? []) available.set(query.declaration.id, query); + const checked = compileQueryComposition( + { id: composition.id, input: composition.input, steps, maxQueries: composition.maxQueries }, + available, + ); + pkg.queryCompositions = pkg.queryCompositions!.map((entry) => (entry.id === composition.id ? checked : entry)); + eager.set(key, exported); + done.add(key); + } + for (const key of modules.keys()) await expandModule(key); + // Retention queries are ordinary checked queries too. They carry the declared + // state projections, including any subject fragments reached through state. + // This is specific to this frontend; no navigation operator enters Camino. + for (const pkg of workspace.packageImports) { + for (const original of [...(pkg.checkedQueries ?? [])]) { + if (!original.declaration.id.startsWith("presentation:navigation:")) continue; + const inputs = (stateInputs.get(original.declaration.root.interface) ?? []).map((input) => ({ + ...input, + query: + original.declaration.placement?.kind === "replica" + ? admissionFragments.get(identity(input.pkg.revisionId, input.query.declaration.id))! + : input.query, + })); + if (!inputs.length) continue; + const document = parse(original.document); + const collection = original.declaration.placement?.kind === "local" ? "availableObjects" : "objects"; + const definitions = document.definitions.map((definition) => { + if (definition.kind !== Kind.OPERATION_DEFINITION) return definition; + return visit(definition, { + Field(node) { + if (node.name.value !== "root" || !node.selectionSet) return; + const source = node.selectionSet.selections.find( + (entry) => entry.kind === Kind.FIELD && entry.name.value === collection, + ); + if (!source || source.kind !== Kind.FIELD) throw Error("Missing navigation collection"); + return { + ...node, + selectionSet: { + ...node.selectionSet, + selections: [ + ...node.selectionSet.selections, + ...inputs.map((input) => ({ + ...source, + alias: { + kind: Kind.NAME as const, + value: `prepared${suffix(identity(input.pkg.revisionId, input.query.declaration.id))}`, + }, + selectionSet: parse( + `{ entries { node { ...${input.query.declaration.fragment!.name} } } }`, + ).definitions.flatMap((entry) => + entry.kind === Kind.OPERATION_DEFINITION ? [entry.selectionSet] : [], + )[0]!, + })), + ], + }, + }; + }, + }); + }); + await install( + pkg, + { + ...original.declaration, + ...(original.declaration.placement?.kind === "local" + ? { + id: original.declaration.id.replace(/:view$/, ":prepared-view"), + displayName: original.declaration.displayName + "Prepared", + } + : {}), + importedFragments: inputs.map((input) => ({ + packageRevisionId: input.pkg.revisionId, + fragmentId: input.query.declaration.id, + variables: {}, + ...(original.declaration.placement?.kind === "local" ? { mode: "reference" as const } : {}), + })), + }, + print({ ...document, definitions }), + ); + } + } +} diff --git a/src/react-build/package-queries.ts b/src/react-build/package-queries.ts new file mode 100644 index 0000000..b9dd3f6 --- /dev/null +++ b/src/react-build/package-queries.ts @@ -0,0 +1,34 @@ +import type { PackageQueryGenerator } from "../capability-language/assembly.js"; +import { presentationQueries } from "../bindings/presentation-queries.js"; +import { presentationDeclarations, preparationArtifact } from "./declarations.js"; +export { completeWorkspace } from "./workspace.js"; + +/** Selected explicitly by the React build. Its output is checked as ordinary + * Camino queries; no React identifiers are interpreted by package assembly. */ +export const generatePackageQueries: PackageQueryGenerator = async (input) => { + const pkg = structuredClone(input.package); + const before = new Set(pkg.queries?.map((query) => query.id)); + const documents = presentationQueries(pkg, input.interfaces); + const reactModules = (pkg.modules ?? []).filter((module) => + input.interfaces + .find((entry) => entry.revisionId === module.interfaceRevisionId) + ?.members.some( + (entry) => + entry.id === module.memberId && entry.kind === "module" && entry.contract.startsWith("org.quixos.react."), + ), + ); + const declarations = reactModules.length + ? presentationDeclarations(await input.readSource("quixos.react.json"), pkg, input.interfaces) + : undefined; + if (declarations) + declarations.artifact.internalQueries = (pkg.queries ?? []) + .filter((query) => !before.has(query.id)) + .map((query) => query.id); + return { + queries: (pkg.queries ?? []).filter((query) => !before.has(query.id)), + documents, + ...(declarations + ? { compositions: declarations.compositions, artifacts: { [preparationArtifact]: declarations.artifact } } + : {}), + }; +}; diff --git a/src/react-build/workspace.ts b/src/react-build/workspace.ts new file mode 100644 index 0000000..a413d57 --- /dev/null +++ b/src/react-build/workspace.ts @@ -0,0 +1,151 @@ +import type { WorkspaceRevision, ValueType } from "../capability-model/types.js"; +import { moduleContract } from "../capability-model/modules.js"; +import { canonicalJson } from "../capability-model/evolution.js"; +import { queryBindingType } from "../query/composition.js"; +import { moduleTypeContext } from "../execution-world/module-contracts.js"; +import { preparationArtifact, type ReactPreparationArtifact } from "./declarations.js"; +import { expandPresentationQueries } from "./expansion.js"; + +/** Explicit frontend stage. Resolve finite provider branches after workspace + * bindings exist; the generic workspace compiler does not invoke this itself. */ +export async function completeWorkspace(workspace: WorkspaceRevision): Promise { + const modules = workspace.packageImports.flatMap((pkg) => { + const artifact = pkg.buildArtifacts?.[preparationArtifact] as ReactPreparationArtifact | undefined; + return (artifact?.modules ?? []).map((preparation) => { + const module = pkg.modules?.find((entry) => entry.id === preparation.module); + if (!module) throw Error(`React preparation has no module ${preparation.module}`); + const contract = moduleContract(workspace.interfaceImports, module)!; + return { pkg, module, contract, preparation }; + }); + }); + const graph = new Map(); + const key = (pkg: string, id: string) => JSON.stringify([pkg, id]); + for (const parent of modules) { + const composition = parent.pkg.queryCompositions?.find((entry) => entry.id === parent.preparation.composition); + if (!composition || composition.output.kind !== "record") + throw Error(`Missing checked inputs for ${parent.module.displayName}`); + parent.preparation.input = structuredClone(composition.output); + const outputs = new Map(Object.entries(composition.output.fields)); + const dependencies: string[] = []; + const children = parent.preparation.children.map((child) => { + const at = `${parent.module.displayName}.${child.name}`; + let item: ValueType | undefined; + if (child.with) { + const type = queryBindingType(child.with, composition.input, outputs); + item = type.kind === "optional" ? type.value : type; + } + if (child.each) { + const type = queryBindingType(child.each.source, composition.input, outputs); + if (type.kind !== "list") throw Error(`${at}: each requires a list`); + item = type.value; + } + const root = queryBindingType(child.root, composition.input, outputs, item); + if ( + root.kind !== "object-ref" || + root.expectation.kind !== "interface" || + root.expectation.interfaceRevisionId !== child.subject + ) + throw Error(`${at}: child root must be a ${child.subject} reference; unwrap nulls and lists explicitly`); + if ( + child.when && + canonicalJson(queryBindingType(child.when, composition.input, outputs, item)) !== + canonicalJson({ kind: "scalar", name: "bool" }) + ) + throw Error(`${at}: when requires a boolean`); + const atoms = workspace.atoms.filter((atom) => + workspace.conformances.some((entry) => entry.atomId === atom.id && entry.interfaceRevisionId === child.subject), + ); + if (!atoms.length) throw Error(`${at}: no installed subject atoms`); + const branches = atoms.map((atom) => { + let candidates = modules.filter( + (entry) => + entry.contract.subject?.kind === "interface" && + entry.contract.subject.interfaceRevisionId === child.subject, + ); + if (child.role) { + const iface = workspace.interfaceImports.find((entry) => entry.revisionId === child.role!.interface); + const member = iface?.members.find( + (entry) => entry.displayName === child.role!.member || entry.id === child.role!.member, + ); + if (member?.kind !== "module") throw Error(`${at}: unknown rendering role`); + if ( + !workspace.conformances.some( + (entry) => entry.atomId === atom.id && entry.interfaceRevisionId === iface!.revisionId, + ) + ) + throw Error(`${at}: ${atom.id} does not implement the requested role`); + candidates = candidates.filter( + (entry) => entry.module.interfaceRevisionId === iface!.revisionId && entry.module.memberId === member.id, + ); + } else { + candidates = candidates.filter((entry) => + ["org.quixos.react.subject-only/1", "org.quixos.react.component/2"].includes(entry.contract.contract), + ); + } + if (child.provider) { + const pkg = parent.pkg.clientPackages?.[child.provider.package]; + if (!pkg) throw Error(`${at}: provider package is not imported`); + candidates = candidates.filter( + (entry) => entry.pkg.revisionId === pkg && entry.module.displayName === child.provider!.module, + ); + } else { + const bound = candidates.filter((entry) => + workspace.conformances.some( + (conformance) => + conformance.atomId === atom.id && + conformance.interfaceRevisionId === entry.module.interfaceRevisionId && + conformance.moduleBindings?.some( + (binding) => + binding.memberId === entry.module.memberId && + binding.packageRevisionId === entry.pkg.revisionId && + binding.moduleId === entry.module.id, + ), + ), + ); + candidates = bound.length + ? bound + : child.role + ? [] + : candidates.filter((entry) => + workspace.presentations?.some( + (presentation) => + presentation.subject === child.subject && + presentation.packageRevisionId === entry.pkg.revisionId && + presentation.componentId === entry.module.id, + ), + ); + } + if (candidates.length !== 1) + throw Error(`${at}: ${atom.id} has ${candidates.length} compatible providers; select one explicitly`); + const selected = candidates[0]!; + dependencies.push(key(selected.pkg.revisionId, selected.module.id)); + return { + atom: atom.id, + packageRevision: selected.pkg.revisionId, + id: selected.module.id, + role: { + interface: selected.module.interfaceRevisionId, + member: selected.module.memberId, + subject: child.subject, + contract: selected.contract.contract, + ...moduleTypeContext( + workspace.interfaceImports.find((entry) => entry.revisionId === selected.module.interfaceRevisionId)!, + ), + }, + }; + }); + return { ...child, branches }; + }); + graph.set(key(parent.pkg.revisionId, parent.module.id), dependencies); + Object.assign(parent.preparation, { children }); + } + const visited = new Set(); + const visit = (node: string, ancestors: string[]) => { + if (ancestors.includes(node)) throw Error(`Eager React preparation cycle: ${[...ancestors, node].join(" -> ")}`); + if (visited.has(node)) return; + for (const child of graph.get(node) ?? []) visit(child, [...ancestors, node]); + visited.add(node); + }; + for (const node of graph.keys()) visit(node, []); + await expandPresentationQueries(workspace); +} diff --git a/src/resource-lock/generated/QuixosLock.interp b/src/resource-lock/generated/QuixosLock.interp index 256f43b..083e8bf 100644 --- a/src/resource-lock/generated/QuixosLock.interp +++ b/src/resource-lock/generated/QuixosLock.interp @@ -7,6 +7,7 @@ null 'source' 'interface' 'package' +'build' 'repository' 'commit' 'policy' @@ -34,6 +35,7 @@ QUIXOS SOURCE INTERFACE PACKAGE +BUILD REPOSITORY COMMIT POLICY @@ -66,4 +68,4 @@ stringLiteral atn: -[4, 1, 24, 87, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, 4, 2, 5, 7, 5, 2, 6, 7, 6, 2, 7, 7, 7, 2, 8, 7, 8, 2, 9, 7, 9, 1, 0, 1, 0, 3, 0, 23, 8, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 5, 0, 31, 8, 0, 10, 0, 12, 0, 34, 9, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 3, 1, 3, 1, 3, 1, 3, 1, 4, 1, 4, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 3, 6, 74, 8, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 7, 1, 7, 1, 8, 1, 8, 1, 9, 1, 9, 1, 9, 0, 0, 10, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 0, 2, 1, 0, 6, 7, 1, 0, 12, 14, 81, 0, 20, 1, 0, 0, 0, 2, 38, 1, 0, 0, 0, 4, 42, 1, 0, 0, 0, 6, 47, 1, 0, 0, 0, 8, 51, 1, 0, 0, 0, 10, 53, 1, 0, 0, 0, 12, 62, 1, 0, 0, 0, 14, 80, 1, 0, 0, 0, 16, 82, 1, 0, 0, 0, 18, 84, 1, 0, 0, 0, 20, 22, 5, 1, 0, 0, 21, 23, 5, 2, 0, 0, 22, 21, 1, 0, 0, 0, 22, 23, 1, 0, 0, 0, 23, 24, 1, 0, 0, 0, 24, 25, 5, 3, 0, 0, 25, 26, 5, 19, 0, 0, 26, 32, 5, 16, 0, 0, 27, 31, 3, 2, 1, 0, 28, 31, 3, 6, 3, 0, 29, 31, 3, 4, 2, 0, 30, 27, 1, 0, 0, 0, 30, 28, 1, 0, 0, 0, 30, 29, 1, 0, 0, 0, 31, 34, 1, 0, 0, 0, 32, 30, 1, 0, 0, 0, 32, 33, 1, 0, 0, 0, 33, 35, 1, 0, 0, 0, 34, 32, 1, 0, 0, 0, 35, 36, 5, 17, 0, 0, 36, 37, 5, 0, 0, 1, 37, 1, 1, 0, 0, 0, 38, 39, 5, 4, 0, 0, 39, 40, 5, 5, 0, 0, 40, 41, 3, 12, 6, 0, 41, 3, 1, 0, 0, 0, 42, 43, 3, 8, 4, 0, 43, 44, 3, 16, 8, 0, 44, 45, 5, 5, 0, 0, 45, 46, 3, 10, 5, 0, 46, 5, 1, 0, 0, 0, 47, 48, 5, 15, 0, 0, 48, 49, 3, 18, 9, 0, 49, 50, 5, 18, 0, 0, 50, 7, 1, 0, 0, 0, 51, 52, 7, 0, 0, 0, 52, 9, 1, 0, 0, 0, 53, 54, 5, 16, 0, 0, 54, 55, 5, 8, 0, 0, 55, 56, 3, 18, 9, 0, 56, 57, 5, 18, 0, 0, 57, 58, 5, 9, 0, 0, 58, 59, 3, 18, 9, 0, 59, 60, 5, 18, 0, 0, 60, 61, 5, 17, 0, 0, 61, 11, 1, 0, 0, 0, 62, 63, 5, 16, 0, 0, 63, 64, 5, 8, 0, 0, 64, 65, 3, 18, 9, 0, 65, 73, 5, 18, 0, 0, 66, 67, 5, 10, 0, 0, 67, 68, 3, 14, 7, 0, 68, 69, 5, 18, 0, 0, 69, 70, 5, 11, 0, 0, 70, 71, 3, 18, 9, 0, 71, 72, 5, 18, 0, 0, 72, 74, 1, 0, 0, 0, 73, 66, 1, 0, 0, 0, 73, 74, 1, 0, 0, 0, 74, 75, 1, 0, 0, 0, 75, 76, 5, 9, 0, 0, 76, 77, 3, 18, 9, 0, 77, 78, 5, 18, 0, 0, 78, 79, 5, 17, 0, 0, 79, 13, 1, 0, 0, 0, 80, 81, 7, 1, 0, 0, 81, 15, 1, 0, 0, 0, 82, 83, 5, 20, 0, 0, 83, 17, 1, 0, 0, 0, 84, 85, 5, 21, 0, 0, 85, 19, 1, 0, 0, 0, 4, 22, 30, 32, 73] \ No newline at end of file +[4, 1, 25, 87, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, 4, 2, 5, 7, 5, 2, 6, 7, 6, 2, 7, 7, 7, 2, 8, 7, 8, 2, 9, 7, 9, 1, 0, 1, 0, 3, 0, 23, 8, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 5, 0, 31, 8, 0, 10, 0, 12, 0, 34, 9, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 3, 1, 3, 1, 3, 1, 3, 1, 4, 1, 4, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 3, 6, 74, 8, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 7, 1, 7, 1, 8, 1, 8, 1, 9, 1, 9, 1, 9, 0, 0, 10, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 0, 2, 1, 0, 6, 8, 1, 0, 13, 15, 81, 0, 20, 1, 0, 0, 0, 2, 38, 1, 0, 0, 0, 4, 42, 1, 0, 0, 0, 6, 47, 1, 0, 0, 0, 8, 51, 1, 0, 0, 0, 10, 53, 1, 0, 0, 0, 12, 62, 1, 0, 0, 0, 14, 80, 1, 0, 0, 0, 16, 82, 1, 0, 0, 0, 18, 84, 1, 0, 0, 0, 20, 22, 5, 1, 0, 0, 21, 23, 5, 2, 0, 0, 22, 21, 1, 0, 0, 0, 22, 23, 1, 0, 0, 0, 23, 24, 1, 0, 0, 0, 24, 25, 5, 3, 0, 0, 25, 26, 5, 20, 0, 0, 26, 32, 5, 17, 0, 0, 27, 31, 3, 2, 1, 0, 28, 31, 3, 6, 3, 0, 29, 31, 3, 4, 2, 0, 30, 27, 1, 0, 0, 0, 30, 28, 1, 0, 0, 0, 30, 29, 1, 0, 0, 0, 31, 34, 1, 0, 0, 0, 32, 30, 1, 0, 0, 0, 32, 33, 1, 0, 0, 0, 33, 35, 1, 0, 0, 0, 34, 32, 1, 0, 0, 0, 35, 36, 5, 18, 0, 0, 36, 37, 5, 0, 0, 1, 37, 1, 1, 0, 0, 0, 38, 39, 5, 4, 0, 0, 39, 40, 5, 5, 0, 0, 40, 41, 3, 12, 6, 0, 41, 3, 1, 0, 0, 0, 42, 43, 3, 8, 4, 0, 43, 44, 3, 16, 8, 0, 44, 45, 5, 5, 0, 0, 45, 46, 3, 10, 5, 0, 46, 5, 1, 0, 0, 0, 47, 48, 5, 16, 0, 0, 48, 49, 3, 18, 9, 0, 49, 50, 5, 19, 0, 0, 50, 7, 1, 0, 0, 0, 51, 52, 7, 0, 0, 0, 52, 9, 1, 0, 0, 0, 53, 54, 5, 17, 0, 0, 54, 55, 5, 9, 0, 0, 55, 56, 3, 18, 9, 0, 56, 57, 5, 19, 0, 0, 57, 58, 5, 10, 0, 0, 58, 59, 3, 18, 9, 0, 59, 60, 5, 19, 0, 0, 60, 61, 5, 18, 0, 0, 61, 11, 1, 0, 0, 0, 62, 63, 5, 17, 0, 0, 63, 64, 5, 9, 0, 0, 64, 65, 3, 18, 9, 0, 65, 73, 5, 19, 0, 0, 66, 67, 5, 11, 0, 0, 67, 68, 3, 14, 7, 0, 68, 69, 5, 19, 0, 0, 69, 70, 5, 12, 0, 0, 70, 71, 3, 18, 9, 0, 71, 72, 5, 19, 0, 0, 72, 74, 1, 0, 0, 0, 73, 66, 1, 0, 0, 0, 73, 74, 1, 0, 0, 0, 74, 75, 1, 0, 0, 0, 75, 76, 5, 10, 0, 0, 76, 77, 3, 18, 9, 0, 77, 78, 5, 19, 0, 0, 78, 79, 5, 18, 0, 0, 79, 13, 1, 0, 0, 0, 80, 81, 7, 1, 0, 0, 81, 15, 1, 0, 0, 0, 82, 83, 5, 21, 0, 0, 83, 17, 1, 0, 0, 0, 84, 85, 5, 22, 0, 0, 85, 19, 1, 0, 0, 0, 4, 22, 30, 32, 73] \ No newline at end of file diff --git a/src/resource-lock/generated/QuixosLock.tokens b/src/resource-lock/generated/QuixosLock.tokens index fe6ae89..19c20a7 100644 --- a/src/resource-lock/generated/QuixosLock.tokens +++ b/src/resource-lock/generated/QuixosLock.tokens @@ -5,23 +5,24 @@ QUIXOS=4 SOURCE=5 INTERFACE=6 PACKAGE=7 -REPOSITORY=8 -COMMIT=9 -POLICY=10 -REF=11 -PINNED=12 -TRACK_RELEASE=13 -TRACK_DEVELOPMENT=14 -IMPORT=15 -LBRACE=16 -RBRACE=17 -SEMI=18 -INTEGER=19 -IDENTIFIER=20 -STRING_LITERAL=21 -LINE_COMMENT=22 -BLOCK_COMMENT=23 -WS=24 +BUILD=8 +REPOSITORY=9 +COMMIT=10 +POLICY=11 +REF=12 +PINNED=13 +TRACK_RELEASE=14 +TRACK_DEVELOPMENT=15 +IMPORT=16 +LBRACE=17 +RBRACE=18 +SEMI=19 +INTEGER=20 +IDENTIFIER=21 +STRING_LITERAL=22 +LINE_COMMENT=23 +BLOCK_COMMENT=24 +WS=25 'quixos-lock'=1 'fragment'=2 'version'=3 @@ -29,14 +30,15 @@ WS=24 'source'=5 'interface'=6 'package'=7 -'repository'=8 -'commit'=9 -'policy'=10 -'ref'=11 -'pinned'=12 -'track-release'=13 -'track-development'=14 -'import'=15 -'{'=16 -'}'=17 -';'=18 +'build'=8 +'repository'=9 +'commit'=10 +'policy'=11 +'ref'=12 +'pinned'=13 +'track-release'=14 +'track-development'=15 +'import'=16 +'{'=17 +'}'=18 +';'=19 diff --git a/src/resource-lock/generated/QuixosLockLexer.interp b/src/resource-lock/generated/QuixosLockLexer.interp index 81c332d..9f53487 100644 --- a/src/resource-lock/generated/QuixosLockLexer.interp +++ b/src/resource-lock/generated/QuixosLockLexer.interp @@ -7,6 +7,7 @@ null 'source' 'interface' 'package' +'build' 'repository' 'commit' 'policy' @@ -34,6 +35,7 @@ QUIXOS SOURCE INTERFACE PACKAGE +BUILD REPOSITORY COMMIT POLICY @@ -60,6 +62,7 @@ QUIXOS SOURCE INTERFACE PACKAGE +BUILD REPOSITORY COMMIT POLICY @@ -88,4 +91,4 @@ mode names: DEFAULT_MODE atn: -[4, 0, 24, 261, 6, -1, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, 4, 2, 5, 7, 5, 2, 6, 7, 6, 2, 7, 7, 7, 2, 8, 7, 8, 2, 9, 7, 9, 2, 10, 7, 10, 2, 11, 7, 11, 2, 12, 7, 12, 2, 13, 7, 13, 2, 14, 7, 14, 2, 15, 7, 15, 2, 16, 7, 16, 2, 17, 7, 17, 2, 18, 7, 18, 2, 19, 7, 19, 2, 20, 7, 20, 2, 21, 7, 21, 2, 22, 7, 22, 2, 23, 7, 23, 2, 24, 7, 24, 2, 25, 7, 25, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 10, 1, 10, 1, 10, 1, 10, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 12, 1, 12, 1, 12, 1, 12, 1, 12, 1, 12, 1, 12, 1, 12, 1, 12, 1, 12, 1, 12, 1, 12, 1, 12, 1, 12, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 15, 1, 15, 1, 16, 1, 16, 1, 17, 1, 17, 1, 18, 4, 18, 197, 8, 18, 11, 18, 12, 18, 198, 1, 19, 1, 19, 5, 19, 203, 8, 19, 10, 19, 12, 19, 206, 9, 19, 1, 20, 1, 20, 1, 20, 5, 20, 211, 8, 20, 10, 20, 12, 20, 214, 9, 20, 1, 20, 1, 20, 1, 21, 1, 21, 1, 21, 1, 21, 1, 21, 1, 21, 1, 21, 1, 21, 3, 21, 226, 8, 21, 1, 22, 1, 22, 1, 23, 1, 23, 1, 23, 1, 23, 5, 23, 234, 8, 23, 10, 23, 12, 23, 237, 9, 23, 1, 23, 1, 23, 1, 24, 1, 24, 1, 24, 1, 24, 5, 24, 245, 8, 24, 10, 24, 12, 24, 248, 9, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 25, 4, 25, 256, 8, 25, 11, 25, 12, 25, 257, 1, 25, 1, 25, 1, 246, 0, 26, 1, 1, 3, 2, 5, 3, 7, 4, 9, 5, 11, 6, 13, 7, 15, 8, 17, 9, 19, 10, 21, 11, 23, 12, 25, 13, 27, 14, 29, 15, 31, 16, 33, 17, 35, 18, 37, 19, 39, 20, 41, 21, 43, 0, 45, 0, 47, 22, 49, 23, 51, 24, 1, 0, 8, 1, 0, 48, 57, 3, 0, 65, 90, 95, 95, 97, 122, 4, 0, 48, 57, 65, 90, 95, 95, 97, 122, 4, 0, 10, 10, 13, 13, 34, 34, 92, 92, 8, 0, 34, 34, 47, 47, 92, 92, 98, 98, 102, 102, 110, 110, 114, 114, 116, 116, 3, 0, 48, 57, 65, 70, 97, 102, 2, 0, 10, 10, 13, 13, 3, 0, 9, 10, 13, 13, 32, 32, 266, 0, 1, 1, 0, 0, 0, 0, 3, 1, 0, 0, 0, 0, 5, 1, 0, 0, 0, 0, 7, 1, 0, 0, 0, 0, 9, 1, 0, 0, 0, 0, 11, 1, 0, 0, 0, 0, 13, 1, 0, 0, 0, 0, 15, 1, 0, 0, 0, 0, 17, 1, 0, 0, 0, 0, 19, 1, 0, 0, 0, 0, 21, 1, 0, 0, 0, 0, 23, 1, 0, 0, 0, 0, 25, 1, 0, 0, 0, 0, 27, 1, 0, 0, 0, 0, 29, 1, 0, 0, 0, 0, 31, 1, 0, 0, 0, 0, 33, 1, 0, 0, 0, 0, 35, 1, 0, 0, 0, 0, 37, 1, 0, 0, 0, 0, 39, 1, 0, 0, 0, 0, 41, 1, 0, 0, 0, 0, 47, 1, 0, 0, 0, 0, 49, 1, 0, 0, 0, 0, 51, 1, 0, 0, 0, 1, 53, 1, 0, 0, 0, 3, 65, 1, 0, 0, 0, 5, 74, 1, 0, 0, 0, 7, 82, 1, 0, 0, 0, 9, 89, 1, 0, 0, 0, 11, 96, 1, 0, 0, 0, 13, 106, 1, 0, 0, 0, 15, 114, 1, 0, 0, 0, 17, 125, 1, 0, 0, 0, 19, 132, 1, 0, 0, 0, 21, 139, 1, 0, 0, 0, 23, 143, 1, 0, 0, 0, 25, 150, 1, 0, 0, 0, 27, 164, 1, 0, 0, 0, 29, 182, 1, 0, 0, 0, 31, 189, 1, 0, 0, 0, 33, 191, 1, 0, 0, 0, 35, 193, 1, 0, 0, 0, 37, 196, 1, 0, 0, 0, 39, 200, 1, 0, 0, 0, 41, 207, 1, 0, 0, 0, 43, 217, 1, 0, 0, 0, 45, 227, 1, 0, 0, 0, 47, 229, 1, 0, 0, 0, 49, 240, 1, 0, 0, 0, 51, 255, 1, 0, 0, 0, 53, 54, 5, 113, 0, 0, 54, 55, 5, 117, 0, 0, 55, 56, 5, 105, 0, 0, 56, 57, 5, 120, 0, 0, 57, 58, 5, 111, 0, 0, 58, 59, 5, 115, 0, 0, 59, 60, 5, 45, 0, 0, 60, 61, 5, 108, 0, 0, 61, 62, 5, 111, 0, 0, 62, 63, 5, 99, 0, 0, 63, 64, 5, 107, 0, 0, 64, 2, 1, 0, 0, 0, 65, 66, 5, 102, 0, 0, 66, 67, 5, 114, 0, 0, 67, 68, 5, 97, 0, 0, 68, 69, 5, 103, 0, 0, 69, 70, 5, 109, 0, 0, 70, 71, 5, 101, 0, 0, 71, 72, 5, 110, 0, 0, 72, 73, 5, 116, 0, 0, 73, 4, 1, 0, 0, 0, 74, 75, 5, 118, 0, 0, 75, 76, 5, 101, 0, 0, 76, 77, 5, 114, 0, 0, 77, 78, 5, 115, 0, 0, 78, 79, 5, 105, 0, 0, 79, 80, 5, 111, 0, 0, 80, 81, 5, 110, 0, 0, 81, 6, 1, 0, 0, 0, 82, 83, 5, 113, 0, 0, 83, 84, 5, 117, 0, 0, 84, 85, 5, 105, 0, 0, 85, 86, 5, 120, 0, 0, 86, 87, 5, 111, 0, 0, 87, 88, 5, 115, 0, 0, 88, 8, 1, 0, 0, 0, 89, 90, 5, 115, 0, 0, 90, 91, 5, 111, 0, 0, 91, 92, 5, 117, 0, 0, 92, 93, 5, 114, 0, 0, 93, 94, 5, 99, 0, 0, 94, 95, 5, 101, 0, 0, 95, 10, 1, 0, 0, 0, 96, 97, 5, 105, 0, 0, 97, 98, 5, 110, 0, 0, 98, 99, 5, 116, 0, 0, 99, 100, 5, 101, 0, 0, 100, 101, 5, 114, 0, 0, 101, 102, 5, 102, 0, 0, 102, 103, 5, 97, 0, 0, 103, 104, 5, 99, 0, 0, 104, 105, 5, 101, 0, 0, 105, 12, 1, 0, 0, 0, 106, 107, 5, 112, 0, 0, 107, 108, 5, 97, 0, 0, 108, 109, 5, 99, 0, 0, 109, 110, 5, 107, 0, 0, 110, 111, 5, 97, 0, 0, 111, 112, 5, 103, 0, 0, 112, 113, 5, 101, 0, 0, 113, 14, 1, 0, 0, 0, 114, 115, 5, 114, 0, 0, 115, 116, 5, 101, 0, 0, 116, 117, 5, 112, 0, 0, 117, 118, 5, 111, 0, 0, 118, 119, 5, 115, 0, 0, 119, 120, 5, 105, 0, 0, 120, 121, 5, 116, 0, 0, 121, 122, 5, 111, 0, 0, 122, 123, 5, 114, 0, 0, 123, 124, 5, 121, 0, 0, 124, 16, 1, 0, 0, 0, 125, 126, 5, 99, 0, 0, 126, 127, 5, 111, 0, 0, 127, 128, 5, 109, 0, 0, 128, 129, 5, 109, 0, 0, 129, 130, 5, 105, 0, 0, 130, 131, 5, 116, 0, 0, 131, 18, 1, 0, 0, 0, 132, 133, 5, 112, 0, 0, 133, 134, 5, 111, 0, 0, 134, 135, 5, 108, 0, 0, 135, 136, 5, 105, 0, 0, 136, 137, 5, 99, 0, 0, 137, 138, 5, 121, 0, 0, 138, 20, 1, 0, 0, 0, 139, 140, 5, 114, 0, 0, 140, 141, 5, 101, 0, 0, 141, 142, 5, 102, 0, 0, 142, 22, 1, 0, 0, 0, 143, 144, 5, 112, 0, 0, 144, 145, 5, 105, 0, 0, 145, 146, 5, 110, 0, 0, 146, 147, 5, 110, 0, 0, 147, 148, 5, 101, 0, 0, 148, 149, 5, 100, 0, 0, 149, 24, 1, 0, 0, 0, 150, 151, 5, 116, 0, 0, 151, 152, 5, 114, 0, 0, 152, 153, 5, 97, 0, 0, 153, 154, 5, 99, 0, 0, 154, 155, 5, 107, 0, 0, 155, 156, 5, 45, 0, 0, 156, 157, 5, 114, 0, 0, 157, 158, 5, 101, 0, 0, 158, 159, 5, 108, 0, 0, 159, 160, 5, 101, 0, 0, 160, 161, 5, 97, 0, 0, 161, 162, 5, 115, 0, 0, 162, 163, 5, 101, 0, 0, 163, 26, 1, 0, 0, 0, 164, 165, 5, 116, 0, 0, 165, 166, 5, 114, 0, 0, 166, 167, 5, 97, 0, 0, 167, 168, 5, 99, 0, 0, 168, 169, 5, 107, 0, 0, 169, 170, 5, 45, 0, 0, 170, 171, 5, 100, 0, 0, 171, 172, 5, 101, 0, 0, 172, 173, 5, 118, 0, 0, 173, 174, 5, 101, 0, 0, 174, 175, 5, 108, 0, 0, 175, 176, 5, 111, 0, 0, 176, 177, 5, 112, 0, 0, 177, 178, 5, 109, 0, 0, 178, 179, 5, 101, 0, 0, 179, 180, 5, 110, 0, 0, 180, 181, 5, 116, 0, 0, 181, 28, 1, 0, 0, 0, 182, 183, 5, 105, 0, 0, 183, 184, 5, 109, 0, 0, 184, 185, 5, 112, 0, 0, 185, 186, 5, 111, 0, 0, 186, 187, 5, 114, 0, 0, 187, 188, 5, 116, 0, 0, 188, 30, 1, 0, 0, 0, 189, 190, 5, 123, 0, 0, 190, 32, 1, 0, 0, 0, 191, 192, 5, 125, 0, 0, 192, 34, 1, 0, 0, 0, 193, 194, 5, 59, 0, 0, 194, 36, 1, 0, 0, 0, 195, 197, 7, 0, 0, 0, 196, 195, 1, 0, 0, 0, 197, 198, 1, 0, 0, 0, 198, 196, 1, 0, 0, 0, 198, 199, 1, 0, 0, 0, 199, 38, 1, 0, 0, 0, 200, 204, 7, 1, 0, 0, 201, 203, 7, 2, 0, 0, 202, 201, 1, 0, 0, 0, 203, 206, 1, 0, 0, 0, 204, 202, 1, 0, 0, 0, 204, 205, 1, 0, 0, 0, 205, 40, 1, 0, 0, 0, 206, 204, 1, 0, 0, 0, 207, 212, 5, 34, 0, 0, 208, 211, 3, 43, 21, 0, 209, 211, 8, 3, 0, 0, 210, 208, 1, 0, 0, 0, 210, 209, 1, 0, 0, 0, 211, 214, 1, 0, 0, 0, 212, 210, 1, 0, 0, 0, 212, 213, 1, 0, 0, 0, 213, 215, 1, 0, 0, 0, 214, 212, 1, 0, 0, 0, 215, 216, 5, 34, 0, 0, 216, 42, 1, 0, 0, 0, 217, 225, 5, 92, 0, 0, 218, 226, 7, 4, 0, 0, 219, 220, 5, 117, 0, 0, 220, 221, 3, 45, 22, 0, 221, 222, 3, 45, 22, 0, 222, 223, 3, 45, 22, 0, 223, 224, 3, 45, 22, 0, 224, 226, 1, 0, 0, 0, 225, 218, 1, 0, 0, 0, 225, 219, 1, 0, 0, 0, 226, 44, 1, 0, 0, 0, 227, 228, 7, 5, 0, 0, 228, 46, 1, 0, 0, 0, 229, 230, 5, 47, 0, 0, 230, 231, 5, 47, 0, 0, 231, 235, 1, 0, 0, 0, 232, 234, 8, 6, 0, 0, 233, 232, 1, 0, 0, 0, 234, 237, 1, 0, 0, 0, 235, 233, 1, 0, 0, 0, 235, 236, 1, 0, 0, 0, 236, 238, 1, 0, 0, 0, 237, 235, 1, 0, 0, 0, 238, 239, 6, 23, 0, 0, 239, 48, 1, 0, 0, 0, 240, 241, 5, 47, 0, 0, 241, 242, 5, 42, 0, 0, 242, 246, 1, 0, 0, 0, 243, 245, 9, 0, 0, 0, 244, 243, 1, 0, 0, 0, 245, 248, 1, 0, 0, 0, 246, 247, 1, 0, 0, 0, 246, 244, 1, 0, 0, 0, 247, 249, 1, 0, 0, 0, 248, 246, 1, 0, 0, 0, 249, 250, 5, 42, 0, 0, 250, 251, 5, 47, 0, 0, 251, 252, 1, 0, 0, 0, 252, 253, 6, 24, 0, 0, 253, 50, 1, 0, 0, 0, 254, 256, 7, 7, 0, 0, 255, 254, 1, 0, 0, 0, 256, 257, 1, 0, 0, 0, 257, 255, 1, 0, 0, 0, 257, 258, 1, 0, 0, 0, 258, 259, 1, 0, 0, 0, 259, 260, 6, 25, 0, 0, 260, 52, 1, 0, 0, 0, 9, 0, 198, 204, 210, 212, 225, 235, 246, 257, 1, 6, 0, 0] \ No newline at end of file +[4, 0, 25, 269, 6, -1, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, 4, 2, 5, 7, 5, 2, 6, 7, 6, 2, 7, 7, 7, 2, 8, 7, 8, 2, 9, 7, 9, 2, 10, 7, 10, 2, 11, 7, 11, 2, 12, 7, 12, 2, 13, 7, 13, 2, 14, 7, 14, 2, 15, 7, 15, 2, 16, 7, 16, 2, 17, 7, 17, 2, 18, 7, 18, 2, 19, 7, 19, 2, 20, 7, 20, 2, 21, 7, 21, 2, 22, 7, 22, 2, 23, 7, 23, 2, 24, 7, 24, 2, 25, 7, 25, 2, 26, 7, 26, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 11, 1, 11, 1, 11, 1, 11, 1, 12, 1, 12, 1, 12, 1, 12, 1, 12, 1, 12, 1, 12, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 16, 1, 16, 1, 17, 1, 17, 1, 18, 1, 18, 1, 19, 4, 19, 205, 8, 19, 11, 19, 12, 19, 206, 1, 20, 1, 20, 5, 20, 211, 8, 20, 10, 20, 12, 20, 214, 9, 20, 1, 21, 1, 21, 1, 21, 5, 21, 219, 8, 21, 10, 21, 12, 21, 222, 9, 21, 1, 21, 1, 21, 1, 22, 1, 22, 1, 22, 1, 22, 1, 22, 1, 22, 1, 22, 1, 22, 3, 22, 234, 8, 22, 1, 23, 1, 23, 1, 24, 1, 24, 1, 24, 1, 24, 5, 24, 242, 8, 24, 10, 24, 12, 24, 245, 9, 24, 1, 24, 1, 24, 1, 25, 1, 25, 1, 25, 1, 25, 5, 25, 253, 8, 25, 10, 25, 12, 25, 256, 9, 25, 1, 25, 1, 25, 1, 25, 1, 25, 1, 25, 1, 26, 4, 26, 264, 8, 26, 11, 26, 12, 26, 265, 1, 26, 1, 26, 1, 254, 0, 27, 1, 1, 3, 2, 5, 3, 7, 4, 9, 5, 11, 6, 13, 7, 15, 8, 17, 9, 19, 10, 21, 11, 23, 12, 25, 13, 27, 14, 29, 15, 31, 16, 33, 17, 35, 18, 37, 19, 39, 20, 41, 21, 43, 22, 45, 0, 47, 0, 49, 23, 51, 24, 53, 25, 1, 0, 8, 1, 0, 48, 57, 3, 0, 65, 90, 95, 95, 97, 122, 4, 0, 48, 57, 65, 90, 95, 95, 97, 122, 4, 0, 10, 10, 13, 13, 34, 34, 92, 92, 8, 0, 34, 34, 47, 47, 92, 92, 98, 98, 102, 102, 110, 110, 114, 114, 116, 116, 3, 0, 48, 57, 65, 70, 97, 102, 2, 0, 10, 10, 13, 13, 3, 0, 9, 10, 13, 13, 32, 32, 274, 0, 1, 1, 0, 0, 0, 0, 3, 1, 0, 0, 0, 0, 5, 1, 0, 0, 0, 0, 7, 1, 0, 0, 0, 0, 9, 1, 0, 0, 0, 0, 11, 1, 0, 0, 0, 0, 13, 1, 0, 0, 0, 0, 15, 1, 0, 0, 0, 0, 17, 1, 0, 0, 0, 0, 19, 1, 0, 0, 0, 0, 21, 1, 0, 0, 0, 0, 23, 1, 0, 0, 0, 0, 25, 1, 0, 0, 0, 0, 27, 1, 0, 0, 0, 0, 29, 1, 0, 0, 0, 0, 31, 1, 0, 0, 0, 0, 33, 1, 0, 0, 0, 0, 35, 1, 0, 0, 0, 0, 37, 1, 0, 0, 0, 0, 39, 1, 0, 0, 0, 0, 41, 1, 0, 0, 0, 0, 43, 1, 0, 0, 0, 0, 49, 1, 0, 0, 0, 0, 51, 1, 0, 0, 0, 0, 53, 1, 0, 0, 0, 1, 55, 1, 0, 0, 0, 3, 67, 1, 0, 0, 0, 5, 76, 1, 0, 0, 0, 7, 84, 1, 0, 0, 0, 9, 91, 1, 0, 0, 0, 11, 98, 1, 0, 0, 0, 13, 108, 1, 0, 0, 0, 15, 116, 1, 0, 0, 0, 17, 122, 1, 0, 0, 0, 19, 133, 1, 0, 0, 0, 21, 140, 1, 0, 0, 0, 23, 147, 1, 0, 0, 0, 25, 151, 1, 0, 0, 0, 27, 158, 1, 0, 0, 0, 29, 172, 1, 0, 0, 0, 31, 190, 1, 0, 0, 0, 33, 197, 1, 0, 0, 0, 35, 199, 1, 0, 0, 0, 37, 201, 1, 0, 0, 0, 39, 204, 1, 0, 0, 0, 41, 208, 1, 0, 0, 0, 43, 215, 1, 0, 0, 0, 45, 225, 1, 0, 0, 0, 47, 235, 1, 0, 0, 0, 49, 237, 1, 0, 0, 0, 51, 248, 1, 0, 0, 0, 53, 263, 1, 0, 0, 0, 55, 56, 5, 113, 0, 0, 56, 57, 5, 117, 0, 0, 57, 58, 5, 105, 0, 0, 58, 59, 5, 120, 0, 0, 59, 60, 5, 111, 0, 0, 60, 61, 5, 115, 0, 0, 61, 62, 5, 45, 0, 0, 62, 63, 5, 108, 0, 0, 63, 64, 5, 111, 0, 0, 64, 65, 5, 99, 0, 0, 65, 66, 5, 107, 0, 0, 66, 2, 1, 0, 0, 0, 67, 68, 5, 102, 0, 0, 68, 69, 5, 114, 0, 0, 69, 70, 5, 97, 0, 0, 70, 71, 5, 103, 0, 0, 71, 72, 5, 109, 0, 0, 72, 73, 5, 101, 0, 0, 73, 74, 5, 110, 0, 0, 74, 75, 5, 116, 0, 0, 75, 4, 1, 0, 0, 0, 76, 77, 5, 118, 0, 0, 77, 78, 5, 101, 0, 0, 78, 79, 5, 114, 0, 0, 79, 80, 5, 115, 0, 0, 80, 81, 5, 105, 0, 0, 81, 82, 5, 111, 0, 0, 82, 83, 5, 110, 0, 0, 83, 6, 1, 0, 0, 0, 84, 85, 5, 113, 0, 0, 85, 86, 5, 117, 0, 0, 86, 87, 5, 105, 0, 0, 87, 88, 5, 120, 0, 0, 88, 89, 5, 111, 0, 0, 89, 90, 5, 115, 0, 0, 90, 8, 1, 0, 0, 0, 91, 92, 5, 115, 0, 0, 92, 93, 5, 111, 0, 0, 93, 94, 5, 117, 0, 0, 94, 95, 5, 114, 0, 0, 95, 96, 5, 99, 0, 0, 96, 97, 5, 101, 0, 0, 97, 10, 1, 0, 0, 0, 98, 99, 5, 105, 0, 0, 99, 100, 5, 110, 0, 0, 100, 101, 5, 116, 0, 0, 101, 102, 5, 101, 0, 0, 102, 103, 5, 114, 0, 0, 103, 104, 5, 102, 0, 0, 104, 105, 5, 97, 0, 0, 105, 106, 5, 99, 0, 0, 106, 107, 5, 101, 0, 0, 107, 12, 1, 0, 0, 0, 108, 109, 5, 112, 0, 0, 109, 110, 5, 97, 0, 0, 110, 111, 5, 99, 0, 0, 111, 112, 5, 107, 0, 0, 112, 113, 5, 97, 0, 0, 113, 114, 5, 103, 0, 0, 114, 115, 5, 101, 0, 0, 115, 14, 1, 0, 0, 0, 116, 117, 5, 98, 0, 0, 117, 118, 5, 117, 0, 0, 118, 119, 5, 105, 0, 0, 119, 120, 5, 108, 0, 0, 120, 121, 5, 100, 0, 0, 121, 16, 1, 0, 0, 0, 122, 123, 5, 114, 0, 0, 123, 124, 5, 101, 0, 0, 124, 125, 5, 112, 0, 0, 125, 126, 5, 111, 0, 0, 126, 127, 5, 115, 0, 0, 127, 128, 5, 105, 0, 0, 128, 129, 5, 116, 0, 0, 129, 130, 5, 111, 0, 0, 130, 131, 5, 114, 0, 0, 131, 132, 5, 121, 0, 0, 132, 18, 1, 0, 0, 0, 133, 134, 5, 99, 0, 0, 134, 135, 5, 111, 0, 0, 135, 136, 5, 109, 0, 0, 136, 137, 5, 109, 0, 0, 137, 138, 5, 105, 0, 0, 138, 139, 5, 116, 0, 0, 139, 20, 1, 0, 0, 0, 140, 141, 5, 112, 0, 0, 141, 142, 5, 111, 0, 0, 142, 143, 5, 108, 0, 0, 143, 144, 5, 105, 0, 0, 144, 145, 5, 99, 0, 0, 145, 146, 5, 121, 0, 0, 146, 22, 1, 0, 0, 0, 147, 148, 5, 114, 0, 0, 148, 149, 5, 101, 0, 0, 149, 150, 5, 102, 0, 0, 150, 24, 1, 0, 0, 0, 151, 152, 5, 112, 0, 0, 152, 153, 5, 105, 0, 0, 153, 154, 5, 110, 0, 0, 154, 155, 5, 110, 0, 0, 155, 156, 5, 101, 0, 0, 156, 157, 5, 100, 0, 0, 157, 26, 1, 0, 0, 0, 158, 159, 5, 116, 0, 0, 159, 160, 5, 114, 0, 0, 160, 161, 5, 97, 0, 0, 161, 162, 5, 99, 0, 0, 162, 163, 5, 107, 0, 0, 163, 164, 5, 45, 0, 0, 164, 165, 5, 114, 0, 0, 165, 166, 5, 101, 0, 0, 166, 167, 5, 108, 0, 0, 167, 168, 5, 101, 0, 0, 168, 169, 5, 97, 0, 0, 169, 170, 5, 115, 0, 0, 170, 171, 5, 101, 0, 0, 171, 28, 1, 0, 0, 0, 172, 173, 5, 116, 0, 0, 173, 174, 5, 114, 0, 0, 174, 175, 5, 97, 0, 0, 175, 176, 5, 99, 0, 0, 176, 177, 5, 107, 0, 0, 177, 178, 5, 45, 0, 0, 178, 179, 5, 100, 0, 0, 179, 180, 5, 101, 0, 0, 180, 181, 5, 118, 0, 0, 181, 182, 5, 101, 0, 0, 182, 183, 5, 108, 0, 0, 183, 184, 5, 111, 0, 0, 184, 185, 5, 112, 0, 0, 185, 186, 5, 109, 0, 0, 186, 187, 5, 101, 0, 0, 187, 188, 5, 110, 0, 0, 188, 189, 5, 116, 0, 0, 189, 30, 1, 0, 0, 0, 190, 191, 5, 105, 0, 0, 191, 192, 5, 109, 0, 0, 192, 193, 5, 112, 0, 0, 193, 194, 5, 111, 0, 0, 194, 195, 5, 114, 0, 0, 195, 196, 5, 116, 0, 0, 196, 32, 1, 0, 0, 0, 197, 198, 5, 123, 0, 0, 198, 34, 1, 0, 0, 0, 199, 200, 5, 125, 0, 0, 200, 36, 1, 0, 0, 0, 201, 202, 5, 59, 0, 0, 202, 38, 1, 0, 0, 0, 203, 205, 7, 0, 0, 0, 204, 203, 1, 0, 0, 0, 205, 206, 1, 0, 0, 0, 206, 204, 1, 0, 0, 0, 206, 207, 1, 0, 0, 0, 207, 40, 1, 0, 0, 0, 208, 212, 7, 1, 0, 0, 209, 211, 7, 2, 0, 0, 210, 209, 1, 0, 0, 0, 211, 214, 1, 0, 0, 0, 212, 210, 1, 0, 0, 0, 212, 213, 1, 0, 0, 0, 213, 42, 1, 0, 0, 0, 214, 212, 1, 0, 0, 0, 215, 220, 5, 34, 0, 0, 216, 219, 3, 45, 22, 0, 217, 219, 8, 3, 0, 0, 218, 216, 1, 0, 0, 0, 218, 217, 1, 0, 0, 0, 219, 222, 1, 0, 0, 0, 220, 218, 1, 0, 0, 0, 220, 221, 1, 0, 0, 0, 221, 223, 1, 0, 0, 0, 222, 220, 1, 0, 0, 0, 223, 224, 5, 34, 0, 0, 224, 44, 1, 0, 0, 0, 225, 233, 5, 92, 0, 0, 226, 234, 7, 4, 0, 0, 227, 228, 5, 117, 0, 0, 228, 229, 3, 47, 23, 0, 229, 230, 3, 47, 23, 0, 230, 231, 3, 47, 23, 0, 231, 232, 3, 47, 23, 0, 232, 234, 1, 0, 0, 0, 233, 226, 1, 0, 0, 0, 233, 227, 1, 0, 0, 0, 234, 46, 1, 0, 0, 0, 235, 236, 7, 5, 0, 0, 236, 48, 1, 0, 0, 0, 237, 238, 5, 47, 0, 0, 238, 239, 5, 47, 0, 0, 239, 243, 1, 0, 0, 0, 240, 242, 8, 6, 0, 0, 241, 240, 1, 0, 0, 0, 242, 245, 1, 0, 0, 0, 243, 241, 1, 0, 0, 0, 243, 244, 1, 0, 0, 0, 244, 246, 1, 0, 0, 0, 245, 243, 1, 0, 0, 0, 246, 247, 6, 24, 0, 0, 247, 50, 1, 0, 0, 0, 248, 249, 5, 47, 0, 0, 249, 250, 5, 42, 0, 0, 250, 254, 1, 0, 0, 0, 251, 253, 9, 0, 0, 0, 252, 251, 1, 0, 0, 0, 253, 256, 1, 0, 0, 0, 254, 255, 1, 0, 0, 0, 254, 252, 1, 0, 0, 0, 255, 257, 1, 0, 0, 0, 256, 254, 1, 0, 0, 0, 257, 258, 5, 42, 0, 0, 258, 259, 5, 47, 0, 0, 259, 260, 1, 0, 0, 0, 260, 261, 6, 25, 0, 0, 261, 52, 1, 0, 0, 0, 262, 264, 7, 7, 0, 0, 263, 262, 1, 0, 0, 0, 264, 265, 1, 0, 0, 0, 265, 263, 1, 0, 0, 0, 265, 266, 1, 0, 0, 0, 266, 267, 1, 0, 0, 0, 267, 268, 6, 26, 0, 0, 268, 54, 1, 0, 0, 0, 9, 0, 206, 212, 218, 220, 233, 243, 254, 265, 1, 6, 0, 0] \ No newline at end of file diff --git a/src/resource-lock/generated/QuixosLockLexer.tokens b/src/resource-lock/generated/QuixosLockLexer.tokens index fe6ae89..19c20a7 100644 --- a/src/resource-lock/generated/QuixosLockLexer.tokens +++ b/src/resource-lock/generated/QuixosLockLexer.tokens @@ -5,23 +5,24 @@ QUIXOS=4 SOURCE=5 INTERFACE=6 PACKAGE=7 -REPOSITORY=8 -COMMIT=9 -POLICY=10 -REF=11 -PINNED=12 -TRACK_RELEASE=13 -TRACK_DEVELOPMENT=14 -IMPORT=15 -LBRACE=16 -RBRACE=17 -SEMI=18 -INTEGER=19 -IDENTIFIER=20 -STRING_LITERAL=21 -LINE_COMMENT=22 -BLOCK_COMMENT=23 -WS=24 +BUILD=8 +REPOSITORY=9 +COMMIT=10 +POLICY=11 +REF=12 +PINNED=13 +TRACK_RELEASE=14 +TRACK_DEVELOPMENT=15 +IMPORT=16 +LBRACE=17 +RBRACE=18 +SEMI=19 +INTEGER=20 +IDENTIFIER=21 +STRING_LITERAL=22 +LINE_COMMENT=23 +BLOCK_COMMENT=24 +WS=25 'quixos-lock'=1 'fragment'=2 'version'=3 @@ -29,14 +30,15 @@ WS=24 'source'=5 'interface'=6 'package'=7 -'repository'=8 -'commit'=9 -'policy'=10 -'ref'=11 -'pinned'=12 -'track-release'=13 -'track-development'=14 -'import'=15 -'{'=16 -'}'=17 -';'=18 +'build'=8 +'repository'=9 +'commit'=10 +'policy'=11 +'ref'=12 +'pinned'=13 +'track-release'=14 +'track-development'=15 +'import'=16 +'{'=17 +'}'=18 +';'=19 diff --git a/src/resource-lock/generated/QuixosLockLexer.ts b/src/resource-lock/generated/QuixosLockLexer.ts index 30003f3..821313f 100644 --- a/src/resource-lock/generated/QuixosLockLexer.ts +++ b/src/resource-lock/generated/QuixosLockLexer.ts @@ -11,23 +11,24 @@ export class QuixosLockLexer extends antlr.Lexer { public static readonly SOURCE = 5; public static readonly INTERFACE = 6; public static readonly PACKAGE = 7; - public static readonly REPOSITORY = 8; - public static readonly COMMIT = 9; - public static readonly POLICY = 10; - public static readonly REF = 11; - public static readonly PINNED = 12; - public static readonly TRACK_RELEASE = 13; - public static readonly TRACK_DEVELOPMENT = 14; - public static readonly IMPORT = 15; - public static readonly LBRACE = 16; - public static readonly RBRACE = 17; - public static readonly SEMI = 18; - public static readonly INTEGER = 19; - public static readonly IDENTIFIER = 20; - public static readonly STRING_LITERAL = 21; - public static readonly LINE_COMMENT = 22; - public static readonly BLOCK_COMMENT = 23; - public static readonly WS = 24; + public static readonly BUILD = 8; + public static readonly REPOSITORY = 9; + public static readonly COMMIT = 10; + public static readonly POLICY = 11; + public static readonly REF = 12; + public static readonly PINNED = 13; + public static readonly TRACK_RELEASE = 14; + public static readonly TRACK_DEVELOPMENT = 15; + public static readonly IMPORT = 16; + public static readonly LBRACE = 17; + public static readonly RBRACE = 18; + public static readonly SEMI = 19; + public static readonly INTEGER = 20; + public static readonly IDENTIFIER = 21; + public static readonly STRING_LITERAL = 22; + public static readonly LINE_COMMENT = 23; + public static readonly BLOCK_COMMENT = 24; + public static readonly WS = 25; public static readonly channelNames = [ "DEFAULT_TOKEN_CHANNEL", "HIDDEN" @@ -35,17 +36,17 @@ export class QuixosLockLexer extends antlr.Lexer { public static readonly literalNames = [ null, "'quixos-lock'", "'fragment'", "'version'", "'quixos'", "'source'", - "'interface'", "'package'", "'repository'", "'commit'", "'policy'", - "'ref'", "'pinned'", "'track-release'", "'track-development'", "'import'", - "'{'", "'}'", "';'" + "'interface'", "'package'", "'build'", "'repository'", "'commit'", + "'policy'", "'ref'", "'pinned'", "'track-release'", "'track-development'", + "'import'", "'{'", "'}'", "';'" ]; public static readonly symbolicNames = [ null, "QUIXOS_LOCK", "FRAGMENT", "VERSION", "QUIXOS", "SOURCE", - "INTERFACE", "PACKAGE", "REPOSITORY", "COMMIT", "POLICY", "REF", - "PINNED", "TRACK_RELEASE", "TRACK_DEVELOPMENT", "IMPORT", "LBRACE", - "RBRACE", "SEMI", "INTEGER", "IDENTIFIER", "STRING_LITERAL", "LINE_COMMENT", - "BLOCK_COMMENT", "WS" + "INTERFACE", "PACKAGE", "BUILD", "REPOSITORY", "COMMIT", "POLICY", + "REF", "PINNED", "TRACK_RELEASE", "TRACK_DEVELOPMENT", "IMPORT", + "LBRACE", "RBRACE", "SEMI", "INTEGER", "IDENTIFIER", "STRING_LITERAL", + "LINE_COMMENT", "BLOCK_COMMENT", "WS" ]; public static readonly modeNames = [ @@ -54,10 +55,10 @@ export class QuixosLockLexer extends antlr.Lexer { public static readonly ruleNames = [ "QUIXOS_LOCK", "FRAGMENT", "VERSION", "QUIXOS", "SOURCE", "INTERFACE", - "PACKAGE", "REPOSITORY", "COMMIT", "POLICY", "REF", "PINNED", "TRACK_RELEASE", - "TRACK_DEVELOPMENT", "IMPORT", "LBRACE", "RBRACE", "SEMI", "INTEGER", - "IDENTIFIER", "STRING_LITERAL", "ESC", "HEX", "LINE_COMMENT", "BLOCK_COMMENT", - "WS", + "PACKAGE", "BUILD", "REPOSITORY", "COMMIT", "POLICY", "REF", "PINNED", + "TRACK_RELEASE", "TRACK_DEVELOPMENT", "IMPORT", "LBRACE", "RBRACE", + "SEMI", "INTEGER", "IDENTIFIER", "STRING_LITERAL", "ESC", "HEX", + "LINE_COMMENT", "BLOCK_COMMENT", "WS", ]; @@ -79,99 +80,101 @@ export class QuixosLockLexer extends antlr.Lexer { public get modeNames(): string[] { return QuixosLockLexer.modeNames; } public static readonly _serializedATN: number[] = [ - 4,0,24,261,6,-1,2,0,7,0,2,1,7,1,2,2,7,2,2,3,7,3,2,4,7,4,2,5,7,5, + 4,0,25,269,6,-1,2,0,7,0,2,1,7,1,2,2,7,2,2,3,7,3,2,4,7,4,2,5,7,5, 2,6,7,6,2,7,7,7,2,8,7,8,2,9,7,9,2,10,7,10,2,11,7,11,2,12,7,12,2, 13,7,13,2,14,7,14,2,15,7,15,2,16,7,16,2,17,7,17,2,18,7,18,2,19,7, - 19,2,20,7,20,2,21,7,21,2,22,7,22,2,23,7,23,2,24,7,24,2,25,7,25,1, - 0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,1,1,1,1,1,1,1,1, - 1,1,1,1,1,1,1,1,1,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,3,1,3,1,3,1, - 3,1,3,1,3,1,3,1,4,1,4,1,4,1,4,1,4,1,4,1,4,1,5,1,5,1,5,1,5,1,5,1, - 5,1,5,1,5,1,5,1,5,1,6,1,6,1,6,1,6,1,6,1,6,1,6,1,6,1,7,1,7,1,7,1, - 7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,8,1,8,1,8,1,8,1,8,1,8,1,8,1,9,1, - 9,1,9,1,9,1,9,1,9,1,9,1,10,1,10,1,10,1,10,1,11,1,11,1,11,1,11,1, - 11,1,11,1,11,1,12,1,12,1,12,1,12,1,12,1,12,1,12,1,12,1,12,1,12,1, - 12,1,12,1,12,1,12,1,13,1,13,1,13,1,13,1,13,1,13,1,13,1,13,1,13,1, - 13,1,13,1,13,1,13,1,13,1,13,1,13,1,13,1,13,1,14,1,14,1,14,1,14,1, - 14,1,14,1,14,1,15,1,15,1,16,1,16,1,17,1,17,1,18,4,18,197,8,18,11, - 18,12,18,198,1,19,1,19,5,19,203,8,19,10,19,12,19,206,9,19,1,20,1, - 20,1,20,5,20,211,8,20,10,20,12,20,214,9,20,1,20,1,20,1,21,1,21,1, - 21,1,21,1,21,1,21,1,21,1,21,3,21,226,8,21,1,22,1,22,1,23,1,23,1, - 23,1,23,5,23,234,8,23,10,23,12,23,237,9,23,1,23,1,23,1,24,1,24,1, - 24,1,24,5,24,245,8,24,10,24,12,24,248,9,24,1,24,1,24,1,24,1,24,1, - 24,1,25,4,25,256,8,25,11,25,12,25,257,1,25,1,25,1,246,0,26,1,1,3, - 2,5,3,7,4,9,5,11,6,13,7,15,8,17,9,19,10,21,11,23,12,25,13,27,14, - 29,15,31,16,33,17,35,18,37,19,39,20,41,21,43,0,45,0,47,22,49,23, - 51,24,1,0,8,1,0,48,57,3,0,65,90,95,95,97,122,4,0,48,57,65,90,95, - 95,97,122,4,0,10,10,13,13,34,34,92,92,8,0,34,34,47,47,92,92,98,98, - 102,102,110,110,114,114,116,116,3,0,48,57,65,70,97,102,2,0,10,10, - 13,13,3,0,9,10,13,13,32,32,266,0,1,1,0,0,0,0,3,1,0,0,0,0,5,1,0,0, - 0,0,7,1,0,0,0,0,9,1,0,0,0,0,11,1,0,0,0,0,13,1,0,0,0,0,15,1,0,0,0, - 0,17,1,0,0,0,0,19,1,0,0,0,0,21,1,0,0,0,0,23,1,0,0,0,0,25,1,0,0,0, - 0,27,1,0,0,0,0,29,1,0,0,0,0,31,1,0,0,0,0,33,1,0,0,0,0,35,1,0,0,0, - 0,37,1,0,0,0,0,39,1,0,0,0,0,41,1,0,0,0,0,47,1,0,0,0,0,49,1,0,0,0, - 0,51,1,0,0,0,1,53,1,0,0,0,3,65,1,0,0,0,5,74,1,0,0,0,7,82,1,0,0,0, - 9,89,1,0,0,0,11,96,1,0,0,0,13,106,1,0,0,0,15,114,1,0,0,0,17,125, - 1,0,0,0,19,132,1,0,0,0,21,139,1,0,0,0,23,143,1,0,0,0,25,150,1,0, - 0,0,27,164,1,0,0,0,29,182,1,0,0,0,31,189,1,0,0,0,33,191,1,0,0,0, - 35,193,1,0,0,0,37,196,1,0,0,0,39,200,1,0,0,0,41,207,1,0,0,0,43,217, - 1,0,0,0,45,227,1,0,0,0,47,229,1,0,0,0,49,240,1,0,0,0,51,255,1,0, - 0,0,53,54,5,113,0,0,54,55,5,117,0,0,55,56,5,105,0,0,56,57,5,120, - 0,0,57,58,5,111,0,0,58,59,5,115,0,0,59,60,5,45,0,0,60,61,5,108,0, - 0,61,62,5,111,0,0,62,63,5,99,0,0,63,64,5,107,0,0,64,2,1,0,0,0,65, - 66,5,102,0,0,66,67,5,114,0,0,67,68,5,97,0,0,68,69,5,103,0,0,69,70, - 5,109,0,0,70,71,5,101,0,0,71,72,5,110,0,0,72,73,5,116,0,0,73,4,1, - 0,0,0,74,75,5,118,0,0,75,76,5,101,0,0,76,77,5,114,0,0,77,78,5,115, - 0,0,78,79,5,105,0,0,79,80,5,111,0,0,80,81,5,110,0,0,81,6,1,0,0,0, - 82,83,5,113,0,0,83,84,5,117,0,0,84,85,5,105,0,0,85,86,5,120,0,0, - 86,87,5,111,0,0,87,88,5,115,0,0,88,8,1,0,0,0,89,90,5,115,0,0,90, - 91,5,111,0,0,91,92,5,117,0,0,92,93,5,114,0,0,93,94,5,99,0,0,94,95, - 5,101,0,0,95,10,1,0,0,0,96,97,5,105,0,0,97,98,5,110,0,0,98,99,5, - 116,0,0,99,100,5,101,0,0,100,101,5,114,0,0,101,102,5,102,0,0,102, - 103,5,97,0,0,103,104,5,99,0,0,104,105,5,101,0,0,105,12,1,0,0,0,106, - 107,5,112,0,0,107,108,5,97,0,0,108,109,5,99,0,0,109,110,5,107,0, - 0,110,111,5,97,0,0,111,112,5,103,0,0,112,113,5,101,0,0,113,14,1, - 0,0,0,114,115,5,114,0,0,115,116,5,101,0,0,116,117,5,112,0,0,117, - 118,5,111,0,0,118,119,5,115,0,0,119,120,5,105,0,0,120,121,5,116, - 0,0,121,122,5,111,0,0,122,123,5,114,0,0,123,124,5,121,0,0,124,16, - 1,0,0,0,125,126,5,99,0,0,126,127,5,111,0,0,127,128,5,109,0,0,128, - 129,5,109,0,0,129,130,5,105,0,0,130,131,5,116,0,0,131,18,1,0,0,0, - 132,133,5,112,0,0,133,134,5,111,0,0,134,135,5,108,0,0,135,136,5, - 105,0,0,136,137,5,99,0,0,137,138,5,121,0,0,138,20,1,0,0,0,139,140, - 5,114,0,0,140,141,5,101,0,0,141,142,5,102,0,0,142,22,1,0,0,0,143, - 144,5,112,0,0,144,145,5,105,0,0,145,146,5,110,0,0,146,147,5,110, - 0,0,147,148,5,101,0,0,148,149,5,100,0,0,149,24,1,0,0,0,150,151,5, - 116,0,0,151,152,5,114,0,0,152,153,5,97,0,0,153,154,5,99,0,0,154, - 155,5,107,0,0,155,156,5,45,0,0,156,157,5,114,0,0,157,158,5,101,0, - 0,158,159,5,108,0,0,159,160,5,101,0,0,160,161,5,97,0,0,161,162,5, - 115,0,0,162,163,5,101,0,0,163,26,1,0,0,0,164,165,5,116,0,0,165,166, - 5,114,0,0,166,167,5,97,0,0,167,168,5,99,0,0,168,169,5,107,0,0,169, - 170,5,45,0,0,170,171,5,100,0,0,171,172,5,101,0,0,172,173,5,118,0, - 0,173,174,5,101,0,0,174,175,5,108,0,0,175,176,5,111,0,0,176,177, - 5,112,0,0,177,178,5,109,0,0,178,179,5,101,0,0,179,180,5,110,0,0, - 180,181,5,116,0,0,181,28,1,0,0,0,182,183,5,105,0,0,183,184,5,109, - 0,0,184,185,5,112,0,0,185,186,5,111,0,0,186,187,5,114,0,0,187,188, - 5,116,0,0,188,30,1,0,0,0,189,190,5,123,0,0,190,32,1,0,0,0,191,192, - 5,125,0,0,192,34,1,0,0,0,193,194,5,59,0,0,194,36,1,0,0,0,195,197, - 7,0,0,0,196,195,1,0,0,0,197,198,1,0,0,0,198,196,1,0,0,0,198,199, - 1,0,0,0,199,38,1,0,0,0,200,204,7,1,0,0,201,203,7,2,0,0,202,201,1, - 0,0,0,203,206,1,0,0,0,204,202,1,0,0,0,204,205,1,0,0,0,205,40,1,0, - 0,0,206,204,1,0,0,0,207,212,5,34,0,0,208,211,3,43,21,0,209,211,8, - 3,0,0,210,208,1,0,0,0,210,209,1,0,0,0,211,214,1,0,0,0,212,210,1, - 0,0,0,212,213,1,0,0,0,213,215,1,0,0,0,214,212,1,0,0,0,215,216,5, - 34,0,0,216,42,1,0,0,0,217,225,5,92,0,0,218,226,7,4,0,0,219,220,5, - 117,0,0,220,221,3,45,22,0,221,222,3,45,22,0,222,223,3,45,22,0,223, - 224,3,45,22,0,224,226,1,0,0,0,225,218,1,0,0,0,225,219,1,0,0,0,226, - 44,1,0,0,0,227,228,7,5,0,0,228,46,1,0,0,0,229,230,5,47,0,0,230,231, - 5,47,0,0,231,235,1,0,0,0,232,234,8,6,0,0,233,232,1,0,0,0,234,237, - 1,0,0,0,235,233,1,0,0,0,235,236,1,0,0,0,236,238,1,0,0,0,237,235, - 1,0,0,0,238,239,6,23,0,0,239,48,1,0,0,0,240,241,5,47,0,0,241,242, - 5,42,0,0,242,246,1,0,0,0,243,245,9,0,0,0,244,243,1,0,0,0,245,248, - 1,0,0,0,246,247,1,0,0,0,246,244,1,0,0,0,247,249,1,0,0,0,248,246, - 1,0,0,0,249,250,5,42,0,0,250,251,5,47,0,0,251,252,1,0,0,0,252,253, - 6,24,0,0,253,50,1,0,0,0,254,256,7,7,0,0,255,254,1,0,0,0,256,257, - 1,0,0,0,257,255,1,0,0,0,257,258,1,0,0,0,258,259,1,0,0,0,259,260, - 6,25,0,0,260,52,1,0,0,0,9,0,198,204,210,212,225,235,246,257,1,6, - 0,0 + 19,2,20,7,20,2,21,7,21,2,22,7,22,2,23,7,23,2,24,7,24,2,25,7,25,2, + 26,7,26,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,3, + 1,3,1,3,1,3,1,3,1,3,1,3,1,4,1,4,1,4,1,4,1,4,1,4,1,4,1,5,1,5,1,5, + 1,5,1,5,1,5,1,5,1,5,1,5,1,5,1,6,1,6,1,6,1,6,1,6,1,6,1,6,1,6,1,7, + 1,7,1,7,1,7,1,7,1,7,1,8,1,8,1,8,1,8,1,8,1,8,1,8,1,8,1,8,1,8,1,8, + 1,9,1,9,1,9,1,9,1,9,1,9,1,9,1,10,1,10,1,10,1,10,1,10,1,10,1,10,1, + 11,1,11,1,11,1,11,1,12,1,12,1,12,1,12,1,12,1,12,1,12,1,13,1,13,1, + 13,1,13,1,13,1,13,1,13,1,13,1,13,1,13,1,13,1,13,1,13,1,13,1,14,1, + 14,1,14,1,14,1,14,1,14,1,14,1,14,1,14,1,14,1,14,1,14,1,14,1,14,1, + 14,1,14,1,14,1,14,1,15,1,15,1,15,1,15,1,15,1,15,1,15,1,16,1,16,1, + 17,1,17,1,18,1,18,1,19,4,19,205,8,19,11,19,12,19,206,1,20,1,20,5, + 20,211,8,20,10,20,12,20,214,9,20,1,21,1,21,1,21,5,21,219,8,21,10, + 21,12,21,222,9,21,1,21,1,21,1,22,1,22,1,22,1,22,1,22,1,22,1,22,1, + 22,3,22,234,8,22,1,23,1,23,1,24,1,24,1,24,1,24,5,24,242,8,24,10, + 24,12,24,245,9,24,1,24,1,24,1,25,1,25,1,25,1,25,5,25,253,8,25,10, + 25,12,25,256,9,25,1,25,1,25,1,25,1,25,1,25,1,26,4,26,264,8,26,11, + 26,12,26,265,1,26,1,26,1,254,0,27,1,1,3,2,5,3,7,4,9,5,11,6,13,7, + 15,8,17,9,19,10,21,11,23,12,25,13,27,14,29,15,31,16,33,17,35,18, + 37,19,39,20,41,21,43,22,45,0,47,0,49,23,51,24,53,25,1,0,8,1,0,48, + 57,3,0,65,90,95,95,97,122,4,0,48,57,65,90,95,95,97,122,4,0,10,10, + 13,13,34,34,92,92,8,0,34,34,47,47,92,92,98,98,102,102,110,110,114, + 114,116,116,3,0,48,57,65,70,97,102,2,0,10,10,13,13,3,0,9,10,13,13, + 32,32,274,0,1,1,0,0,0,0,3,1,0,0,0,0,5,1,0,0,0,0,7,1,0,0,0,0,9,1, + 0,0,0,0,11,1,0,0,0,0,13,1,0,0,0,0,15,1,0,0,0,0,17,1,0,0,0,0,19,1, + 0,0,0,0,21,1,0,0,0,0,23,1,0,0,0,0,25,1,0,0,0,0,27,1,0,0,0,0,29,1, + 0,0,0,0,31,1,0,0,0,0,33,1,0,0,0,0,35,1,0,0,0,0,37,1,0,0,0,0,39,1, + 0,0,0,0,41,1,0,0,0,0,43,1,0,0,0,0,49,1,0,0,0,0,51,1,0,0,0,0,53,1, + 0,0,0,1,55,1,0,0,0,3,67,1,0,0,0,5,76,1,0,0,0,7,84,1,0,0,0,9,91,1, + 0,0,0,11,98,1,0,0,0,13,108,1,0,0,0,15,116,1,0,0,0,17,122,1,0,0,0, + 19,133,1,0,0,0,21,140,1,0,0,0,23,147,1,0,0,0,25,151,1,0,0,0,27,158, + 1,0,0,0,29,172,1,0,0,0,31,190,1,0,0,0,33,197,1,0,0,0,35,199,1,0, + 0,0,37,201,1,0,0,0,39,204,1,0,0,0,41,208,1,0,0,0,43,215,1,0,0,0, + 45,225,1,0,0,0,47,235,1,0,0,0,49,237,1,0,0,0,51,248,1,0,0,0,53,263, + 1,0,0,0,55,56,5,113,0,0,56,57,5,117,0,0,57,58,5,105,0,0,58,59,5, + 120,0,0,59,60,5,111,0,0,60,61,5,115,0,0,61,62,5,45,0,0,62,63,5,108, + 0,0,63,64,5,111,0,0,64,65,5,99,0,0,65,66,5,107,0,0,66,2,1,0,0,0, + 67,68,5,102,0,0,68,69,5,114,0,0,69,70,5,97,0,0,70,71,5,103,0,0,71, + 72,5,109,0,0,72,73,5,101,0,0,73,74,5,110,0,0,74,75,5,116,0,0,75, + 4,1,0,0,0,76,77,5,118,0,0,77,78,5,101,0,0,78,79,5,114,0,0,79,80, + 5,115,0,0,80,81,5,105,0,0,81,82,5,111,0,0,82,83,5,110,0,0,83,6,1, + 0,0,0,84,85,5,113,0,0,85,86,5,117,0,0,86,87,5,105,0,0,87,88,5,120, + 0,0,88,89,5,111,0,0,89,90,5,115,0,0,90,8,1,0,0,0,91,92,5,115,0,0, + 92,93,5,111,0,0,93,94,5,117,0,0,94,95,5,114,0,0,95,96,5,99,0,0,96, + 97,5,101,0,0,97,10,1,0,0,0,98,99,5,105,0,0,99,100,5,110,0,0,100, + 101,5,116,0,0,101,102,5,101,0,0,102,103,5,114,0,0,103,104,5,102, + 0,0,104,105,5,97,0,0,105,106,5,99,0,0,106,107,5,101,0,0,107,12,1, + 0,0,0,108,109,5,112,0,0,109,110,5,97,0,0,110,111,5,99,0,0,111,112, + 5,107,0,0,112,113,5,97,0,0,113,114,5,103,0,0,114,115,5,101,0,0,115, + 14,1,0,0,0,116,117,5,98,0,0,117,118,5,117,0,0,118,119,5,105,0,0, + 119,120,5,108,0,0,120,121,5,100,0,0,121,16,1,0,0,0,122,123,5,114, + 0,0,123,124,5,101,0,0,124,125,5,112,0,0,125,126,5,111,0,0,126,127, + 5,115,0,0,127,128,5,105,0,0,128,129,5,116,0,0,129,130,5,111,0,0, + 130,131,5,114,0,0,131,132,5,121,0,0,132,18,1,0,0,0,133,134,5,99, + 0,0,134,135,5,111,0,0,135,136,5,109,0,0,136,137,5,109,0,0,137,138, + 5,105,0,0,138,139,5,116,0,0,139,20,1,0,0,0,140,141,5,112,0,0,141, + 142,5,111,0,0,142,143,5,108,0,0,143,144,5,105,0,0,144,145,5,99,0, + 0,145,146,5,121,0,0,146,22,1,0,0,0,147,148,5,114,0,0,148,149,5,101, + 0,0,149,150,5,102,0,0,150,24,1,0,0,0,151,152,5,112,0,0,152,153,5, + 105,0,0,153,154,5,110,0,0,154,155,5,110,0,0,155,156,5,101,0,0,156, + 157,5,100,0,0,157,26,1,0,0,0,158,159,5,116,0,0,159,160,5,114,0,0, + 160,161,5,97,0,0,161,162,5,99,0,0,162,163,5,107,0,0,163,164,5,45, + 0,0,164,165,5,114,0,0,165,166,5,101,0,0,166,167,5,108,0,0,167,168, + 5,101,0,0,168,169,5,97,0,0,169,170,5,115,0,0,170,171,5,101,0,0,171, + 28,1,0,0,0,172,173,5,116,0,0,173,174,5,114,0,0,174,175,5,97,0,0, + 175,176,5,99,0,0,176,177,5,107,0,0,177,178,5,45,0,0,178,179,5,100, + 0,0,179,180,5,101,0,0,180,181,5,118,0,0,181,182,5,101,0,0,182,183, + 5,108,0,0,183,184,5,111,0,0,184,185,5,112,0,0,185,186,5,109,0,0, + 186,187,5,101,0,0,187,188,5,110,0,0,188,189,5,116,0,0,189,30,1,0, + 0,0,190,191,5,105,0,0,191,192,5,109,0,0,192,193,5,112,0,0,193,194, + 5,111,0,0,194,195,5,114,0,0,195,196,5,116,0,0,196,32,1,0,0,0,197, + 198,5,123,0,0,198,34,1,0,0,0,199,200,5,125,0,0,200,36,1,0,0,0,201, + 202,5,59,0,0,202,38,1,0,0,0,203,205,7,0,0,0,204,203,1,0,0,0,205, + 206,1,0,0,0,206,204,1,0,0,0,206,207,1,0,0,0,207,40,1,0,0,0,208,212, + 7,1,0,0,209,211,7,2,0,0,210,209,1,0,0,0,211,214,1,0,0,0,212,210, + 1,0,0,0,212,213,1,0,0,0,213,42,1,0,0,0,214,212,1,0,0,0,215,220,5, + 34,0,0,216,219,3,45,22,0,217,219,8,3,0,0,218,216,1,0,0,0,218,217, + 1,0,0,0,219,222,1,0,0,0,220,218,1,0,0,0,220,221,1,0,0,0,221,223, + 1,0,0,0,222,220,1,0,0,0,223,224,5,34,0,0,224,44,1,0,0,0,225,233, + 5,92,0,0,226,234,7,4,0,0,227,228,5,117,0,0,228,229,3,47,23,0,229, + 230,3,47,23,0,230,231,3,47,23,0,231,232,3,47,23,0,232,234,1,0,0, + 0,233,226,1,0,0,0,233,227,1,0,0,0,234,46,1,0,0,0,235,236,7,5,0,0, + 236,48,1,0,0,0,237,238,5,47,0,0,238,239,5,47,0,0,239,243,1,0,0,0, + 240,242,8,6,0,0,241,240,1,0,0,0,242,245,1,0,0,0,243,241,1,0,0,0, + 243,244,1,0,0,0,244,246,1,0,0,0,245,243,1,0,0,0,246,247,6,24,0,0, + 247,50,1,0,0,0,248,249,5,47,0,0,249,250,5,42,0,0,250,254,1,0,0,0, + 251,253,9,0,0,0,252,251,1,0,0,0,253,256,1,0,0,0,254,255,1,0,0,0, + 254,252,1,0,0,0,255,257,1,0,0,0,256,254,1,0,0,0,257,258,5,42,0,0, + 258,259,5,47,0,0,259,260,1,0,0,0,260,261,6,25,0,0,261,52,1,0,0,0, + 262,264,7,7,0,0,263,262,1,0,0,0,264,265,1,0,0,0,265,263,1,0,0,0, + 265,266,1,0,0,0,266,267,1,0,0,0,267,268,6,26,0,0,268,54,1,0,0,0, + 9,0,206,212,218,220,233,243,254,265,1,6,0,0 ]; private static __ATN: antlr.ATN; diff --git a/src/resource-lock/generated/QuixosLockParser.ts b/src/resource-lock/generated/QuixosLockParser.ts index 7f413ef..e557640 100644 --- a/src/resource-lock/generated/QuixosLockParser.ts +++ b/src/resource-lock/generated/QuixosLockParser.ts @@ -17,23 +17,24 @@ export class QuixosLockParser extends antlr.Parser { public static readonly SOURCE = 5; public static readonly INTERFACE = 6; public static readonly PACKAGE = 7; - public static readonly REPOSITORY = 8; - public static readonly COMMIT = 9; - public static readonly POLICY = 10; - public static readonly REF = 11; - public static readonly PINNED = 12; - public static readonly TRACK_RELEASE = 13; - public static readonly TRACK_DEVELOPMENT = 14; - public static readonly IMPORT = 15; - public static readonly LBRACE = 16; - public static readonly RBRACE = 17; - public static readonly SEMI = 18; - public static readonly INTEGER = 19; - public static readonly IDENTIFIER = 20; - public static readonly STRING_LITERAL = 21; - public static readonly LINE_COMMENT = 22; - public static readonly BLOCK_COMMENT = 23; - public static readonly WS = 24; + public static readonly BUILD = 8; + public static readonly REPOSITORY = 9; + public static readonly COMMIT = 10; + public static readonly POLICY = 11; + public static readonly REF = 12; + public static readonly PINNED = 13; + public static readonly TRACK_RELEASE = 14; + public static readonly TRACK_DEVELOPMENT = 15; + public static readonly IMPORT = 16; + public static readonly LBRACE = 17; + public static readonly RBRACE = 18; + public static readonly SEMI = 19; + public static readonly INTEGER = 20; + public static readonly IDENTIFIER = 21; + public static readonly STRING_LITERAL = 22; + public static readonly LINE_COMMENT = 23; + public static readonly BLOCK_COMMENT = 24; + public static readonly WS = 25; public static readonly RULE_document = 0; public static readonly RULE_quixosEntry = 1; public static readonly RULE_resourceEntry = 2; @@ -47,17 +48,17 @@ export class QuixosLockParser extends antlr.Parser { public static readonly literalNames = [ null, "'quixos-lock'", "'fragment'", "'version'", "'quixos'", "'source'", - "'interface'", "'package'", "'repository'", "'commit'", "'policy'", - "'ref'", "'pinned'", "'track-release'", "'track-development'", "'import'", - "'{'", "'}'", "';'" + "'interface'", "'package'", "'build'", "'repository'", "'commit'", + "'policy'", "'ref'", "'pinned'", "'track-release'", "'track-development'", + "'import'", "'{'", "'}'", "';'" ]; public static readonly symbolicNames = [ null, "QUIXOS_LOCK", "FRAGMENT", "VERSION", "QUIXOS", "SOURCE", - "INTERFACE", "PACKAGE", "REPOSITORY", "COMMIT", "POLICY", "REF", - "PINNED", "TRACK_RELEASE", "TRACK_DEVELOPMENT", "IMPORT", "LBRACE", - "RBRACE", "SEMI", "INTEGER", "IDENTIFIER", "STRING_LITERAL", "LINE_COMMENT", - "BLOCK_COMMENT", "WS" + "INTERFACE", "PACKAGE", "BUILD", "REPOSITORY", "COMMIT", "POLICY", + "REF", "PINNED", "TRACK_RELEASE", "TRACK_DEVELOPMENT", "IMPORT", + "LBRACE", "RBRACE", "SEMI", "INTEGER", "IDENTIFIER", "STRING_LITERAL", + "LINE_COMMENT", "BLOCK_COMMENT", "WS" ]; public static readonly ruleNames = [ "document", "quixosEntry", "resourceEntry", "importEntry", "resourceKind", @@ -107,7 +108,7 @@ export class QuixosLockParser extends antlr.Parser { this.state = 32; this.errorHandler.sync(this); _la = this.tokenStream.LA(1); - while ((((_la) & ~0x1F) === 0 && ((1 << _la) & 32976) !== 0)) { + while ((((_la) & ~0x1F) === 0 && ((1 << _la) & 66000) !== 0)) { { this.state = 30; this.errorHandler.sync(this); @@ -126,6 +127,7 @@ export class QuixosLockParser extends antlr.Parser { break; case QuixosLockParser.INTERFACE: case QuixosLockParser.PACKAGE: + case QuixosLockParser.BUILD: { this.state = 29; this.resourceEntry(); @@ -250,7 +252,7 @@ export class QuixosLockParser extends antlr.Parser { { this.state = 51; _la = this.tokenStream.LA(1); - if(!(_la === 6 || _la === 7)) { + if(!((((_la) & ~0x1F) === 0 && ((1 << _la) & 448) !== 0))) { this.errorHandler.recoverInline(this); } else { @@ -327,7 +329,7 @@ export class QuixosLockParser extends antlr.Parser { this.state = 73; this.errorHandler.sync(this); _la = this.tokenStream.LA(1); - if (_la === 10) { + if (_la === 11) { { this.state = 66; this.match(QuixosLockParser.POLICY); @@ -376,7 +378,7 @@ export class QuixosLockParser extends antlr.Parser { { this.state = 80; _la = this.tokenStream.LA(1); - if(!((((_la) & ~0x1F) === 0 && ((1 << _la) & 28672) !== 0))) { + if(!((((_la) & ~0x1F) === 0 && ((1 << _la) & 57344) !== 0))) { this.errorHandler.recoverInline(this); } else { @@ -446,31 +448,31 @@ export class QuixosLockParser extends antlr.Parser { } public static readonly _serializedATN: number[] = [ - 4,1,24,87,2,0,7,0,2,1,7,1,2,2,7,2,2,3,7,3,2,4,7,4,2,5,7,5,2,6,7, + 4,1,25,87,2,0,7,0,2,1,7,1,2,2,7,2,2,3,7,3,2,4,7,4,2,5,7,5,2,6,7, 6,2,7,7,7,2,8,7,8,2,9,7,9,1,0,1,0,3,0,23,8,0,1,0,1,0,1,0,1,0,1,0, 1,0,5,0,31,8,0,10,0,12,0,34,9,0,1,0,1,0,1,0,1,1,1,1,1,1,1,1,1,2, 1,2,1,2,1,2,1,2,1,3,1,3,1,3,1,3,1,4,1,4,1,5,1,5,1,5,1,5,1,5,1,5, 1,5,1,5,1,5,1,6,1,6,1,6,1,6,1,6,1,6,1,6,1,6,1,6,1,6,1,6,3,6,74,8, 6,1,6,1,6,1,6,1,6,1,6,1,7,1,7,1,8,1,8,1,9,1,9,1,9,0,0,10,0,2,4,6, - 8,10,12,14,16,18,0,2,1,0,6,7,1,0,12,14,81,0,20,1,0,0,0,2,38,1,0, + 8,10,12,14,16,18,0,2,1,0,6,8,1,0,13,15,81,0,20,1,0,0,0,2,38,1,0, 0,0,4,42,1,0,0,0,6,47,1,0,0,0,8,51,1,0,0,0,10,53,1,0,0,0,12,62,1, 0,0,0,14,80,1,0,0,0,16,82,1,0,0,0,18,84,1,0,0,0,20,22,5,1,0,0,21, 23,5,2,0,0,22,21,1,0,0,0,22,23,1,0,0,0,23,24,1,0,0,0,24,25,5,3,0, - 0,25,26,5,19,0,0,26,32,5,16,0,0,27,31,3,2,1,0,28,31,3,6,3,0,29,31, + 0,25,26,5,20,0,0,26,32,5,17,0,0,27,31,3,2,1,0,28,31,3,6,3,0,29,31, 3,4,2,0,30,27,1,0,0,0,30,28,1,0,0,0,30,29,1,0,0,0,31,34,1,0,0,0, 32,30,1,0,0,0,32,33,1,0,0,0,33,35,1,0,0,0,34,32,1,0,0,0,35,36,5, - 17,0,0,36,37,5,0,0,1,37,1,1,0,0,0,38,39,5,4,0,0,39,40,5,5,0,0,40, + 18,0,0,36,37,5,0,0,1,37,1,1,0,0,0,38,39,5,4,0,0,39,40,5,5,0,0,40, 41,3,12,6,0,41,3,1,0,0,0,42,43,3,8,4,0,43,44,3,16,8,0,44,45,5,5, - 0,0,45,46,3,10,5,0,46,5,1,0,0,0,47,48,5,15,0,0,48,49,3,18,9,0,49, - 50,5,18,0,0,50,7,1,0,0,0,51,52,7,0,0,0,52,9,1,0,0,0,53,54,5,16,0, - 0,54,55,5,8,0,0,55,56,3,18,9,0,56,57,5,18,0,0,57,58,5,9,0,0,58,59, - 3,18,9,0,59,60,5,18,0,0,60,61,5,17,0,0,61,11,1,0,0,0,62,63,5,16, - 0,0,63,64,5,8,0,0,64,65,3,18,9,0,65,73,5,18,0,0,66,67,5,10,0,0,67, - 68,3,14,7,0,68,69,5,18,0,0,69,70,5,11,0,0,70,71,3,18,9,0,71,72,5, - 18,0,0,72,74,1,0,0,0,73,66,1,0,0,0,73,74,1,0,0,0,74,75,1,0,0,0,75, - 76,5,9,0,0,76,77,3,18,9,0,77,78,5,18,0,0,78,79,5,17,0,0,79,13,1, - 0,0,0,80,81,7,1,0,0,81,15,1,0,0,0,82,83,5,20,0,0,83,17,1,0,0,0,84, - 85,5,21,0,0,85,19,1,0,0,0,4,22,30,32,73 + 0,0,45,46,3,10,5,0,46,5,1,0,0,0,47,48,5,16,0,0,48,49,3,18,9,0,49, + 50,5,19,0,0,50,7,1,0,0,0,51,52,7,0,0,0,52,9,1,0,0,0,53,54,5,17,0, + 0,54,55,5,9,0,0,55,56,3,18,9,0,56,57,5,19,0,0,57,58,5,10,0,0,58, + 59,3,18,9,0,59,60,5,19,0,0,60,61,5,18,0,0,61,11,1,0,0,0,62,63,5, + 17,0,0,63,64,5,9,0,0,64,65,3,18,9,0,65,73,5,19,0,0,66,67,5,11,0, + 0,67,68,3,14,7,0,68,69,5,19,0,0,69,70,5,12,0,0,70,71,3,18,9,0,71, + 72,5,19,0,0,72,74,1,0,0,0,73,66,1,0,0,0,73,74,1,0,0,0,74,75,1,0, + 0,0,75,76,5,10,0,0,76,77,3,18,9,0,77,78,5,19,0,0,78,79,5,18,0,0, + 79,13,1,0,0,0,80,81,7,1,0,0,81,15,1,0,0,0,82,83,5,21,0,0,83,17,1, + 0,0,0,84,85,5,22,0,0,85,19,1,0,0,0,4,22,30,32,73 ]; private static __ATN: antlr.ATN; @@ -648,6 +650,9 @@ export class ResourceKindContext extends antlr.ParserRuleContext { public PACKAGE(): antlr.TerminalNode | null { return this.getToken(QuixosLockParser.PACKAGE, 0); } + public BUILD(): antlr.TerminalNode | null { + return this.getToken(QuixosLockParser.BUILD, 0); + } public override get ruleIndex(): number { return QuixosLockParser.RULE_resourceKind; } diff --git a/src/resource-lock/parser.ts b/src/resource-lock/parser.ts index 4e633af..4242635 100644 --- a/src/resource-lock/parser.ts +++ b/src/resource-lock/parser.ts @@ -296,7 +296,11 @@ export const parseQuixosLockDocument = (source: string, fileName = ""): const resources: LockedResource[] = []; const bindings = new Set(); for (const [index, context] of tree.resourceEntry().entries()) { - const kind = context.resourceKind().INTERFACE() ? "interface" : "package"; + const kind = context.resourceKind().INTERFACE() + ? "interface" + : context.resourceKind().BUILD() + ? "build" + : "package"; const binding = context.identifier().getText(); const key = `${kind}\0${binding}`; if (bindings.has(key)) { diff --git a/src/resource-lock/types.ts b/src/resource-lock/types.ts index a7cf809..d397b75 100644 --- a/src/resource-lock/types.ts +++ b/src/resource-lock/types.ts @@ -21,7 +21,7 @@ export type QuixosSource = GitSource & } ); -export type LockedResourceKind = "interface" | "package"; +export type LockedResourceKind = "interface" | "package" | "build"; export type LockedResource = { kind: LockedResourceKind; diff --git a/test/acceptance-fixture.ts b/test/acceptance-fixture.ts new file mode 100644 index 0000000..e9dc4f8 --- /dev/null +++ b/test/acceptance-fixture.ts @@ -0,0 +1,42 @@ +import assert from "node:assert/strict"; +import { compileCapabilityResourceSource, compileCapabilitySource } from "../src/capability-language/parser.js"; +const contract = `interface Limit id "limit" revision "limit@1" { + operation change id "change" : int32 -> unit { + call id "change:call" execution transaction; + } +}`; +export const runtime = `symbol JavaScript id "target:org.quixos.execution.javascript@1"; +external atom Item id "item"; +package Runtime id "runtime" revision "runtime@1" { + operation change id "runtime:change" : int32 -> unit mode call execution transaction targets [JavaScript, server] + receiver atom Item requires { state level id "write:value" : int32 [write]; } accept using check; + operation check id "runtime:check" : int32 -> unit mode call execution snapshot targets [server] + receiver atom Item requires { + state level id "read:value" : int32 [read]; + state ceiling id "read:ceiling" : int32 [read]; + }; +}`; +export const workspace = `workspace LimitTest id "workspace:limit" revision "workspace:limit@1" commit "${"a".repeat(40)}" { + import interface Limit; + import package Runtime; + atom Item id "item"; + conform Item as Limit id "item:limit" { + private state Value id "value" on Item : int32 policy register default 0; + private state Ceiling id "ceiling" on Item : int32 policy register default 100; + bind change.call to package Runtime.change with { level to state Value; } + accept with { level to state Value; ceiling to state Ceiling; }; + } +}`; +export function compile(packageSource = runtime, workspaceSource = workspace) { + const source = { repository: "https://example.test/limit.git", commit: "a".repeat(40) }; + const iface = compileCapabilityResourceSource(contract, { source }); + assert.ok(iface.ok && iface.resource.kind === "interface", JSON.stringify(iface.diagnostics)); + const pkg = compileCapabilityResourceSource(packageSource, { source }); + if (!pkg.ok) return pkg; + assert.equal(pkg.resource.kind, "package"); + if (pkg.resource.kind !== "package") throw Error("Expected package"); + return compileCapabilitySource(workspaceSource, "workspace.qx", { + interfaces: new Map([["Limit", iface.resource.revision]]), + packages: new Map([["Runtime", pkg.resource.revision]]), + }); +} diff --git a/test/acceptance-hooks.test.ts b/test/acceptance-hooks.test.ts new file mode 100644 index 0000000..444e32b --- /dev/null +++ b/test/acceptance-hooks.test.ts @@ -0,0 +1,70 @@ +import assert from "node:assert/strict"; +import { test } from "node:test"; +import { compileCapabilityResourceSource } from "../src/capability-language/parser.js"; +import { formatQx } from "../src/capability-language/source.js"; +import { compileExecutionPlan, compileKernelProgram, checkExecutionWorld } from "../src/execution-world/index.js"; +import { generatePortableTypeScriptBindings } from "../src/bindings/portable.js"; +import { buildArtifactFixture } from "./artifact-fixture.js"; +import { compile, runtime, workspace } from "./acceptance-fixture.js"; +test("acceptance syntax retains separate final-state ports through formatting, planning, adapters and artifacts", async () => { + const result = compile(); + assert.ok(result.ok, JSON.stringify(result.diagnostics)); + const formatted = compile(formatQx(runtime), formatQx(workspace)); + assert.ok(formatted.ok, JSON.stringify(formatted.diagnostics)); + const plan = compileExecutionPlan(result.workspace); + assert.equal(compileExecutionPlan(formatted.workspace).digest, plan.digest); + const kernel = compileKernelProgram(plan) as { + operations: { binding: { ports: { id: string }[]; acceptance: { export: string; ports: { id: string }[] } } }[]; + packages: { exports: { id: string; acceptance?: string }[] }[]; + }; + assert.deepEqual( + kernel.operations[0]!.binding.ports.map((p) => p.id), + ["write:value"], + ); + assert.equal(kernel.operations[0]!.binding.acceptance.export, "runtime:check"); + assert.deepEqual(kernel.operations[0]!.binding.acceptance.ports.map((p) => p.id).sort(), [ + "read:ceiling", + "read:value", + ]); + assert.equal(kernel.packages[0]!.exports.find((e) => e.id === "runtime:change")!.acceptance, "runtime:check"); + assert.ok( + !generatePortableTypeScriptBindings(plan.definition.bindingSchema, "runtime@1", { + target: "target:org.quixos.execution.javascript@1", + }).includes('"runtime:check"'), + ); + assert.ok( + generatePortableTypeScriptBindings(plan.definition.bindingSchema, "runtime@1", { target: "server" }).includes( + '"runtime:check"', + ), + ); + const artifacts = buildArtifactFixture(plan, "runtime@1"); + await checkExecutionWorld(plan, [artifacts.manifest], artifacts.read); +}); +test("acceptance declarations reject wrong effects/signatures and missing or mutable ports", () => { + for (const [pkg, ws] of [ + [runtime.replace("snapshot targets [server]", "transaction targets [server]"), workspace], + [runtime.replace("snapshot targets [server]", "snapshot targets [JavaScript, server]"), workspace], + [ + runtime.replace('check id "runtime:check" : int32 -> unit', 'check id "runtime:check" : string -> unit'), + workspace, + ], + [ + runtime.replace('check id "runtime:check" : int32 -> unit', 'check id "runtime:check" : int32 -> int32'), + workspace, + ], + [runtime.replace('id "read:ceiling" : int32 [read]', 'id "read:ceiling" : int32 [write]'), workspace], + [runtime, workspace.replace("ceiling to state Ceiling;", "")], + [runtime.replace(" accept using check", ""), workspace], + ]) + assert.equal(compile(pkg, ws).ok, false, `${pkg}\n${ws}`); +}); +test("generic acceptance syntax rejects explicitly until specialization is supported", () => { + const result = compileCapabilityResourceSource( + runtime.replace("operation change id", "operation change id"), + { + source: { repository: "https://example.test/limit.git", commit: "a".repeat(40) }, + }, + ); + assert.equal(result.ok, false); + assert.ok(result.diagnostics.some((d) => d.code === "unsupported-generic-acceptance")); +}); diff --git a/test/artifact-files.test.ts b/test/artifact-files.test.ts new file mode 100644 index 0000000..f6cf5b9 --- /dev/null +++ b/test/artifact-files.test.ts @@ -0,0 +1,177 @@ +import assert from "node:assert/strict"; +import { test } from "node:test"; +import { mkdtemp, mkdir, writeFile, symlink, rm } from "node:fs/promises"; +import os from "node:os"; +import path from "node:path"; +import { createHash } from "node:crypto"; +import { + compileExecutionPlan, + artifactFileReader, + loadArtifactWorld, + BrowserArtifactGateway, + implementationDigest, + contentArtifactUrl, +} from "../src/execution-world/index.js"; +import { loadReaderWorkspace } from "./reader-fixture.js"; +import { buildArtifactFixture } from "./artifact-fixture.js"; + +test("immutable-output loading rejects traversal, links, and files larger than the declared bound", async () => { + const root = await mkdtemp(path.join(os.tmpdir(), "camino-artifact-read-")); + try { + await mkdir(path.join(root, "output")); + await writeFile(path.join(root, "outside"), "secret"); + await writeFile(path.join(root, "output", "file"), "data"); + await symlink(path.join(root, "outside"), path.join(root, "output", "linked-file")); + await symlink(root, path.join(root, "output", "linked-dir")); + const read = await artifactFileReader({ package: path.join(root, "output") }); + assert.equal(new TextDecoder().decode(await read("package", "file", 4)), "data"); + await assert.rejects(read("package", "file", 3), /exceeds its declared bound/); + await assert.rejects(read("package", "linked-file", 100), /Only real directories/); + await assert.rejects(read("package", "linked-dir/outside", 100), /Only real directories/); + await assert.rejects(read("package", "../outside", 100), /normalized relative/); + await assert.rejects(read("unknown", "file", 100), /Unknown package output/); + await assert.rejects(read("package", "file", Infinity), /Invalid file byte limit/); + await assert.rejects( + artifactFileReader({ package: path.join(root, "output", "linked-dir") }), + /canonical absolute/, + ); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +test("gateway serves only declared browser assets with correct Wasm MIME and content identity", async () => { + const root = await mkdtemp(path.join(os.tmpdir(), "camino-artifact-world-")); + try { + const plan = compileExecutionPlan(await loadReaderWorkspace()), + fixture = buildArtifactFixture(plan); + const wasm = new Uint8Array([0, 97, 115, 109, 1, 0, 0, 0]); + const browser = fixture.manifest.implementations.find( + (i) => i.target === "target:org.quixos.execution.javascript@1", + )!; + browser.files.push({ + path: "portable/browser/assets/café.wasm", + bytes: wasm.length, + digest: createHash("sha256").update(wasm).digest("hex"), + mediaType: "application/wasm", + }); + const shared = { ...browser.files[0]!, path: "share/quixos/shared/core.mjs" }; + browser.files.push(shared); + const { contentDigest, ...body } = browser; + browser.contentDigest = implementationDigest(body); + for (const implementation of fixture.manifest.implementations) + for (const file of implementation.files) { + const target = path.join(root, file.path); + await mkdir(path.dirname(target), { recursive: true }); + await writeFile(target, file.mediaType === "application/wasm" ? wasm : fixture.bytes); + } + await mkdir(path.join(root, "share/quixos"), { recursive: true }); + await writeFile(path.join(root, "share/quixos/package-artifacts.json"), JSON.stringify(fixture.manifest)); + const loaded = await loadArtifactWorld(plan, { [fixture.manifest.packageRevisionId]: root }); + const gateway = new BrowserArtifactGateway(loaded); + assert.equal(gateway.entries.length, 1); + assert.deepEqual((await gateway.response(contentArtifactUrl(shared)))?.body, Buffer.from(fixture.bytes)); + const entry = gateway.entries[0]!; + const response = await gateway.response(entry.url); + assert.equal(response?.status, 200); + assert.equal(response?.headers["content-type"], "text/javascript"); + assert.equal(response?.headers["cache-control"], "private, max-age=31536000, immutable"); + assert.deepEqual(response?.body, Buffer.from(fixture.bytes)); + assert.equal((await gateway.response(entry.url, response?.headers.etag))?.status, 304); + const wasmUrl = entry.url.replace("module.mjs", "assets/caf%C3%A9.wasm"); + const wasmResponse = await gateway.response(wasmUrl); + assert.equal(wasmResponse?.headers["content-type"], "application/wasm"); + assert.deepEqual(wasmResponse?.body, Buffer.from(wasm)); + assert.equal(await gateway.response(entry.url.replace("portable/browser/module.mjs", "bin/service")), undefined); + assert.equal(await gateway.response("/nix/store/secret"), undefined); + assert.equal(await gateway.response(entry.url.replace(loaded.digest, "0".repeat(64))), undefined); + await writeFile(path.join(root, browser.path), "x".repeat(fixture.bytes.length)); + await assert.rejects(gateway.response(entry.url), /changed after world validation/); + await assert.rejects(loadArtifactWorld(plan, { [fixture.manifest.packageRevisionId]: root }), /Artifact bytes/); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +test("checked receipts bind regenerated code, generator identity and manifests", async () => { + const { expectedArtifactReceipt, checkArtifactReceipt } = await import("../src/execution-world/receipts.js"); + const plan = compileExecutionPlan(await loadReaderWorkspace()); + const fixture = buildArtifactFixture(plan); + const generator = "a".repeat(64); + const { receipt, sources } = expectedArtifactReceipt(plan, fixture.manifest, generator); + const read = async (_revision: string, file: string) => sources.get(file)!; + await checkArtifactReceipt(plan, fixture.manifest, receipt, generator, read); + await assert.rejects( + checkArtifactReceipt(plan, fixture.manifest, { ...receipt, generatorDigest: "b".repeat(64) }, generator, read), + /trusted compiler/, + ); + await assert.rejects( + checkArtifactReceipt(plan, fixture.manifest, { ...receipt, manifestDigest: "b".repeat(64) }, generator, read), + /trusted compiler/, + ); + await assert.rejects( + checkArtifactReceipt(plan, fixture.manifest, receipt, generator, async () => + new TextEncoder().encode("substituted"), + ), + /replaced or omitted/, + ); +}); + +test("remote JS modules witness generated adapters without imposing TypeScript on custom services", async () => { + const { asyncObjectPlan } = await import("./async-fixture.js"); + const { expectedArtifactReceipt, checkArtifactReceipt } = await import("../src/execution-world/receipts.js"); + const plan = await asyncObjectPlan(), + fixture = buildArtifactFixture(plan, "async:provider-runtime@2"), + generator = "a".repeat(64); + fixture.manifest.implementations = fixture.manifest.implementations.filter( + (implementation) => implementation.target === "server", + ); + const { receipt, sources } = expectedArtifactReceipt(plan, fixture.manifest, generator); + assert.deepEqual( + receipt.generated.map((file) => file.path), + ["share/quixos/generated/remote.ts"], + ); + await checkArtifactReceipt(plan, fixture.manifest, receipt, generator, async (_revision, path) => sources.get(path)!); + await assert.rejects( + checkArtifactReceipt(plan, fixture.manifest, receipt, generator, async () => new Uint8Array()), + /replaced or omitted/, + ); + fixture.manifest.implementations = fixture.manifest.implementations.filter( + (implementation) => implementation.compatibility === "quixos-service-v1", + ); + assert.deepEqual(expectedArtifactReceipt(plan, fixture.manifest, generator).receipt.generated, []); +}); + +test("transaction process services do not promise generated TypeScript modules", async () => { + const { expectedArtifactReceipt, checkArtifactReceipt } = await import("../src/execution-world/receipts.js"); + const plan = compileExecutionPlan(await loadReaderWorkspace()), + fixture = buildArtifactFixture(plan); + fixture.manifest.implementations = fixture.manifest.implementations.filter( + (i) => i.compatibility === "quixos-service-v1", + ); + const generator = "a".repeat(64), + { receipt } = expectedArtifactReceipt(plan, fixture.manifest, generator); + assert.deepEqual(receipt.generated, []); + await checkArtifactReceipt(plan, fixture.manifest, receipt, generator, async () => { + throw Error("No adapter claimed"); + }); +}); + +test("a remote export group in a mixed package witnesses only its own adapter", async () => { + const { asyncObjectPlan } = await import("./async-fixture.js"); + const { expectedArtifactReceipt } = await import("../src/execution-world/receipts.js"); + const plan = await asyncObjectPlan(); + const fixture = buildArtifactFixture(plan, "async:controller-runtime@1"); + const pkg = plan.definition.packages.find((entry) => entry.revision === fixture.manifest.packageRevisionId)!; + fixture.manifest.implementations = fixture.manifest.implementations.filter( + (entry) => entry.target === "server" && entry.compatibility === "quixos-js-module-v1", + ); + for (const implementation of fixture.manifest.implementations) + implementation.exports = implementation.exports.filter((id) => + pkg.exports.some((entry) => entry.id === id && entry.execution.kind === "remote"), + ); + assert.deepEqual( + expectedArtifactReceipt(plan, fixture.manifest, "a".repeat(64)).receipt.generated.map((file) => file.path), + ["share/quixos/generated/remote.ts"], + ); +}); diff --git a/test/artifact-fixture.ts b/test/artifact-fixture.ts new file mode 100644 index 0000000..1c28f16 --- /dev/null +++ b/test/artifact-fixture.ts @@ -0,0 +1,51 @@ +import { createHash } from "node:crypto"; +import { + implementationDigest, + type ExecutionPlan, + type PackageArtifacts, + type ArtifactImplementation, +} from "../src/execution-world/index.js"; +export const buildArtifactFixture = (plan: ExecutionPlan, revision = plan.definition.packages[0]!.revision) => { + const expected = plan.definition.packages.find((pkg) => pkg.revision === revision)!; + const bytes = new TextEncoder().encode("export const handlers = Object.freeze({});\n"); + const implementation = ( + id: string, + target: string, + compatibility: ArtifactImplementation["compatibility"], + ): ArtifactImplementation => { + const path = compatibility === "quixos-service-v1" ? "bin/service" : `portable/${id}/module.mjs`; + const value = { + id, + target, + compatibility, + path, + ports: "quixos-transaction-ports-v1" as const, + ...(compatibility === "quixos-js-module-v1" ? { registryExport: "handlers" } : {}), + exports: expected.exports + .filter((e) => target === "server" || (e.execution.kind !== "remote" && e.execution.targets.includes(target))) + .map((e) => e.id), + files: [ + { + path, + digest: createHash("sha256").update(bytes).digest("hex"), + bytes: bytes.length, + mediaType: compatibility === "quixos-service-v1" ? "application/octet-stream" : "text/javascript", + }, + ], + }; + return { ...value, contentDigest: implementationDigest(value) }; + }; + const manifest: PackageArtifacts = { + schemaVersion: 1, + packageRevisionId: expected.revision, + bindingDigest: expected.bindingDigest, + sourceDigest: expected.sourceDigest, + versions: plan.definition.versions, + implementations: [ + implementation("service", "server", "quixos-service-v1"), + implementation("server", "server", "quixos-js-module-v1"), + implementation("browser", "target:org.quixos.execution.javascript@1", "quixos-js-module-v1"), + ], + }; + return { manifest, bytes, read: async () => bytes }; +}; diff --git a/test/async-fixture.ts b/test/async-fixture.ts new file mode 100644 index 0000000..54e594e --- /dev/null +++ b/test/async-fixture.ts @@ -0,0 +1,25 @@ +import assert from "node:assert/strict"; +import { readFile } from "node:fs/promises"; +import { compileCapabilityResourceSource, compileCapabilitySource } from "../src/capability-language/parser.js"; +import { compileExecutionPlan } from "../src/execution-world/plan.js"; +import type { InterfaceRevision, PackageRevision } from "../src/capability-model/types.js"; +export async function asyncObjectPlan() { + const read = (name: string) => readFile(`test/fixtures/async-objects/${name}.qx`, "utf8"); + const source = { repository: "https://example.test/async.git", commit: "a".repeat(40) }; + const interfaces = new Map(); + const packages = new Map(); + for (const name of ["DocumentData", "SummaryController", "Summarizer"]) { + const result = compileCapabilityResourceSource(await read(name), { source }); + assert.ok(result.ok && result.resource.kind === "interface", JSON.stringify(result.diagnostics)); + interfaces.set(name, result.resource.revision); + } + const environment = { interfaces, interfaceClosure: [...interfaces.values()] }; + for (const name of ["ControllerRuntime", "ProviderRuntime"]) { + const result = compileCapabilityResourceSource(await read(name), { source, environment }); + assert.ok(result.ok && result.resource.kind === "package", JSON.stringify(result.diagnostics)); + packages.set(name, result.resource.revision); + } + const result = compileCapabilitySource(await read("workspace"), "workspace.qx", { ...environment, packages }); + assert.ok(result.ok, JSON.stringify(result.diagnostics)); + return compileExecutionPlan(result.workspace); +} diff --git a/test/bindings.test.ts b/test/bindings.test.ts index 4dccdaa..4e204cf 100644 --- a/test/bindings.test.ts +++ b/test/bindings.test.ts @@ -12,6 +12,17 @@ const schemaFor = (body: string): BindingSchema => { if (!compiled.ok || compiled.resource.kind !== "package") throw new Error("expected package"); return { format: "quixos-bindings", version: 1, interfaces: [], packages: [compiled.resource.revision] }; }; +test("reflection ports generate explicit selector-restricted package bindings", () => { + const schema = schemaFor( + 'function inspect id "inspect" : unit -> unit requires { reflection meta id "meta" [active, atoms, interfaces, conformances, queries, indexes]; };', + ); + const generated = generateTypeScriptBindings(schema, "demo@1"); + assert.match( + generated, + /QxReflectionPort<"active" \| "atoms" \| "interfaces" \| "conformances" \| "queries" \| "indexes">/, + ); + assert.match(generated, /"kind": "reflection"/); +}); test("generator uses exact IDs, restricted ports, nominal references, and lossless scalar types", () => { const schema = schemaFor( 'operation run id "run-id" : list -> optional> mode call receiver atom Thing requires { state payload id "payload-id" : bytes [read]; };', diff --git a/test/build-contract.test.ts b/test/build-contract.test.ts new file mode 100644 index 0000000..4dd9b22 --- /dev/null +++ b/test/build-contract.test.ts @@ -0,0 +1,78 @@ +import assert from "node:assert/strict"; +import { test } from "node:test"; +import { compileCapabilityResourceSource } from "../src/capability-language/index.js"; +import { packageBuildContract } from "../src/execution-world/build-contract.js"; +import { generatePortableTypeScriptBindings } from "../src/bindings/portable.js"; +import type { BindingSchema } from "../src/bindings/index.js"; +import type { InterfaceRevision } from "../src/capability-model/types.js"; + +function fixture() { + const source = { repository: "https://example.test/source", commit: "1".repeat(40) }; + const interfaces = new Map(); + for (const name of ["Value", "Unrelated"]) { + const result = compileCapabilityResourceSource( + `interface ${name} id "${name}" revision "${name}@1" { + value text id "text" : string { get id "text:get" execution snapshot; } + }`, + { source }, + ); + assert.ok(result.ok && result.resource.kind === "interface", JSON.stringify(result.diagnostics)); + interfaces.set(name, result.resource.revision); + } + const result = compileCapabilityResourceSource( + `import interface Value; + package Adapter id "adapter" revision "adapter@1" { + operation fetchText id "read" : unit -> string mode call execution snapshot targets [server] + receiver any requires { interface data id "value" : Value using [text.get]; }; + }`, + { source, environment: { interfaces, interfaceClosure: [...interfaces.values()] } }, + ); + assert.ok(result.ok && result.resource.kind === "package", JSON.stringify(result.diagnostics)); + return { + format: "quixos-bindings", + version: 1, + interfaces: [...interfaces.values()], + packages: [result.resource.revision], + } satisfies BindingSchema; +} + +test("package builds ignore exact pins and unrelated declarations, and preserve generated port types", () => { + const schema = fixture(); + const original = packageBuildContract(schema, "adapter@1"); + const changed = structuredClone(schema); + changed.interfaces.forEach((iface) => { + iface.source.commit = "2".repeat(40); + }); + changed.packages[0]!.source.commit = "3".repeat(40); + changed.interfaces.find((iface) => iface.displayName === "Unrelated")!.members = []; + assert.deepEqual(packageBuildContract(changed, "adapter@1"), original); + assert.equal(JSON.stringify(original).includes("https://example.test/source"), false); + assert.equal( + generatePortableTypeScriptBindings(original.definition.bindings, "adapter@1", { target: "server" }), + generatePortableTypeScriptBindings(schema, "adapter@1", { target: "server" }), + ); +}); + +test("changing a consumed interface or an owned port contract invalidates the build", () => { + const schema = fixture(); + const original = packageBuildContract(schema, "adapter@1"); + const changed = structuredClone(schema); + changed.interfaces[0]!.members[0]!.operations[0]!.outputType = { kind: "scalar", name: "bool" }; + assert.notEqual(packageBuildContract(changed, "adapter@1").digest, original.digest); + const own = structuredClone(schema); + own.packages[0]!.exports[0]!.outputType = { kind: "scalar", name: "bool" }; + assert.notEqual(packageBuildContract(own, "adapter@1").digest, original.digest); +}); + +test("a nominal reference does not import the referenced interface's entire dependency graph", () => { + const schema = fixture(); + schema.packages[0]!.exports[0]!.inputType = { + kind: "object-ref", + expectation: { kind: "interface", interfaceRevisionId: schema.interfaces[1]!.revisionId }, + }; + const original = packageBuildContract(schema, "adapter@1"); + schema.interfaces[1]!.members = []; + assert.deepEqual(packageBuildContract(schema, "adapter@1"), original); + schema.packages[0]!.exports[0]!.inputType = { kind: "scalar", name: "string" }; + assert.notEqual(packageBuildContract(schema, "adapter@1").digest, original.digest); +}); diff --git a/test/build-dependencies.test.ts b/test/build-dependencies.test.ts new file mode 100644 index 0000000..ccd9f82 --- /dev/null +++ b/test/build-dependencies.test.ts @@ -0,0 +1,76 @@ +import assert from "node:assert/strict"; +import test from "node:test"; +import fs from "node:fs/promises"; +import os from "node:os"; +import path from "node:path"; +import { compileWorkspaceRepository, compileCapabilityResourceRepository } from "../src/capability-language/index.js"; +import { bindingSchema } from "../src/bindings/index.js"; +import { parseQuixosLockDocument } from "../src/resource-lock/index.js"; +import { editStructure } from "../src/capability-language/structural-edits.js"; + +const commit = "a".repeat(40); +const entry = (kind: string, name: string) => + `${kind} ${name} source {repository "https://example.test/${name}.git"; commit "${commit}";}`; +const lock = (entries = "") => + `quixos-lock version 1 {quixos source {repository "https://example.test/quixos.git"; commit "${commit}";} ${entries}}`; + +test("build dependency closures are retained without installing their capability descendants", async (t) => { + const directory = await fs.mkdtemp(path.join(os.tmpdir(), "qx-build-deps-")); + t.after(() => fs.rm(directory, { recursive: true, force: true })); + const write = async (name: string, files: Record) => { + await fs.mkdir(path.join(directory, name)); + for (const [file, text] of Object.entries(files)) await fs.writeFile(path.join(directory, name, file), text); + }; + await write("root", { + "quixos.lock": lock(entry("build", "Library")), + "workspace.qx": `workspace Empty id "empty" revision "empty@1" commit "${commit}" {}`, + }); + await write("Library", { + "quixos.lock": lock(entry("package", "Hidden")), + "types.ts": "export type Label = string;", + }); + await write("Hidden", { + "quixos.lock": lock(entry("interface", "Data")), + "package.qx": 'import interface Data; package Hidden id "hidden" revision "hidden@1" {}', + }); + await write("Data", { "quixos.lock": lock(), "interface.qx": 'interface Data id "data" revision "data@1" {}' }); + const resolveResource = async (source: { repository: string }) => ({ + directory: path.join(directory, new URL(source.repository).pathname.slice(1, -4)), + }); + const compiled = await compileWorkspaceRepository({ rootDirectory: path.join(directory, "root"), resolveResource }); + assert.equal(compiled.resources.length, 3); + assert.deepEqual(compiled.capabilityResources, []); + assert.deepEqual(compiled.workspace.packageImports, []); + assert.deepEqual(compiled.workspace.interfaceImports, []); + assert.deepEqual(bindingSchema(compiled).packages, []); + const library = await compileCapabilityResourceRepository({ + rootDirectory: path.join(directory, "Library"), + kind: "build", + source: { resolver: "git", repository: "https://example.test/Library.git", commit }, + resolveResource, + }); + assert.deepEqual(library.resource, { kind: "build" }); + assert.deepEqual(bindingSchema(library).interfaces, []); + await fs.writeFile(path.join(directory, "Library/quixos.lock"), lock(entry("build", "Library"))); + await assert.rejects( + compileWorkspaceRepository({ rootDirectory: path.join(directory, "root"), resolveResource }), + /dependency cycle/, + ); +}); + +test("build pins retain their kind through parsing and structural updates", () => { + const initial = lock(entry("build", "Library")); + const parsed = parseQuixosLockDocument(initial); + assert.ok(parsed.ok); + assert.equal(parsed.document.resources[0]!.kind, "build"); + const edited = editStructure(initial, { + operation: "dependency", + kind: "build", + name: "Library", + source: { repository: "https://example.test/Library.git", commit: "b".repeat(40) }, + }); + const changed = parseQuixosLockDocument(edited); + assert.ok(changed.ok); + assert.equal(changed.document.resources[0]!.kind, "build"); + assert.equal(changed.document.resources[0]!.source.commit, "b".repeat(40)); +}); diff --git a/test/capability-assembly.test.ts b/test/capability-assembly.test.ts index 84f72b5..33a16b1 100644 --- a/test/capability-assembly.test.ts +++ b/test/capability-assembly.test.ts @@ -107,6 +107,35 @@ package Runtime id "package:runtime" revision "package:runtime@1" { assert.equal(resource.resource.kind, "package"); assert.equal(resource.resources.length, 2); assert.equal(resource.directResources.size, 1); + + const checked = new Map(result.resources.map((node) => [node.key, node.resource])); + let generated = 0; + const reused = await compileWorkspaceRepository({ + rootDirectory: root, + resolveResource: async (source, kind) => ({ + directory: directories.get(`${kind}\0${source.repository}\0${source.commit}`)!, + }), + checkedResource: async (kind, source) => { + const value = checked.get(`${kind}\0${source.repository}\0${source.commit}`); + return value?.kind === "build" ? undefined : value; + }, + generateQueries: async () => { + generated++; + return { queries: [], documents: new Map() }; + }, + }); + assert.deepEqual(reused, result, "Nix-composed resource checks preserve full assembly results"); + assert.equal(generated, 0, "unchanged checked packages do not run their query frontend again"); + await assert.rejects( + compileWorkspaceRepository({ + rootDirectory: root, + resolveResource: async (source, kind) => ({ + directory: directories.get(`${kind}\0${source.repository}\0${source.commit}`)!, + }), + checkedResource: async () => (resource.resource.kind === "build" ? undefined : resource.resource), + }), + /Checked resource identity mismatch/, + ); }); test("resource repositories cannot own workspace Quixos selection policy", async (context) => { @@ -218,3 +247,79 @@ package Runtime id "package:runtime" revision "package:runtime@1" { } /requires external interface Named \(interface:named@1\)/, ); }); + +test("resource-owned fragment imports compile through the locked package closure", async (context) => { + const directory = await mkdtemp(path.join(os.tmpdir(), "quixos-fragment-assembly-")); + context.after(() => rm(directory, { recursive: true, force: true })); + const dirs = Object.fromEntries(["item", "rows", "screen"].map((name) => [name, path.join(directory, name)])); + await Promise.all(Object.values(dirs).map((dir) => mkdir(dir))); + const dependency = (kind: string, name: string) => + `${kind} ${name} source {repository "https://example.test/${name.toLowerCase()}.git"; commit "${namedCommit}";}`; + await writeFile(path.join(dirs.item!, "quixos.lock"), lock()); + await writeFile( + path.join(dirs.item!, "interface.qx"), + `interface Item id "item" revision "item@1" {queryable value title id "title" : string {get id "title:get" execution snapshot;}}`, + ); + await writeFile(path.join(dirs.rows!, "quixos.lock"), lock(dependency("interface", "Item"))); + await writeFile( + path.join(dirs.rows!, "package.qx"), + `import interface Item; package Rows id "rows" revision "rows@1" {query fragment Row id "row" root Item document "row.graphql" fragment "Row";}`, + ); + await writeFile(path.join(dirs.rows!, "row.graphql"), "fragment Row on Item {_camino {ref} title}"); + await writeFile( + path.join(dirs.screen!, "quixos.lock"), + lock(dependency("interface", "Item") + dependency("package", "Rows")), + ); + await writeFile( + path.join(dirs.screen!, "package.qx"), + `import interface Item; import package Rows; package Screen id "screen" revision "screen@1" { + query Inputs id "inputs" root Item document "screen.graphql" operation "Screen" {replica; fragments Rows.Row;} + query View id "view" root Item document "screen.graphql" operation "Screen" {local from Inputs; fragments Rows.Row;} + }`, + ); + await writeFile(path.join(dirs.screen!, "screen.graphql"), "query Screen {root {...Row}}"); + const options = { + rootDirectory: dirs.screen!, + kind: "package" as const, + source: { resolver: "git" as const, repository: "https://example.test/screen.git", commit: packageCommit }, + resolveResource: async (source: { repository: string }) => { + const name = new URL(source.repository).pathname.slice(1, -4); + assert.ok(dirs[name], name); + return { directory: dirs[name]! }; + }, + }; + const result = await compileCapabilityResourceRepository(options); + assert.equal(result.resource.kind, "package"); + if (result.resource.kind !== "package") throw Error("expected package"); + const view = result.resource.revision.checkedQueries!.find((q) => q.declaration.id === "view")!; + assert.equal(view.selection[0]!.selection[0]!.fragmentSpread?.package, "rows@1"); + assert.equal(result.resources.length, 3); + const checked = new Map(result.resources.map((node) => [node.key, node.resource])); + let generated = 0; + const reused = await compileCapabilityResourceRepository({ + ...options, + checkedResource: async (kind, source) => { + if (source.repository === options.source.repository) return undefined; + const resource = checked.get(`${kind}\0${source.repository}\0${source.commit}`); + return resource?.kind === "build" ? undefined : resource; + }, + generateQueries: async () => { + generated++; + return { queries: [], documents: new Map() }; + }, + }); + assert.deepEqual(reused, result, "cached imported fragments and full checking produce identical contracts"); + assert.equal(generated, 1, "only the uncached package checks its queries"); + await writeFile(path.join(dirs.screen!, "screen.graphql"), "query Screen {root {unknownField}}"); + await assert.rejects( + compileCapabilityResourceRepository({ + ...options, + source: { ...options.source, commit: "5".repeat(40) }, + checkedResource: async (kind, source) => { + const resource = checked.get(`${kind}\0${source.repository}\0${source.commit}`); + return resource?.kind === "build" ? undefined : resource; + }, + }), + /unknownField/, + ); +}); diff --git a/test/capability-language.test.ts b/test/capability-language.test.ts index 826cad3..593097b 100644 --- a/test/capability-language.test.ts +++ b/test/capability-language.test.ts @@ -148,10 +148,10 @@ test("interfaces can declare ordinary call operations", () => { test("state defaults accept recursive JSON values", () => { const source = capabilityFixtureSource.replace( - /policy optimistic-register default "Untitled project";/, - ` policy optimistic-register default "Untitled project"; + /policy register default "Untitled project";/, + ` policy register default "Untitled project"; shared state ProjectMetadata id "slot:project:metadata" on Project : message "example.Metadata" - policy optimistic-register default {"labels":["compiler","runtime"],"score":1.5,"enabled":true,"extra":null};`, + policy register default {"labels":["compiler","runtime"],"score":1.5,"enabled":true,"extra":null};`, ); const result = compileCapabilityFixture({ workspace: source }); assert.equal(result.ok, true); diff --git a/test/client-contracts.test.ts b/test/client-contracts.test.ts new file mode 100644 index 0000000..26394ac --- /dev/null +++ b/test/client-contracts.test.ts @@ -0,0 +1,211 @@ +import assert from "node:assert/strict"; +import { test } from "node:test"; +import { compileCapabilityResourceSource, compileCapabilitySource } from "../src/capability-language/index.js"; +import { + compileExecutionPlan, + generateInterfaceClientBindings, + publicOperationContract, + linkInterfaceOperation, + publicQueryContract, + linkPackageQuery, +} from "../src/execution-world/index.js"; +import { compileQuery } from "../src/query/compile.js"; +import { parseControllerCatalog, validateControllers } from "../src/capability-model/controllers.js"; +const source = { repository: "https://example.test/contracts.git", commit: "a".repeat(40) }; +const parsed = compileCapabilityResourceSource( + `interface Data id "data" revision "data@1" { + queryable portable value label id "label" : string { get id "label:get" execution snapshot; } + operation publish id "publish" : string -> unit { call id "publish:call"; } +}`, + { source }, +); +assert.ok(parsed.ok && parsed.resource.kind === "interface", JSON.stringify(parsed.diagnostics)); +const iface = parsed.resource.revision; +const environment = { interfaces: new Map([["Data", iface]]), interfaceClosure: [iface] }; +test("native fields certify one storage slot; only browser writes get live descriptors", () => { + const parsed = compileCapabilityResourceSource( + `interface Fields id "fields" revision "fields@1" { + queryable field position id "position" : string { + get id "position:get" execution snapshot; + set id "position:set" execution transaction; + } + field remote id "remote" : string { + get id "remote:get"; + set id "remote:set"; + } + value arbitrary id "arbitrary" : string { + get id "arbitrary:get" execution snapshot; + set id "arbitrary:set" execution transaction; + } + value text id "text" : string { + get id "text:get"; + edit id "text:edit" execution transaction; + } + }`, + { source }, + ); + assert.ok(parsed.ok && parsed.resource.kind === "interface", JSON.stringify(parsed.diagnostics)); + const revision = parsed.resource.revision; + const generated = generateInterfaceClientBindings({ Fields: revision }); + assert.match(generated, /defineFieldOperation d.message.includes("same native state slot")), + JSON.stringify(bad.diagnostics), + ); + const forwarded = workspace + .replace("bind position.get to state First.read", "bind position.get to interface Fields.arbitrary.get") + .replace("bind position.set to state First.write", "bind position.set to interface Fields.arbitrary.set"); + const goodForward = compileCapabilitySource(forwarded, "workspace.qx", env); + assert.ok(goodForward.ok, JSON.stringify(goodForward.diagnostics)); + const pkg = compileCapabilityResourceSource( + `symbol JavaScript id "target:org.quixos.execution.javascript@1"; +package Setter id "setter" revision "setter@1" { + operation replaceValue id "write" : string -> unit mode call execution transaction targets [JavaScript,server] receiver any; + }`, + { source }, + ); + assert.ok(pkg.ok && pkg.resource.kind === "package", JSON.stringify(pkg.diagnostics)); + const badForward = compileCapabilitySource( + forwarded + .replace("import interface Fields;", "import interface Fields; import package Setter;") + .replace("bind arbitrary.set to state Second.write", "bind arbitrary.set to package Setter.replaceValue"), + "workspace.qx", + { ...env, packages: new Map([["Setter", pkg.resource.revision]]) }, + ); + assert.equal(badForward.ok, false); + assert.ok( + badForward.diagnostics.some((d) => d.message.includes("same native state slot")), + JSON.stringify(badForward.diagnostics), + ); +}); +const compile = async () => { + const pkg = compileCapabilityResourceSource( + `import interface Data; package Views id "views" revision "views@1" { + query Labels id "labels" root workspace Data document "labels.graphql" operation "Labels" { replica; } + operation publish id "export:publish" : string -> unit mode call receiver any; + }`, + { source, environment }, + ); + assert.ok(pkg.ok && pkg.resource.kind === "package", JSON.stringify(pkg.diagnostics)); + const revision = pkg.resource.revision; + revision.checkedQueries = [ + await compileQuery( + revision.queries![0]!, + [iface], + async () => "query Labels { root { objects(first: 10) { entries { node { label } } } } }", + ), + ]; + const result = compileCapabilitySource( + `workspace Test id "test" revision "test@1" commit "${source.commit}" { + import interface Data; import package Views; + atom One id "one"; atom Two id "two"; + ${["One", "Two"] + .map( + (atom) => `conform ${atom} as Data id "${atom}:data" { + private state ${atom}Label id "${atom}:label" on ${atom} : string policy register default ""; + bind label.get to state ${atom}Label.read; + bind publish.call to package Views.publish; + }`, + ) + .join("\n")} + }`, + "workspace.qx", + { ...environment, packages: new Map([["Views", revision]]) }, + ); + assert.ok(result.ok, JSON.stringify(result.diagnostics)); + return compileExecutionPlan(result.workspace); +}; + +test("one imported operation contract links to two atom implementations without containing either", async () => { + const plan = await compile(); + const contract = publicOperationContract(iface, iface.members[0]!.operations[0]!); + assert.equal(linkInterfaceOperation(plan, "one", contract).atomId, "one"); + assert.equal(linkInterfaceOperation(plan, "two", contract).atomId, "two"); + const generated = generateInterfaceClientBindings({ Data: iface }); + assert.ok(generated.includes("defineInterfaceOperation linkInterfaceOperation(plan, "absent", contract), /UNINSTALLED_OPERATION/); + for (const change of [{ scope: "class" }, { input: { kind: "string" } }, { execution: { kind: "remote" } }]) + assert.throws( + () => linkInterfaceOperation(plan, "one", { ...contract, ...change } as typeof contract), + /CLIENT_CONTRACT_MISMATCH/, + ); + assert.throws(() => linkInterfaceOperation({ ...plan, digest: "bad" }, "one", contract), /INVALID_PLAN/); +}); + +test("package query handles bind the checked definition, placement and codecs", async () => { + const plan = await compile(); + const contract = publicQueryContract(plan.definition.workspace.packageImports[0]!, "labels"); + assert.equal(linkPackageQuery(plan, contract).query.checked.declaration.id, "labels"); + for (const change of [{ placement: "local" }, { definitionDigest: "bad" }, { output: null }]) + assert.throws(() => linkPackageQuery(plan, { ...contract, ...change } as typeof contract), /CLIENT_QUERY_MISMATCH/); +}); + +test("controller templates check object capability effects and exact input mapping", () => { + const catalog = { + schemaVersion: 1, + controllers: [ + { id: "publish-label", subject: "data@1", snapshot: "label:get", remote: "publish:call", input: "value" }, + ], + }; + assert.equal(parseControllerCatalog(JSON.stringify(catalog), [iface]).length, 1); + for (const change of [ + { input: "unit" }, + { input: "callback" }, + { subject: "missing" }, + { snapshot: "publish:call" }, + { remote: "label:get" }, + { url: "https://example.test" }, + ]) { + const invalid = { ...catalog, controllers: [{ ...catalog.controllers[0], ...change }] }; + assert.throws(() => parseControllerCatalog(JSON.stringify(invalid), [iface]), /Controller|controller/); + } + assert.throws( + () => + parseControllerCatalog( + JSON.stringify({ ...catalog, controllers: [...catalog.controllers, ...catalog.controllers] }), + [iface], + ), + /unique/, + ); + const classIface = structuredClone(iface); + classIface.members[1]!.operations[0]!.scope = "class"; + assert.ok( + validateControllers(parseControllerCatalog(JSON.stringify(catalog), [iface]), [classIface], "controllers").length, + ); +}); diff --git a/test/collections-fixture.ts b/test/collections-fixture.ts new file mode 100644 index 0000000..921acda --- /dev/null +++ b/test/collections-fixture.ts @@ -0,0 +1,23 @@ +import assert from "node:assert/strict"; +import { readFile } from "node:fs/promises"; +import { compileCapabilityResourceSource, compileCapabilitySource } from "../src/capability-language/parser.js"; +import { compileExecutionPlan } from "../src/execution-world/plan.js"; +import type { InterfaceRevision } from "../src/capability-model/types.js"; +export async function collectionsPlan() { + const read = (name: string) => readFile(`test/fixtures/collections/${name}.qx`, "utf8"); + const source = { repository: "https://example.test/collections.git", commit: "a".repeat(40) }; + const interfaces = new Map(); + for (const name of ["NodeData"]) { + const result = compileCapabilityResourceSource(await read(name), { source, environment: { interfaces } }); + assert.ok(result.ok && result.resource.kind === "interface", JSON.stringify(result.diagnostics)); + interfaces.set(name, result.resource.revision); + } + const pkg = compileCapabilityResourceSource(await read("CollectionRuntime"), { source, environment: { interfaces } }); + assert.ok(pkg.ok && pkg.resource.kind === "package", JSON.stringify(pkg.diagnostics)); + const result = compileCapabilitySource(await read("workspace"), "workspace.qx", { + interfaces, + packages: new Map([["CollectionRuntime", pkg.resource.revision]]), + }); + assert.ok(result.ok, JSON.stringify(result.diagnostics)); + return compileExecutionPlan(result.workspace); +} diff --git a/test/component-nix.test.ts b/test/component-nix.test.ts new file mode 100644 index 0000000..6582e57 --- /dev/null +++ b/test/component-nix.test.ts @@ -0,0 +1,27 @@ +import assert from "node:assert/strict"; +import test from "node:test"; +import { componentEntries } from "../src/capability-language/component-nix.js"; +test("component edit surface parses literal Nix without evaluating arbitrary expressions", async () => { + assert.deepEqual( + { + ...(await componentEntries( + `{\n # retained author comment\n "component:card" = { export = "Card"; entry = "src/card.tsx"; };\n}\n`, + )), + }, + { "component:card": { entry: "src/card.tsx", export: "Card" } }, + ); + await assert.rejects( + componentEntries('let x = builtins.abort "must not evaluate"; in x'), + /supported literal-entry shape|literal strings|supported scaffold shape/, + ); + await assert.rejects( + componentEntries( + '{\n "component:card" = { entry = "${builtins.abort "must not evaluate"}"; export = "Card"; };\n}\n', + ), + /literal strings|supported literal-entry shape/, + ); + await assert.rejects( + componentEntries('{\n "component:card" = { entry = "src/card.tsx"; export = "Card"; extra = true; };\n}\n'), + /Unsupported component entry property/, + ); +}); diff --git a/test/component-scaffold.test.ts b/test/component-scaffold.test.ts new file mode 100644 index 0000000..9cf9615 --- /dev/null +++ b/test/component-scaffold.test.ts @@ -0,0 +1,124 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs/promises"; +import os from "node:os"; +import path from "node:path"; +import { execFile as callback } from "node:child_process"; +import { promisify } from "node:util"; +import { componentEntries } from "../src/capability-language/component-nix.js"; +import { scaffoldRecipe } from "../src/capability-language/scaffold-recipes.js"; +import { planStructure, applyStructure } from "../src/capability-language/structural-plan.js"; +const execFile = promisify(callback); +test("checked component scaffolding composes with execution handlers without replacing authored files", async (context) => { + const root = await fs.mkdtemp(path.join(os.tmpdir(), "qx-component-scaffold-")); + context.after(() => fs.rm(root, { recursive: true, force: true })); + await execFile("git", ["-C", root, "init"]); + const source = { repository: "https://example.test/component.git", commit: "a".repeat(40) }; + const apply = async ( + command: Parameters[1], + spec: Omit[2], "source">, + ) => applyStructure(await planStructure(root, await scaffoldRecipe(root, command, { ...spec, source }))); + await apply("package", { + name: "Widgets", + id: "package:widgets", + revision: "package:widgets@1", + template: "typescript-component", + tools: { quixos: source, protocol: source, helpers: source, sdk: source }, + }); + const packageFile = path.join(root, "package.qx"); + await fs.writeFile(packageFile, "import interface View;\n" + (await fs.readFile(packageFile, "utf8"))); + await apply("component", { + name: "Card", + id: "component:card", + declaration: 'module Card id "component:card" implements View.render;', + }); + assert.deepEqual( + { ...(await componentEntries(await fs.readFile(path.join(root, "components.nix"), "utf8"))) }, + { + "component:card": { entry: "src/components/Card.tsx", export: "Card" }, + }, + ); + const card = path.join(root, "src/components/Card.tsx"); + await fs.appendFile(card, "\n// authored extension\n"); + const retained = await fs.readFile(card, "utf8"); + const flake = path.join(root, "flake.nix"); + await fs.appendFile(flake, "\n# retained authored toolchain configuration\n"); + const retainedFlake = await fs.readFile(flake, "utf8"); + const entriesFile = path.join(root, "components.nix"); + await fs.writeFile( + entriesFile, + (await fs.readFile(entriesFile, "utf8")).replace("{\n", "{\n # retained entry comment\n"), + ); + await apply("function", { + name: "echo", + id: "echo", + declaration: 'function echo id "echo" : string -> string execution snapshot targets [JavaScript,server];', + }); + await apply("component", { + name: "Details", + id: "component:details", + declaration: 'module Details id "component:details" implements View.render;', + }); + assert.equal(await fs.readFile(card, "utf8"), retained); + assert.equal(await fs.readFile(flake, "utf8"), retainedFlake); + assert.match(await fs.readFile(entriesFile, "utf8"), /retained entry comment/); + assert.match(await fs.readFile(path.join(root, "src/server.ts"), "utf8"), /echo/); + assert.match(await fs.readFile(path.join(root, "flake.nix"), "utf8"), /mkCaminoReactPackage/); + await assert.rejects( + apply("component", { + name: "Card", + id: "component:other", + declaration: 'module Card id "component:other" implements View.render;', + }), + /unique/, + ); + await assert.rejects( + apply("component", { + name: "Injected", + id: "component:inject", + declaration: + 'module Injected id "component:inject" implements View.render; function sneak id "sneak" : unit -> unit;', + }), + /exactly one/, + ); + const beforeUnsupported = await fs.readFile(packageFile, "utf8"); + await fs.writeFile(entriesFile, '{\n custom = builtins.abort "must not evaluate";\n}\n'); + await assert.rejects( + apply("component", { + name: "Unsupported", + id: "component:unsupported", + declaration: 'module Unsupported id "component:unsupported" implements View.render;', + }), + /literal-entry shape/, + ); + assert.equal(await fs.readFile(packageFile, "utf8"), beforeUnsupported); +}); + +test("controller scaffolding preserves existing registrations and rejects duplicate identities", async (context) => { + const root = await fs.mkdtemp(path.join(os.tmpdir(), "qx-controller-scaffold-")); + context.after(() => fs.rm(root, { recursive: true, force: true })); + await execFile("git", ["-C", root, "init"]); + const source = { repository: "https://example.test/adapter.git", commit: "a".repeat(40) }; + const apply = async ( + command: Parameters[1], + spec: Omit[2], "source">, + ) => applyStructure(await planStructure(root, await scaffoldRecipe(root, command, { ...spec, source }))); + await apply("package", { + name: "Adapter", + id: "adapter", + revision: "adapter@1", + tools: { quixos: source, protocol: source, helpers: source }, + }); + const controller = { + subject: "display@1", + snapshot: "display:requested", + remote: "display:apply", + input: "value" as const, + }; + await apply("controller", { id: "display:controller", controller }); + await apply("controller", { id: "display:monitor", controller: { ...controller, snapshot: "display:status" } }); + const entries = JSON.parse(await fs.readFile(path.join(root, "quixos.controllers.json"), "utf8")).controllers; + assert.equal(entries.length, 2); + assert.deepEqual(entries[0], { id: "display:controller", ...controller }); + await assert.rejects(apply("controller", { id: "display:controller", controller }), /duplicate/); +}); diff --git a/test/composite-fixture.ts b/test/composite-fixture.ts new file mode 100644 index 0000000..f60412c --- /dev/null +++ b/test/composite-fixture.ts @@ -0,0 +1,58 @@ +import assert from "node:assert/strict"; +import { readFile } from "node:fs/promises"; +import { resolve } from "node:path"; +import { + compileCapabilityResourceSource, + compileCapabilitySource, + formatQx, +} from "../src/capability-language/index.js"; +import type { InterfaceRevision, PackageRevision } from "../src/capability-model/types.js"; +import { compileQuery } from "../src/query/compile.js"; +import { readReaderFixture } from "./reader-fixture.js"; +const directory = resolve("test/fixtures/local-first-composite"); +export async function loadCompositeWorkspace() { + const changed = new Set(["DocumentFactory", "DocumentMetadata", "DocumentLabel", "CommentFactory"]); + const read = (name: string) => readFile(resolve(directory, `${name}.qx`), "utf8"); + const source = { repository: "https://example.test/composite.git", commit: "b".repeat(40) }; + const interfaces = new Map(); + for (const name of [ + "DocumentData", + "HighlightData", + "CommentData", + "DocumentActions", + "DocumentFactory", + "DocumentMetadata", + "DocumentLabel", + "CommentFactory", + ]) { + const result = compileCapabilityResourceSource( + formatQx(await (changed.has(name) ? read(name) : readReaderFixture(`${name}.qx`))), + { source }, + ); + assert.ok(result.ok && result.resource.kind === "interface", JSON.stringify(result.diagnostics)); + interfaces.set(name, result.resource.revision); + } + const environment = { interfaces, interfaceClosure: [...interfaces.values()] }, + packages = new Map(); + for (const name of ["ReaderRuntime", "CommentRuntime"]) { + const selected = + name === "CommentRuntime" ? new Map([["CommentData", interfaces.get("CommentData")!]]) : interfaces; + const result = compileCapabilityResourceSource(formatQx(await read(name)), { + source, + environment: { interfaces: selected, interfaceClosure: [...selected.values()] }, + }); + assert.ok(result.ok && result.resource.kind === "package", JSON.stringify(result.diagnostics)); + const revision = result.resource.revision; + revision.checkedQueries = await Promise.all( + (revision.queries ?? []).map((query) => compileQuery(query, [...interfaces.values()], readReaderFixture)), + ); + packages.set(name, revision); + } + const result = compileCapabilitySource(formatQx(await read("workspace")), "workspace.qx", { + ...environment, + packages, + packageClosure: [...packages.values()], + }); + assert.ok(result.ok, JSON.stringify(result.diagnostics)); + return result.workspace; +} diff --git a/test/compound-crdt-fixture.ts b/test/compound-crdt-fixture.ts new file mode 100644 index 0000000..2f22a29 --- /dev/null +++ b/test/compound-crdt-fixture.ts @@ -0,0 +1,23 @@ +import assert from "node:assert/strict"; +import { readFile } from "node:fs/promises"; +import { compileCapabilityResourceSource, compileCapabilitySource } from "../src/capability-language/parser.js"; +import { compileExecutionPlan } from "../src/execution-world/plan.js"; +import type { InterfaceRevision } from "../src/capability-model/types.js"; +export async function compoundCrdtPlan() { + const read = (name: string) => readFile(`test/fixtures/compound-crdt/${name}.qx`, "utf8"); + const source = { repository: "https://example.test/compound.git", commit: "a".repeat(40) }; + const interfaces = new Map(); + for (const name of ["NoteData"]) { + const result = compileCapabilityResourceSource(await read(name), { source, environment: { interfaces } }); + assert.ok(result.ok && result.resource.kind === "interface", JSON.stringify(result.diagnostics)); + interfaces.set(name, result.resource.revision); + } + const pkg = compileCapabilityResourceSource(await read("EditRuntime"), { source, environment: { interfaces } }); + assert.ok(pkg.ok && pkg.resource.kind === "package", JSON.stringify(pkg.diagnostics)); + const result = compileCapabilitySource(await read("workspace"), "workspace.qx", { + interfaces, + packages: new Map([["EditRuntime", pkg.resource.revision]]), + }); + assert.ok(result.ok, JSON.stringify(result.diagnostics)); + return compileExecutionPlan(result.workspace); +} diff --git a/test/compound-crdt.test.ts b/test/compound-crdt.test.ts new file mode 100644 index 0000000..d6bf3f4 --- /dev/null +++ b/test/compound-crdt.test.ts @@ -0,0 +1,73 @@ +import assert from "node:assert/strict"; +import { readFile } from "node:fs/promises"; +import { test } from "node:test"; +import { compoundCrdtPlan } from "./compound-crdt-fixture.js"; +import { compileCapabilityResourceSource, compileCapabilitySource } from "../src/capability-language/parser.js"; +import { compileKernelProgram } from "../src/execution-world/index.js"; +import { generatePortableTypeScriptBindings } from "../src/bindings/portable.js"; +const source = { repository: "https://example.test/change.git", commit: "a".repeat(40) }; +test("compound captured changes survive planning, transport lowering and typed edit ports", async () => { + const plan = await compoundCrdtPlan(); + const kernel = compileKernelProgram(plan) as { + operations: { + operation: string; + inputChanges?: (string | null)[][]; + input: { fields: { change: { kind: string } } }; + }[]; + }; + const operation = kernel.operations.find((op) => op.operation === "compound:update:call")!; + assert.equal(operation.input.fields.change.kind, "record"); + const paths = kernel.operations as unknown as { operation: string; inputChanges?: (string | null)[][] }[]; + assert.deepEqual(paths.find((op) => op.operation === "compound:update:call")!.inputChanges, [["change"]]); + assert.deepEqual(paths.find((op) => op.operation === "compound:batch:call")!.inputChanges, [ + ["edits", null, "change"], + ]); + assert.deepEqual(paths.find((op) => op.operation === "compound:content:edit")!.inputChanges, [[]]); + const code = generatePortableTypeScriptBindings(plan.definition.bindingSchema, "compound:runtime@1", { + target: "target:org.quixos.execution.javascript@1", + }); + assert.ok(code.includes("PreparedChange<")); + assert.ok(code.includes('"edit"')); + assert.ok(!code.includes('"compound:runtime:check"')); +}); +test("prepared changes require one storable materialized type", () => { + for (const shape of ["change", "change", "change>", "change>"]) { + const result = compileCapabilityResourceSource( + `symbol JavaScript id "target:org.quixos.execution.javascript@1"; +external atom N id "n"; package P id "p" revision "p@1" { + function f id "f" : ${shape} -> unit execution transaction targets [JavaScript, server]; + }`, + { source }, + ); + assert.equal(result.ok, false, shape); + } +}); +test("captured changes cannot be persisted in a register or CRDT", async () => { + const iface = compileCapabilityResourceSource(await readFile("test/fixtures/compound-crdt/NoteData.qx", "utf8"), { + source, + }); + assert.ok(iface.ok && iface.resource.kind === "interface", JSON.stringify(iface.diagnostics)); + const pkg = compileCapabilityResourceSource(await readFile("test/fixtures/compound-crdt/EditRuntime.qx", "utf8"), { + source, + }); + assert.ok(pkg.ok && pkg.resource.kind === "package", JSON.stringify(pkg.diagnostics)); + const workspace = await readFile("test/fixtures/compound-crdt/workspace.qx", "utf8"); + for (const policy of ["register", "crdt(change)"]) { + const result = compileCapabilitySource( + workspace.replace( + "private state Allowed", + `private state Captured id "captured" on Note : record { captured: change; } policy ${policy};\n private state Allowed`, + ), + "workspace.qx", + { + interfaces: new Map([["NoteData", iface.resource.revision]]), + packages: new Map([["EditRuntime", pkg.resource.revision]]), + }, + ); + assert.equal(result.ok, false); + assert.ok( + result.diagnostics.some((d) => d.code === "invalid-attachment"), + JSON.stringify(result.diagnostics), + ); + } +}); diff --git a/test/conformance-rules.test.ts b/test/conformance-rules.test.ts new file mode 100644 index 0000000..46a6d9d --- /dev/null +++ b/test/conformance-rules.test.ts @@ -0,0 +1,354 @@ +import assert from "node:assert/strict"; +import test from "node:test"; +import { + compileCapabilityResourceSource, + compileCapabilitySource, + type CapabilityImportEnvironment, +} from "../src/capability-language/parser.js"; +import type { InterfaceRevision, PackageRevision } from "../src/capability-model/types.js"; + +function fixture(declarations: string[]) { + const interfaces = new Map(); + const packages = new Map(); + const environment: CapabilityImportEnvironment = { interfaces, packages }; + for (const sourceText of declarations) { + const result = compileCapabilityResourceSource(sourceText, { + source: { repository: "https://example.test/rules.git", commit: "a".repeat(40) }, + environment, + }); + assert.ok(result.ok, JSON.stringify(result.diagnostics)); + if (result.resource.kind === "interface") + interfaces.set(result.resource.revision.displayName, result.resource.revision); + else packages.set(result.resource.revision.displayName, result.resource.revision); + } + return (body: string) => + compileCapabilitySource( + `workspace W id "w" revision "w@1" commit "${"a".repeat(40)}" { + ${[...interfaces.keys()].map((name) => `import interface ${name};`).join("\n")} + ${[...packages.keys()].map((name) => `import package ${name};`).join("\n")} + ${body} + }`, + "rules.qx", + environment, + ); +} +const ok = (result: ReturnType>) => { + assert.ok(result.ok, JSON.stringify(result.diagnostics)); + return result; +}; +const nameResources = [ + 'interface Person id "person" revision "person@1" {value name id "p:name" : string {get id "p:get";set id "p:set";}}', + 'interface HasName id "name" revision "name@1" {value name id "n:name" : string {get id "n:get";set id "n:set";}}', +]; +const person = (name: string) => `atom ${name} id "${name}"; conform ${name} as Person id "${name}:person" { + private state Name${name} id "${name}:name" on ${name} : string policy register default "hello"; + bind name.get to state Name${name}.read; bind name.set to state Name${name}.write; +}`; +const namedRule = + 'conform Person as HasName id "person-name" {bind name.get to interface Person.name.get;bind name.set to interface Person.name.set;}'; + +test("interface rules are order independent and preserve each original storage owner", () => { + const compile = fixture(nameResources); + const first = ok(compile(namedRule + person("Living") + person("Avatar"))); + const second = ok(compile(person("Avatar") + person("Living") + namedRule)); + const generated = (result: typeof first) => + result.workspace.conformances + .filter((c) => c.ruleOrigin) + .map((c) => [c.atomId, c.id]) + .sort(); + assert.deepEqual(generated(first), generated(second)); + for (const atom of ["Living", "Avatar"]) { + const c = first.workspace.conformances.find((c) => c.atomId === atom && c.interfaceRevisionId === "name@1")!; + assert(c); + assert.equal(c.privateAttachments.length, 0); + assert( + c.operationBindings.every((b) => b.binding.kind === "interface" && b.binding.interfaceRevisionId === "person@1"), + ); + } +}); + +test("rule conflicts identify competing implementations", () => { + const result = fixture(nameResources)( + person("Living") + namedRule + namedRule.replace('id "person-name"', 'id "other-rule"'), + ); + assert.equal(result.ok, false); + assert.match(JSON.stringify(result.diagnostics), /duplicate-conformance/); +}); + +test("rules validate unused bodies and ambiguous names", () => { + const compile = fixture(nameResources); + for (const [body, code] of [ + ["conform Person as HasName { }", "missing-rule-id"], + ['conform Person as HasName id "r" {bind name.get to interface Person.missing.get;}', "unknown-operation"], + ['conform Person as HasName id "r" { initialize { content to state Missing; }; }', "invalid-initialization"], + ['conform Person as HasName id "r" { bind name to state Missing; }', "invalid-state-field-binding"], + [ + 'conform Person as HasName id "r" {private state Bad id "bad" on Self : Missing policy register;}', + "unknown-type", + ], + ['atom Person id "p";' + namedRule, "ambiguous-conformance-source"], + [namedRule + namedRule, "duplicate-rule-id"], + ]) { + const result = compile(body); + assert.equal(result.ok, false, body); + assert.match(JSON.stringify(result.diagnostics), new RegExp(code)); + } +}); + +test("rule storage is scoped per application, including repeated specializations on one atom", async () => { + const compile = fixture([ + 'interface Source id "source" revision "source@1" {}', + 'interface Stored id "stored" revision "stored@1" {value content id "value" : optional {get id "get";set id "set";}}', + ]); + const rule = `conform Source as Stored id "store-rule" { + private state Value id "stored:value" on Self : optional policy register default null; + bind content to state Value; + }`; + const atoms = `atom A id "a"; atom B id "b"; + conform A as Source id "a-string" {} + conform A as Source id "a-number" {} + conform B as Source id "b-string" {}`; + const first = ok(compile(rule + atoms)).workspace; + const second = ok(compile(atoms + rule)).workspace; + const storage = (workspace: typeof first) => + workspace.conformances + .filter((c) => c.ruleOrigin) + .map((c) => [c.id, c.privateAttachments, c.operationBindings]) + .sort((a, b) => String(a[0]).localeCompare(String(b[0]))); + assert.deepEqual(storage(first), storage(second)); + const owners = first.conformances.filter((c) => c.ruleOrigin); + assert.equal(owners.length, 3); + assert.equal(new Set(owners.map((c) => c.privateAttachments[0]!.id)).size, 3); + for (const owner of owners) { + const state = owner.privateAttachments[0]!; + assert.equal(state.kind, "state"); + if (state.kind !== "state") throw Error("state expected"); + assert.equal(state.attachedTo, owner.atomId); + assert(owner.operationBindings.every((b) => b.binding.kind === "state" && b.binding.slotId === state.id)); + } + const { storageContracts, storageChangeRequiresMigration } = await import("../src/capability-model/evolution.js"); + const before = storageContracts(first); + const changed = ok( + compile( + atoms.replace('conform A as Source id "a-number" {}', "") + + rule.replace("default null", 'default "new default"'), + ), + ).workspace; + // Use the string specialization for default evolution; all nominal identities + // remain stable, and changing a default must not rewrite existing state. + const oldSlot = before.find((s) => s.id === owners.find((c) => c.atomId === "b")!.privateAttachments[0]!.id)!; + const newSlot = storageContracts(changed).find((s) => s.id === oldSlot.id)!; + assert.equal(storageChangeRequiresMigration(oldSlot, newSlot, new Set(["a", "b"])), false); + assert.equal(storageChangeRequiresMigration(oldSlot, undefined, new Set(["a", "b"])), true); +}); + +test("rule attachments shadow outside names without leaking into another owner", () => { + const rule = `conform Person as HasName id "r" { + private state NameLiving id "rule:name" on Self : string policy register default "rule"; + bind name to state NameLiving; + }`; + const workspace = ok(fixture(nameResources)(rule + person("Living"))).workspace; + const original = workspace.conformances.find((c) => c.interfaceRevisionId === "person@1")!; + const derived = workspace.conformances.find((c) => c.interfaceRevisionId === "name@1")!; + assert.notEqual(original.privateAttachments[0]!.id, derived.privateAttachments[0]!.id); + assert( + derived.operationBindings.every( + (b) => b.binding.kind === "state" && b.binding.slotId === derived.privateAttachments[0]!.id, + ), + ); + assert( + original.operationBindings.every( + (b) => b.binding.kind === "state" && b.binding.slotId === original.privateAttachments[0]!.id, + ), + ); +}); + +test("rule private edges substitute Self and captured endpoints with stable projection IDs", () => { + const compile = fixture([ + 'interface Linkable id "linkable" revision "linkable@1" {}', + 'interface Links id "links" revision "links@1" {relation items id "items" : many object T {resolve id "resolve";}}', + ]); + const source = `atom A id "a"; atom B id "b"; atom Target id "target"; + conform A as Linkable id "a-link" {} conform B as Linkable id "b-link" {} + conform Linkable as Links id "links" { + private edge Items id "items" { + object Self projection items id "forward" many; + object T projection owners id "inverse" many; + } + bind items.resolve to edge Items.items.resolve; + }`; + const workspace = ok(compile(source)).workspace; + const edges = workspace.conformances.filter((c) => c.ruleOrigin).map((c) => c.privateAttachments[0]!); + assert.equal(edges.length, 2); + assert.notEqual(edges[0]!.id, edges[1]!.id); + const projections = edges.flatMap((edge) => { + assert.equal(edge.kind, "edge"); + if (edge.kind !== "edge") throw Error("edge expected"); + assert.deepEqual(edge.endpoints[1].constraint, { kind: "atom", atomId: "target" }); + return edge.endpoints.map((e) => e.projectionId); + }); + assert.equal(new Set(projections).size, 4); +}); + +test("rule creation ports close captured atom parameters and retain initialization checking", () => { + const compile = fixture([ + 'interface Factory id "factory" revision "factory@1" {operation create id "create" : unit -> ref {call id "call" execution transaction;}}', + 'interface Creates id "creates" revision "creates@1" {}', + 'symbol JavaScript id "target:org.quixos.execution.javascript@1";\npackage Runtime id "runtime" revision "runtime@1" {operation create id "create" : unit -> ref mode call execution transaction targets [JavaScript,server] receiver any requires {creation result id "result" : T input unit;};}', + ]); + const rule = `conform Creates as Factory id "factory-rule" { + bind create.call to package Runtime.create with {result to creation T with {};}; + }`; + const result = ok( + compile(`atom Subject id "subject"; atom Product id "product"; + conform Subject as Creates id "creates-product" {} ${rule}`), + ); + const owner = result.workspace.conformances.find((c) => c.ruleOrigin)!; + const binding = owner.operationBindings[0]!.binding; + assert.equal(binding.kind, "package"); + if (binding.kind !== "package") throw Error("package expected"); + assert.deepEqual(binding.dependencies[0]!.binding, { kind: "creation", atomId: "product", owners: [] }); + const invalid = compile(`atom Subject id "subject"; + conform Subject as Creates> id "creates-interface" {} ${rule}`); + assert.equal(invalid.ok, false); + assert.match(JSON.stringify(invalid.diagnostics), /concrete atom/); +}); + +const genericResources = [ + 'interface Placeable id "placeable" revision "placeable@1" {}', + 'interface Eligible id "eligible" revision "eligible@1" {}', + 'interface Factory id "factory" revision "factory@1" {}', + 'import interface Placeable; interface CanvasFactory id "canvas-factory" revision "canvas-factory@1" {}', +]; +const factoryRule = + 'conform Factory as CanvasFactory id "factory-canvas" {}'; +test("generic bounds filter factories and can be supplied by later rules", () => { + const result = ok( + fixture(genericResources)(` + ${factoryRule} + atom Owner id "owner"; atom Product id "product"; atom Other id "other"; + conform Owner as Factory id "unit-factory" {} + conform Owner as Factory id "record-factory" {} + conform Owner as Factory id "ineligible" {} + conform Product as Eligible id "eligible-product" {} + conform Eligible as Placeable id "eligible-placeable" {} + `), + ); + const generated = result.workspace.conformances.filter((c) => c.ruleOrigin?.ruleId === "factory-canvas"); + assert.equal(generated.length, 2); + assert(generated.every((c) => c.atomId === "owner")); + for (const c of generated) { + const iface = result.workspace.interfaceImports.find((i) => i.revisionId === c.interfaceRevisionId)!; + assert.deepEqual(iface.application?.arguments[1], { kind: "object", target: { kind: "atom", atomId: "product" } }); + } +}); + +test("rules reject recursive graphs, nested capture, wrong kinds and unbound parameters", () => { + const compile = fixture([ + 'interface A id "a" revision "a@1" {}', + 'interface B id "b" revision "b@1" {}', + ]); + for (const [body, code] of [ + ['conform A as A> id "grow" {}', "recursive-conformance-rule"], + ['conform A as B id "ab" {} conform B as A id "ba" {}', "recursive-conformance-rule"], + ['conform A> as B id "nested" {}', "unsupported-rule-pattern"], + ['conform A as B id "uncaptured" {}', "uncaptured-rule-parameter"], + ['conform A as B id "kind" {}', "parameter-kind"], + ]) { + const result = compile(body); + assert.equal(result.ok, false, body); + assert.match(JSON.stringify(result.diagnostics), new RegExp(code)); + } +}); + +test("package-backed rules retain declared interface dependencies", () => { + const result = ok( + fixture([ + ...nameResources, + 'import interface Person; package Names id "names" revision "names@1" {operation displayName id "read" : unit -> string mode call receiver any requires {interface person id "person-port" : Person using [name.get];};}', + ])( + person("Living") + + `conform Person as HasName id "formatted" { + bind name.get to package Names.displayName with {person to interface Person;}; + bind name.set to interface Person.name.set; + }`, + ), + ); + const generated = result.workspace.conformances.find((c) => c.ruleOrigin)!; + const binding = generated.operationBindings[0].binding; + assert.equal(binding.kind, "package"); + if (binding.kind === "package") + assert.deepEqual(binding.dependencies[0].binding, { kind: "interface", interfaceRevisionId: "person@1" }); +}); + +test("repeated arguments, closed patterns, Self and seedless bounds remain exact", () => { + const compile = fixture([ + 'interface Pair id "pair" revision "pair@1" {}', + 'interface Same id "same" revision "same@1" {}', + ...genericResources, + ]); + const result = ok( + compile(` + atom A id "atom-a"; + conform A as Pair id "equal" {} + conform A as Pair id "unequal" {} + conform Pair as Same id "same-rule" {} + conform A as Factory id "factory" {} + ${factoryRule} + conform Factory as Placeable id "seedless" {} + conform Factory as Eligible id "seedless-other" {} + `), + ); + const generated = result.workspace.conformances.filter((c) => c.ruleOrigin); + assert.equal(generated.length, 1); + const target = result.workspace.interfaceImports.find((i) => i.revisionId === generated[0].interfaceRevisionId)!; + assert.deepEqual(target.application?.arguments[1], { kind: "object", target: { kind: "atom", atomId: "atom-a" } }); +}); + +test("chains do not duplicate an application and explicit targets conflict", () => { + const compile = fixture([ + 'interface A id "a" revision "a@1" {}', + 'interface B id "b" revision "b@1" {}', + 'interface C id "c" revision "c@1" {}', + ]); + const source = `atom Item id "item"; conform Item as A id "a-impl" {} + conform B as C id "bc" {} conform A as B id "ab" {}`; + assert.equal(ok(compile(source)).workspace.conformances.length, 3); + const conflict = compile(source + 'conform Item as C id "explicit-c" {}'); + assert.equal(conflict.ok, false); + assert.match(JSON.stringify(conflict.diagnostics), /explicit-c/); + assert.match(JSON.stringify(conflict.diagnostics), /rule bc/); +}); + +test("rule inverse reads retain the concrete conformance ownership checks", () => { + const compile = fixture([ + 'interface Parent id "parent" revision "parent@1" {}', + 'interface Child id "child" revision "child@1" {}', + 'import interface Parent; interface Owned id "owned" revision "owned@1" {queryable relation parent id "parent-ref" : optional-one interface Parent {resolve id "parent-read" execution snapshot;}}', + ]); + const source = ` + atom P id "p"; atom C id "c"; + conform P as Parent id "parent-impl" { + private edge Membership id "membership" { + atom P projection children id "children" many-unique; + interface Child projection parent id "parent" optional-one public-traversal; + } + } + conform C as Child id "child-impl" {} + conform Child as Owned id "owned-rule" {bind parent.resolve to edge Membership.parent.resolve;} + `; + ok(compile(source)); + const hidden = compile(source.replace(" public-traversal", "")); + assert.equal(hidden.ok, false); + assert.match(JSON.stringify(hidden.diagnostics), /private|public|own/i); + const missing = compile(source.replace("Membership.parent.resolve", "Missing.parent.resolve")); + assert.equal(missing.ok, false); + assert.match(JSON.stringify(missing.diagnostics), /Unknown attachment/); + const unused = compile( + source + .replace('conform C as Child id "child-impl" {}', "") + .replace("Membership.parent.resolve", "Missing.parent.resolve"), + ); + assert.equal(unused.ok, false); + assert.match(JSON.stringify(unused.diagnostics), /Unknown attachment/); +}); diff --git a/test/controller-registry.test.ts b/test/controller-registry.test.ts new file mode 100644 index 0000000..de7cc84 --- /dev/null +++ b/test/controller-registry.test.ts @@ -0,0 +1,50 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { readFile } from "node:fs/promises"; +import { compileCapabilityResourceSource } from "../src/capability-language/index.js"; +import { compileQuery } from "../src/query/compile.js"; +import { parseControllerCatalog } from "../src/capability-model/controllers.js"; +const source = { repository: "https://example.test/device.git", commit: "a".repeat(40) }; +test("controller discovery requires complete bounded accepted-state membership", async () => { + const result = compileCapabilityResourceSource( + await readFile("test/fixtures/controller-device/DeviceController.qx", "utf8"), + { source }, + ); + assert.ok(result.ok && result.resource.kind === "interface", JSON.stringify(result.diagnostics)); + const iface = result.resource.revision; + const pkg = compileCapabilityResourceSource( + 'import interface DeviceController;package Driver id "driver" revision "driver@1" {query Enabled id "enabled" root workspace DeviceController document "enabled.graphql" operation "Enabled" {max rows 100;}}', + { source, environment: { interfaces: new Map([["DeviceController", iface]]), interfaceClosure: [iface] } }, + ); + assert.ok(pkg.ok && pkg.resource.kind === "package", JSON.stringify(pkg.diagnostics)); + const declaration = pkg.resource.revision.queries![0]!; + const metadata = JSON.stringify({ + schemaVersion: 1, + controllers: [ + { + id: "driver", + registry: "enabled", + subject: "device-controller@1", + snapshot: "device:snapshot:call", + remote: "device:apply:call", + input: "value", + }, + ], + }); + const check = async (document: string) => + parseControllerCatalog(metadata, [iface], [await compileQuery(declaration, [iface], async () => document)]); + assert.equal( + (await check("query Enabled {root {objects(all:64,where:{enabled:{eq:true}}) {entries {node {_camino {ref}}}}}}")) + .length, + 1, + ); + assert.equal((await check("query Enabled {root {objects(all:64) {entries {node {enabled}}}}}")).length, 1); + for (const document of [ + "query Enabled {root {objects(first:64) {entries {node {_camino {ref}}}}}}", + "query Enabled {root {objects(all:65) {entries {node {_camino {ref}}}}}}", + "query Enabled {root {devices:objects(all:64) {entries {node {_camino {ref}}}}}}", + "query Enabled($enabled:Boolean!) {root {objects(all:64,where:{enabled:{eq:$enabled}}) {entries {node {_camino {ref}}}}}}", + ]) + await assert.rejects(check(document), /registry|Controller/); + assert.throws(() => parseControllerCatalog(metadata, [iface], []), /registry/); +}); diff --git a/test/crdt-edit-contracts.test.ts b/test/crdt-edit-contracts.test.ts new file mode 100644 index 0000000..5daf7d6 --- /dev/null +++ b/test/crdt-edit-contracts.test.ts @@ -0,0 +1,91 @@ +import assert from "node:assert/strict"; +import { test } from "node:test"; +import { compileCapabilityResourceSource, compileCapabilitySource } from "../src/capability-language/index.js"; +const source = { repository: "https://example.test/edit.git", commit: "a".repeat(40) }; +const content = "record { text:string; tags:list; }"; +const iface = compileCapabilityResourceSource( + `interface Comment id "comment" revision "comment@1" { + value content id "content" : ${content} { + get id "content:get" execution snapshot; + edit id "content:edit" execution transaction; + } +}`, + { source }, +); +assert.ok(iface.ok && iface.resource.kind === "interface", JSON.stringify(iface.diagnostics)); +const contract = iface.resource.revision; +const compile = (policy = `crdt(${content})`, binding = "edit") => + compileCapabilitySource( + ` +workspace Comments id "w" revision "w@1" commit "${"a".repeat(40)}" { + import interface Comment; + atom CommentAtom id "comment"; + conform CommentAtom as Comment id "owner:comment" { + private state Content id "content" on CommentAtom : ${content} policy ${policy}; + initialize { content to state Content; }; + bind content.get to state Content.read; + bind content.edit to state Content.${binding}; + } +}`, + "comments.qx", + { interfaces: new Map([["Comment", contract]]), interfaceClosure: [contract] }, + ); +test("native edit is an explicit typed change capability over structured content", () => { + const result = compile(); + assert.ok(result.ok, JSON.stringify(result.diagnostics)); + const member = contract.members[0]!; + assert.equal(member.kind, "value"); + if (member.kind !== "value") return; + assert.deepEqual(member.operations[1]!.inputType, { kind: "change", value: member.valueType }); + assert.equal(result.workspace.conformances[0]!.operationBindings[1]!.binding.kind, "state"); +}); +test("edit cannot target a register, mismatched profile, or ordinary replacement", () => { + for (const [policy, binding] of [ + ["register", "edit"], + ["crdt(bytes)", "edit"], + [`crdt(${content})`, "write"], + ]) { + const result = compile(policy, binding); + assert.equal(result.ok, false); + assert.ok( + result.diagnostics.some((d) => d.code === "invalid-state-binding"), + JSON.stringify(result.diagnostics), + ); + } +}); +test("authored prepared changes retain their materialized type as transport values", () => { + const result = compileCapabilityResourceSource( + 'package P id "p" revision "p@1" { function applyChange id "apply" : change -> unit; }', + { source }, + ); + assert.ok(result.ok && result.resource.kind === "package", JSON.stringify(result.diagnostics)); + assert.deepEqual(result.resource.revision.exports[0]!.inputType, { + kind: "change", + value: { kind: "scalar", name: "string" }, + }); +}); + +test("generic edit retains its materialized type through specialization", () => { + const generic = compileCapabilityResourceSource( + `interface Cell id "cell" revision "cell@1" { + value content id "content" : T { get id "get"; edit id "edit" execution transaction; } + }`, + { source }, + ); + assert.ok(generic.ok && generic.resource.kind === "interface", JSON.stringify(generic.diagnostics)); + const result = compileCapabilitySource( + `workspace W id "w" revision "w@1" commit "${"a".repeat(40)}" { + import interface Cell; + atom Note id "note"; + conform Note as Cell<${content}> { + private state Content id "content" on Note : ${content} policy crdt(${content}); + bind content to state Content; + } + }`, + "cell.qx", + { interfaces: new Map([["Cell", generic.resource.revision]]), interfaceClosure: [generic.resource.revision] }, + ); + assert.ok(result.ok, JSON.stringify(result.diagnostics)); + const applied = result.workspace.interfaceImports.find((i) => i.application)!; + assert.equal(applied.members[0]!.operations[1]!.inputType.kind, "change"); +}); diff --git a/test/creation-contracts.test.ts b/test/creation-contracts.test.ts new file mode 100644 index 0000000..e6d1219 --- /dev/null +++ b/test/creation-contracts.test.ts @@ -0,0 +1,220 @@ +import assert from "node:assert/strict"; +import { test } from "node:test"; +import { + compileCapabilityResourceSource, + compileCapabilitySource, + formatQx, +} from "../src/capability-language/index.js"; +import type { InterfaceRevision } from "../src/capability-model/types.js"; +const source = { repository: "https://example.test/factories.git", commit: "a".repeat(40) }; +const seed = "record { data: record { title: string; }; tags: record { tags: list; }; }"; +const iface = (text: string): InterfaceRevision => { + const result = compileCapabilityResourceSource(text, { source }); + assert.ok(result.ok && result.resource.kind === "interface", JSON.stringify(result.diagnostics)); + return result.resource.revision; +}; +const data = iface(`interface Data id "data" revision "data@1" { + value title id "title" : string { get id "title:get" execution snapshot; } +}`); +const tagged = iface(`interface Tagged id "tagged" revision "tagged@1" { + value tags id "tags" : list { get id "tags:get" execution snapshot; } +}`); +const factory = iface(`external atom Note id "note"; +interface Factory id "factory" revision "factory@1" { + static operation create id "create" : ${seed} -> atom-ref { call id "create:call" execution transaction; } + static operation importNote id "import" : ${seed} -> atom-ref { call id "import:call" execution transaction; } +}`); +const runtime = compileCapabilityResourceSource( + `symbol JavaScript id "target:org.quixos.execution.javascript@1"; +external atom Note id "note"; +package Runtime id "runtime" revision "runtime@1" { + function createNote id "create-note" : ${seed} -> atom-ref execution transaction targets [JavaScript, server] requires { + creation note id "new-note" : Note input ${seed}; + }; + function importNote id "import-note" : ${seed} -> atom-ref execution transaction targets [JavaScript, server] requires { + creation note id "new-note" : Note input ${seed}; + }; +}`, + { source }, +); +assert.ok(runtime.ok && runtime.resource.kind === "package", JSON.stringify(runtime.diagnostics)); +const packageRevision = runtime.resource.revision; +const text = `workspace Notes id "notes" revision "notes@1" commit "${"a".repeat(40)}" { + import interface Data; import interface Tagged; import interface Factory; import package Runtime; + atom Note id "note"; + conform Note as Data id "owner:data" { + private state Title id "title-state" on Note : string policy register; + initialize { title to state Title; }; + bind title.get to state Title.read; + } + conform Note as Tagged id "owner:tags" { + private state Tags id "tags-state" on Note : list policy register; + initialize { tags to state Tags; }; + bind tags.get to state Tags.read; + } + conform Note as Factory id "owner:factory" { + bind create.call to package Runtime.createNote with { + note to creation Note with { Data from input.data; Tagged from input.tags; }; + }; + bind importNote.call to package Runtime.importNote with { + note to creation Note with { Data from input.data; Tagged from input.tags; }; + }; + } +}`; +const compile = (textOverride = text) => + compileCapabilitySource(formatQx(textOverride), "notes.qx", { + interfaces: new Map([ + ["Data", data], + ["Tagged", tagged], + ["Factory", factory], + ]), + interfaceClosure: [data, tagged, factory], + packages: new Map([["Runtime", packageRevision]]), + packageClosure: [packageRevision], + }); +test("two factories initialize two independent state owners without constructors", () => { + const result = compile(); + assert.ok(result.ok, JSON.stringify(result.diagnostics)); + assert.deepEqual(result.workspace.constructors, []); + assert.equal(result.workspace.conformances.filter((c) => c.initialization).length, 2); + const owners = result.workspace.conformances[2]!.operationBindings.map((b) => + b.binding.kind === "package" ? b.binding.dependencies[0]!.binding : undefined, + ); + for (const binding of owners) { + assert.ok(binding?.kind === "creation"); + assert.deepEqual(binding.owners, [ + { interfaceRevisionId: "data@1", inputPath: ["data"] }, + { interfaceRevisionId: "tagged@1", inputPath: ["tags"] }, + ]); + } +}); + +test("rule-owned state uses the same checked initialization and creation obligations", () => { + const source = text + .replace('conform Note as Data id "owner:data"', 'conform Tagged as Data id "rule:data"') + .replace("on Note : string", "on Self : string"); + const result = compile(source); + assert.ok(result.ok, JSON.stringify(result.diagnostics)); + const owner = result.workspace.conformances.find((c) => c.ruleOrigin?.ruleId === "rule:data")!; + assert.equal(owner.initialization?.[0]?.binding.kind, "state"); + assert(owner.privateAttachments[0]!.id.startsWith("rule:sha256:")); + for (const [invalid, diagnostic] of [ + [source.replaceAll("Data from input.data;", ""), "Required state"], + [source.replace("initialize { title to state Title; };", ""), "no declared initialization mapping"], + [source.replace("title to state Title;", "title to state Tags;"), "rule-private storage"], + [source.replaceAll("Data from input.data;", "Data from input.tags;"), "does not match"], + ]) { + const failure = compile(invalid!); + assert.equal(failure.ok, false); + assert.match(JSON.stringify(failure.diagnostics), new RegExp(diagnostic!)); + } +}); +test("creation rejects missing seeds, wrong input shapes, and private initialization laundering", () => { + for (const [edit, message] of [ + [text.replaceAll("Tagged from input.tags;", ""), "Required state tags-state"], + [text.replaceAll("Data from input.data;", "Data from input.tags;"), "does not match"], + [text.replace("title to state Title;", "title to state Tags;"), "owned by this conformance"], + [ + text.replaceAll("Data from input.data;", "Data from input.data; Data from input.data;"), + "Duplicate initialization owner", + ], + ]) { + const result = compile(edit!); + assert.equal(result.ok, false); + assert.match(JSON.stringify(result.diagnostics), new RegExp(message!)); + } +}); + +test("defaults supply unmapped state and seeds cannot invoke an undeclared owner", () => { + const withDefault = text + .replace("on Note : list policy register;", "on Note : list policy register default [];") + .replaceAll("Tagged from input.tags;", ""); + const result = compile(withDefault); + assert.ok(result.ok, JSON.stringify(result.diagnostics)); + const missing = compile(text.replace("initialize { tags to state Tags; };", "")); + assert.equal(missing.ok, false); + assert.match(JSON.stringify(missing.diagnostics), /no declared initialization mapping/); +}); + +test("creation ports cannot be used in snapshot execution", () => { + const result = compileCapabilityResourceSource( + `symbol JavaScript id "target:org.quixos.execution.javascript@1"; +external atom Note id "note"; + package P id "p" revision "p@1" { + function createNote id "create" : unit -> atom-ref execution snapshot targets [JavaScript, server] requires { + creation note id "note" : Note input record { title:string; }; + }; + }`, + { source }, + ); + assert.equal(result.ok, false); + assert.match(JSON.stringify(result.diagnostics), /creation requires transaction/); +}); + +test("the authored storage policy is register, without an old spelling alias", () => { + const result = compileCapabilitySource(text.replaceAll("policy register", "policy optimistic-register"), "notes.qx"); + assert.equal(result.ok, false); + assert.ok(result.diagnostics.some((d) => d.phase === "syntax")); +}); + +test("initialization contracts participate in executable source identity", async () => { + const { runtimeContracts } = await import("../src/capability-model/index.js"); + const result = compile(); + assert.ok(result.ok, JSON.stringify(result.diagnostics)); + const before = runtimeContracts(result.workspace); + const changed = structuredClone(result.workspace); + changed.conformances[0]!.initialization![0]!.input = "renamed"; + assert.notDeepEqual(runtimeContracts(changed), before); +}); + +test("to-many initialization requires stable entry keys and typed targets", async () => { + const { validateInitialization } = await import("../src/capability-model/initialization.js"); + const { capabilityId } = await import("../src/capability-model/index.js"); + const result = compile(); + assert.ok(result.ok, JSON.stringify(result.diagnostics)); + const workspace = result.workspace; + const owner = workspace.conformances[1]!; + const state = owner.privateAttachments[0]!; + assert.equal(state.kind, "state"); + if (state.kind !== "state") return; + state.defaultValue = []; + const edgeId = capabilityId.edgeType("children"), + projectionId = capabilityId.edgeProjection("children:parent"); + owner.privateAttachments.push({ + kind: "edge", + id: edgeId, + displayName: "Children", + endpoints: [ + { + projectionId, + displayName: "children", + constraint: { kind: "atom", atomId: owner.atomId }, + cardinality: "many-unique", + ordered: false, + }, + { + projectionId: capabilityId.edgeProjection("children:child"), + displayName: "parent", + constraint: { kind: "atom", atomId: owner.atomId }, + cardinality: "optional-one", + ordered: false, + }, + ], + }); + owner.initialization = [{ input: "children", binding: { kind: "edge", edgeTypeId: edgeId, projectionId } }]; + const entryType = { + kind: "record" as const, + fields: { + key: { kind: "scalar" as const, name: "string" as const }, + target: { kind: "object-ref" as const, expectation: { kind: "atom" as const, atomId: owner.atomId } }, + }, + }; + for (const entry of workspace.packageImports[0]!.exports) { + const port = entry.dependencyPorts[0]!.requirement; + assert.ok(port.kind === "creation" && port.inputType.kind === "record"); + port.inputType.fields.tags = { kind: "record", fields: { children: { kind: "list", value: entryType } } }; + } + assert.deepEqual(validateInitialization(workspace), []); + Reflect.deleteProperty(entryType.fields, "key"); + assert.ok(validateInitialization(workspace).some((i) => i.message.includes("does not match"))); +}); diff --git a/test/emit-controllers.ts b/test/emit-controllers.ts new file mode 100644 index 0000000..c342f6e --- /dev/null +++ b/test/emit-controllers.ts @@ -0,0 +1,86 @@ +import assert from "node:assert/strict"; +import { readFile, mkdir, writeFile } from "node:fs/promises"; +import { compileCapabilityResourceSource, compileCapabilitySource } from "../src/capability-language/index.js"; +import { compileQuery } from "../src/query/compile.js"; +import { parseControllerCatalog } from "../src/capability-model/controllers.js"; +import { + compileExecutionPlan, + generateInterfaceClientBindings, + generatePackageQueryClientBindings, +} from "../src/execution-world/index.js"; +const read = (file: string) => readFile("test/fixtures/controller-device/" + file, "utf8"); +const source = { repository: "https://example.test/device.git", commit: "a".repeat(40) }; +const device = compileCapabilityResourceSource(await read("Device.qx"), { source }); +assert.ok(device.ok && device.resource.kind === "interface", JSON.stringify(device.diagnostics)); +const control = compileCapabilityResourceSource(await read("DeviceController.qx"), { source }); +assert.ok(control.ok && control.resource.kind === "interface", JSON.stringify(control.diagnostics)); +const iface = device.resource.revision, + controllerInterface = control.resource.revision, + environment = { + interfaces: new Map([ + ["Device", iface], + ["DeviceController", controllerInterface], + ]), + interfaceClosure: [iface, controllerInterface], + }; +const driver = compileCapabilityResourceSource(await read("Driver.qx"), { source, environment }); +assert.ok(driver.ok && driver.resource.kind === "package", JSON.stringify(driver.diagnostics)); +const pkg = driver.resource.revision; +pkg.checkedQueries = await Promise.all( + pkg.queries!.map((query) => compileQuery(query, [iface, controllerInterface], read)), +); +pkg.controllers = parseControllerCatalog( + JSON.stringify({ + schemaVersion: 1, + controllers: [ + { + id: "device-driver", + registry: "enabled", + subject: "device-controller@1", + snapshot: "device:snapshot:call", + remote: "device:apply:call", + input: "value", + }, + ], + }), + [iface, controllerInterface], + pkg.checkedQueries, +); +const viewRole = compileCapabilityResourceSource(await read("DeviceView.qx"), { source, environment }); +assert.ok(viewRole.ok && viewRole.resource.kind === "interface", JSON.stringify(viewRole.diagnostics)); +environment.interfaces.set("DeviceView", viewRole.resource.revision); +environment.interfaceClosure.push(viewRole.resource.revision); +const viewer = compileCapabilityResourceSource(await read("Viewer.qx"), { + source: { ...source, repository: "https://example.test/device-viewer.git" }, + environment, +}); +assert.ok(viewer.ok && viewer.resource.kind === "package", JSON.stringify(viewer.diagnostics)); +const viewerPackage = viewer.resource.revision; +viewerPackage.checkedQueries = await Promise.all( + viewerPackage.queries!.map((query) => compileQuery(query, [iface], read)), +); +for (const variant of ["controller", "controller-next"]) { + const extra = variant.endsWith("next") + ? 'atom Dashboard id "dashboard"; import package DeviceViewer; presentation DeviceStatus id "device:status" for Device using DeviceViewer.Status;' + : ""; + const result = compileCapabilitySource((await read("workspace.qx")).replace(/}\s*$/, extra + "\n}"), "workspace.qx", { + ...environment, + packages: new Map([ + ["Driver", pkg], + ...(variant.endsWith("next") ? [["DeviceViewer", viewerPackage] as const] : []), + ]), + }); + assert.ok(result.ok, JSON.stringify(result.diagnostics)); + const plan = compileExecutionPlan(result.workspace), + root = "dist/managed/" + variant; + await mkdir(root, { recursive: true }); + await writeFile(root + "/client.ts", generateInterfaceClientBindings({ Device: iface })); + await writeFile(root + "/queries.ts", generatePackageQueryClientBindings(pkg)); + for (const [file, value] of Object.entries({ + "candidate.json": result.workspace, + "execution-plan.json": plan, + "entry.json": null, + "source.json": { kind: "workspace", ...source }, + })) + await writeFile(root + "/" + file, JSON.stringify(value)); +} diff --git a/test/emit-managed.ts b/test/emit-managed.ts new file mode 100644 index 0000000..95ef2c7 --- /dev/null +++ b/test/emit-managed.ts @@ -0,0 +1,239 @@ +/** Protocol/process qualification only; no application fixture enters runtime code. */ +import assert from "node:assert/strict"; +import { mkdir, writeFile } from "node:fs/promises"; +import { compileCapabilityResourceSource, compileCapabilitySource } from "../src/capability-language/index.js"; +import { compileExecutionPlan } from "../src/execution-world/plan.js"; +import { compileKernelProgram } from "../src/execution-world/kernel.js"; +import { compileQuery } from "../src/query/compile.js"; +import { + generateInterfaceClientBindings, + generatePackageQueryClientBindings, +} from "../src/execution-world/client-contracts.js"; +import { compileQueryProgram } from "../src/execution-world/queries.js"; +import { presentationDeclarations, preparationArtifact } from "../src/react-build/declarations.js"; +import { compileQueryComposition } from "../src/query/composition.js"; +import type { PackageRevision } from "../src/capability-model/types.js"; +const source = { repository: "https://example.test/device-owner.git", commit: "a".repeat(40) }; +const factory = compileCapabilityResourceSource( + `external atom Entry id "atom:entry"; +interface EntryFactory id "interface:entry-factory" revision "interface:entry-factory@1" { + static operation create id "entry:create" : unit -> atom-ref { + call id "entry:create:call" execution transaction; + } +}`, + { source }, +); +assert.ok(factory.ok && factory.resource.kind === "interface", JSON.stringify(factory.diagnostics)); +const data = compileCapabilityResourceSource( + `interface Data id "interface:data" revision "interface:data@1" { + queryable value body id "body" : string { get id "body:get" execution snapshot; edit id "body:edit" execution transaction; } + queryable value notes id "notes" : string { get id "notes:get" execution snapshot; edit id "notes:edit" execution transaction; } + operation revise id "revise" : record {body:change; notes:change; reject:bool;} -> unit {call id "revise:call" execution transaction;} + queryable value label id "label" : string { + get id "label:get" execution snapshot; + set id "label:set" execution transaction; + } +}`, + { source }, +); +const constructors = compileCapabilityResourceSource( + `external atom Entry id "atom:entry"; external atom Independent id "atom:independent"; +interface Factories id "interface:factories" revision "interface:factories@1" { + static operation createOne id "create-one" : string -> atom-ref { call id "create-one:call" execution transaction; } + static operation createTwo id "create-two" : unit -> atom-ref { call id "create-two:call" execution transaction; } +}`, + { source }, +); +assert.ok(data.ok && data.resource.kind === "interface", JSON.stringify(data.diagnostics)); +assert.ok(constructors.ok && constructors.resource.kind === "interface", JSON.stringify(constructors.diagnostics)); +const dataInterface = data.resource.revision, + factoriesInterface = constructors.resource.revision; +const view = compileCapabilityResourceSource( + 'import interface Data; interface View id "interface:device-view" revision "interface:device-view@1" { module render id "member:device-view:render" contract "org.quixos.react.subject-only/1" subject interface Data; }', + { source, environment: { interfaces: new Map([["Data", dataInterface]]), interfaceClosure: [dataInterface] } }, +); +assert.ok(view.ok && view.resource.kind === "interface", JSON.stringify(view.diagnostics)); +const viewInterface = view.resource.revision; +function declareInputs(pkg: PackageRevision) { + const declarations = presentationDeclarations( + JSON.stringify({ + modules: Object.fromEntries((pkg.modules ?? []).map((module) => [module.displayName, { inputs: [] }])), + }), + pkg, + [dataInterface, viewInterface], + ); + pkg.buildArtifacts = { [preparationArtifact]: declarations.artifact }; + pkg.queryCompositions = declarations.compositions.map((entry) => compileQueryComposition(entry, new Map())); +} +const ui = compileCapabilityResourceSource( + 'import interface Data; import interface View; package UI id "package:ui" revision "package:ui@1" { module Card id "component:card" implements View.render; }', + { + source, + environment: { + interfaces: new Map([ + ["Data", dataInterface], + ["View", viewInterface], + ]), + interfaceClosure: [dataInterface, viewInterface], + }, + }, +); +assert.ok(ui.ok && ui.resource.kind === "package", JSON.stringify(ui.diagnostics)); +declareInputs(ui.resource.revision); +for (const variant of ["base", "added", "gateway", "gateway-next", "session"]) { + const session = variant === "session", + gateway = session || variant.startsWith("gateway"), + commit = { base: "a", added: "b", gateway: "c", "gateway-next": "d", session: "e" }[variant]!.repeat(40); + const pkg = compileCapabilityResourceSource( + `symbol JavaScript id "target:org.quixos.execution.javascript@1"; +${gateway ? 'external atom Entry id "atom:entry";' : ""} +${session ? 'external atom Independent id "atom:independent"; import interface Data; import interface View;' : ""} +package Owner id "package:owner" revision "package:owner@1" { + function identity id "identity" : int32 -> record { pid: int32; child: int32; } execution remote; + ${gateway ? 'function create id "create" : unit -> atom-ref execution transaction targets [server] requires { creation entry id "entry:birth" : Entry input unit; };' : ""} + ${ + session + ? `module Editor id "component:owner-editor" implements View.render; + function createOne id "create-one" : string -> atom-ref execution transaction targets [JavaScript, server] requires { creation entry id "one:birth" : Entry input unit; }; + function createTwo id "create-two" : unit -> atom-ref execution transaction targets [JavaScript, server] requires { creation entry id "two:birth" : Independent input unit; }; + operation revise id "revise" : record {body:change; notes:change; reject:bool;} -> unit mode call execution transaction targets [JavaScript, server] receiver any requires { + state body id "revise:body" : string [edit]; state notes id "revise:notes" : string [edit]; + }; + query Inputs id "inputs" root workspace Data document "inputs.graphql" operation "Inputs" { replica; } + query View id "view" root workspace Data document "view.graphql" operation "View" { local from Inputs; watch; } + query EditorView id "editor-view" root Data document "editor-view.graphql" operation "EditorView" { local from EditorInputs; watch; } + query EditorInputs id "editor-inputs" root Data document "editor.graphql" operation "Editor" { replica; }` + : "" + } +}`, + { + source, + ...(session + ? { + environment: { + interfaces: new Map([ + ["Data", dataInterface], + ["View", viewInterface], + ]), + interfaceClosure: [dataInterface, viewInterface], + }, + } + : {}), + }, + ); + assert.ok(pkg.ok && pkg.resource.kind === "package", JSON.stringify(pkg.diagnostics)); + if (session) declareInputs(pkg.resource.revision); + if (session) + pkg.resource.revision.checkedQueries = await Promise.all( + pkg.resource.revision.queries!.map((query) => + compileQuery( + query, + [dataInterface], + async (name) => + ({ + "inputs.graphql": + "query Inputs($label: String!) { root { objects(all: 100, where: {label: {eq: $label}}) { entries { key node { _camino {ref} label } } } } }", + "view.graphql": + "query View { root { availableObjects(all: 100) { entries { key node { _camino {ref} label } } } } }", + "editor.graphql": "query Editor { root { _camino {ref} label body notes } }", + "editor-view.graphql": "query EditorView { root { label body notes } }", + })[name]!, + ), + ), + ); + const result = compileCapabilitySource( + `workspace Managed id "workspace:managed-process" revision "workspace:managed-process@${variant}" commit "${commit}" { + import package Owner; + ${session ? "import package UI;" : ""} + ${ + session + ? `import interface Data; import interface View; import interface Factories; + conform Entry as Factories id "owner:factories" { bind createOne.call to package Owner.createOne with { entry to creation Entry with {}; }; bind createTwo.call to package Owner.createTwo with { entry to creation Independent with {}; }; } + ${["Entry", "Independent"].map((atom) => `conform ${atom} as Data id "owner:${atom}:data" { private state ${atom}Label id "label:${atom}" on ${atom} : string policy register default "${atom}"; bind label.get to state ${atom}Label.read; bind label.set to state ${atom}Label.write; private state ${atom}Body id "body:${atom}" on ${atom} : string policy crdt(string) default "hello 🌍"; private state ${atom}Notes id "notes:${atom}" on ${atom} : string policy crdt(string) default "notes"; bind body.get to state ${atom}Body.read; bind body.edit to state ${atom}Body.edit; bind notes.get to state ${atom}Notes.read; bind notes.edit to state ${atom}Notes.edit; bind revise.call to package Owner.revise with {body to state ${atom}Body; notes to state ${atom}Notes;}; }`).join("\n")} + ` + : "" + } + ${variant !== "base" ? 'atom Independent id "atom:independent";' : ""} + ${ + gateway + ? `import interface EntryFactory; atom Entry id "atom:entry"; + conform Entry as EntryFactory id "owner:entry-factory" { + bind create.call to package Owner.create with { entry to creation Entry with {}; }; + }` + : "" + } + }`, + "workspace.qx", + { + packages: new Map([ + ["Owner", pkg.resource.revision], + ...(session ? [["UI", ui.resource.revision] as const] : []), + ]), + packageClosure: [pkg.resource.revision, ...(session ? [ui.resource.revision] : [])], + ...(gateway + ? { + interfaces: new Map([ + ["EntryFactory", factory.resource.revision], + ...(session + ? ([ + ["Data", dataInterface], + ["View", viewInterface], + ["Factories", factoriesInterface], + ] as const) + : []), + ]), + interfaceClosure: [ + factory.resource.revision, + ...(session ? [dataInterface, viewInterface, factoriesInterface] : []), + ], + } + : {}), + }, + ); + assert.ok(result.ok, JSON.stringify(result.diagnostics)); + const plan = compileExecutionPlan(result.workspace), + root = `dist/managed/${variant}`; + await mkdir(root, { recursive: true }); + if (session) { + await writeFile( + `${root}/client.ts`, + generateInterfaceClientBindings({ Data: dataInterface, Factories: factoriesInterface }), + ); + await writeFile(`${root}/queries.ts`, generatePackageQueryClientBindings(pkg.resource.revision)); + } + for (const [file, value] of Object.entries({ + "candidate.json": result.workspace, + "entry.json": gateway + ? { atom: "atom:entry", interface: "interface:entry-factory@1", operation: "entry:create:call" } + : null, + "execution-plan.json": plan, + "kernel.json": compileKernelProgram(plan), + "queries.json": compileQueryProgram(plan), + "source.json": { kind: "workspace", repository: "https://example.test/managed.git", commit }, + })) + await writeFile(`${root}/${file}`, JSON.stringify(value)); +} + +await import("./emit-controllers.js"); + +// Standalone public builder roots must work without opting into the other modes. +for (const kind of ["portable", "service"]) { + const pkg = compileCapabilityResourceSource( + `symbol JavaScript id "target:org.quixos.execution.javascript@1"; +package Owner id "package:owner" revision "package:owner@1" { + function identity id "identity" : int32 -> record {pid:int32;child:int32;} + execution ${kind === "portable" ? "snapshot targets [JavaScript,server]" : "remote"}; + }`, + { source }, + ); + assert.ok(pkg.ok && pkg.resource.kind === "package", JSON.stringify(pkg.diagnostics)); + const workspace = compileCapabilitySource( + `workspace Simple id "workspace:simple" revision "workspace:simple@1" commit "${source.commit}" {import package Owner;}`, + "workspace.qx", + { packages: new Map([["Owner", pkg.resource.revision]]) }, + ); + assert.ok(workspace.ok, JSON.stringify(workspace.diagnostics)); + const directory = `dist/managed/standalone-${kind}`; + await mkdir(directory, { recursive: true }); + await writeFile(`${directory}/execution-plan.json`, JSON.stringify(compileExecutionPlan(workspace.workspace))); +} diff --git a/test/emit-portable-reader.ts b/test/emit-portable-reader.ts new file mode 100644 index 0000000..d3d548f --- /dev/null +++ b/test/emit-portable-reader.ts @@ -0,0 +1,181 @@ +/** Qualification artifact emitted after tests, never a production fixture dependency. */ +import { mkdir, writeFile } from "node:fs/promises"; +import { loadReaderWorkspace } from "./reader-fixture.js"; +import { compileKernelProgram } from "../src/execution-world/kernel.js"; +import { compileQueryProgram } from "../src/execution-world/queries.js"; +import { compileExecutionPlan } from "../src/execution-world/plan.js"; +import { generatePortableTypeScriptBindings } from "../src/bindings/portable.js"; +import { generatePortableClientBindings } from "../src/bindings/portable-client.js"; +import { loadCompositeWorkspace } from "./composite-fixture.js"; +import { javascriptTarget } from "../src/execution-world/native-backends.js"; +const executionTarget = (lane: string) => (lane === "browser" ? javascriptTarget : lane); +const plan = compileExecutionPlan(await loadReaderWorkspace()); +await mkdir("dist/portable-reader", { recursive: true }); +for (const target of ["browser", "server"]) + await writeFile( + `dist/portable-reader/${target}.ts`, + generatePortableTypeScriptBindings(plan.definition.bindingSchema, "package:reader-runtime@1", { + target: executionTarget(target), + }), + ); +await writeFile("dist/portable-reader/plan.json", JSON.stringify(plan)); +await writeFile("dist/portable-reader/kernel.json", JSON.stringify(compileKernelProgram(plan))); +await writeFile("dist/portable-reader/queries.json", JSON.stringify(compileQueryProgram(plan))); +await writeFile("dist/portable-reader/client.ts", generatePortableClientBindings(plan, { target: javascriptTarget })); +const serviceWorkspace = structuredClone(plan.definition.workspace); +serviceWorkspace.atoms = []; +serviceWorkspace.conformances = []; +serviceWorkspace.constructors = []; +serviceWorkspace.interfaceImports = []; +serviceWorkspace.sharedAttachments = []; +serviceWorkspace.packageImports[0]!.queries = []; +serviceWorkspace.packageImports[0]!.checkedQueries = []; +const original = serviceWorkspace.packageImports[0]!.exports.find((e) => e.kind === "function")!; +serviceWorkspace.packageImports[0]!.exports = [ + { + ...original, + kind: "function", + execution: { kind: "remote" }, + inputType: { kind: "scalar", name: "string" }, + outputType: { kind: "scalar", name: "string" }, + dependencyPorts: [], + }, +]; +await writeFile("dist/portable-reader/service-plan.json", JSON.stringify(compileExecutionPlan(serviceWorkspace))); +const composite = compileExecutionPlan(await loadCompositeWorkspace()); +await mkdir("dist/portable-reader/composite", { recursive: true }); +await writeFile("dist/portable-reader/composite/plan.json", JSON.stringify(composite)); +await writeFile("dist/portable-reader/composite/kernel.json", JSON.stringify(compileKernelProgram(composite))); +for (const target of ["browser", "server"]) + for (const [name, revision] of [ + ["reader", "package:reader-runtime@1"], + ["comment", "package:comment-runtime@1"], + ] as const) + await writeFile( + `dist/portable-reader/composite/${name}-${target}.ts`, + generatePortableTypeScriptBindings(composite.definition.bindingSchema, revision, { + target: executionTarget(target), + }), + ); + +const { portableGenericPlan } = await import("./portable-generic-fixture.js"); +const generic = portableGenericPlan(); +const genericPackage = generic.definition.bindingSchema.packages[0]!; +await mkdir("dist/portable-reader/generic", { recursive: true }); +for (const target of ["browser", "server"]) { + await writeFile( + `dist/portable-reader/generic/${target}.ts`, + generatePortableTypeScriptBindings(generic.definition.bindingSchema, genericPackage.revisionId, { + target: executionTarget(target), + }), + ); +} +const stringEntry = genericPackage.exports.find((e) => e.inputType.kind === "scalar")!; +const listEntry = genericPackage.exports.find((e) => e.inputType.kind === "list")!; +await writeFile( + "dist/portable-reader/generic/implementation.ts", + ` +import { createRegistry, type PortableImplementation } from "./gen/qx.js"; +export const registry = createRegistry({ + ${JSON.stringify(stringEntry.displayName)}: context => context.input.toUpperCase(), + ${JSON.stringify(listEntry.displayName)}: context => context.input.map(value => value + 1n), +}); +// The specialization must retain its actual contract, not become unknown or any. +const negative: PortableImplementation = { + // @ts-expect-error a string specialization cannot return a number + ${JSON.stringify(stringEntry.displayName)}: context => context.input.length, + ${JSON.stringify(listEntry.displayName)}: context => { + // @ts-expect-error input collections are read-only + context.input.push(0n); + // @ts-expect-error integer values are exact bigint, not number + const lostPrecision: number = context.input[0]!; + return context.input; + }, +}; +void negative; +`, +); + +const { asyncObjectPlan } = await import("./async-fixture.js"); +const { treePlan } = await import("./tree-fixture.js"); +const tree = await treePlan(); +await mkdir("dist/portable-reader/tree", { recursive: true }); +await writeFile("dist/portable-reader/tree/plan.json", JSON.stringify(tree)); +await writeFile("dist/portable-reader/tree/kernel.json", JSON.stringify(compileKernelProgram(tree))); +const { compile: compileAcceptance } = await import("./acceptance-fixture.js"); +const acceptance = compileAcceptance(); +if (!acceptance.ok) throw Error(JSON.stringify(acceptance.diagnostics)); +const acceptancePlan = compileExecutionPlan(acceptance.workspace); +await mkdir("dist/portable-reader/acceptance", { recursive: true }); +await writeFile("dist/portable-reader/acceptance/plan.json", JSON.stringify(acceptancePlan)); +await writeFile("dist/portable-reader/acceptance/kernel.json", JSON.stringify(compileKernelProgram(acceptancePlan))); +const { recurringTaskPlan } = await import("./recurring-fixture.js"); +const recurringPlan = await recurringTaskPlan(); +await mkdir("dist/portable-reader/recurring", { recursive: true }); +await writeFile("dist/portable-reader/recurring/plan.json", JSON.stringify(recurringPlan)); +await writeFile("dist/portable-reader/recurring/kernel.json", JSON.stringify(compileKernelProgram(recurringPlan))); +const { generateRemoteTypeScriptBindings } = await import("../src/bindings/portable.js"); +const asyncPlan = await asyncObjectPlan(); +await mkdir("dist/portable-reader/async", { recursive: true }); +await writeFile("dist/portable-reader/async/plan.json", JSON.stringify(asyncPlan)); +await writeFile("dist/portable-reader/async/kernel.json", JSON.stringify(compileKernelProgram(asyncPlan))); +for (const [name, revision] of [ + ["controller", "async:controller-runtime@1"], + ["provider", "async:provider-runtime@2"], +] as const) + await writeFile( + `dist/portable-reader/async/${name}.ts`, + generateRemoteTypeScriptBindings(asyncPlan.definition.bindingSchema, revision), + ); + +const { collectionsPlan } = await import("./collections-fixture.js"); +const collections = await collectionsPlan(); +await mkdir("dist/portable-reader/collections", { recursive: true }); +await writeFile("dist/portable-reader/collections/plan.json", JSON.stringify(collections)); +await writeFile("dist/portable-reader/collections/kernel.json", JSON.stringify(compileKernelProgram(collections))); + +const { presentationMountPlan } = await import("./presentation-mount-fixture.js"); +const presentationMount = await presentationMountPlan(); +await mkdir("dist/portable-reader/presentation-mount", { recursive: true }); +await writeFile( + "dist/portable-reader/presentation-mount/queries.json", + JSON.stringify(compileQueryProgram(presentationMount)), +); +await writeFile("dist/portable-reader/presentation-mount/plan.json", JSON.stringify(presentationMount)); +await writeFile( + "dist/portable-reader/presentation-mount/kernel.json", + JSON.stringify(compileKernelProgram(presentationMount)), +); +await writeFile( + "dist/portable-reader/presentation-mount/browser.ts", + generatePortableTypeScriptBindings(presentationMount.definition.bindingSchema, "presentation-runtime@1", { + target: javascriptTarget, + }), +); + +const { compoundCrdtPlan } = await import("./compound-crdt-fixture.js"); +const compoundCrdt = await compoundCrdtPlan(); +await mkdir("dist/portable-reader/compound-crdt", { recursive: true }); +await writeFile("dist/portable-reader/compound-crdt/plan.json", JSON.stringify(compoundCrdt)); +await writeFile("dist/portable-reader/compound-crdt/kernel.json", JSON.stringify(compileKernelProgram(compoundCrdt))); + +const { emitExecutionScaffold } = await import("./execution-scaffold-fixture.js"); +const executionScaffold = await emitExecutionScaffold("dist/portable-reader/execution-scaffold/source"); +await writeFile("dist/portable-reader/execution-scaffold/plan.json", JSON.stringify(executionScaffold)); + +// Native variant fixture shared by generated-type and browser editing qualification. +const { readFile } = await import("node:fs/promises"); +const { compileCapabilityResourceSource } = await import("../src/capability-language/index.js"); +const { portableValueSchema, tsType } = await import("../src/bindings/portable.js"); +const variantSource = compileCapabilityResourceSource(await readFile("test/fixtures/canvas-content.qx", "utf8"), { + source: { repository: "https://example.test/canvas-content.git", commit: "a".repeat(40) }, +}); +if (!variantSource.ok || variantSource.resource.kind !== "interface") throw Error("Invalid variant fixture"); +const entries = variantSource.resource.revision.members[0]; +if (entries.kind !== "value") throw Error("Expected entries value"); +await mkdir("dist/portable-reader/variants", { recursive: true }); +await writeFile("dist/portable-reader/variants/schema.json", JSON.stringify(portableValueSchema(entries.valueType))); +await writeFile( + "dist/portable-reader/variants/entries.ts", + "export type Entries = " + tsType(entries.valueType) + ";\n", +); diff --git a/test/evolution.test.ts b/test/evolution.test.ts index 93c91b1..97ccb97 100644 --- a/test/evolution.test.ts +++ b/test/evolution.test.ts @@ -4,6 +4,7 @@ import { capabilityId as id, valueType, validateWorkspaceRevision } from "../src import { contentDigest, planEvolution, + reviewSemanticMajors, runtimeContracts, storageContracts, storageChangeRequiresMigration, @@ -126,6 +127,42 @@ test("semantic review receipts cover exact consumer/provider contracts", () => { assert.equal(planEvolution(before, after, { allowLegacy: true, reviews: [receipt] }).reviews[0]!.accepted, false); }); +test("execution semantic review is independent of storage migration and process action planning", () => { + const before = makeValidCapabilityWorkspace(); + before.conformances[0]!.id = id.conformance("conformance:project:named"); + const after = structuredClone(before); + after.conformances[0]!.semanticMajor = 2; + const report = reviewSemanticMajors(before, after); + assert.equal(report.checkerVersion, "quixos-semantic-review-v1"); + assert.equal(report.reviews.length, 1); + assert.equal(report.reviews[0]!.accepted, false); + assert.equal("migrationRequired" in report, false); + assert.equal("runtimeActions" in report, false); + assert.equal(report.blockers.length, 1); + const receipt = { + requirementDigest: report.reviews[0]!.requirementDigest, + decision: "accepted-unchanged" as const, + rationale: "The consumer only displays this provider's unchanged label contract", + agentId: "fixture-agent", + }; + assert.deepEqual(reviewSemanticMajors(before, after, [receipt]).blockers, []); + const oldReceipt = { + ...receipt, + requirementDigest: planEvolution(before, after, { allowLegacy: true }).reviews[0]!.requirementDigest, + }; + assert.equal(reviewSemanticMajors(before, after, [oldReceipt]).reviews[0]!.accepted, false); + after.packageImports[0]!.source.commit = "f".repeat(40); + assert.equal(reviewSemanticMajors(before, after, [receipt]).reviews[0]!.accepted, false); + assert.deepEqual(reviewSemanticMajors(null, after).blockers, []); + assert.throws( + () => reviewSemanticMajors(before, { ...after, workspaceId: id.workspace("different") }), + /different workspace/, + ); + const earlier = structuredClone(after); + earlier.conformances[0]!.semanticMajor = 1; + assert.ok(reviewSemanticMajors(after, earlier).blockers.some((message) => message.includes("decreases"))); +}); + test("evolution enrollment is explicit and never erases legacy ownership", () => { const workspace = makeValidCapabilityWorkspace(); const report = planEvolution(workspace, workspace); diff --git a/test/execution-contracts.test.ts b/test/execution-contracts.test.ts new file mode 100644 index 0000000..8346690 --- /dev/null +++ b/test/execution-contracts.test.ts @@ -0,0 +1,268 @@ +import assert from "node:assert/strict"; +import { test } from "node:test"; +import { compileCapabilityResourceSource } from "../src/capability-language/index.js"; + +const source = { repository: "https://example.test/execution.git", commit: "a".repeat(40) }; +const data = (execution = "snapshot") => + compileCapabilityResourceSource( + ` +interface Data id "interface:data" revision "interface:data@1" { + queryable portable value label id "member:label" : string { + get id "operation:label:get" execution ${execution}; + } + operation refresh id "member:refresh" : unit -> unit { call id "operation:refresh"; } +}`, + { source }, + ); +const program = (selection: string, execution = "snapshot targets [JavaScript, server]") => { + const contract = data(); + assert.ok(contract.ok, JSON.stringify(contract.diagnostics)); + assert.equal(contract.resource.kind, "interface"); + if (contract.resource.kind !== "interface") throw new Error("Expected interface"); + return compileCapabilityResourceSource( + `symbol JavaScript id "target:org.quixos.execution.javascript@1"; + +import interface Data; +package Reader id "package:reader" revision "package:reader@1" { + function label id "export:label" : unit -> string execution ${execution} requires { + interface data id "port:data" : Data ${selection}; + }; +}`, + { + source, + environment: { + interfaces: new Map([["Data", contract.resource.revision]]), + interfaceClosure: [contract.resource.revision], + }, + }, + ); +}; + +test("portable contracts preserve exact selected operations and targets", () => { + const result = program("using [label.get]"); + assert.ok(result.ok, JSON.stringify(result.diagnostics)); + assert.equal(result.resource.kind, "package"); + if (result.resource.kind !== "package") return; + const entry = result.resource.revision.exports[0]!; + assert.deepEqual(entry.execution, { + kind: "snapshot", + targets: ["target:org.quixos.execution.javascript@1", "server"], + }); + assert.deepEqual(entry.dependencyPorts[0]!.requirement, { + kind: "interface", + interfaceRevisionId: "interface:data@1", + operations: ["operation:label:get"], + }); +}); + +test("portable dependency diagnostics reject remote defaults and broad acquisition", () => { + for (const [selection, execution, message] of [ + ["using [refresh.call]", "snapshot targets [JavaScript, server]", "remote-only"], + ["", "snapshot targets [JavaScript, server]", "select"], + ["using [label.get, label.get]", "snapshot targets [JavaScript, server]", "duplicates"], + ["using [label.set]", "snapshot targets [JavaScript, server]", "Unknown"], + ]) { + const result = program(selection!, execution); + assert.equal(result.ok, false); + assert.match(JSON.stringify(result.diagnostics), new RegExp(message!)); + } +}); + +test("execution targets must resolve nominal declarations", () => { + const result = program("using [label.get]", "snapshot targets [arduino]"); + assert.equal(result.ok, false); + assert.match(JSON.stringify(result.diagnostics), /Unknown nominal execution target arduino/); +}); + +test("nominal targets are identified by symbols rather than platform spellings or interfaces", () => { + const compile = (alias: string, identity: string) => + compileCapabilityResourceSource( + `symbol ${alias} id "${identity}"; package P id "p" revision "p@1" { function f id "f" : unit -> unit execution transaction targets [${alias},server]; }`, + { source }, + ); + const device = compile("ESP32", "target:example.device@1"); + const renamed = compile("MyController", "target:example.device@1"); + assert.ok(device.ok && device.resource.kind === "package"); + assert.ok(renamed.ok && renamed.resource.kind === "package"); + assert.deepEqual(device.resource.revision.exports[0]!.execution, renamed.resource.revision.exports[0]!.execution); + assert.deepEqual(device.resource.externalInterfaces, [], "A target identity has no interface definition dependency"); + assert.equal(compile("server", "target:example.device@1").ok, false); + assert.equal(compile("Alias", "server").ok, false); +}); + +test("portable query getters and target declarations are checked", () => { + assert.equal(data("remote").ok, false); + assert.equal(data("transaction targets [browser]").ok, false); + assert.equal(data("snapshot targets [browser, browser]").ok, false); +}); + +test("snapshot exports reject mutable state ports but transactions admit them", () => { + const compile = (effect: string) => + compileCapabilityResourceSource( + `symbol JavaScript id "target:org.quixos.execution.javascript@1"; + +package Reader id "package:reader" revision "package:reader@1" { + function update id "export:update" : string -> unit execution ${effect} targets [JavaScript, server] requires { + state storage id "port:value" : string [read, write]; + }; +}`, + { source }, + ); + const snapshot = compile("snapshot"); + assert.equal(snapshot.ok, false); + assert.match(JSON.stringify(snapshot.diagnostics), /write primitives/); + const transaction = compile("transaction"); + assert.ok(transaction.ok, JSON.stringify(transaction.diagnostics)); +}); + +test("query placement distinguishes confirmed windows from local candidate extents", async () => { + const { compileQuery } = await import("../src/query/compile.js"); + const { linkQueries } = await import("../src/query/link.js"); + const { toBinary, fromBinary, fromJson } = await import("@bufbuild/protobuf"); + const { InstalledQuerySchema } = await import("../src/gen/camino/schema_pb.js"); + const { formatQx, parseQx, walkSyntax, applySourceEdits } = await import("../src/capability-language/index.js"); + const contract = data(); + assert.ok(contract.ok && contract.resource.kind === "interface"); + const iface = contract.resource.revision; + const text = `import interface Data; +package Reader id "package:reader" revision "package:reader@1" { + query Visible id "visible" root workspace Data document "visible.graphql" operation "Visible" { replica; } + query Presentation id "presentation" root workspace Data document "presentation.graphql" operation "Presentation" { local from Visible; } +}`; + const environment = { interfaces: new Map([["Data", iface]]), interfaceClosure: [iface] }; + const compiled = compileCapabilityResourceSource(formatQx(text), { source, environment }); + assert.ok(compiled.ok && compiled.resource.kind === "package", JSON.stringify(compiled.diagnostics)); + const pkg = compiled.resource.revision; + assert.deepEqual( + pkg.queries!.map((q) => q.placement), + [{ kind: "replica" }, { kind: "local", sourceQueryId: "visible" }], + ); + const parsed = parseQx(text); + const clause = [...walkSyntax(parsed.root)].find( + (node) => node.kind === "queryClause" && text.slice(node.start, node.end).startsWith("local"), + )!; + const edited = applySourceEdits(text, [{ ...clause, text: "local from Missing;" }]); + assert.equal(compileCapabilityResourceSource(edited, { source, environment }).ok, false); + const admission = pkg.queries![0]!, + local = pkg.queries![1]!; + const document = (name: string, field: string) => + `query ${name} { root { ${field}(first: 10) { entries { node { label } } } } }`; + pkg.checkedQueries = [ + await compileQuery(admission, [iface], async () => document("Visible", "objects")), + await compileQuery(local, [iface], async () => document("Presentation", "availableObjects")), + ]; + const confirmed = await compileQuery(local, [iface], async () => document("Presentation", "objects")); + assert.equal(confirmed.selection[0]!.selection[0]!.name, "objects"); + const remote = structuredClone(iface); + delete remote.members[0]!.operations[0]!.execution; + await assert.rejects( + compileQuery(local, [remote], async () => document("Presentation", "availableObjects")), + /graph snapshot getter/, + ); + const { capabilityId } = await import("../src/capability-model/index.js"); + const linked = linkQueries({ + id: capabilityId.workspaceRevision("w@1"), + workspaceId: capabilityId.workspace("w"), + sourceRootCommit: "a".repeat(40), + parentRevisionIds: [], + atoms: [], + sharedAttachments: [], + interfaceImports: [iface], + packageImports: [pkg], + conformances: [], + constructors: [], + }); + const wire = fromJson(InstalledQuerySchema, linked[1]!.runtime!); + const roundtrip = fromBinary(InstalledQuerySchema, toBinary(InstalledQuerySchema, wire)); + assert.deepEqual(roundtrip.placement?.kind, { case: "localSourceQueryId", value: "package:reader@1:visible" }); +}); + +test("generated interface ports expose only selected operations", async () => { + const { generateTypeScriptBindings } = await import("../src/bindings/index.js"); + const contract = data(); + const result = program("using [label.get]"); + assert.ok(contract.ok && contract.resource.kind === "interface"); + assert.ok(result.ok && result.resource.kind === "package"); + const generated = generateTypeScriptBindings( + { + format: "quixos-bindings", + version: 1, + interfaces: [contract.resource.revision], + packages: [result.resource.revision], + }, + "package:reader@1", + ); + assert.match(generated, /label\.get/); + assert.doesNotMatch(generated.split("export type Results")[0]!, /refresh\.call/); + assert.doesNotMatch(generated.split("const specs")[1]!, /refresh\.call/); +}); + +test("workspace validation checks concrete package and storage effects", async () => { + const { makeValidCapabilityWorkspace, fixtureId } = await import("./fixtures/capability-model.js"); + const { validateWorkspaceRevision } = await import("../src/capability-model/index.js"); + const workspace = makeValidCapabilityWorkspace(); + const named = workspace.interfaceImports.find((i) => i.revisionId === fixtureId.namedV1)!; + const setter = named.members.flatMap((m) => m.operations).find((o) => o.id === fixtureId.namedSet)!; + setter.execution = { kind: "snapshot", targets: ["browser"] }; + assert.ok(validateWorkspaceRevision(workspace).some((i) => i.message.includes("bind write primitives"))); + setter.execution.kind = "transaction"; + assert.deepEqual(validateWorkspaceRevision(workspace), []); + const summary = workspace.interfaceImports.find((i) => i.revisionId === fixtureId.summaryV1)!; + summary.members[0]!.operations[0]!.execution = { kind: "snapshot", targets: ["browser"] }; + assert.ok( + validateWorkspaceRevision(workspace).some( + (i) => i.path.includes(fixtureId.summaryGet) && i.message.includes("remote-only"), + ), + ); +}); + +test("generic selected ports preserve operation identities and execution through specialization", async () => { + const { specializePackageExport, capabilityId } = await import("../src/capability-model/index.js"); + const iface = compileCapabilityResourceSource( + `interface Cell id "cell" revision "cell@1" { + value content id "content" : T { get id "content:get" execution snapshot; } + }`, + { source }, + ); + assert.ok(iface.ok && iface.resource.kind === "interface", JSON.stringify(iface.diagnostics)); + const pkg = compileCapabilityResourceSource( + `symbol JavaScript id "target:org.quixos.execution.javascript@1"; +import interface Cell; + package P id "p" revision "p@1" { + function load id "read" : unit -> T execution snapshot targets [JavaScript, server] requires { + interface cell id "cell" : Cell using [content.get]; + }; + }`, + { + source, + environment: { + interfaces: new Map([["Cell", iface.resource.revision]]), + interfaceClosure: [iface.resource.revision], + }, + }, + ); + assert.ok(pkg.ok && pkg.resource.kind === "package", JSON.stringify(pkg.diagnostics)); + const definition = pkg.resource.revision.genericExports![0]!; + const specialized = specializePackageExport( + pkg.resource.revision, + definition, + [{ kind: "value", type: { kind: "scalar", name: "string" } }], + { + arguments: new Map(), + applyInterface: (id, args) => { + assert.equal(id, "cell@1"); + assert.deepEqual(args, [{ kind: "value", type: { kind: "scalar", name: "string" } }]); + return capabilityId.interfaceRevision("cell:string"); + }, + implementsInterface: () => false, + }, + ); + assert.deepEqual(specialized.execution, { + kind: "snapshot", + targets: ["target:org.quixos.execution.javascript@1", "server"], + }); + assert.deepEqual(specialized.dependencyPorts[0]!.requirement.kind, "interface"); + const requirement = specialized.dependencyPorts[0]!.requirement; + if (requirement.kind === "interface") + assert.deepEqual(requirement.operations, [capabilityId.operation("content:get")]); +}); diff --git a/test/execution-groups.test.ts b/test/execution-groups.test.ts new file mode 100644 index 0000000..22cc215 --- /dev/null +++ b/test/execution-groups.test.ts @@ -0,0 +1,122 @@ +import assert from "node:assert/strict"; +import { test } from "node:test"; +import { loadReaderWorkspace } from "./reader-fixture.js"; +import { buildArtifactFixture } from "./artifact-fixture.js"; +import { capabilityId as id, type WorkspaceRevision } from "../src/capability-model/types.js"; +import { + compileExecutionPlan, + compileExecutionGroups, + checkExecutionWorld, + checkedWorldDigest, + type ExecutionInstallation, + type ExecutionEnvironment, +} from "../src/execution-world/index.js"; +const environment: ExecutionEnvironment = { + placement: "service", + hostDigest: "a".repeat(64), + policyDigest: "b".repeat(64), + credentialGeneration: "config:1", +}; +const prepare = async (workspace: WorkspaceRevision) => { + const plan = compileExecutionPlan(workspace); + const fixtures = plan.definition.packages.map((pkg) => buildArtifactFixture(plan, pkg.revision)); + const world = await checkExecutionWorld( + plan, + fixtures.map((f) => f.manifest), + fixtures[0]!.read, + ); + const installation: ExecutionInstallation = { + codec: "camino-execution-installation-v1", + plan: plan.digest, + world: checkedWorldDigest(world), + routes: {}, + components: [], + packages: fixtures.map((f) => ({ + revision: f.manifest.packageRevisionId, + root: "/fixture", + service: f.manifest.implementations[0]!, + module: f.manifest.implementations[1]!, + })), + }; + const environments = Object.fromEntries(plan.definition.packages.map((pkg) => [pkg.revision, { ...environment }])); + return { plan, installation, environments, groups: () => compileExecutionGroups(plan, installation, environments) }; +}; + +test("execution reuse ignores world/source/display changes and unrelated packages", async () => { + const workspace = await loadReaderWorkspace(), + original = await prepare(workspace); + const before = original.groups()[0]!.executionDigest; + workspace.sourceRootCommit = "f".repeat(40); + workspace.packageImports[0]!.displayName = "Renamed"; + workspace.packageImports[0]!.source.commit = "e".repeat(40); + workspace.packageImports.push({ + packageId: id.package("unrelated"), + revisionId: id.packageRevision("unrelated@1"), + displayName: "Other", + source: workspace.packageImports[0]!.source, + exports: [], + }); + const after = await prepare(workspace); + assert.notEqual(after.plan.digest, original.plan.digest); + assert.equal( + after.groups().find((g) => g.logicalId === workspace.packageImports[0]!.packageId)!.executionDigest, + before, + ); + original.environments[workspace.packageImports[0]!.revisionId]!.credentialGeneration = "config:2"; + assert.notEqual(original.groups()[0]!.executionDigest, before); + original.environments[workspace.packageImports[0]!.revisionId]!.placement = "module"; + assert.notEqual(original.groups()[0]!.executionDigest, before); +}); + +test("outbound provider changes invalidate consumers while caller changes preserve providers", async () => { + const workspace = await loadReaderWorkspace((name, text) => + name === "DocumentData.qx" ? text.replace("queryable value content", "queryable portable value content") : text, + ); + const revision = id.packageRevision("provider@1"), + exportId = id.packageExport("provider:content"), + portId = id.dependencyPort("provider:state"); + workspace.packageImports.push({ + packageId: id.package("provider"), + revisionId: revision, + displayName: "Provider", + source: workspace.packageImports[0]!.source, + exports: [ + { + kind: "operation", + id: exportId, + displayName: "content", + mode: "call", + inputType: { kind: "builtin", name: "unit" }, + outputType: { kind: "scalar", name: "string" }, + execution: { kind: "snapshot", targets: ["target:org.quixos.execution.javascript@1", "server"] }, + receiverRequirement: { kind: "exact-atom", atomId: id.atom("atom:reader:document") }, + dependencyPorts: [ + { + id: portId, + displayName: "state", + requirement: { kind: "state", valueType: { kind: "scalar", name: "string" }, primitives: ["read"] }, + }, + ], + }, + ], + }); + const owner = workspace.conformances.find((c) => c.interfaceRevisionId === "interface:reader:document-data@1")!; + owner.operationBindings.find((b) => b.operationId === "document:content:get")!.binding = { + kind: "package", + packageRevisionId: revision, + exportId, + dependencies: [{ portId, binding: { kind: "state", slotId: id.slot("slot:document:content") } }], + }; + const prepared = await prepare(workspace); + const values = () => new Map(prepared.groups().map((g) => [g.logicalId, g.executionDigest])); + const before = values(); + prepared.installation.packages.find((p) => p.revision === "package:reader-runtime@1")!.service!.contentDigest = + "c".repeat(64); + const callerChanged = values(); + assert.equal(callerChanged.get("provider"), before.get("provider")); + assert.notEqual(callerChanged.get("package:reader-runtime"), before.get("package:reader-runtime")); + prepared.installation.packages.find((p) => p.revision === revision)!.service!.contentDigest = "d".repeat(64); + const providerChanged = values(); + assert.notEqual(providerChanged.get("provider"), callerChanged.get("provider")); + assert.notEqual(providerChanged.get("package:reader-runtime"), callerChanged.get("package:reader-runtime")); +}); diff --git a/test/execution-scaffold-fixture.ts b/test/execution-scaffold-fixture.ts new file mode 100644 index 0000000..a108ccc --- /dev/null +++ b/test/execution-scaffold-fixture.ts @@ -0,0 +1,53 @@ +import assert from "node:assert/strict"; +import fs from "node:fs/promises"; +import path from "node:path"; +import { execFileSync } from "node:child_process"; +import { scaffoldRecipe } from "../src/capability-language/scaffold-recipes.js"; +import { planStructure, applyStructure } from "../src/capability-language/structural-plan.js"; +import { compileCapabilityResourceSource, compileCapabilitySource } from "../src/capability-language/parser.js"; +import { compileExecutionPlan } from "../src/execution-world/plan.js"; + +/** Exercise the authoring insertion path, then let the Nix gate build its actual flake output. */ +export async function emitExecutionScaffold(root: string) { + await fs.mkdir(root, { recursive: true }); + execFileSync("git", ["-C", root, "init"], { stdio: "ignore" }); + const source = { repository: "https://example.test/scaffold.git", commit: "a".repeat(40) }; + const apply = async ( + command: Parameters[1], + spec: Partial[2]>, + ) => applyStructure(await planStructure(root, await scaffoldRecipe(root, command, { source, ...spec }))); + await apply("package", { + name: "Scaffold", + id: "package:scaffold", + revision: "package:scaffold@1", + template: "typescript-execution", + tools: { quixos: source, protocol: source, helpers: source, sdk: source }, + }); + for (const [name, execution] of [ + ["echo", "snapshot targets [JavaScript, server]"], + ["serverEcho", "snapshot targets [server]"], + ["remoteEcho", "remote"], + ]) { + await apply("function", { + name, + id: "scaffold:" + name, + declaration: `function ${name} id "scaffold:${name}" : string -> string execution ${execution};`, + }); + const file = path.join(root, `src/impl/${name}.ts`); + const placeholder = await fs.readFile(file, "utf8"); + await fs.writeFile(file, placeholder.replace(/async \(_context\) => \{.*\};/, "async ({ input }) => input;")); + } + const pkg = compileCapabilityResourceSource(await fs.readFile(path.join(root, "package.qx"), "utf8"), { source }); + assert.ok(pkg.ok && pkg.resource.kind === "package", JSON.stringify(pkg.diagnostics)); + const workspace = compileCapabilitySource( + `workspace W id "scaffold-workspace" revision "scaffold-workspace@1" commit "${source.commit}" { import package Scaffold; }`, + "workspace.qx", + { + packages: new Map([["Scaffold", pkg.resource.revision]]), + }, + ); + assert.ok(workspace.ok, JSON.stringify(workspace.diagnostics)); + await fs.rm(path.join(root, ".quixos"), { recursive: true, force: true }); + await fs.rm(path.join(root, ".git"), { recursive: true, force: true }); + return compileExecutionPlan(workspace.workspace); +} diff --git a/test/execution-world.test.ts b/test/execution-world.test.ts new file mode 100644 index 0000000..072d13a --- /dev/null +++ b/test/execution-world.test.ts @@ -0,0 +1,404 @@ +import assert from "node:assert/strict"; +import { test } from "node:test"; +import { createHash } from "node:crypto"; +import { loadReaderWorkspace } from "./reader-fixture.js"; +import { + compileExecutionPlan, + checkExecutionWorld, + checkedWorldDigest, + implementationDigest, + hexDigest, + parsePackageArtifacts, + artifactPath, + type PackageArtifacts, + type ExecutionPlan, + type ArtifactImplementation, +} from "../src/execution-world/index.js"; + +import { buildArtifactFixture as buildFixture } from "./artifact-fixture.js"; +import { compileKernelProgram } from "../src/execution-world/kernel.js"; +import { loadCompositeWorkspace } from "./composite-fixture.js"; +import { standardNativeBackends } from "../src/execution-world/native-backends.js"; + +test("optional targets close over calls into another package", async () => { + const workspace = await loadCompositeWorkspace(); + const before = compileExecutionPlan(workspace).definition.operations.find( + (op) => op.operationId === "document:annotate:call", + )!; + assert.ok(before, "fixture exposes the composed add-highlight operation"); + assert.equal(before.availableTargets.length, 2); + workspace.packageImports.find((pkg) => pkg.revisionId === "package:comment-runtime@1")!.exports[0]!.execution = { + kind: "transaction", + targets: ["server"], + }; + const after = compileExecutionPlan(workspace).definition.operations.find((op) => op.key === before.key)!; + assert.deepEqual(after.availableTargets, ["server"]); + assert.deepEqual(after.execution, before.execution); +}); + +test("native support can be supplied for a nominal device target without platform syntax", async () => { + const workspace = await loadReaderWorkspace(); + const target = "interface:example.esp32@1"; + for (const pkg of workspace.packageImports) + for (const entry of pkg.exports) { + if (entry.execution && entry.execution.kind !== "remote") entry.execution.targets.push(target); + } + const plan = compileExecutionPlan(workspace, [ + ...standardNativeBackends, + { target, supports: (binding) => binding.kind === "state" || binding.kind === "edge" }, + ]); + assert.ok( + plan.definition.operations + .filter((op) => op.binding.kind === "state") + .every((op) => op.availableTargets.includes(target)), + ); + assert.ok( + plan.definition.operations.some((op) => op.binding.kind === "package" && op.availableTargets.includes(target)), + ); +}); +const resign = (implementation: ArtifactImplementation) => { + const { contentDigest, ...body } = implementation; + implementation.contentDigest = implementationDigest(body); +}; + +test("installed targets are implementation properties; authoritative fallback preserves the interface", async () => { + const workspace = await loadReaderWorkspace(); + const plan = compileExecutionPlan(workspace); + const factory = plan.definition.operations.find( + (op) => op.binding.kind === "package" && op.binding.exportId === "reader:create-document", + )!; + assert.deepEqual(factory.execution, { kind: "transaction", targets: ["server"] }); + assert.deepEqual(factory.availableTargets, ["server", "target:org.quixos.execution.javascript@1"]); + const kernel = compileKernelProgram(plan) as { operations: { key: string; targets: string[] }[] }; + assert.deepEqual(kernel.operations.find((op) => op.key === factory.key)!.targets, [ + "server", + "target:org.quixos.execution.javascript@1", + ]); + + const provider = workspace.packageImports + .flatMap((pkg) => pkg.exports) + .find((entry) => entry.id === "reader:create-document")!; + provider.execution = { kind: "transaction", targets: ["server"] }; + const authoritative = compileExecutionPlan(workspace).definition.operations.find((op) => op.key === factory.key)!; + assert.deepEqual(authoritative.availableTargets, ["server"]); + assert.deepEqual(authoritative.execution, factory.execution); +}); + +test("reader execution plan closes selector storage and traversed interface dependencies", async () => { + const workspace = await loadReaderWorkspace(); + const plan = compileExecutionPlan(workspace); + assert.equal(plan.digest, compileExecutionPlan(structuredClone(workspace)).digest); + assert.equal(plan.definition.owners.length, 5); + const excerpt = + plan.definition.operations.find((o) => o.operationId === "highlight:excerpt:get") ?? + plan.definition.operations.find((o) => o.binding.kind === "package" && o.binding.exportId === "reader:excerpt")!; + assert.ok(excerpt.dependencies.some((d) => d.kind === "state" && d.slot === "slot:highlight:range")); + assert.ok( + excerpt.dependencies.some( + (d) => d.kind === "edge" && d.edge === "edge:document-highlights" && d.projection === "projection:document", + ), + ); + const calls = excerpt.dependencies.filter((d) => d.kind === "operation"); + assert.equal(calls.length, 2); + for (const call of calls) { + assert.equal(call.via?.projectionId, "projection:document"); + const target = plan.definition.operations.find((o) => o.key === call.operation)!; + assert.ok(target.dependencies.some((d) => d.kind === "state" && d.slot.startsWith("slot:document:"))); + } + const admission = plan.definition.queries.find((q) => q.query.checked.declaration.displayName === "ReplicaInputs")!; + assert.ok(admission.admittedOperations.includes(excerpt.key)); + assert.equal(plan.definition.profiles[0]!.profile, "camino-automerge-3.4.1-structured-v1"); + const factory = plan.definition.operations.find( + (o) => o.binding.kind === "package" && o.binding.exportId === "reader:create-document", + )!; + assert.equal(factory.scope, "class"); + assert.ok( + factory.binding.kind === "package" && factory.binding.dependencies.some((d) => d.binding.kind === "creation"), + ); + assert.ok(plan.definition.owners.some((o) => Array.isArray(o.initialization) && o.initialization.length > 0)); +}); + +test("artifact closure checks bytes, exact source/schema, ABI, exports and target coverage", async () => { + const plan = compileExecutionPlan(await loadReaderWorkspace()); + const fixture = buildFixture(plan); + const world = await checkExecutionWorld(plan, [fixture.manifest], fixture.read); + assert.equal(world.artifacts.length, 3); + assert.equal( + checkedWorldDigest(world), + checkedWorldDigest(await checkExecutionWorld(plan, [structuredClone(fixture.manifest)], fixture.read)), + ); + const negative = async (change: (manifest: PackageArtifacts) => void, pattern: RegExp) => { + const manifest = structuredClone(fixture.manifest); + change(manifest); + await assert.rejects(checkExecutionWorld(plan, [manifest], fixture.read), pattern); + }; + await negative((m) => { + m.bindingDigest = "0".repeat(64); + }, /Substituted binding schema/); + await negative((m) => { + m.sourceDigest = "0".repeat(64); + }, /source revision/); + await negative((m) => { + (m.versions as unknown as Record).value = "v2"; + }, /Incompatible runtime codecs/); + await negative((m) => { + m.implementations = m.implementations.filter((i) => i.target !== "target:org.quixos.execution.javascript@1"); + }, /Missing target:org.quixos.execution.javascript@1 implementation/); + await negative((m) => { + m.implementations = m.implementations.filter((i) => i.compatibility !== "quixos-service-v1"); + }, /Missing service fallback/); + await negative((m) => { + m.implementations[0]!.exports.push("wrong-export"); + resign(m.implementations[0]!); + }, /Unknown export/); + await negative((m) => { + m.implementations[0]!.files[0]!.digest = "0".repeat(64); + resign(m.implementations[0]!); + }, /Artifact bytes/); + await negative((m) => { + m.implementations[0]!.exports.pop(); + }, /metadata digest mismatch/); + await assert.rejects( + checkExecutionWorld(plan, [fixture.manifest], async () => new Uint8Array([1])), + /Artifact bytes/, + ); + for (const path of ["/tmp/code", "../code", "a/../b", "a//b", "a\\b", "a%2fb", "a?query"]) + assert.throws(() => artifactPath(path), /normalized relative/); + const changed = structuredClone(plan); + changed.definition.versions.value = "wrong" as typeof changed.definition.versions.value; + await assert.rejects( + checkExecutionWorld(changed, [fixture.manifest], fixture.read), + /Unsupported plan codec versions/, + ); +}); + +test("artifact identity changes when code or registry changes without hashing its own digest", async () => { + const plan = compileExecutionPlan(await loadReaderWorkspace()), + fixture = buildFixture(plan); + const before = checkedWorldDigest(await checkExecutionWorld(plan, [fixture.manifest], fixture.read)); + const manifest = structuredClone(fixture.manifest); + manifest.implementations[2]!.registryExport = "anotherRegistry"; + resign(manifest.implementations[2]!); + const after = checkedWorldDigest(await checkExecutionWorld(plan, [manifest], fixture.read)); + assert.notEqual(before, after); + assert.equal(parsePackageArtifacts(manifest).implementations[2]!.registryExport, "anotherRegistry"); + assert.equal(hexDigest(plan.definition), plan.digest); +}); + +test("transactions cannot derive their membership from an admitted presentation query", async () => { + const workspace = await loadReaderWorkspace(), + pkg = workspace.packageImports[0]!; + const query = pkg.queries!.find((q) => q.placement?.kind === "local")!; + const entry = pkg.exports.find((e) => e.id === "reader:add-highlight")!; + const portId = "test:query-port" as (typeof entry.dependencyPorts)[number]["id"]; + entry.dependencyPorts.push({ + id: portId, + displayName: "visible", + requirement: { kind: "query", packageRevisionId: pkg.revisionId, queryId: query.id }, + }); + const bound = workspace.conformances + .flatMap((c) => c.operationBindings) + .find((o) => o.binding.kind === "package" && o.binding.exportId === entry.id)!.binding; + assert.equal(bound.kind, "package"); + if (bound.kind !== "package") return; + bound.dependencies.push({ + portId, + binding: { kind: "query", packageRevisionId: pkg.revisionId, queryId: query.id, scope: "object" }, + }); + assert.throws(() => compileExecutionPlan(workspace), /QUERY_IN_TRANSACTION.*test:query-port/); +}); + +test("port ABI carries only declared port actions, never caller-selected storage IDs", async () => { + const { checkPortInvocation } = await import("../src/execution-world/index.js"); + const frame = { + abi: "quixos-transaction-ports-v1", + frame: "frame1", + invocation: "invoke1", + sequence: 1, + portId: "content", + action: { kind: "state", primitive: "read" }, + input: { codec: "camino-value-v1", usage: "argument", value: null }, + }; + assert.deepEqual(checkPortInvocation(frame), frame); + assert.throws(() => checkPortInvocation({ ...frame, slotId: "private" }), /fields or ABI/); + assert.throws( + () => checkPortInvocation({ ...frame, action: { kind: "state", primitive: "read", slotId: "private" } }), + /Invalid port action/, + ); + assert.throws(() => checkPortInvocation({ ...frame, sequence: NaN }), /sequence/); + assert.throws(() => checkPortInvocation({ ...frame, action: { kind: "creation", key: "" } }), /Invalid port action/); +}); + +test("local queries cannot silently expand the replica relationship traversal", async () => { + const workspace = await loadReaderWorkspace((name, text) => + name === "queries/replica-inputs.graphql" ? "query ReplicaInputs { root { sourceRevision content } }" : text, + ); + assert.throws(() => compileExecutionPlan(workspace), /REPLICA_TRAVERSAL_MISSING/); +}); + +test("local relationships require complete source candidates even when a filter initially matches", async () => { + for (const selection of ["highlights(first: 10)", 'highlights(all: 1000, where: {color: {eq: "yellow"}})']) { + const workspace = await loadReaderWorkspace((name, text) => + name === "queries/replica-inputs.graphql" ? text.replace("highlights(all: 1000)", selection) : text, + ); + assert.throws(() => compileExecutionPlan(workspace), /REPLICA_RELATION_INCOMPLETE/); + } +}); + +test("closed generic applications retain distinct identities and codec shapes", async () => { + const { compileCapabilityResourceSource, compileCapabilitySource } = + await import("../src/capability-language/index.js"); + const source = { repository: "https://example.test/generic.git", commit: "a".repeat(40) }; + const definition = compileCapabilityResourceSource( + `interface Cell id "cell" revision "cell@1" { + value content id "value" : V { get id "get" execution snapshot; } + }`, + { source }, + ); + assert.ok(definition.ok && definition.resource.kind === "interface", JSON.stringify(definition.diagnostics)); + const compiled = compileCapabilitySource( + `workspace Demo id "ws" revision "ws@1" commit "${source.commit}" { + import interface Cell; atom Item id "item"; + conform Item as Cell id "text-owner" { private state Text id "text-slot" on Item : string policy register; bind content.get to state Text.read; } + conform Item as Cell id "number-owner" { private state Number id "number-slot" on Item : int64 policy register; bind content.get to state Number.read; } + }`, + "workspace.qx", + { interfaces: new Map([["Cell", definition.resource.revision]]) }, + ); + assert.ok(compiled.ok, JSON.stringify(compiled.diagnostics)); + const plan = compileExecutionPlan(compiled.workspace); + assert.equal(plan.definition.operations.length, 2); + assert.notEqual( + plan.definition.operations[0]!.interfaceRevisionId, + plan.definition.operations[1]!.interfaceRevisionId, + ); + assert.deepEqual( + new Set(plan.definition.operations.map((o) => (o.output.kind === "scalar" ? o.output.name : null))), + new Set(["string", "int64"]), + ); + assert.ok(plan.definition.bindingSchema.interfaces.every((i) => i.application)); +}); + +test("unbound portable exports cannot claim an unsupported transport codec", async () => { + const workspace = await loadReaderWorkspace(); + workspace.packageImports[0]!.exports.find((e) => e.id === "reader:validate-range")!.outputType = { + kind: "message", + descriptorId: "opaque-message", + }; + assert.throws(() => compileExecutionPlan(workspace), /UNSUPPORTED_PORTABLE_CODEC.*reader:validate-range/); +}); + +test("query dependencies propagate target requirements through selected getters", async () => { + const workspace = await loadReaderWorkspace(), + pkg = workspace.packageImports[0]!; + const query = pkg.queries!.find((q) => q.displayName === "HighlightList")!; + const entry = pkg.exports.find((e) => e.id === "reader:excerpt")!; + const portId = "test:selector-query" as (typeof entry.dependencyPorts)[number]["id"]; + entry.dependencyPorts.push({ + id: portId, + displayName: "visible", + requirement: { kind: "query", packageRevisionId: pkg.revisionId, queryId: query.id }, + }); + const bound = workspace.conformances + .flatMap((c) => c.operationBindings) + .find((o) => o.binding.kind === "package" && o.binding.exportId === entry.id)!.binding; + assert.equal(bound.kind, "package"); + if (bound.kind !== "package") return; + bound.dependencies.push({ + portId, + binding: { kind: "query", packageRevisionId: pkg.revisionId, queryId: query.id, scope: "workspace" }, + }); + compileExecutionPlan(workspace); + // Package artifact support is checked over the composed graph; an interface + // declaration cannot manufacture an authoritative implementation. + entry.execution = { kind: "snapshot", targets: ["target:org.quixos.execution.javascript@1"] }; + assert.throws(() => compileExecutionPlan(workspace), /MISSING_SERVER_IMPLEMENTATION/); +}); + +test("implementation selection is explicit, target checked and independent of manifest order", async () => { + const { selectArtifactImplementation } = await import("../src/execution-world/index.js"); + const plan = compileExecutionPlan(await loadReaderWorkspace()); + const { manifest, read } = buildFixture(plan); + const selection = { + target: "target:org.quixos.execution.javascript@1", + compatibility: "quixos-js-module-v1" as const, + exports: [manifest.implementations[0]!.exports[0]!], + }; + assert.equal(selectArtifactImplementation(manifest, selection).id, "browser"); + const alternative = structuredClone(manifest.implementations.find((i) => i.id === "browser")!); + alternative.id = "browser-alternative"; + resign(alternative); + manifest.implementations.unshift(alternative); + await checkExecutionWorld(plan, [manifest], read); + assert.throws(() => selectArtifactImplementation(manifest, selection), /AMBIGUOUS_IMPLEMENTATION/); + assert.equal(selectArtifactImplementation(manifest, { ...selection, id: alternative.id }).id, alternative.id); + manifest.implementations.reverse(); + assert.equal(selectArtifactImplementation(manifest, { ...selection, id: alternative.id }).id, alternative.id); + for (const invalid of [ + { ...selection, id: "missing" }, + { ...selection, id: "service" }, + { ...selection, id: alternative.id, target: "arduino" }, + { ...selection, id: alternative.id, exports: ["not-exported"] }, + ]) + assert.throws(() => selectArtifactImplementation(manifest, invalid), /IMPLEMENTATION_UNAVAILABLE/); + assert.equal( + selectArtifactImplementation(manifest, { ...selection, target: "server", compatibility: "quixos-service-v1" }).id, + "service", + ); +}); + +test("execution planning rejects unsupported grouping before installing the query", async () => { + const workspace = await loadReaderWorkspace((name, text) => + name === "queries/reader-screen.graphql" + ? text.replace("aggregate { count }", "groups(by: {color: true}, first: 10) { entries { aggregate { count } } }") + : text, + ); + assert.throws(() => compileExecutionPlan(workspace), /QUERY_OPERATOR_UNSUPPORTED.*grouping/); +}); + +test("a count-only local query still requires complete relationship admission", async () => { + for (const source of [ + "query ReplicaInputs { root { sourceRevision } }", + "query ReplicaInputs { root { highlights(first: 1) { entries { node { color } } } } }", + ]) { + const workspace = await loadReaderWorkspace((name, text) => { + if (name === "queries/replica-inputs.graphql") return source; + if (name === "queries/reader-screen.graphql") + return "query ReaderScreen { root { _camino { relations { highlights { aggregate { count } } } } } }"; + return text; + }); + assert.throws(() => compileExecutionPlan(workspace), /REPLICA_TRAVERSAL_MISSING|REPLICA_RELATION_INCOMPLETE/); + } +}); + +test("public reflection contains nominal contracts and callable descriptors without private bindings", async () => { + const { publicDefinitionSnapshot } = await import("../src/execution-world/public-reflection.js"); + const plan = compileExecutionPlan(await loadReaderWorkspace()); + const installation = { + codec: "camino-execution-installation-v1" as const, + plan: plan.digest, + world: "test", + packages: plan.definition.workspace.packageImports.map((pkg) => ({ + revision: pkg.revisionId, + root: "private-installation-root", + })), + components: [], + routes: {}, + }; + const snapshot = publicDefinitionSnapshot(plan, installation); + assert.ok(snapshot.rows.interfaces.length); + assert.ok(snapshot.rows.queries.length); + assert.ok(snapshot.rows.conformances.length); + const publicText = JSON.stringify(snapshot); + for (const privateKey of [ + "privateAttachments", + "operationBindings", + "relationshipMaterializations", + "storagePolicy", + "ports", + ]) + assert.ok(!publicText.includes('"' + privateKey + '"'), privateKey); + assert.ok(publicText.includes('"descriptor"')); + assert.ok(publicText.includes('"scope":"class"')); + assert.equal(snapshot.id, publicDefinitionSnapshot(plan, installation).id); +}); diff --git a/test/fixtures/async-objects/ControllerRuntime.qx b/test/fixtures/async-objects/ControllerRuntime.qx new file mode 100644 index 0000000..9e4e742 --- /dev/null +++ b/test/fixtures/async-objects/ControllerRuntime.qx @@ -0,0 +1,22 @@ +external atom Document id "async:document"; +import interface DocumentData; +import interface Summarizer; +package ControllerRuntime id "async:controller-runtime" revision "async:controller-runtime@1" { + function echo id "async:controller:echo" : string -> string execution snapshot targets [server]; + operation refresh id "async:controller:refresh" : record { operationId: string; } -> string + mode call execution remote receiver atom Document requires { + interface document id "async:controller:document" : DocumentData using [content.get, summary.set]; + interface provider id "async:controller:provider" : Summarizer using [summarize.call]; + }; + operation finish id "async:controller:finish" + : record { operationId: string; summary: record { text: string; basedOn: string; }; } -> unit + mode call execution transaction targets [server] receiver atom Document requires { + state job id "async:finish:job" : optional [read, write]; + state summary id "async:finish:summary" : record { text: string; basedOn: string; } [write]; + }; + operation runPending id "async:controller:run-pending" : optional -> unit + mode call execution remote receiver atom Document requires { + interface document id "async:pending:document" : DocumentData using [content.get, recordSummary.call]; + interface provider id "async:pending:provider" : Summarizer using [summarize.call]; + }; +} diff --git a/test/fixtures/async-objects/DocumentData.qx b/test/fixtures/async-objects/DocumentData.qx new file mode 100644 index 0000000..2427bcf --- /dev/null +++ b/test/fixtures/async-objects/DocumentData.qx @@ -0,0 +1,17 @@ +type Summary = record { text: string; basedOn: string; }; +interface DocumentData id "async:document-data" revision "async:document-data@1" { + queryable value content id "async:content" : string { + get id "async:content:get" execution snapshot; + } + queryable value summary id "async:summary" : Summary { + get id "async:summary:get" execution snapshot; + set id "async:summary:set" execution transaction; + } + queryable value pendingJob id "async:pending-job" : optional { + get id "async:pending-job:get" execution snapshot; + set id "async:pending-job:set" execution transaction; + } + operation recordSummary id "async:record-summary" : record { operationId: string; summary: Summary; } -> unit { + call id "async:record-summary:call" execution transaction; + } +} diff --git a/test/fixtures/async-objects/ProviderRuntime.qx b/test/fixtures/async-objects/ProviderRuntime.qx new file mode 100644 index 0000000..8dfe9f9 --- /dev/null +++ b/test/fixtures/async-objects/ProviderRuntime.qx @@ -0,0 +1,5 @@ +external atom Provider id "async:provider"; +package ProviderRuntime id "async:provider-runtime" revision "async:provider-runtime@2" { + operation summarize id "async:provider:summarize" : string -> string + mode call execution remote receiver atom Provider; +} diff --git a/test/fixtures/async-objects/Summarizer.qx b/test/fixtures/async-objects/Summarizer.qx new file mode 100644 index 0000000..84f14f4 --- /dev/null +++ b/test/fixtures/async-objects/Summarizer.qx @@ -0,0 +1,5 @@ +interface Summarizer id "async:summarizer" revision "async:summarizer@1" { + operation summarize id "async:summarize" : string -> string { + call id "async:summarize:call" execution remote; + } +} diff --git a/test/fixtures/async-objects/SummaryController.qx b/test/fixtures/async-objects/SummaryController.qx new file mode 100644 index 0000000..75b6002 --- /dev/null +++ b/test/fixtures/async-objects/SummaryController.qx @@ -0,0 +1,8 @@ +interface SummaryController id "async:controller" revision "async:controller@1" { + operation refresh id "async:refresh" : record { operationId: string; } -> string { + call id "async:refresh:call" execution remote; + } + operation processPending id "async:process-pending" : optional -> unit { + call id "async:process-pending:call" execution remote; + } +} diff --git a/test/fixtures/async-objects/workspace.qx b/test/fixtures/async-objects/workspace.qx new file mode 100644 index 0000000..85e8bd9 --- /dev/null +++ b/test/fixtures/async-objects/workspace.qx @@ -0,0 +1,43 @@ +workspace AsyncObjects id "workspace:async" revision "workspace:async@1" commit "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" { + import interface DocumentData; + import interface SummaryController; + import interface Summarizer; + import package ControllerRuntime; + import package ProviderRuntime; + atom Document id "async:document"; + atom Provider id "async:provider"; + conform Document as DocumentData id "async:document:data" { + private state Content id "async:state:content" on Document : string policy register; + private state Summary id "async:state:summary" on Document : record { text: string; basedOn: string; } + policy register default { "text": "", "basedOn": "" }; + private state PendingJob id "async:state:pending-job" on Document : optional + policy register default null; + private edge ProviderLink id "async:provider-link" { + atom Document projection provider id "async:provider-projection" exactly-one public-traversal; + atom Provider projection documents id "async:documents-projection" many-unique; + } + initialize { content to state Content; provider to edge ProviderLink.provider; }; + bind content.get to state Content.read; + bind summary.get to state Summary.read; + bind summary.set to state Summary.write; + bind pendingJob.get to state PendingJob.read; + bind pendingJob.set to state PendingJob.write; + bind recordSummary.call to package ControllerRuntime.finish with { + job to state PendingJob; + summary to state Summary; + }; + } + conform Document as SummaryController id "async:document:controller" { + bind refresh.call to package ControllerRuntime.refresh with { + document to interface DocumentData; + provider to interface Summarizer via edge ProviderLink.provider; + }; + bind processPending.call to package ControllerRuntime.runPending with { + document to interface DocumentData; + provider to interface Summarizer via edge ProviderLink.provider; + }; + } + conform Provider as Summarizer id "async:provider:summarizer" { + bind summarize.call to package ProviderRuntime.summarize; + } +} diff --git a/test/fixtures/canvas-content.qx b/test/fixtures/canvas-content.qx new file mode 100644 index 0000000..8d4f4cc --- /dev/null +++ b/test/fixtures/canvas-content.qx @@ -0,0 +1,12 @@ +type Point = record { x:double; y:double; }; +type Entry = variant id "canvas:entry@1" { + paragraph id "canvas:paragraph" : record { text:string; }; + stroke id "canvas:stroke" : record { points:list; }; + image id "canvas:image" : record { asset:string; }; +}; +interface CanvasContent id "canvas" revision "canvas@1" { + queryable value entries id "entries" : list { + get id "entries:get" execution snapshot; + edit id "entries:edit" execution transaction; + } +} diff --git a/test/fixtures/collections/CollectionRuntime.qx b/test/fixtures/collections/CollectionRuntime.qx new file mode 100644 index 0000000..fae5c92 --- /dev/null +++ b/test/fixtures/collections/CollectionRuntime.qx @@ -0,0 +1,19 @@ +symbol JavaScript id "target:org.quixos.execution.javascript@1"; +type Replacement = record { expected: list; targets: list>; }; +type Seed = record { label: string; }; +external atom Node id "collection:node"; +package CollectionRuntime id "collection:runtime" revision "collection:runtime@1" { + operation version id "collection:runtime:version" : unit -> list + mode call execution snapshot targets [JavaScript, server] receiver atom Node requires { + edge items id "collection:version:items" : many atom Node [resolve]; + }; + operation replace id "collection:runtime:replace" : Replacement -> unit + mode call execution transaction targets [JavaScript, server] receiver atom Node requires { + edge items id "collection:replace:items" : many atom Node [resolve, connect, disconnect]; + }; + operation ensure id "collection:runtime:ensure" : unit -> atom-ref + mode call execution transaction targets [JavaScript, server] receiver atom Node requires { + edge detail id "collection:ensure:detail" : optional-one atom Node [resolve, connect]; + creation node id "collection:ensure:node" : Node input Seed; + }; +} diff --git a/test/fixtures/collections/NodeData.qx b/test/fixtures/collections/NodeData.qx new file mode 100644 index 0000000..ed90dd5 --- /dev/null +++ b/test/fixtures/collections/NodeData.qx @@ -0,0 +1,26 @@ +type Replacement = record { expected: list; targets: list>; }; +external atom Node id "collection:node"; +interface NodeData id "collection:data" revision "collection:data@1" { + queryable portable value version id "collection:version" : list { + get id "collection:version:get" execution snapshot; + } + operation replace id "collection:replace" : Replacement -> unit { + call id "collection:replace:call" execution transaction; + } + operation ensure id "collection:ensure" : unit -> atom-ref { + call id "collection:ensure:call" execution transaction; + } + queryable value label id "collection:label" : string { + get id "collection:label:get" execution snapshot; + set id "collection:label:set" execution transaction; + } + queryable relation items id "collection:items" : many atom Node { + resolve id "collection:items:resolve" execution snapshot; + } + queryable relation containers id "collection:containers" : many atom Node { + resolve id "collection:containers:resolve" execution snapshot; + } + queryable relation detail id "collection:detail" : optional-one atom Node { + resolve id "collection:detail:resolve" execution snapshot; + } +} diff --git a/test/fixtures/collections/workspace.qx b/test/fixtures/collections/workspace.qx new file mode 100644 index 0000000..8a726d5 --- /dev/null +++ b/test/fixtures/collections/workspace.qx @@ -0,0 +1,28 @@ +workspace Collections id "workspace:collections" revision "workspace:collections@1" commit "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" { + import interface NodeData; + import package CollectionRuntime; + atom Node id "collection:node"; + conform Node as NodeData id "collection:node:data" { + private state Label id "collection:label-slot" on Node : string policy register; + private edge Items id "collection:items-edge" { + atom Node projection items id "collection:items-projection" many; + atom Node projection containers id "collection:containers-projection" many; + } + private edge Detail id "collection:detail-edge" { + atom Node projection detail id "collection:detail-projection" optional-one; + atom Node projection owner id "collection:owner-projection" optional-one; + } + initialize { label to state Label; }; + bind label.get to state Label.read; + bind label.set to state Label.write; + bind items.resolve to edge Items.items.resolve; + bind containers.resolve to edge Items.containers.resolve; + bind detail.resolve to edge Detail.detail.resolve; + bind version.get to package CollectionRuntime.version with { items to edge Items.items; }; + bind replace.call to package CollectionRuntime.replace with { items to edge Items.items; }; + bind ensure.call to package CollectionRuntime.ensure with { + detail to edge Detail.detail; + node to creation Node with { NodeData from input; }; + }; + } +} diff --git a/test/fixtures/compound-crdt/EditRuntime.qx b/test/fixtures/compound-crdt/EditRuntime.qx new file mode 100644 index 0000000..c6412ef --- /dev/null +++ b/test/fixtures/compound-crdt/EditRuntime.qx @@ -0,0 +1,24 @@ +symbol JavaScript id "target:org.quixos.execution.javascript@1"; +external atom Note id "compound:note"; +type Content = record { text: string; tags: list; }; +type Edit = record { change: change; resolved: bool; }; +package EditRuntime id "compound:runtime" revision "compound:runtime@1" { + operation batch id "compound:runtime:batch" : record { edits: list; } -> unit + mode call execution transaction targets [JavaScript, server] receiver atom Note requires { + state content id "compound:batch:content" : Content [edit]; + state resolved id "compound:batch:resolved" : bool [write]; + } accept using checkBatch; + operation checkBatch id "compound:runtime:check-batch" : record { edits: list; } -> unit + mode call execution snapshot targets [server] receiver atom Note requires { + state allowed id "compound:batch:allowed" : bool [read]; + }; + operation update id "compound:runtime:update" : Edit -> unit + mode call execution transaction targets [JavaScript, server] receiver atom Note requires { + state content id "compound:edit:content" : Content [edit]; + state resolved id "compound:edit:resolved" : bool [write]; + } accept using check; + operation check id "compound:runtime:check" : Edit -> unit + mode call execution snapshot targets [server] receiver atom Note requires { + state allowed id "compound:check:allowed" : bool [read]; + }; +} diff --git a/test/fixtures/compound-crdt/NoteData.qx b/test/fixtures/compound-crdt/NoteData.qx new file mode 100644 index 0000000..a452ad0 --- /dev/null +++ b/test/fixtures/compound-crdt/NoteData.qx @@ -0,0 +1,21 @@ +type Content = record { text: string; tags: list; }; +type Edit = record { change: change; resolved: bool; }; +interface NoteData id "compound:data" revision "compound:data@1" { + operation batch id "compound:batch" : record { edits: list; } -> unit { + call id "compound:batch:call" execution transaction; + } + operation update id "compound:update" : Edit -> unit { + call id "compound:update:call" execution transaction; + } + queryable value content id "compound:content" : Content { + get id "compound:content:get" execution snapshot; + edit id "compound:content:edit" execution transaction; + } + queryable value resolved id "compound:resolved" : bool { + get id "compound:resolved:get" execution snapshot; + } + queryable value title id "compound:title" : string { + get id "compound:title:get" execution snapshot; + set id "compound:title:set" execution transaction; + } +} diff --git a/test/fixtures/compound-crdt/workspace.qx b/test/fixtures/compound-crdt/workspace.qx new file mode 100644 index 0000000..dbe4d80 --- /dev/null +++ b/test/fixtures/compound-crdt/workspace.qx @@ -0,0 +1,26 @@ +workspace Compound id "workspace:compound" revision "workspace:compound@1" commit "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" { + import interface NoteData; + import package EditRuntime; + type Content = record { text: string; tags: list; }; + atom Note id "compound:note"; + conform Note as NoteData id "compound:note:data" { + private state Content id "compound:content-slot" on Note : Content policy crdt(Content); + private state Resolved id "compound:resolved-slot" on Note : bool policy register default false; + private state Title id "compound:title-slot" on Note : string policy register default "note"; + private state Allowed id "compound:allowed-slot" on Note : bool policy register default true; + initialize { content to state Content; }; + bind content.get to state Content.read; + bind content.edit to state Content.edit; + bind resolved.get to state Resolved.read; + bind title.get to state Title.read; + bind title.set to state Title.write; + bind batch.call to package EditRuntime.batch with { + content to state Content; + resolved to state Resolved; + } accept with { allowed to state Allowed; }; + bind update.call to package EditRuntime.update with { + content to state Content; + resolved to state Resolved; + } accept with { allowed to state Allowed; }; + } +} diff --git a/test/fixtures/controller-device/Device.qx b/test/fixtures/controller-device/Device.qx new file mode 100644 index 0000000..3f86662 --- /dev/null +++ b/test/fixtures/controller-device/Device.qx @@ -0,0 +1,11 @@ +external atom Display id "device:display"; +type Request=record {url:string;text:string;version:int32;}; +interface Device id "device" revision "device@1" { + static operation create id "device:create" : string -> atom-ref {call id "device:create:call" execution transaction;} + queryable value enabled id "device:enabled" : bool {get id "device:enabled:get" execution snapshot;set id "device:enabled:set" execution transaction;} + queryable value requested id "device:requested" : Request {get id "device:requested:get" execution snapshot;} + queryable value observed id "device:observed" : optional {get id "device:observed:get" execution snapshot;} + operation requestText id "device:request" : string -> unit {call id "device:request:call" execution transaction;} + operation recordObservation id "device:record" : Request -> unit {call id "device:record:call" execution transaction;} + operation remove id "device:remove" : unit -> unit {call id "device:remove:call" execution transaction;} +} diff --git a/test/fixtures/controller-device/DeviceController.qx b/test/fixtures/controller-device/DeviceController.qx new file mode 100644 index 0000000..999536b --- /dev/null +++ b/test/fixtures/controller-device/DeviceController.qx @@ -0,0 +1,6 @@ +type Request=record {url:string;text:string;version:int32;}; +interface DeviceController id "device-controller" revision "device-controller@1" { + queryable portable value enabled id "controller:enabled" : bool {get id "controller:enabled:get" execution snapshot;} + operation snapshot id "device:snapshot" : unit -> optional {call id "device:snapshot:call" execution snapshot;} + operation apply id "device:apply" : optional -> unit {call id "device:apply:call" execution remote;} +} diff --git a/test/fixtures/controller-device/DeviceView.qx b/test/fixtures/controller-device/DeviceView.qx new file mode 100644 index 0000000..985fb0c --- /dev/null +++ b/test/fixtures/controller-device/DeviceView.qx @@ -0,0 +1,5 @@ +import interface Device; +external atom Display id "device:display"; +interface DeviceView id "device-view" revision "device-view@1" { + module render id "device-view:render" contract "org.quixos.react.subject-only/1" subject interface Device; +} diff --git a/test/fixtures/controller-device/Driver.qx b/test/fixtures/controller-device/Driver.qx new file mode 100644 index 0000000..00ffb9b --- /dev/null +++ b/test/fixtures/controller-device/Driver.qx @@ -0,0 +1,16 @@ +symbol JavaScript id "target:org.quixos.execution.javascript@1"; +import interface Device; +import interface DeviceController; +external atom Display id "device:display"; +type Request=record {url:string;text:string;version:int32;}; +package Driver id "driver" revision "driver@1" { + function create id "driver:create" : string -> atom-ref execution transaction targets [JavaScript,server] requires {creation device id "device" : Display input record {enabled:bool;requested:Request;observed:optional;};}; + operation request id "driver:request" : string -> unit mode call execution transaction targets [JavaScript,server] receiver any requires {state requested id "requested" : Request [read,write];}; + operation snapshot id "driver:snapshot" : unit -> optional mode call execution snapshot targets [server] receiver any requires {interface data id "data" : Device using [enabled.get,requested.get,observed.get];}; + operation recordObservation id "driver:record" : Request -> unit mode call execution transaction targets [server] receiver any requires {state requested id "requested" : Request [read];state observed id "observed" : optional [write];}; + operation apply id "driver:apply" : optional -> unit mode call execution remote receiver any requires {interface device id "device" : Device using [recordObservation.call];}; + operation enabled id "driver:enabled" : unit -> bool mode call execution snapshot targets [server] receiver any requires {interface data id "data" : Device using [enabled.get];}; + query Enabled id "enabled" root workspace DeviceController document "enabled.graphql" operation "Enabled" {max rows 64;} + query Inputs id "inputs" root Device document "inputs.graphql" operation "Inputs" {replica;} + query View id "view" root Device document "inputs.graphql" operation "Inputs" {local from Inputs;watch;} +} diff --git a/test/fixtures/controller-device/Viewer.qx b/test/fixtures/controller-device/Viewer.qx new file mode 100644 index 0000000..6d7897a --- /dev/null +++ b/test/fixtures/controller-device/Viewer.qx @@ -0,0 +1,8 @@ +import interface DeviceView; +import interface Device; +external atom Display id "device:display"; +package DeviceViewer id "device-viewer" revision "device-viewer@1" { + module Status id "component:device:status" implements DeviceView.render; + query Inputs id "device-viewer:inputs" root Device document "viewer.graphql" operation "Status" {replica;} + query View id "device-viewer:view" root Device document "viewer.graphql" operation "Status" {local from Inputs;watch;} +} diff --git a/test/fixtures/controller-device/enabled.graphql b/test/fixtures/controller-device/enabled.graphql new file mode 100644 index 0000000..5911764 --- /dev/null +++ b/test/fixtures/controller-device/enabled.graphql @@ -0,0 +1 @@ +query Enabled {root {objects(all:64, where:{enabled:{eq:true}}) {entries {node {_camino {ref}}}}}} diff --git a/test/fixtures/controller-device/inputs.graphql b/test/fixtures/controller-device/inputs.graphql new file mode 100644 index 0000000..f330a2b --- /dev/null +++ b/test/fixtures/controller-device/inputs.graphql @@ -0,0 +1 @@ +query Inputs {root {enabled requested {url text version} observed {url text version}}} diff --git a/test/fixtures/controller-device/viewer.graphql b/test/fixtures/controller-device/viewer.graphql new file mode 100644 index 0000000..8511345 --- /dev/null +++ b/test/fixtures/controller-device/viewer.graphql @@ -0,0 +1 @@ +query Status {root {enabled requested {text version} observed {text version}}} diff --git a/test/fixtures/controller-device/workspace.qx b/test/fixtures/controller-device/workspace.qx new file mode 100644 index 0000000..595a63f --- /dev/null +++ b/test/fixtures/controller-device/workspace.qx @@ -0,0 +1,25 @@ +workspace Controllers id "workspace:controllers" revision "workspace:controllers@1" commit "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" { + import interface Device; + import interface DeviceController; + import package Driver; + atom Display id "device:display"; + conform Display as Device id "display:device" { + private state Enabled id "display:enabled" on Display : bool policy register; + private state Requested id "display:requested" on Display : record {url:string;text:string;version:int32;} policy register; + private state Observed id "display:observed" on Display : optional policy register; + initialize {enabled to state Enabled;requested to state Requested;observed to state Observed;}; + bind create.call to package Driver.create with {device to creation Display with {Device from input;};}; + bind enabled.get to state Enabled.read; + bind enabled.set to state Enabled.write; + bind requested.get to state Requested.read; + bind observed.get to state Observed.read; + bind requestText.call to package Driver.request with {requested to state Requested;}; + bind recordObservation.call to package Driver.recordObservation with {requested to state Requested;observed to state Observed;}; + bind remove.call to lifecycle.delete; + } + conform Display as DeviceController id "display:controller" { + bind enabled.get to package Driver.enabled with {data to interface Device;}; + bind snapshot.call to package Driver.snapshot with {data to interface Device;}; + bind apply.call to package Driver.apply with {device to interface Device;}; + } +} diff --git a/test/fixtures/events/event-handler.qx b/test/fixtures/events/event-handler.qx new file mode 100644 index 0000000..ce7bc44 --- /dev/null +++ b/test/fixtures/events/event-handler.qx @@ -0,0 +1,5 @@ +interface EventHandler id "interface:org.quixos.events:handler" revision "interface:org.quixos.events:handler@1" { + operation handle id "events:handle" : record {event:Payload;parameters:Parameters;} -> unit { + call id "events:handle:call" execution transaction; + } +} diff --git a/test/fixtures/events/event-stream-record.qx b/test/fixtures/events/event-stream-record.qx new file mode 100644 index 0000000..f7ef49e --- /dev/null +++ b/test/fixtures/events/event-stream-record.qx @@ -0,0 +1,3 @@ +import interface EventStream; +// Opt into the standard workspace journal conformance rule. +interface EventStreamRecord id "interface:org.quixos.events:stream-record" revision "interface:org.quixos.events:stream-record@1" requires EventStream {} diff --git a/test/fixtures/events/event-stream.qx b/test/fixtures/events/event-stream.qx new file mode 100644 index 0000000..0607317 --- /dev/null +++ b/test/fixtures/events/event-stream.qx @@ -0,0 +1,13 @@ +// Required server support; a checked installed implementation may additionally predict locally. +interface EventStream id "interface:org.quixos.events:stream" revision "interface:org.quixos.events:stream@1" { + operation publish id "events:publish" : Payload -> record {publication:string;} { + call id "events:publish:call" execution transaction; + } + queryable value head id "events:head" : optional { + get id "events:head:get" execution snapshot; + } + operation readPage id "events:read-page" : record {after:optional;limit:uint32;} + -> record {entries:list;next:optional;asOfHead:optional;} { + call id "events:read-page:call" execution snapshot; + } +} diff --git a/test/fixtures/events/event-subscription-record.qx b/test/fixtures/events/event-subscription-record.qx new file mode 100644 index 0000000..d0dce83 --- /dev/null +++ b/test/fixtures/events/event-subscription-record.qx @@ -0,0 +1,2 @@ +import interface EventSubscription; +interface EventSubscriptionRecord id "interface:org.quixos.events:subscription-record" revision "interface:org.quixos.events:subscription-record@1" requires EventSubscription {} diff --git a/test/fixtures/events/event-subscription.qx b/test/fixtures/events/event-subscription.qx new file mode 100644 index 0000000..66426aa --- /dev/null +++ b/test/fixtures/events/event-subscription.qx @@ -0,0 +1,18 @@ +import interface EventStream; +import interface EventHandler; +type StartPosition = variant id "org.quixos.events:start@1" { + future id "events:start:future" : unit; + retained id "events:start:retained" : unit; + afterCursor id "events:start:after" : string; +}; +interface EventSubscription id "interface:org.quixos.events:subscription" revision "interface:org.quixos.events:subscription@1" { + operation attach id "events:attach" : record {source:interface-ref>;handler:interface-ref>;parameters:Parameters;start:StartPosition;} -> unit { + call id "events:attach:call" execution transaction; + } + queryable value status id "events:status" : record {status:string;generation:uint64;cursor:uint64;failure:optional;} { + get id "events:status:get" execution snapshot; + } + operation pause id "events:pause" : unit -> unit {call id "events:pause:call" execution transaction;} + operation resume id "events:resume" : unit -> unit {call id "events:resume:call" execution transaction;} + operation retire id "events:retire" : unit -> unit {call id "events:retire:call" execution transaction;} +} diff --git a/test/fixtures/events/rules.qx b/test/fixtures/events/rules.qx new file mode 100644 index 0000000..df1f9a1 --- /dev/null +++ b/test/fixtures/events/rules.qx @@ -0,0 +1,21 @@ +fragment { + import interface EventStream; + import interface EventHandler; + import interface EventSubscription; + import interface EventStreamRecord; + import interface EventSubscriptionRecord; + conform EventStreamRecord as EventStream id "events:standard-stream" { + private state Entries id "events:entries" on Self : Payload policy journal {"maxEntries":128,"maxBytes":1048576,"maxPublicationBytes":262144,"maxProtectedSubscriptions":32}; + bind publish.call to journal Entries.publish; + bind head.get to journal Entries.head; + bind readPage.call to journal Entries.readPage; + } + conform EventSubscriptionRecord as EventSubscription id "events:standard-subscription" { + private state Delivery id "events:delivery" on Self : Parameters policy subscription(Payload); + bind attach.call to subscription Delivery.attach; + bind status.get to subscription Delivery.status; + bind pause.call to subscription Delivery.pause; + bind resume.call to subscription Delivery.resume; + bind retire.call to subscription Delivery.retire; + } +} diff --git a/test/fixtures/local-first-composite/CommentFactory.qx b/test/fixtures/local-first-composite/CommentFactory.qx new file mode 100644 index 0000000..a6ebf2c --- /dev/null +++ b/test/fixtures/local-first-composite/CommentFactory.qx @@ -0,0 +1,7 @@ +external atom Comment id "atom:reader:comment"; +interface CommentFactory id "interface:reader:comment-factory" +revision "interface:reader:comment-factory@1" { + operation prepareComment id "document:prepare-comment" : bool -> atom-ref { + call id "document:prepare-comment:call" execution transaction; + } +} diff --git a/test/fixtures/local-first-composite/CommentRuntime.qx b/test/fixtures/local-first-composite/CommentRuntime.qx new file mode 100644 index 0000000..6096d59 --- /dev/null +++ b/test/fixtures/local-first-composite/CommentRuntime.qx @@ -0,0 +1,12 @@ +symbol JavaScript id "target:org.quixos.execution.javascript@1"; +external atom Document id "atom:reader:document"; +external atom Comment id "atom:reader:comment"; +import interface CommentData; +type CommentSeed = record { content: record { text: string; tags: list; }; }; +package CommentRuntime id "package:comment-runtime" revision "package:comment-runtime@1" { + operation prepareComment id "comment:prepare" + : bool -> atom-ref mode call execution transaction targets [JavaScript, server] + receiver atom Document requires { + creation comment id "comment:prepare:comment" : Comment input CommentSeed; + }; +} diff --git a/test/fixtures/local-first-composite/DocumentFactory.qx b/test/fixtures/local-first-composite/DocumentFactory.qx new file mode 100644 index 0000000..165bbaa --- /dev/null +++ b/test/fixtures/local-first-composite/DocumentFactory.qx @@ -0,0 +1,25 @@ +// Shared record definitions, repeated explicitly in independently locked resources. +external atom Document id "atom:reader:document"; +external atom Highlight id "atom:reader:highlight"; +external atom Comment id "atom:reader:comment"; + +type SourceRange = record { + sourceRevision: string; + start: int32; + end: int32; +}; + +type CommentContent = record { + text: string; + tags: list; +}; + +type DocumentSeed = record { document: record { sourceRevision: string; content: string; }; metadata: record { label: string; }; }; + +interface DocumentFactory id "interface:reader:document-factory" +revision "interface:reader:document-factory@1" { + static operation create id "document:create" : DocumentSeed -> atom-ref { + call id "document:create:call" + execution transaction; + } +} diff --git a/test/fixtures/local-first-composite/DocumentLabel.qx b/test/fixtures/local-first-composite/DocumentLabel.qx new file mode 100644 index 0000000..63fa6c3 --- /dev/null +++ b/test/fixtures/local-first-composite/DocumentLabel.qx @@ -0,0 +1,6 @@ +interface DocumentLabel id "interface:reader:document-label" +revision "interface:reader:document-label@1" { + queryable value title id "document:view-title" : string { + get id "document:view-title:get" execution snapshot; + } +} diff --git a/test/fixtures/local-first-composite/DocumentMetadata.qx b/test/fixtures/local-first-composite/DocumentMetadata.qx new file mode 100644 index 0000000..b09b21c --- /dev/null +++ b/test/fixtures/local-first-composite/DocumentMetadata.qx @@ -0,0 +1,6 @@ +interface DocumentMetadata id "interface:reader:document-metadata" +revision "interface:reader:document-metadata@1" { + queryable value label id "document:label" : string { + get id "document:label:get" execution snapshot; + } +} diff --git a/test/fixtures/local-first-composite/ReaderRuntime.qx b/test/fixtures/local-first-composite/ReaderRuntime.qx new file mode 100644 index 0000000..b0a1de7 --- /dev/null +++ b/test/fixtures/local-first-composite/ReaderRuntime.qx @@ -0,0 +1,85 @@ +symbol JavaScript id "target:org.quixos.execution.javascript@1"; +// Shared record definitions, repeated explicitly in independently locked resources. +external atom Document id "atom:reader:document"; +external atom Highlight id "atom:reader:highlight"; +external atom Comment id "atom:reader:comment"; + +type SourceRange = record { + sourceRevision: string; + start: int32; + end: int32; +}; + +type CommentContent = record { + text: string; + tags: list; +}; + +type DocumentSeed = record { document: record { sourceRevision: string; content: string; }; metadata: record { label: string; }; }; + +import interface DocumentData; +import interface CommentFactory; +import interface HighlightData; +import interface CommentData; +type CommentSeed = record { content: CommentContent; }; +type HighlightSeed = record { + range: SourceRange; + comments: list; }>; +}; + +package ReaderRuntime id "package:reader-runtime" +revision "package:reader-runtime@1" { + operation addHighlight id "reader:add-highlight" + : SourceRange -> atom-ref + mode call execution transaction targets [JavaScript, server] + receiver atom Document requires { + interface document id "reader:add:document" : DocumentData + using [sourceRevision.get, content.get, highlights.connect]; + creation highlight id "reader:add:highlight" : Highlight input HighlightSeed; + interface comments id "reader:add:comments" : CommentFactory using [prepareComment.call]; + }; + + operation excerpt id "reader:excerpt" : unit -> string + mode call execution snapshot targets [JavaScript, server] + receiver atom Highlight requires { + state range id "reader:excerpt:range" : SourceRange [read]; + interface document id "reader:excerpt:document" : DocumentData + using [sourceRevision.get, content.get]; + }; + + // A receiverless function needs no synthetic service object. + function validateRange id "reader:validate-range" + : record { range: SourceRange; content: string; } -> bool + execution snapshot targets [JavaScript, server]; + + function createDocument id "reader:create-document" + : DocumentSeed -> atom-ref + execution transaction targets [JavaScript, server] requires { + creation document id "reader:create:document" : Document input DocumentSeed; + }; + query ReplicaInputs id "reader:replica-inputs" root DocumentData + document "queries/replica-inputs.graphql" operation "ReplicaInputs" { + replica; + view Highlight as HighlightData; + view Comment as CommentData; + max rows 5000; + max depth 8; + max result-bytes 8388608; + } + + query ReaderScreen id "reader:screen" root DocumentData + document "queries/reader-screen.graphql" operation "ReaderScreen" { + local from ReplicaInputs; + view Highlight as HighlightData; + view Comment as CommentData; + max rows 5000; + max depth 8; + max result-bytes 8388608; + watch; + } + + query HighlightScope id "reader:highlight-scope" root workspace HighlightData document "queries/highlight-scope.graphql" operation "HighlightScope" { view Comment as CommentData; replica; max rows 200; } + query HighlightList id "reader:highlight-list" root workspace HighlightData document "queries/highlight-list.graphql" operation "HighlightList" { view Comment as CommentData; local from HighlightScope; max rows 200; watch; } + query HighlightEditorInputs id "reader:editor-inputs" root HighlightData document "queries/editor-inputs.graphql" operation "HighlightEditorInputs" { view Comment as CommentData; replica; max rows 1; } + query HighlightEditorView id "reader:editor-view" root HighlightData document "queries/editor-view.graphql" operation "HighlightEditorView" { view Comment as CommentData; local from HighlightEditorInputs; max rows 1; watch; } +} diff --git a/test/fixtures/local-first-composite/workspace.qx b/test/fixtures/local-first-composite/workspace.qx new file mode 100644 index 0000000..62e7a44 --- /dev/null +++ b/test/fixtures/local-first-composite/workspace.qx @@ -0,0 +1,113 @@ +workspace Reader id "workspace:reader" revision "workspace:reader@1" commit "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" { + import interface DocumentData; + import interface HighlightData; + import interface CommentData; + import interface DocumentActions; + import interface DocumentFactory; + import package ReaderRuntime; + import package CommentRuntime; + import interface DocumentMetadata; + import interface DocumentLabel; + import interface CommentFactory; + // Shared record definitions, repeated explicitly in independently locked resources. + + type SourceRange = record { + sourceRevision: string; + start: int32; + end: int32; + }; + + type CommentContent = record { + text: string; + tags: list; + }; + + type DocumentSeed = record { sourceRevision: string; content: string; }; + + atom Document id "atom:reader:document"; + atom Highlight id "atom:reader:highlight"; + atom Comment id "atom:reader:comment"; + + conform Document as DocumentData id "conformance:reader:document-data" { + private state Revision id "slot:document:revision" + on Document : string policy register; + private state Content id "slot:document:content" + on Document : string policy register; + private edge DocumentHighlights id "edge:document-highlights" { + atom Document projection highlights id "projection:highlights" many-unique; + atom Highlight projection document id "projection:document" + exactly-one public-traversal; + } + initialize { + sourceRevision to state Revision; + content to state Content; + }; + bind sourceRevision.get to state Revision.read; + bind content.get to state Content.read; + bind highlights.resolve to edge DocumentHighlights.highlights.resolve; + bind highlights.connect to edge DocumentHighlights.highlights.connect; + } + + conform Document as DocumentActions id "conformance:reader:document-actions" { + + bind annotate.call to package ReaderRuntime.addHighlight with { + document to interface DocumentData; + highlight to creation Highlight with { HighlightData from input; }; + comments to interface CommentFactory; + }; + } + + conform Document as CommentFactory id "conformance:reader:comment-factory" { + bind prepareComment.call to package CommentRuntime.prepareComment with { + comment to creation Comment with { CommentData from input; }; + }; + } + + conform Document as DocumentLabel id "conformance:reader:document-label" { + bind title.get to interface DocumentMetadata.label.get; + } + + conform Document as DocumentMetadata id "conformance:reader:document-metadata" { + private state Label id "slot:document:label" on Document : string policy register; + initialize { label to state Label; }; + bind label.get to state Label.read; + } + + conform Document as DocumentFactory id "conformance:reader:document-factory" { + bind create.call to package ReaderRuntime.createDocument with { + document to creation Document with { DocumentData from input.document; DocumentMetadata from input.metadata; }; + }; + } + + conform Highlight as HighlightData id "conformance:reader:highlight-data" { + private state Range id "slot:highlight:range" + on Highlight : SourceRange policy register; + private state Color id "slot:highlight:color" + on Highlight : string policy register default "yellow"; + private edge HighlightComments id "edge:highlight-comments" { + atom Highlight projection comments id "projection:comments" many-unique; + atom Comment projection highlight id "projection:highlight" + exactly-one public-traversal; + } + initialize { + range to state Range; + comments to edge HighlightComments.comments; + }; + bind range.get to state Range.read; + bind color.get to state Color.read; + bind color.set to state Color.write; + bind comments.resolve to edge HighlightComments.comments.resolve; + bind excerpt.get to package ReaderRuntime.excerpt with { + range to state Range; + document to interface DocumentData via edge DocumentHighlights.document; + }; + } + + conform Comment as CommentData id "conformance:reader:comment-data" { + private state CommentBody id "slot:comment:content" + on Comment : CommentContent policy crdt(CommentContent); + initialize { content to state CommentBody; }; + bind content.get to state CommentBody.read; + bind content.edit to state CommentBody.edit; + } +} diff --git a/test/fixtures/local-first-reader/CommentData.qx b/test/fixtures/local-first-reader/CommentData.qx new file mode 100644 index 0000000..2dba67a --- /dev/null +++ b/test/fixtures/local-first-reader/CommentData.qx @@ -0,0 +1,27 @@ +// Shared record definitions, repeated explicitly in independently locked resources. +external atom Document id "atom:reader:document"; +external atom Highlight id "atom:reader:highlight"; +external atom Comment id "atom:reader:comment"; + +type SourceRange = record { + sourceRevision: string; + start: int32; + end: int32; +}; + +type CommentContent = record { + text: string; + tags: list; +}; + +type DocumentSeed = record { sourceRevision: string; content: string; }; + +interface CommentData id "interface:reader:comment-data" +revision "interface:reader:comment-data@1" { + queryable value content id "comment:content" : CommentContent { + get id "comment:content:get" + execution snapshot; + edit id "comment:content:edit" + execution transaction; + } +} diff --git a/test/fixtures/local-first-reader/DocumentActions.qx b/test/fixtures/local-first-reader/DocumentActions.qx new file mode 100644 index 0000000..f42bfb5 --- /dev/null +++ b/test/fixtures/local-first-reader/DocumentActions.qx @@ -0,0 +1,25 @@ +// Shared record definitions, repeated explicitly in independently locked resources. +external atom Document id "atom:reader:document"; +external atom Highlight id "atom:reader:highlight"; +external atom Comment id "atom:reader:comment"; + +type SourceRange = record { + sourceRevision: string; + start: int32; + end: int32; +}; + +type CommentContent = record { + text: string; + tags: list; +}; + +type DocumentSeed = record { sourceRevision: string; content: string; }; + +interface DocumentActions id "interface:reader:document-actions" +revision "interface:reader:document-actions@1" { + operation annotate id "document:annotate" : SourceRange -> atom-ref { + call id "document:annotate:call" + execution transaction; + } +} diff --git a/test/fixtures/local-first-reader/DocumentData.qx b/test/fixtures/local-first-reader/DocumentData.qx new file mode 100644 index 0000000..c17412c --- /dev/null +++ b/test/fixtures/local-first-reader/DocumentData.qx @@ -0,0 +1,36 @@ +// Shared record definitions, repeated explicitly in independently locked resources. +external atom Document id "atom:reader:document"; +external atom Highlight id "atom:reader:highlight"; +external atom Comment id "atom:reader:comment"; + +type SourceRange = record { + sourceRevision: string; + start: int32; + end: int32; +}; + +type CommentContent = record { + text: string; + tags: list; +}; + +type DocumentSeed = record { sourceRevision: string; content: string; }; + +interface DocumentData id "interface:reader:document-data" +revision "interface:reader:document-data@1" { + queryable value sourceRevision id "document:source-revision" : string { + get id "document:source-revision:get" + execution snapshot; + } + queryable value content id "document:content" : string { + get id "document:content:get" + execution snapshot; + } + queryable relation highlights id "document:highlights" + : many-unique atom Highlight { + resolve id "document:highlights:resolve" + execution snapshot; + connect id "document:highlights:connect" + execution transaction; + } +} diff --git a/test/fixtures/local-first-reader/DocumentFactory.qx b/test/fixtures/local-first-reader/DocumentFactory.qx new file mode 100644 index 0000000..953ebfb --- /dev/null +++ b/test/fixtures/local-first-reader/DocumentFactory.qx @@ -0,0 +1,25 @@ +// Shared record definitions, repeated explicitly in independently locked resources. +external atom Document id "atom:reader:document"; +external atom Highlight id "atom:reader:highlight"; +external atom Comment id "atom:reader:comment"; + +type SourceRange = record { + sourceRevision: string; + start: int32; + end: int32; +}; + +type CommentContent = record { + text: string; + tags: list; +}; + +type DocumentSeed = record { sourceRevision: string; content: string; }; + +interface DocumentFactory id "interface:reader:document-factory" +revision "interface:reader:document-factory@1" { + static operation create id "document:create" : DocumentSeed -> atom-ref { + call id "document:create:call" + execution transaction; + } +} diff --git a/test/fixtures/local-first-reader/HighlightData.qx b/test/fixtures/local-first-reader/HighlightData.qx new file mode 100644 index 0000000..c0923ba --- /dev/null +++ b/test/fixtures/local-first-reader/HighlightData.qx @@ -0,0 +1,39 @@ +// Shared record definitions, repeated explicitly in independently locked resources. +external atom Document id "atom:reader:document"; +external atom Highlight id "atom:reader:highlight"; +external atom Comment id "atom:reader:comment"; + +type SourceRange = record { + sourceRevision: string; + start: int32; + end: int32; +}; + +type CommentContent = record { + text: string; + tags: list; +}; + +type DocumentSeed = record { sourceRevision: string; content: string; }; + +interface HighlightData id "interface:reader:highlight-data" +revision "interface:reader:highlight-data@1" { + queryable value range id "highlight:range" : SourceRange { + get id "highlight:range:get" + execution snapshot; + } + queryable value color id "highlight:color" : string { + get id "highlight:color:get" + execution snapshot; + set id "highlight:color:set" + execution transaction; + } + queryable portable value excerpt id "highlight:excerpt" : string { + get id "highlight:excerpt:get" + execution snapshot; + } + queryable relation comments id "highlight:comments" : many-unique atom Comment { + resolve id "highlight:comments:resolve" + execution snapshot; + } +} diff --git a/test/fixtures/local-first-reader/ReaderRuntime.qx b/test/fixtures/local-first-reader/ReaderRuntime.qx new file mode 100644 index 0000000..55ddbf7 --- /dev/null +++ b/test/fixtures/local-first-reader/ReaderRuntime.qx @@ -0,0 +1,87 @@ +symbol JavaScript id "target:org.quixos.execution.javascript@1"; +// Shared record definitions, repeated explicitly in independently locked resources. +external atom Document id "atom:reader:document"; +external atom Highlight id "atom:reader:highlight"; +external atom Comment id "atom:reader:comment"; + +type SourceRange = record { + sourceRevision: string; + start: int32; + end: int32; +}; + +type CommentContent = record { + text: string; + tags: list; +}; + +type DocumentSeed = record { sourceRevision: string; content: string; }; + +import interface DocumentData; +import interface HighlightData; +import interface CommentData; +type CommentSeed = record { content: CommentContent; }; +type HighlightSeed = record { + range: SourceRange; + comments: list; }>; +}; + +package ReaderRuntime id "package:reader-runtime" +revision "package:reader-runtime@1" { + operation addHighlight id "reader:add-highlight" + : SourceRange -> atom-ref + mode call execution transaction targets [JavaScript, server] + receiver atom Document requires { + interface document id "reader:add:document" : DocumentData + using [sourceRevision.get, content.get, highlights.connect]; + creation highlight id "reader:add:highlight" : Highlight input HighlightSeed; + creation comment id "reader:add:comment" : Comment input CommentSeed; + }; + + operation excerpt id "reader:excerpt" : unit -> string + mode call execution snapshot targets [JavaScript, server] + receiver atom Highlight requires { + state range id "reader:excerpt:range" : SourceRange [read]; + interface document id "reader:excerpt:document" : DocumentData + using [sourceRevision.get, content.get]; + }; + + // A receiverless function needs no synthetic service object. + function validateRange id "reader:validate-range" + : record { range: SourceRange; content: string; } -> bool + execution snapshot targets [JavaScript, server]; + + function createDocument id "reader:create-document" + : DocumentSeed -> atom-ref + execution transaction targets [JavaScript, server] requires { + creation document id "reader:create:document" : Document input DocumentSeed; + }; + query ReplicaInputs id "reader:replica-inputs" root DocumentData + document "queries/replica-inputs.graphql" operation "ReplicaInputs" { + replica; + view Highlight as HighlightData; + view Comment as CommentData; + max rows 5000; + max depth 8; + max result-bytes 8388608; + } + + query ReaderScreen id "reader:screen" root DocumentData + document "queries/reader-screen.graphql" operation "ReaderScreen" { + local from ReplicaInputs; + view Highlight as HighlightData; + view Comment as CommentData; + max rows 5000; + max depth 8; + max result-bytes 8388608; + watch; + } + + query HighlightScope id "reader:highlight-scope" root workspace HighlightData document "queries/highlight-scope.graphql" operation "HighlightScope" { view Comment as CommentData; replica; max rows 200; } + query HighlightWindow id "reader:highlight-window" root workspace HighlightData document "queries/highlight-window.graphql" operation "HighlightWindow" { view Comment as CommentData; replica; max rows 200; } + query HighlightList id "reader:highlight-list" root workspace HighlightData document "queries/highlight-list.graphql" operation "HighlightList" { view Comment as CommentData; local from HighlightScope; max rows 200; watch; } + query HighlightWindowList id "reader:highlight-window-list" root workspace HighlightData document "queries/highlight-list.graphql" operation "HighlightList" { view Comment as CommentData; local from HighlightWindow; max rows 200; watch; } + query HighlightEditorInputs id "reader:editor-inputs" root HighlightData document "queries/editor-inputs.graphql" operation "HighlightEditorInputs" { view Comment as CommentData; replica; max rows 1; } + query HighlightEditorView id "reader:editor-view" root HighlightData document "queries/editor-view.graphql" operation "HighlightEditorView" { view Comment as CommentData; local from HighlightEditorInputs; max rows 1; watch; } + query ConfirmedColor id "reader:confirmed-color" root HighlightData document "queries/confirmed-color.graphql" operation "ConfirmedColor" { view Comment as CommentData; max rows 1; watch; } +} diff --git a/test/fixtures/local-first-reader/queries/confirmed-color.graphql b/test/fixtures/local-first-reader/queries/confirmed-color.graphql new file mode 100644 index 0000000..44f7cb2 --- /dev/null +++ b/test/fixtures/local-first-reader/queries/confirmed-color.graphql @@ -0,0 +1,3 @@ +query ConfirmedColor { + root { color } +} diff --git a/test/fixtures/local-first-reader/queries/editor-inputs.graphql b/test/fixtures/local-first-reader/queries/editor-inputs.graphql new file mode 100644 index 0000000..d506888 --- /dev/null +++ b/test/fixtures/local-first-reader/queries/editor-inputs.graphql @@ -0,0 +1 @@ +query HighlightEditorInputs { root { _camino {ref} color range {sourceRevision start end} } } diff --git a/test/fixtures/local-first-reader/queries/editor-view.graphql b/test/fixtures/local-first-reader/queries/editor-view.graphql new file mode 100644 index 0000000..f205319 --- /dev/null +++ b/test/fixtures/local-first-reader/queries/editor-view.graphql @@ -0,0 +1 @@ +query HighlightEditorView { root { _camino {ref} color range {sourceRevision start end} } } diff --git a/test/fixtures/local-first-reader/queries/highlight-list.graphql b/test/fixtures/local-first-reader/queries/highlight-list.graphql new file mode 100644 index 0000000..3fb23b7 --- /dev/null +++ b/test/fixtures/local-first-reader/queries/highlight-list.graphql @@ -0,0 +1 @@ +query HighlightList($color: String!) { root { availableObjects(all: 200, where: {color: {eq: $color}}, orderBy: {range: {start: ASC}}) { entries { key node { _camino {ref} color range {sourceRevision start end} } } } } } diff --git a/test/fixtures/local-first-reader/queries/highlight-scope.graphql b/test/fixtures/local-first-reader/queries/highlight-scope.graphql new file mode 100644 index 0000000..db46a43 --- /dev/null +++ b/test/fixtures/local-first-reader/queries/highlight-scope.graphql @@ -0,0 +1 @@ +query HighlightScope($color: String!) { root { objects(all: 200, where: {color: {eq: $color}}) { entries { key node { _camino {ref} color range {sourceRevision start end} } } } } } diff --git a/test/fixtures/local-first-reader/queries/highlight-window.graphql b/test/fixtures/local-first-reader/queries/highlight-window.graphql new file mode 100644 index 0000000..3fc3d1c --- /dev/null +++ b/test/fixtures/local-first-reader/queries/highlight-window.graphql @@ -0,0 +1,8 @@ +query HighlightWindow($color: String!) { + root { + objects(first: 2, where: {color: {eq: $color}}, orderBy: {range: {start: ASC}}) { + entries { key node { _camino {ref} color range {sourceRevision start end} } } + pageInfo { hasNextPage endCursor } + } + } +} diff --git a/test/fixtures/local-first-reader/queries/reader-screen.graphql b/test/fixtures/local-first-reader/queries/reader-screen.graphql new file mode 100644 index 0000000..3ed97d2 --- /dev/null +++ b/test/fixtures/local-first-reader/queries/reader-screen.graphql @@ -0,0 +1,21 @@ +query ReaderScreen($color: String!) { + root { + sourceRevision + content + highlights(all: 1000, where: {color: {eq: $color}}, orderBy: [{range: {start: ASC}}]) { + entries { + key + node { + _camino { ref } + range { sourceRevision start end } + color + excerpt + comments(all: 200) { + entries { key node { _camino { ref } content { text tags } } } + } + } + } + } + _camino { relations { highlights { aggregate { count } } } } + } +} diff --git a/test/fixtures/local-first-reader/queries/replica-inputs.graphql b/test/fixtures/local-first-reader/queries/replica-inputs.graphql new file mode 100644 index 0000000..7c8dde8 --- /dev/null +++ b/test/fixtures/local-first-reader/queries/replica-inputs.graphql @@ -0,0 +1,20 @@ +query ReplicaInputs { + root { + sourceRevision + content + highlights(all: 1000) { + entries { + key + node { + _camino { ref } + range { sourceRevision start end } + color + excerpt + comments(all: 200) { + entries { key node { _camino { ref } content { text tags } } } + } + } + } + } + } +} diff --git a/test/fixtures/local-first-reader/workspace.qx b/test/fixtures/local-first-reader/workspace.qx new file mode 100644 index 0000000..ad0b81d --- /dev/null +++ b/test/fixtures/local-first-reader/workspace.qx @@ -0,0 +1,92 @@ +workspace Reader id "workspace:reader" revision "workspace:reader@1" commit "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" { + import interface DocumentData; + import interface HighlightData; + import interface CommentData; + import interface DocumentActions; + import interface DocumentFactory; + import package ReaderRuntime; + // Shared record definitions, repeated explicitly in independently locked resources. + + type SourceRange = record { + sourceRevision: string; + start: int32; + end: int32; + }; + + type CommentContent = record { + text: string; + tags: list; + }; + + type DocumentSeed = record { sourceRevision: string; content: string; }; + + atom Document id "atom:reader:document"; + atom Highlight id "atom:reader:highlight"; + atom Comment id "atom:reader:comment"; + + conform Document as DocumentData id "conformance:reader:document-data" { + private state Revision id "slot:document:revision" + on Document : string policy register; + private state Content id "slot:document:content" + on Document : string policy register; + private edge DocumentHighlights id "edge:document-highlights" { + atom Document projection highlights id "projection:highlights" many-unique; + atom Highlight projection document id "projection:document" + exactly-one public-traversal; + } + initialize { + sourceRevision to state Revision; + content to state Content; + }; + bind sourceRevision.get to state Revision.read; + bind content.get to state Content.read; + bind highlights.resolve to edge DocumentHighlights.highlights.resolve; + bind highlights.connect to edge DocumentHighlights.highlights.connect; + } + + conform Document as DocumentActions id "conformance:reader:document-actions" { + bind annotate.call to package ReaderRuntime.addHighlight with { + document to interface DocumentData; + highlight to creation Highlight with { HighlightData from input; }; + comment to creation Comment with { CommentData from input; }; + }; + } + + conform Document as DocumentFactory id "conformance:reader:document-factory" { + bind create.call to package ReaderRuntime.createDocument with { + document to creation Document with { DocumentData from input; }; + }; + } + + conform Highlight as HighlightData id "conformance:reader:highlight-data" { + private state Range id "slot:highlight:range" + on Highlight : SourceRange policy register; + private state Color id "slot:highlight:color" + on Highlight : string policy register default "yellow"; + private edge HighlightComments id "edge:highlight-comments" { + atom Highlight projection comments id "projection:comments" many-unique; + atom Comment projection highlight id "projection:highlight" + exactly-one public-traversal; + } + initialize { + range to state Range; + comments to edge HighlightComments.comments; + }; + bind range.get to state Range.read; + bind color.get to state Color.read; + bind color.set to state Color.write; + bind comments.resolve to edge HighlightComments.comments.resolve; + bind excerpt.get to package ReaderRuntime.excerpt with { + range to state Range; + document to interface DocumentData via edge DocumentHighlights.document; + }; + } + + conform Comment as CommentData id "conformance:reader:comment-data" { + private state CommentBody id "slot:comment:content" + on Comment : CommentContent policy crdt(CommentContent); + initialize { content to state CommentBody; }; + bind content.get to state CommentBody.read; + bind content.edit to state CommentBody.edit; + } +} diff --git a/test/fixtures/presentation-mount/DocumentNavigation.qx b/test/fixtures/presentation-mount/DocumentNavigation.qx new file mode 100644 index 0000000..6570e07 --- /dev/null +++ b/test/fixtures/presentation-mount/DocumentNavigation.qx @@ -0,0 +1,7 @@ +import interface PresentationState; +interface DocumentNavigation id "document-navigation" revision "document-navigation@1" requires PresentationState { + queryable value compact id "document-navigation:compact" : bool { + get id "document-navigation:compact:get" execution snapshot; + set id "document-navigation:compact:set" execution transaction; + } +} diff --git a/test/fixtures/presentation-mount/Presentation.qx b/test/fixtures/presentation-mount/Presentation.qx new file mode 100644 index 0000000..7bae9c6 --- /dev/null +++ b/test/fixtures/presentation-mount/Presentation.qx @@ -0,0 +1,2 @@ +import interface PresentationState; +interface Presentation id "presentation" revision "presentation@1" {} diff --git a/test/fixtures/presentation-mount/PresentationMount.qx b/test/fixtures/presentation-mount/PresentationMount.qx new file mode 100644 index 0000000..29df943 --- /dev/null +++ b/test/fixtures/presentation-mount/PresentationMount.qx @@ -0,0 +1,15 @@ +import interface PresentationState; +interface PresentationMount id "presentation-mount" revision "presentation-mount@1" { + static operation create id "presentation-mount:create" : unit -> interface-ref { + call id "presentation-mount:create:call" execution transaction; + } + queryable relation members id "presentation-mount:members" : many-unique interface PresentationState { + resolve id "presentation-mount:members:resolve" execution snapshot; + } + queryable relation root id "presentation-mount:root" : optional-one interface PresentationState { + resolve id "presentation-mount:root:resolve" execution snapshot; + } + operation attach id "presentation-mount:attach" : interface-ref -> unit { + call id "presentation-mount:attach:call" execution transaction; + } +} diff --git a/test/fixtures/presentation-mount/PresentationRuntime.qx b/test/fixtures/presentation-mount/PresentationRuntime.qx new file mode 100644 index 0000000..bef78f5 --- /dev/null +++ b/test/fixtures/presentation-mount/PresentationRuntime.qx @@ -0,0 +1,34 @@ +symbol JavaScript id "target:org.quixos.execution.javascript@1"; +import interface PresentationStates; +import interface ReaderNavigation; +external atom ReaderNavigationRecord id "reader-navigation-record"; +import interface PresentationState; +import interface PresentationMount; +import interface StateFactory; +external atom PresentationMountRecord id "presentation-mount-record"; +package PresentationRuntime id "presentation-runtime" revision "presentation-runtime@1" { + function createReaderState id "presentation-runtime:create-reader-state" : record {occurrence:string;parent:optional>;} -> interface-ref execution transaction targets [JavaScript,server] requires { + creation navigation id "navigation" : ReaderNavigationRecord input record {occurrence:string;parent:optional>;}; + }; + function createPresentationMount id "presentation-runtime:create" : unit -> interface-ref execution transaction targets [JavaScript,server] requires { + creation presentationMount id "presentationMount" : PresentationMountRecord input unit; + }; + operation attach id "presentation-runtime:attach" : interface-ref -> unit mode call execution transaction targets [JavaScript,server] receiver any requires { + edge members id "members" : many-unique interface PresentationState [resolve,connect]; + edge root id "root" : optional-one interface PresentationState [resolve,connect]; + interface metadata id "state" : PresentationState using [presentationMount.resolve,parent.resolve] from input; + }; + operation ensure id "presentation-runtime:ensure" : record {presentationMount:interface-ref;occurrence:string;parent:optional>;} -> ref mode call execution transaction targets [JavaScript,server] receiver any requires { + edge states id "states" : many-unique object Navigation keyed "string" [resolve,connect]; + interface metadata id "state" : PresentationState using [occurrence.get,presentationMount.resolve,parent.resolve] from input; + interface presentationMount id "presentationMount" : PresentationMount using [attach.call] from input; + interface factory id "factory" : StateFactory using [create.call]; + }; + function createState id "presentation-runtime:create-state" : record {occurrence:string;parent:optional>;} -> ref execution transaction targets [JavaScript,server] requires { + creation navigation id "navigation" : Navigation input record {occurrence:string;parent:optional>;}; + }; + query ReaderStateInputs id "reader-state:inputs" root PresentationStates document "queries/reader-inputs.graphql" operation "ReaderState" { replica; view ReaderNavigationRecord as ReaderNavigation; max rows 100; } + query ReaderStateView id "reader-state:view" root PresentationStates document "queries/reader-state.graphql" operation "ReaderState" { local from ReaderStateInputs; watch; view ReaderNavigationRecord as ReaderNavigation; max rows 100; } + query ReaderWindowInputs id "reader-state:window-inputs" root PresentationStates document "queries/reader-window.graphql" operation "ReaderState" { replica; view ReaderNavigationRecord as ReaderNavigation; max rows 100; } + query ReaderWindowView id "reader-state:window-view" root PresentationStates document "queries/reader-window.graphql" operation "ReaderState" { local from ReaderWindowInputs; watch; view ReaderNavigationRecord as ReaderNavigation; max rows 100; } +} diff --git a/test/fixtures/presentation-mount/PresentationState.qx b/test/fixtures/presentation-mount/PresentationState.qx new file mode 100644 index 0000000..f112431 --- /dev/null +++ b/test/fixtures/presentation-mount/PresentationState.qx @@ -0,0 +1,12 @@ +external interface PresentationMount revision "presentation-mount@1"; +interface PresentationState id "presentation-state" revision "presentation-state@1" { + queryable value occurrence id "state:occurrence" : string { + get id "state:occurrence:get" execution snapshot; + } + queryable relation presentationMount id "state:presentation-mount" : optional-one interface PresentationMount { + resolve id "state:presentation-mount:resolve" execution snapshot; + } + queryable relation parent id "state:parent" : optional-one interface PresentationState { + resolve id "state:parent:resolve" execution snapshot; + } +} diff --git a/test/fixtures/presentation-mount/PresentationStates.qx b/test/fixtures/presentation-mount/PresentationStates.qx new file mode 100644 index 0000000..2bf312a --- /dev/null +++ b/test/fixtures/presentation-mount/PresentationStates.qx @@ -0,0 +1,10 @@ +import interface PresentationState; +import interface PresentationMount; +interface PresentationStates id "presentation-states" revision "presentation-states@1" { + queryable relation states id "presentation-states:states" : many-unique object Navigation keyed "string" { + resolve id "presentation-states:states:resolve" execution snapshot; + } + operation ensure id "presentation-states:ensure" : record {presentationMount:interface-ref;occurrence:string;parent:optional>;} -> ref { + call id "presentation-states:ensure:call" execution transaction; + } +} diff --git a/test/fixtures/presentation-mount/ReaderNavigation.qx b/test/fixtures/presentation-mount/ReaderNavigation.qx new file mode 100644 index 0000000..1e36a11 --- /dev/null +++ b/test/fixtures/presentation-mount/ReaderNavigation.qx @@ -0,0 +1,7 @@ +import interface PresentationState; +interface ReaderNavigation id "reader-navigation" revision "reader-navigation@1" requires PresentationState { + queryable value page id "reader-navigation:mode" : string { + get id "reader-navigation:mode:get" execution snapshot; + set id "reader-navigation:mode:set" execution transaction; + } +} diff --git a/test/fixtures/presentation-mount/StateFactory.qx b/test/fixtures/presentation-mount/StateFactory.qx new file mode 100644 index 0000000..1b87bb2 --- /dev/null +++ b/test/fixtures/presentation-mount/StateFactory.qx @@ -0,0 +1,6 @@ +import interface PresentationState; +interface StateFactory id "state-factory" revision "state-factory@1" { + static operation create id "state-factory:create" : record {occurrence:string;parent:optional>;} -> ref { + call id "state-factory:create:call" execution transaction; + } +} diff --git a/test/fixtures/presentation-mount/StateRecord.qx b/test/fixtures/presentation-mount/StateRecord.qx new file mode 100644 index 0000000..3088af6 --- /dev/null +++ b/test/fixtures/presentation-mount/StateRecord.qx @@ -0,0 +1 @@ +interface StateRecord id "state-record" revision "state-record@1" {} diff --git a/test/fixtures/presentation-mount/queries/reader-inputs.graphql b/test/fixtures/presentation-mount/queries/reader-inputs.graphql new file mode 100644 index 0000000..e586c15 --- /dev/null +++ b/test/fixtures/presentation-mount/queries/reader-inputs.graphql @@ -0,0 +1,7 @@ +query ReaderState { + root { + states(all: 100) { + entries { key mapKey node { _camino { ref } page } } + } + } +} diff --git a/test/fixtures/presentation-mount/queries/reader-state.graphql b/test/fixtures/presentation-mount/queries/reader-state.graphql new file mode 100644 index 0000000..7de27cf --- /dev/null +++ b/test/fixtures/presentation-mount/queries/reader-state.graphql @@ -0,0 +1,7 @@ +query ReaderState($key: String!) { + root { + states(all: 100, where: {_camino: {mapKey: {eq: $key}}}) { + entries { key mapKey node { _camino { ref } page } } + } + } +} diff --git a/test/fixtures/presentation-mount/queries/reader-window.graphql b/test/fixtures/presentation-mount/queries/reader-window.graphql new file mode 100644 index 0000000..a76550c --- /dev/null +++ b/test/fixtures/presentation-mount/queries/reader-window.graphql @@ -0,0 +1,7 @@ +query ReaderState($key: String!) { + root { + states(first: 100, where: {_camino: {mapKey: {eq: $key}}}) { + entries { key mapKey node { _camino { ref } page } } + } + } +} diff --git a/test/fixtures/presentation-mount/workspace.qx b/test/fixtures/presentation-mount/workspace.qx new file mode 100644 index 0000000..18ff3fe --- /dev/null +++ b/test/fixtures/presentation-mount/workspace.qx @@ -0,0 +1,95 @@ +workspace Example id "presentation-example" revision "presentation-example@1" commit "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" { + import interface PresentationState; + import interface StateRecord; + import interface PresentationMount; + import interface Presentation; + import interface PresentationStates; + import interface StateFactory; + import interface ReaderNavigation; + import interface DocumentNavigation; + import package PresentationRuntime; + atom PresentationMountRecord id "presentation-mount-record"; + atom Library id "library"; + atom OtherLibrary id "other-library"; + atom PublicLibrary id "public-library"; + atom Document id "document"; + atom ReaderNavigationRecord id "reader-navigation-record"; + atom DocumentNavigationRecord id "document-navigation-record"; + + conform PresentationMountRecord as PresentationMount id "presentation-mount-record:implementation" { + private edge Membership id "presentation-mount:membership" { + atom PresentationMountRecord projection members id "presentation-mount:members-projection" many-unique public-traversal; + interface PresentationState projection presentationMount id "presentation-mount:owner-projection" optional-one public-traversal; + } + private edge Root id "presentation-mount:root-edge" { + atom PresentationMountRecord projection root id "presentation-mount:root-projection" optional-one; + interface PresentationState projection rootOf id "presentation-mount:root-of-projection" optional-one; + } + bind members.resolve to edge Membership.members.resolve; + bind root.resolve to edge Root.root.resolve; + bind create.call to package PresentationRuntime.createPresentationMount with {presentationMount to creation PresentationMountRecord with {};}; + bind attach.call to package PresentationRuntime.attach with { + members to edge Membership.members; + root to edge Root.root; + metadata to interface PresentationState from input; + }; + } + conform Presentation as PresentationStates id "presentation:state-association" { + private edge States id "presentation:states-edge" { + atom Self projection states id "presentation:states-projection" many-unique keyed "string"; + object Navigation projection subject id "presentation:subject-projection" optional-one; + } + bind states.resolve to edge States.states.resolve; + bind ensure.call to package PresentationRuntime.ensure with { + states to edge States.states; + metadata to interface PresentationState from input; + presentationMount to interface PresentationMount from input; + factory to interface StateFactory; + }; + } + conform StateRecord as PresentationState id "state-record:metadata" { + private state Occurrence id "metadata:occurrence" on Self : string policy register; + private edge Parent id "metadata:parent" { + atom Self projection parent id "metadata:parent-projection" optional-one; + interface PresentationState projection children id "metadata:children-projection" many-unique; + } + initialize {occurrence to state Occurrence;parent to edge Parent.parent;}; + bind occurrence.get to state Occurrence.read; + bind parent.resolve to edge Parent.parent.resolve; + bind presentationMount.resolve to edge Membership.presentationMount.resolve; + } + conform ReaderNavigationRecord as StateRecord id "reader:state-record" {} + conform DocumentNavigationRecord as StateRecord id "document:state-record" {} + conform ReaderNavigationRecord as ReaderNavigation id "reader:navigation" { + private state Mode id "reader:mode" on Self : string policy register default "library"; + bind page to state Mode; + } + conform DocumentNavigationRecord as DocumentNavigation id "document:navigation" { + private state Compact id "document:compact" on Self : bool policy register default false; + bind compact to state Compact; + } + conform Library as StateFactory id "reader:factory" { + bind create.call to package PresentationRuntime.createState with { + navigation to creation ReaderNavigationRecord with {PresentationState from input;}; + }; + } + conform Document as StateFactory id "document:factory" { + bind create.call to package PresentationRuntime.createState with { + navigation to creation DocumentNavigationRecord with {PresentationState from input;}; + }; + } + conform Library as Presentation id "library:presentation" {} + conform Document as Presentation id "document:presentation" {} + conform OtherLibrary as StateFactory id "other-reader:factory" { + bind create.call to package PresentationRuntime.createState with { + navigation to creation ReaderNavigationRecord with {PresentationState from input;}; + }; + } + conform OtherLibrary as Presentation id "other-library:presentation" {} + conform PublicLibrary as StateFactory id "public-reader:factory" { + bind create.call to package PresentationRuntime.createReaderState with { + navigation to creation ReaderNavigationRecord with {PresentationState from input;}; + }; + } + conform PublicLibrary as Presentation id "public-library:presentation" {} +} diff --git a/test/fixtures/query-aggregation.ts b/test/fixtures/query-aggregation.ts index 10c30e9..b107ebd 100644 --- a/test/fixtures/query-aggregation.ts +++ b/test/fixtures/query-aggregation.ts @@ -67,5 +67,5 @@ export const compileAggregation = (body: string, variables = "") => compileQuery( declaration, interfaces, - async () => `query Report${variables} { root { _qx { relations { tasks { ${body} } } } } }`, + async () => `query Report${variables} { root { _camino { relations { tasks { ${body} } } } } }`, ); diff --git a/test/fixtures/query-workspace.ts b/test/fixtures/query-workspace.ts index 91cc26e..9999d65 100644 --- a/test/fixtures/query-workspace.ts +++ b/test/fixtures/query-workspace.ts @@ -44,6 +44,9 @@ export async function queryWorkspaceFixture(options: { live?: boolean } = {}) { operation titleSet id "title-set" : string -> unit mode call receiver any requires {state title id "title-port" : string [write];}; operation score id "score" : unit -> int32 mode call receiver any; + query Extent id "extent" root workspace TaskFacts document "extent.graphql" operation "Extent" { + max rows 30; watch; + } query Upcoming id "upcoming" root Collection document "upcoming.graphql" operation "Upcoming" { fragments "row.graphql"; max rows 30; watch; } @@ -73,29 +76,27 @@ export async function queryWorkspaceFixture(options: { live?: boolean } = {}) { const pkg: PackageRevision = pkgResult.resource.revision; const allInterfaces = [...interfaces.values(), ...(pkgResult.resource.specializations ?? [])]; const documents: Record = { - "upcoming.graphql": `query Upcoming($first: Int!, $after: Cursor) {root {items(first: $first, after: $after, where: {done: {eq: false}}, orderBy: [{rank: ASC}]) {entries {key cursor node {_qx {ref} ...TaskRow}} pageInfo {hasNextPage endCursor}}}}`, + "extent.graphql": `query Extent($text: String! = "") {root {objects(first: 30, where: {title: {containsInsensitive: $text}}, orderBy: [{rank: ASC}]) {entries {node {_camino {ref} title rank}}}}}`, + "upcoming.graphql": `query Upcoming($first: Int!, $after: Cursor) {root {items(first: $first, after: $after, where: {done: {eq: false}}, orderBy: [{rank: ASC}]) {entries {key cursor node {_camino {ref} ...TaskRow}} pageInfo {hasNextPage endCursor}}}}`, "row.graphql": `fragment TaskRow on TaskFacts {title rank done assignee {name}}`, - "enriched.graphql": `query Enriched {root {items(first: 3) {entries {key node {_qx {ref} title score}}}}}`, - "ranked.graphql": `query Ranked {root {items(first: 3, where: {score: {gt: 0}}, orderBy: [{score: DESC}]) {entries {key node {_qx {ref} title}}}}}`, - "totals.graphql": `query Totals {root {_qx {relations {items {aggregate {count sum {rank}} groups(by: {done: true}, first: 10) {entries {group {done} aggregate {count sum {rank}}}}}}}}}`, - "score-totals.graphql": `query ScoreTotals {root {_qx {relations {items {aggregate {count sum {score}}}}}}}`, + "enriched.graphql": `query Enriched {root {items(first: 3) {entries {key node {_camino {ref} title score}}}}}`, + "ranked.graphql": `query Ranked {root {items(first: 3, where: {score: {gt: 0}}, orderBy: [{score: DESC}]) {entries {key node {_camino {ref} title}}}}}`, + "totals.graphql": `query Totals {root {_camino {relations {items {aggregate {count sum {rank}} groups(by: {done: true}, first: 10) {entries {group {done} aggregate {count sum {rank}}}}}}}}}`, + "score-totals.graphql": `query ScoreTotals {root {_camino {relations {items {aggregate {count sum {score}}}}}}}`, }; pkg.checkedQueries = await Promise.all( pkg.queries!.map((query) => compileQuery(query, allInterfaces, async (name) => options.live - ? documents[name]!.replace(/\btitle\b/g, "title @live").replace( - "title @live rank done", - "title @live rank @live done @live", - ) + ? documents[name]!.replace(/\btitle\b/g, "title").replace("title rank done", "title rank done") : documents[name]!, ), ), ); const implementation = (atom: string) => `conform ${atom} as TaskFacts id "${atom}-facts" { private state Title${atom} id "${atom}:title" on ${atom} : string policy crdt(string) default "Untitled"; - private state Done${atom} id "${atom}:done" on ${atom} : bool policy optimistic-register default false; - private state Rank${atom} id "${atom}:rank" on ${atom} : int64 policy optimistic-register default 0; + private state Done${atom} id "${atom}:done" on ${atom} : bool policy register default false; + private state Rank${atom} id "${atom}:rank" on ${atom} : int64 policy register default 0; private edge Assignee${atom} id "${atom}:assignee" { atom ${atom} projection assignee id "${atom}:assignee:forward" optional-one; interface PersonFacts projection tasks id "${atom}:assignee:inverse" many; @@ -110,7 +111,7 @@ export async function queryWorkspaceFixture(options: { live?: boolean } = {}) { import interface PersonFacts; import interface TaskFacts; import interface Collection; import package Queries; atom Task id "Task"; atom Reminder id "Reminder"; atom Person id "Person"; atom Tasks id "Tasks"; conform Person as PersonFacts id "person-facts" { - private state Name id "person:name" on Person : string policy optimistic-register default "Nobody"; + private state Name id "person:name" on Person : string policy register default "Nobody"; bind name to state Name; } ${implementation("Task")} ${implementation("Reminder")} diff --git a/test/fixtures/recurring-task/Task.qx b/test/fixtures/recurring-task/Task.qx new file mode 100644 index 0000000..f5f5c4f --- /dev/null +++ b/test/fixtures/recurring-task/Task.qx @@ -0,0 +1,15 @@ +interface Task id "recurring:task-data" revision "recurring:task-data@1" { + queryable value occurrence id "recurring:occurrence" : uint64 { + get id "recurring:occurrence:get" execution snapshot; + } + queryable value due id "recurring:due" : int64 { + get id "recurring:due:get" execution snapshot; + } + queryable value title id "recurring:title" : string { + get id "recurring:title:get" execution snapshot; + set id "recurring:title:set" execution transaction; + } + operation complete id "recurring:complete" : uint64 -> unit { + call id "recurring:complete:call" execution transaction; + } +} diff --git a/test/fixtures/recurring-task/TaskRuntime.qx b/test/fixtures/recurring-task/TaskRuntime.qx new file mode 100644 index 0000000..ead34a5 --- /dev/null +++ b/test/fixtures/recurring-task/TaskRuntime.qx @@ -0,0 +1,9 @@ +symbol JavaScript id "target:org.quixos.execution.javascript@1"; +external atom TaskObject id "recurring:task"; +package TaskRuntime id "recurring:runtime" revision "recurring:runtime@1" { + operation complete id "recurring:runtime:complete" : uint64 -> unit + mode call execution transaction targets [JavaScript, server] receiver atom TaskObject requires { + state occurrence id "recurring:port:occurrence" : uint64 [read, write]; + state due id "recurring:port:due" : int64 [read, write]; + }; +} diff --git a/test/fixtures/recurring-task/workspace.qx b/test/fixtures/recurring-task/workspace.qx new file mode 100644 index 0000000..adb2164 --- /dev/null +++ b/test/fixtures/recurring-task/workspace.qx @@ -0,0 +1,19 @@ +workspace RecurringTask id "workspace:recurring" revision "workspace:recurring@1" commit "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" { + import interface Task; + import package TaskRuntime; + atom TaskObject id "recurring:task"; + conform TaskObject as Task id "recurring:task:owner" { + private state Occurrence id "recurring:state:occurrence" on TaskObject : uint64 policy register default 0; + private state Due id "recurring:state:due" on TaskObject : int64 policy register; + private state Title id "recurring:state:title" on TaskObject : string policy register; + initialize { due to state Due; title to state Title; }; + bind occurrence.get to state Occurrence.read; + bind due.get to state Due.read; + bind title.get to state Title.read; + bind title.set to state Title.write; + bind complete.call to package TaskRuntime.complete with { + occurrence to state Occurrence; + due to state Due; + }; + } +} diff --git a/test/fixtures/todo.capabilities.qx b/test/fixtures/todo.capabilities.qx index 466277e..d94945d 100644 --- a/test/fixtures/todo.capabilities.qx +++ b/test/fixtures/todo.capabilities.qx @@ -8,7 +8,7 @@ workspace Todo id "workspace:todo" revision "workspace:todo@1" commit "111111111 import package TodoRuntime; shared state ProjectTitle id "slot:project:title" on Project : string - policy optimistic-register default "Untitled project"; + policy register default "Untitled project"; conform Project as Named { bind name.get to state ProjectTitle.read; @@ -19,7 +19,7 @@ workspace Todo id "workspace:todo" revision "workspace:todo@1" commit "111111111 conform Person as Named { private state PersonName id "slot:person:name" on Person : string - policy optimistic-register default "Anonymous"; + policy register default "Anonymous"; bind name.get to state PersonName.read; bind name.set to state PersonName.write; bind name.watch-start to state PersonName.watch-start; diff --git a/test/fixtures/tree/TreeData.qx b/test/fixtures/tree/TreeData.qx new file mode 100644 index 0000000..e696935 --- /dev/null +++ b/test/fixtures/tree/TreeData.qx @@ -0,0 +1,11 @@ +interface TreeData id "tree:data" revision "tree:data@1" { + operation reparent id "tree:reparent" : optional> -> unit { + call id "tree:reparent:call" execution transaction; + } + queryable relation parent id "tree:parent" : optional-one interface TreeData { + resolve id "tree:parent:resolve" execution snapshot; + } + queryable relation children id "tree:children" : many interface TreeData { + resolve id "tree:children:resolve" execution snapshot; + } +} diff --git a/test/fixtures/tree/TreeRuntime.qx b/test/fixtures/tree/TreeRuntime.qx new file mode 100644 index 0000000..81b9fb3 --- /dev/null +++ b/test/fixtures/tree/TreeRuntime.qx @@ -0,0 +1,14 @@ +symbol JavaScript id "target:org.quixos.execution.javascript@1"; +external atom Node id "tree:node"; +import interface TreeData; +package TreeRuntime id "tree:runtime" revision "tree:runtime@1" { + operation reparent id "tree:runtime:reparent" : optional> -> unit + mode call execution transaction targets [JavaScript, server] receiver atom Node requires { + edge parent id "tree:move:parent" : optional-one interface TreeData [resolve, connect, disconnect]; + } accept using check; + operation check id "tree:runtime:check" : optional> -> unit + mode call execution snapshot targets [server] receiver atom Node requires { + edge current id "tree:check:current" : optional-one interface TreeData [resolve]; + interface ancestors id "tree:check:ancestors" : TreeData using [parent.resolve] from input; + }; +} diff --git a/test/fixtures/tree/workspace.qx b/test/fixtures/tree/workspace.qx new file mode 100644 index 0000000..f7c41b9 --- /dev/null +++ b/test/fixtures/tree/workspace.qx @@ -0,0 +1,19 @@ +workspace Tree id "workspace:tree" revision "workspace:tree@1" commit "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" { + import interface TreeData; + import package TreeRuntime; + atom Node id "tree:node"; + conform Node as TreeData id "tree:node:data" { + private edge Parent id "tree:parent-edge" { + atom Node projection parent id "tree:parent-projection" optional-one; + interface TreeData projection children id "tree:children-projection" many; + } + bind parent.resolve to edge Parent.parent.resolve; + bind children.resolve to edge Parent.children.resolve; + bind reparent.call to package TreeRuntime.reparent with { + parent to edge Parent.parent; + } accept with { + current to edge Parent.parent; + ancestors to interface TreeData from input; + }; + } +} diff --git a/test/fixtures/web-studio.capabilities.qx b/test/fixtures/web-studio.capabilities.qx index 5ead8b3..ed39f0d 100644 --- a/test/fixtures/web-studio.capabilities.qx +++ b/test/fixtures/web-studio.capabilities.qx @@ -8,7 +8,7 @@ workspace WebStudioFixture id "workspace:web-studio-fixture" revision "workspace import package ComponentRuntime; shared state ProjectName id "slot:project:name" on Project : string - policy optimistic-register default "Untitled project"; + policy register default "Untitled project"; shared edge ProjectComponentEdge id "edge:project:component" { atom ProjectComponent projection subject id "projection:component:subject" exactly-one; atom Project projection component id "projection:project:component" optional-one; diff --git a/test/forwarding.test.ts b/test/forwarding.test.ts new file mode 100644 index 0000000..5674429 --- /dev/null +++ b/test/forwarding.test.ts @@ -0,0 +1,149 @@ +import assert from "node:assert/strict"; +import test from "node:test"; +import { compileCapabilityResourceSource, compileCapabilitySource } from "../src/capability-language/index.js"; +import { compileWorkspaceRevision, resolveOperationPlan } from "../src/capability-model/validation.js"; +import { capabilityId, type InterfaceRevision } from "../src/capability-model/types.js"; +import { compileQuery } from "../src/query/compile.js"; +import { linkQueries } from "../src/query/link.js"; +import { compileCapabilityFixture, fixtureId } from "./fixtures/capability-model.js"; + +const source = { repository: "https://example.test/forwarding.git", commit: "a".repeat(40) }; +function fixture(transform = (s: string) => s, generic = false) { + const interfaces = new Map(); + for (const name of ["Owner", "Alias", "Relay"]) { + const result = compileCapabilityResourceSource( + `interface ${name}${generic && name === "Owner" ? "" : ""} id "${name}" revision "${name}@1" { + queryable value title id "${name}:title" : ${generic && name === "Owner" ? "T" : "string"} { + get id "${name}:get"; set id "${name}:set"; + watch start id "${name}:start" stop id "${name}:stop"; + } + }`, + { source }, + ); + assert.ok(result.ok, JSON.stringify(result.diagnostics)); + if (result.resource.kind !== "interface") throw new Error("interface expected"); + interfaces.set(name, result.resource.revision); + } + const forward = (name: string, target: string) => `conform Item as ${name} id "${name}-conformance" { + bind title.get to interface ${target}.title.get; + bind title.set to interface ${target}.title.set; + bind title.watch-start to interface ${target}.title.watch-start; + bind title.watch-stop to interface ${target}.title.watch-stop; + }`; + const text = `workspace Test id "ws" revision "ws@1" commit "${source.commit}" { + import interface Owner; import interface Alias; import interface Relay; + atom Item id "item"; + ${forward("Relay", "Alias")} + ${forward("Alias", generic ? "Owner" : "Owner")} + conform Item as Owner${generic ? "" : ""} id "owner-conformance" { + private state Title id "title" on Item : string policy register default ""; + bind title to state Title; + } + }`; + return { result: compileCapabilitySource(transform(text), "workspace.qx", { interfaces }), interfaces }; +} + +test("bind chains forward native reads, writes and watches without changing storage ownership", async () => { + const { result, interfaces } = fixture(); + assert.ok(result.ok, JSON.stringify(result.diagnostics)); + const compiled = compileWorkspaceRevision(result.workspace); + assert.ok(compiled.ok); + for (const [operation, primitive] of [ + ["get", "read"], + ["set", "write"], + ["start", "watch-start"], + ["stop", "watch-stop"], + ]) { + const resolved = resolveOperationPlan( + compiled.plan, + capabilityId.atom("item"), + capabilityId.interfaceRevision("Relay@1"), + capabilityId.operation(`Relay:${operation}`), + ); + assert.equal(resolved?.binding.kind, "state"); + if (resolved?.kind !== "state") throw new Error("state expected"); + assert.equal(resolved.binding.primitive, primitive); + assert.deepEqual(resolved.attachment.owner, { + kind: "conformance", + atomId: "item", + interfaceRevisionId: "Owner@1", + }); + } + const pkg = compileCapabilityResourceSource( + `import interface Relay; + package Views id "views" revision "views@1" { query Rows id "rows" root workspace Relay document "rows.graphql" operation "Rows" {max rows 10; watch;} }`, + { source, environment: { interfaces } }, + ); + assert.ok(pkg.ok, JSON.stringify(pkg.diagnostics)); + if (pkg.resource.kind !== "package") throw new Error("package expected"); + const revision = pkg.resource.revision; + revision.checkedQueries = [ + await compileQuery( + revision.queries![0]!, + [...interfaces.values()], + async () => `query Rows {root {objects(first: 10) {entries {node {title}}}}}`, + ), + ]; + result.workspace.packageImports.push(revision); + const linked = linkQueries(result.workspace)[0]!; + assert.equal(linked.fields[0]!.binding.kind, "state"); + assert.equal(linked.attachments[0]!.id, "title"); +}); + +test("forwarding rejects cycles, wrong signatures, missing targets, and direct private access", () => { + for (const transform of [ + (s: string) => s.replaceAll("interface Owner.title", "interface Relay.title"), + (s: string) => s.replace("interface Owner.title.get", "interface Owner.title.set"), + (s: string) => + s + .replace("conform Item as Owner", "conform Other as Owner") + .replace('atom Item id "item";', 'atom Item id "item"; atom Other id "other";') + .replace("on Item :", "on Other :"), + (s: string) => s.replace("interface Owner.title.get", "state Title.read"), + (s: string) => s.replace("interface Owner.title.watch-stop", "interface Relay.title.watch-stop"), + ]) + assert.equal(fixture(transform).result.ok, false); +}); + +test("forwarding closes generic targets and rejects incompatible applications", () => { + const good = fixture(undefined, true).result; + assert.ok(good.ok, JSON.stringify(good.diagnostics)); + const bad = fixture( + (s) => s.replace("interface Owner.title.get", "interface Owner.title.get"), + true, + ).result; + assert.equal(bad.ok, false); +}); + +test("forwarded package actions retain the provider and its dependency ports", () => { + const fixture = compileCapabilityFixture(); + assert.ok(fixture.ok, JSON.stringify(fixture.diagnostics)); + const ws = fixture.workspace; + const target = ws.interfaceImports.find((i) => i.revisionId === fixtureId.summaryV1)!; + const alias = { + ...structuredClone(target), + interfaceId: capabilityId.interface("alias"), + revisionId: capabilityId.interfaceRevision("alias@1"), + }; + ws.interfaceImports.push(alias); + ws.conformances.push({ + atomId: fixtureId.project, + interfaceRevisionId: alias.revisionId, + id: capabilityId.conformance("alias-owner"), + privateAttachments: [], + relationshipMaterializations: [], + operationBindings: [ + { + operationId: fixtureId.summaryGet, + binding: { kind: "interface", interfaceRevisionId: fixtureId.summaryV1, operationId: fixtureId.summaryGet }, + }, + ], + }); + const result = compileWorkspaceRevision(ws); + assert.ok(result.ok, JSON.stringify(result)); + const plan = resolveOperationPlan(result.plan, fixtureId.project, alias.revisionId, fixtureId.summaryGet); + assert.equal(plan?.kind, "package"); + if (plan?.kind !== "package") throw new Error("package expected"); + assert.equal(plan.binding.exportId, fixtureId.summaryGetExport); + assert.ok(plan.dependencies.length > 0); +}); diff --git a/test/generic-packages.test.ts b/test/generic-packages.test.ts index 790afc2..994b622 100644 --- a/test/generic-packages.test.ts +++ b/test/generic-packages.test.ts @@ -1,3 +1,4 @@ +import { specializePackageExport } from "../src/capability-model/generic-packages.js"; import assert from "node:assert/strict"; import test from "node:test"; import fs from "node:fs/promises"; @@ -43,6 +44,7 @@ test("runtime conformance descriptors preserve closed and universal collection i }; operation inspect id "inspect" : ref -> unit mode call receiver any requires { interface items id "items" : Collection; + reflection metadata id "metadata" [atoms, interfaces]; }; }`, { source, environment: { interfaces: new Map([["Collection", collection.resource.revision]]) } }, @@ -77,6 +79,11 @@ test("runtime conformance descriptors preserve closed and universal collection i return null; }, inspect: async (context) => { + await context.ports.metadata.read({selector:{kind:"atoms",interfaceRevisionId:"named"}}); + // @ts-expect-error Queries are outside this port's declared authority. + await context.ports.metadata.read({selector:{kind:"queries"}}); + // @ts-expect-error Interface inventory does not support atom filters. + await context.ports.metadata.read({selector:{kind:"interfaces",atomId:"book"}}); const view = await context.conform.tryConform(context.objectId, context.ports.items.contract); if (view) await view["items.connect"](context.input); return null; @@ -291,3 +298,25 @@ test("typed presentation and factory consumers preserve distinct closed applicat await fs.rm(directory, { recursive: true, force: true }); } }); + +test("specialized dispatch keys retain nominal arguments, not build provenance", () => { + const pkg = definition(); + const generic = pkg.genericExports![0]!; + const environment = { + arguments: new Map(), + applyInterface: () => { + throw Error("unused"); + }, + implementsInterface: () => false, + }; + const arguments_ = [{ kind: "value" as const, type: { kind: "scalar" as const, name: "string" as const } }]; + const original = specializePackageExport(pkg, generic, arguments_, environment); + const rebuilt = structuredClone(pkg); + rebuilt.source.commit = "f".repeat(40); + assert.deepEqual(specializePackageExport(rebuilt, generic, arguments_, environment), original); + assert.notEqual( + specializePackageExport(rebuilt, generic, [{ kind: "value", type: { kind: "scalar", name: "bool" } }], environment) + .id, + original.id, + ); +}); diff --git a/test/generics.test.ts b/test/generics.test.ts index b2e86ba..03e3049 100644 --- a/test/generics.test.ts +++ b/test/generics.test.ts @@ -165,7 +165,7 @@ test("bounds require evidence and storable constraints inspect nested values", ( ); }); -test("closed applications canonicalize records without erasing nominal identity or provenance", () => { +test("closed applications preserve nominal identity while separating source provenance", () => { const a: ClosedTypeArgument = { kind: "value", type: { kind: "record", fields: { b: valueType.int64, a: valueType.string } }, @@ -183,6 +183,10 @@ test("closed applications canonicalize records without erasing nominal identity assert.equal(appliedInterfaceId(application), appliedInterfaceId({ ...application, arguments: [b] })); assert.notEqual(appliedInterfaceId(application), appliedInterfaceId({ ...application, self: note })); assert.notEqual( + appliedInterfaceId(application), + appliedInterfaceId({ ...application, definitionId: capabilityId.interfaceRevision("other@1") }), + ); + assert.equal( appliedInterfaceId(application), appliedInterfaceId({ ...application, source: { ...application.source, commit: "b".repeat(40) } }), ); @@ -272,7 +276,7 @@ test("generic conformances bind state against specialized signatures", () => { atom Document id "document"; import interface Reader; conform Document as Reader id "reader-conformance" { - private state Values id "values-slot" on Document : list> policy optimistic-register; + private state Values id "values-slot" on Document : list> policy register; bind values.get to state Values.read; bind values.watch-start to state Values.watch-start; bind values.watch-stop to state Values.watch-stop; @@ -328,7 +332,7 @@ test("package receivers and injected dependencies select exact applications", () import interface Reader; import interface Summary; import package P; atom Note id "note"; conform Note as Reader id "reader-conformance" { - private state Values id "values" on Note : list> policy optimistic-register; + private state Values id "values" on Note : list> policy register; bind values.get to state Values.read; bind values.watch-start to state Values.watch-start; bind values.watch-stop to state Values.watch-stop; @@ -455,8 +459,8 @@ test("expanding recursive generic interfaces fail with a bounded diagnostic", () assert.equal(result.diagnostics[0].code, "recursive-application"); }); -test("storable parameters do not imply support for RPC-only records", () => { - assert.equal(isStorableType({ kind: "record", fields: { name: valueType.string } }), false); +test("storable records recursively require storable leaves", () => { + assert.equal(isStorableType({ kind: "record", fields: { name: valueType.string } }), true); assert.equal(isStorableType(valueType.list(valueType.optional(valueType.string))), true); }); @@ -599,3 +603,32 @@ test("prerequisites require explicit conformances and remain in the capability c applied.application!.arguments = [{ kind: "value", type: valueType.int64 }]; assert.equal(compileWorkspaceRevision(forged).ok, false); }); + +test("edge read targets honor declared interface prerequisites without inferring unrelated conformances", () => { + const interfaces = new Map(); + for (const text of [ + 'interface Base id "base" revision "base@1" {}', + 'import interface Base; interface Detailed id "detailed" revision "detailed@1" requires Base {}', + 'import interface Base; interface Holder id "holder" revision "holder@1" {queryable relation item id "item" : optional-one interface Base {resolve id "item:resolve" execution snapshot;}}', + ]) { + const parsed = compileCapabilityResourceSource(text, { source, environment: { interfaces } }); + assert.ok(parsed.ok, JSON.stringify(parsed.diagnostics)); + if (parsed.resource.kind !== "interface") throw Error("interface expected"); + interfaces.set(parsed.resource.revision.displayName, parsed.resource.revision); + } + const assembly = `workspace W id "w" revision "w@1" commit "${source.commit}" { + import interface Base;import interface Detailed;import interface Holder; + atom Item id "item";atom Box id "box"; + conform Item as Base id "item-base" {} conform Item as Detailed id "item-detailed" {} + conform Box as Holder id "box-holder" { + private edge ItemEdge id "items" {atom Self projection item id "item" optional-one;interface Detailed projection holders id "holders" many;} + bind item.resolve to edge ItemEdge.item.resolve; + } + }`; + const valid = compileCapabilitySource(assembly, "workspace.qx", { interfaces }); + assert.ok(valid.ok, JSON.stringify(valid.diagnostics)); + const unrelated = new Map(interfaces); + unrelated.set("Detailed", { ...interfaces.get("Detailed"), requiredInterfaces: [] }); + const invalid = compileCapabilitySource(assembly, "workspace.qx", { interfaces: unrelated }); + assert.equal(invalid.ok, false, "installed co-occurrence alone is not proof of a declared interface implication"); +}); diff --git a/test/graph-execution.test.ts b/test/graph-execution.test.ts new file mode 100644 index 0000000..ba6f8ac --- /dev/null +++ b/test/graph-execution.test.ts @@ -0,0 +1,107 @@ +import assert from "node:assert/strict"; +import { test } from "node:test"; +import { loadReaderWorkspace } from "./reader-fixture.js"; +import { buildArtifactFixture } from "./artifact-fixture.js"; +import { + generatePortableClientBindings, + compileExecutionPlan, + compileKernelProgram, + compileQueryProgram, + graphExecutionRepresentation, + hexDigest, + type ExecutionPlan, + type ExecutionInstallation, +} from "../src/execution-world/index.js"; + +function prepare(plan: ExecutionPlan) { + const installation: ExecutionInstallation = { + codec: "camino-execution-installation-v1", + plan: plan.digest, + world: "0".repeat(64), + routes: {}, + components: [], + packages: plan.definition.packages.map((pkg) => { + const { manifest } = buildArtifactFixture(plan, pkg.revision); + return { + revision: pkg.revision, + root: "/build/provenance", + service: manifest.implementations[0]!, + browserImplementation: manifest.implementations[2]!, + }; + }), + }; + const kernel = compileKernelProgram(plan) as any, + queries = compileQueryProgram(plan) as any; + return { + installation, + kernel, + queries, + graph: () => graphExecutionRepresentation(plan, installation, kernel, queries), + }; +} + +test("actual interpreter inputs and creation recipes exclude source provenance", async () => { + const workspace = await loadReaderWorkspace(); + const originalPlan = compileExecutionPlan(workspace); + const before = prepare(originalPlan); + workspace.sourceRootCommit = "f".repeat(40); + for (const pkg of workspace.packageImports) pkg.source.commit = "e".repeat(40); + const nextPlan = compileExecutionPlan(workspace); + const after = prepare(nextPlan); + assert.equal( + generatePortableClientBindings(originalPlan, { target: "browser" }), + generatePortableClientBindings(nextPlan, { target: "browser" }), + ); + assert.notEqual(before.installation.plan, after.installation.plan); + assert.deepEqual(before.kernel, after.kernel); + assert.deepEqual(before.queries, after.queries); + assert.deepEqual(before.graph(), after.graph()); + assert.ok(JSON.stringify(before.kernel).includes('"recipe"')); +}); + +test("graph identity covers executable closures, query semantics and real interpreter input", async () => { + const plan = compileExecutionPlan(await loadReaderWorkspace()); + const fixture = prepare(plan), + baseline = hexDigest(fixture.graph()); + fixture.installation.packages[0]!.service!.files[0]!.digest = "c".repeat(64); + assert.notEqual(hexDigest(fixture.graph()), baseline); + const query = prepare(plan); + query.queries.queries[0].budgets.rows++; + assert.notEqual(hexDigest(query.graph()), baseline); + query.kernel.operations[0].output = { kind: "scalar", name: "string" }; + assert.throws(() => query.graph(), /GRAPH_PROGRAM_MISMATCH/); + const shared = prepare(plan); + (shared.installation as any).engineURL = "/changed-engine.mjs"; + assert.notEqual(hexDigest(shared.graph()), baseline); + const future = prepare(plan); + future.kernel.codec = "future-kernel"; + assert.throws(() => future.graph(), /GRAPH_PROGRAM_MISMATCH/); +}); + +test("initialization, storage, nominal contracts and bound ports affect executed kernel identity", async () => { + const workspace = await loadReaderWorkspace(), + plan = compileExecutionPlan(workspace); + const original = prepare(plan); + for (const change of [ + (p: ExecutionPlan) => { + (p.definition.owners[0]!.initialization as any[]).push({ + input: "extra", + binding: { kind: "state", slotId: "extra" }, + }); + }, + (p: ExecutionPlan) => { + p.definition.owners[0]!.id += ":different"; + }, + (p: ExecutionPlan) => { + p.definition.operations[0]!.operationId += ":different"; + }, + (p: ExecutionPlan) => { + p.definition.versions.crdt = "future-genesis" as any; + }, + ]) { + const changed = structuredClone(plan); + change(changed); + changed.digest = hexDigest(changed.definition); + assert.notEqual((compileKernelProgram(changed) as any).plan, original.kernel.plan); + } +}); diff --git a/test/inspection.test.ts b/test/inspection.test.ts new file mode 100644 index 0000000..062f9d8 --- /dev/null +++ b/test/inspection.test.ts @@ -0,0 +1,36 @@ +import assert from "node:assert/strict"; +import { test } from "node:test"; +import { loadReaderWorkspace } from "./reader-fixture.js"; +import { compileExecutionPlan, compileQueryProgram } from "../src/execution-world/index.js"; +import { compileInspectionTemplates } from "../src/execution-world/inspection.js"; +test("public member inspection installs a finite set of typed lazy templates", async () => { + const plan = compileExecutionPlan(await loadReaderWorkspace()); + const result = compileInspectionTemplates(plan); + assert.ok(result.templates.length); + const document = result.templates.find( + (template) => + template.members.some((member) => member.name === "content") && + template.members.some((member) => member.name === "highlights"), + )!; + assert.ok(document.inputs && document.view && document.server && document.completeInputs && document.completeView); + assert.equal(document.completeView.sourceQuery, document.completeInputs.query); + assert.equal(document.view.sourceQuery, document.inputs.query); + assert.ok(document.members.find((member) => member.name === "highlights")?.many); + assert.ok(!document.members.some((member) => member.name === "revise")); + const compiled = compileQueryProgram(plan) as { + queries: { id: string; selection: { selection: { conditions: unknown[] }[] }[] }[]; + }; + for (const descriptor of result.contracts) { + const query = compiled.queries.find( + (query) => query.id === JSON.stringify([descriptor.package, descriptor.query]), + )!; + assert.ok(query); + assert.ok( + query.selection[0]!.selection.every((field) => field.conditions.length === 1), + "Each field admission is independently selected", + ); + } + assert.ok(!JSON.stringify(result.templates).includes("slot:")); + assert.equal(result.queries.length, result.contracts.length); + assert.ok(result.queries.length <= plan.definition.workspace.interfaceImports.length * 5); +}); diff --git a/test/journals.test.ts b/test/journals.test.ts new file mode 100644 index 0000000..f5151ec --- /dev/null +++ b/test/journals.test.ts @@ -0,0 +1,186 @@ +import assert from "node:assert/strict"; +import { test } from "node:test"; +import { compileCapabilityResourceSource, compileCapabilitySource } from "../src/capability-language/parser.js"; +import { formatQx } from "../src/capability-language/source.js"; +import { compileExecutionPlan, compileKernelProgram } from "../src/execution-world/index.js"; +import type { InterfaceRevision } from "../src/capability-model/types.js"; +import { readFileSync } from "node:fs"; + +const source = { repository: "https://example.test/journals", commit: "a".repeat(40) }; + +test("standard event contracts instantiate generic native conformance rules", () => { + const interfaces = new Map(); + for (const [name, slug] of [ + ["EventStream", "event-stream"], + ["EventHandler", "event-handler"], + ["EventSubscription", "event-subscription"], + ["EventStreamRecord", "event-stream-record"], + ["EventSubscriptionRecord", "event-subscription-record"], + ]) { + const text = readFileSync(new URL(`../../test/fixtures/events/${slug}.qx`, import.meta.url), "utf8"); + const result = compileCapabilityResourceSource(text, { + source, + environment: { interfaces, interfaceClosure: [...interfaces.values()] }, + }); + assert.ok(result.ok && result.resource.kind === "interface", JSON.stringify(result.diagnostics)); + interfaces.set(name!, result.resource.revision); + } + const rules = readFileSync(new URL("../../test/fixtures/events/rules.qx", import.meta.url), "utf8") + .replace(/^fragment\s*\{/, "") + .replace(/}\s*$/, ""); + const result = compileCapabilitySource( + `workspace Events id "events" revision "events@1" commit "${source.commit}" { + ${rules} + atom TextStream id "text-stream"; + conform TextStream as EventStreamRecord id "text-marker" {} + atom TextSubscription id "text-subscription"; + conform TextSubscription as EventSubscriptionRecord id "text-subscription-marker" {} + atom NumberStream id "number-stream"; + conform NumberStream as EventStreamRecord id "number-marker" {} + }`, + "events.qx", + { interfaces, interfaceClosure: [...interfaces.values()] }, + ); + assert.ok(result.ok, JSON.stringify(result.diagnostics)); + const kernel = compileKernelProgram(compileExecutionPlan(result.workspace)) as { + model: { owners: { slots: { policy: string }[] }[] }; + }; + assert.equal( + kernel.model.owners.flatMap((owner) => owner.slots).filter((slot) => slot.policy === "journal").length, + 2, + ); + assert.equal( + kernel.model.owners.flatMap((owner) => owner.slots).filter((slot) => slot.policy === "subscription").length, + 1, + ); +}); +const contract = `external atom Document id "document"; +type Event = record { document: atom-ref; title: string; }; +interface Stream id "stream" revision "stream@1" { + operation publish id "publish" : Event -> record { publication: string; } { + call id "publish:call" execution transaction; + } + queryable value head id "head" : optional { get id "head:get" execution snapshot; } + operation readPage id "page" : record { after: optional; limit: uint32; } + -> record { entries: list; next: optional; asOfHead: optional; } { + call id "page:call" execution snapshot; + } +}`; +const assembly = `workspace Journals id "journals" revision "journals@1" commit "${source.commit}" { + import interface Stream; + atom Document id "document"; + atom Journal id "journal-object"; + conform Journal as Stream id "journal-owner" { + private state Entries id "entries" on Journal : record { document: atom-ref; title: string; } + policy journal { "maxEntries": 32, "maxBytes": 65536, "maxPublicationBytes": 8192, "maxProtectedSubscriptions": 8 }; + bind publish.call to journal Entries.publish; + bind head.get to journal Entries.head; + bind readPage.call to journal Entries.readPage; + } +}`; +function compile(text = assembly, iface = contract) { + const resource = compileCapabilityResourceSource(iface, { source }); + assert.ok(resource.ok && resource.resource.kind === "interface", JSON.stringify(resource.diagnostics)); + return compileCapabilitySource(formatQx(text), "journal.qx", { + interfaces: new Map([["Stream", resource.resource.revision]]), + }); +} +test("journal bindings typecheck and lower managed reference pins", () => { + const result = compile(); + assert.ok(result.ok, JSON.stringify(result.diagnostics)); + const kernel = compileKernelProgram(compileExecutionPlan(result.workspace)) as { + model: { edges: unknown[]; owners: { slots: { policy: string; journal: { pins: unknown[] } }[] }[] }; + operations: { binding: { kind: string; primitive: string } }[]; + }; + assert.equal(kernel.model.owners[0]!.slots[0]!.policy, "journal"); + assert.equal(kernel.model.owners[0]!.slots[0]!.journal.pins.length, 1); + assert.equal(kernel.model.edges.length, 1); + assert.deepEqual(kernel.operations.map((op) => op.binding.primitive).sort(), ["head", "publish", "readPage"]); + assert.ok(kernel.operations.every((op) => op.binding.kind === "journal")); +}); +test("journal declarations reject state access, bad bounds, seeding and signature substitution", () => { + for (const [ws, iface] of [ + [assembly.replace("journal Entries.head", "state Entries.read"), contract], + [assembly.replace('"maxEntries": 32', '"maxEntries": 0'), contract], + [assembly.replace("bind publish.call", "initialize { seed to state Entries; }; bind publish.call"), contract], + [assembly, contract.replace("publication: string", "publication: uint32")], + [assembly, contract.replace("execution transaction", "execution snapshot")], + ]) { + const result = compile(ws, iface); + assert.equal(result.ok, false, `${ws}\n${iface}`); + } +}); + +test("subscription storage infers its typed handler and retains configuration references", () => { + const interfaces = new Map(); + const resource = (name: string, text: string) => { + const result = compileCapabilityResourceSource(text, { + source, + environment: { interfaces, interfaceClosure: [...interfaces.values()] }, + }); + assert.ok(result.ok && result.resource.kind === "interface", JSON.stringify(result.diagnostics)); + interfaces.set(name, result.resource.revision); + }; + resource("Stream", contract); + resource( + "Receiver", + `external atom Document id "document"; +interface Receiver id "receiver" revision "receiver@1" { + operation receive id "receive" : record { event: record { document: atom-ref; title: string; }; parameters: string; } -> unit { + call id "receive:call" execution transaction; + } +}`, + ); + resource( + "Subscription", + `external atom Document id "document"; +import interface Stream; +import interface Receiver; +type Start = variant id "start" { future id "future" : unit; retained id "retained" : unit; afterCursor id "after" : string; }; +interface Subscription id "subscription" revision "subscription@1" { + operation attach id "attach" : record { source: interface-ref; handler: interface-ref; parameters: string; start: Start; } -> unit { + call id "attach:call" execution transaction; + } + queryable value status id "status" : record { status: string; generation: uint64; cursor: uint64; failure: optional; } { + get id "status:get" execution snapshot; + } + operation retire id "retire" : unit -> unit { call id "retire:call" execution transaction; } +}`, + ); + const ws = assembly + .replace( + "import interface Stream;", + "import interface Stream; import interface Receiver; import interface Subscription;", + ) + .replace( + /\n}$/, + ` + atom Delivery id "delivery"; + conform Delivery as Subscription id "delivery-owner" { + private state DeliveryState id "delivery-state" on Delivery : string policy subscription(record { document: atom-ref; title: string; }); + bind attach.call to subscription DeliveryState.attach; + bind status.get to subscription DeliveryState.status; + bind retire.call to subscription DeliveryState.retire; + } +}`, + ); + const result = compileCapabilitySource(formatQx(ws), "subscriptions.qx", { + interfaces, + interfaceClosure: [...interfaces.values()], + }); + assert.ok(result.ok, JSON.stringify(result.diagnostics)); + const kernel = compileKernelProgram(compileExecutionPlan(result.workspace)) as { + model: { + owners: { slots: { policy: string; subscription?: { operation: string; pins: unknown[]; active: unknown } }[] }[]; + }; + }; + const slot = kernel.model.owners.flatMap((owner) => owner.slots).find((slot) => slot.policy === "subscription")!; + assert.equal(slot.subscription!.operation, "receive:call"); + assert.equal(slot.subscription!.pins.length, 2); + assert.ok(slot.subscription!.active); + const bad = compileCapabilitySource(ws.replace("on Delivery : string", "on Delivery : uint32"), "bad.qx", { + interfaces, + interfaceClosure: [...interfaces.values()], + }); + assert.equal(bad.ok, false, "remembered parameter type must agree with attachment and handler"); +}); diff --git a/test/keyed-relationships.test.ts b/test/keyed-relationships.test.ts new file mode 100644 index 0000000..e196bb7 --- /dev/null +++ b/test/keyed-relationships.test.ts @@ -0,0 +1,111 @@ +import assert from "node:assert/strict"; +import { test } from "node:test"; +import { + compileCapabilityResourceSource, + compileCapabilitySource, + formatQx, +} from "../src/capability-language/index.js"; +import { compileExecutionPlan } from "../src/execution-world/plan.js"; +import { compileKernelProgram } from "../src/execution-world/kernel.js"; +import { generatePortableTypeScriptBindings } from "../src/bindings/portable.js"; +const source = { repository: "https://example.test/keyed.git", commit: "a".repeat(40) }; +const iface = compileCapabilityResourceSource( + `interface Index id "index" revision "index@1" { + queryable relation entries id "entries" : many object Item keyed "string" { + resolve id "resolve" execution snapshot; + connect id "connect" execution transaction; + } + operation add id "add" : record {mapKey:string;target:ref;} -> unit { + call id "add-call" execution transaction; + } +}`, + { source }, +); +assert.ok(iface.ok && iface.resource.kind === "interface", JSON.stringify(iface.diagnostics)); +const pkg = compileCapabilityResourceSource( + `symbol JavaScript id "target:org.quixos.execution.javascript@1"; +package Runtime id "runtime" revision "runtime@1" { + operation add id "add" : record {mapKey:string;target:ref;} -> unit mode call execution transaction targets [JavaScript,server] receiver any requires { + edge entries id "entries" : many object Item keyed "string" [resolve,connect]; + }; +}`, + { source }, +); +assert.ok(pkg.ok && pkg.resource.kind === "package", JSON.stringify(pkg.diagnostics)); +const indexContract = iface.resource.revision, + runtimeContract = pkg.resource.revision; +const declarations = `workspace W id "w" revision "w@1" commit "${source.commit}" { + import interface Index; import package Runtime; atom Node id "node"; + conform Node as Index id "node:index" { + private edge Entries id "entries" { + atom Node projection entries id "entries" many keyed "string"; + atom Node projection parent id "parent" optional-one; + } + bind entries.resolve to edge Entries.entries.resolve; + bind entries.connect to edge Entries.entries.connect; + bind add.call to package Runtime.add with {entries to edge Entries.entries;}; + } +}`; +const compile = (text = declarations) => + compileCapabilitySource(formatQx(text), "workspace.qx", { + interfaces: new Map([["Index", indexContract]]), + packages: new Map([["Runtime", runtimeContract]]), + }); +test("generic keyed relationships and portable ports have matching checked shapes", () => { + const result = compile(); + assert.ok(result.ok, JSON.stringify(result.diagnostics)); + const closed = result.workspace.interfaceImports.find((i) => i.application)!; + assert.equal(closed.members[0]!.kind, "relationship"); + assert.equal("keyType" in closed.members[0]! && closed.members[0]!.keyType, "string"); + const operation = closed.members[0]!.operations.find((o) => o.displayName === "connect")!; + assert.equal(operation.inputType.kind, "record"); + const plan = compileExecutionPlan(result.workspace); + assert.doesNotThrow(() => compileKernelProgram(plan)); + const bindings = generatePortableTypeScriptBindings(plan.definition.bindingSchema, "runtime@1", { + target: "target:org.quixos.execution.javascript@1", + }); + assert.match(bindings, /readonly "mapKey": string/); + assert.match(bindings, /readonly "connect": \(input: \{ readonly "mapKey": string;/); +}); +test("key contracts cannot silently bind to unkeyed storage or the inverse projection", () => { + for (const text of [ + declarations.replace('many keyed "string";', "many;"), + declarations.replace("entries to edge Entries.entries", "entries to edge Entries.parent"), + ]) { + const result = compile(text); + assert.equal(result.ok, false); + assert.match(JSON.stringify(result.diagnostics), /key type|keyed projection|signature/i); + } +}); + +test("checked shared edges lower without granting conformance initialization ownership", () => { + const edge = declarations.match(/ private edge Entries[\s\S]*?\n }/)![0]; + const shared = declarations + .replace(edge, "") + .replace(" conform Node as", edge.replace("private edge", "shared edge") + "\n conform Node as"); + const result = compile(shared); + assert.ok(result.ok, JSON.stringify(result.diagnostics)); + const program = compileKernelProgram(compileExecutionPlan(result.workspace)) as { + model: { edges: { id: string; owner: string | null }[] }; + }; + assert.deepEqual( + program.model.edges.map((edge) => ({ id: edge.id, owner: edge.owner })), + [{ id: "entries", owner: null }], + ); + const seeded = compile( + shared.replace( + " bind entries.resolve", + " initialize { initial to edge Entries.entries; };\n bind entries.resolve", + ), + ); + assert.ok(!seeded.ok); + assert.ok(seeded.diagnostics.some((issue) => issue.code === "invalid-initialization")); + const scalar = compile( + shared.replace( + ' atom Node id "node";', + ' atom Node id "node"; shared state Shared id "shared" on Node : string policy register default "";', + ), + ); + assert.ok(scalar.ok, JSON.stringify(scalar.diagnostics)); + assert.throws(() => compileKernelProgram(compileExecutionPlan(scalar.workspace)), /UNSUPPORTED_SHARED_STORAGE/); +}); diff --git a/test/lifecycle.test.ts b/test/lifecycle.test.ts new file mode 100644 index 0000000..6e6cd03 --- /dev/null +++ b/test/lifecycle.test.ts @@ -0,0 +1,65 @@ +import assert from "node:assert/strict"; +import { test } from "node:test"; +import { compileCapabilityResourceSource, compileCapabilitySource } from "../src/capability-language/parser.js"; +import { formatQx } from "../src/capability-language/source.js"; +import { compileExecutionPlan, compileKernelProgram } from "../src/execution-world/index.js"; +const contract = `interface Item id "item" revision "item@1" { + operation remove id "remove" : unit -> unit { + call id "remove:call" execution transaction; + } +}`; +const workspace = `workspace Lifecycle id "workspace:lifecycle" revision "workspace:lifecycle@1" commit "${"a".repeat(40)}" { + import interface Item; + atom Node id "node"; + conform Node as Item id "node:item" { + private edge Links id "links" { + atom Node projection children id "children" many on-delete "cascade-other"; + atom Node projection parent id "parent" optional-one on-delete "detach"; + } + bind remove.call to lifecycle.delete; + } +}`; +function compile(ifaceSource = contract, workspaceSource = workspace) { + const iface = compileCapabilityResourceSource(ifaceSource, { + source: { repository: "https://example.test/lifecycle.git", commit: "a".repeat(40) }, + }); + assert.ok(iface.ok && iface.resource.kind === "interface", JSON.stringify(iface.diagnostics)); + return compileCapabilitySource(workspaceSource, "workspace.qx", { + interfaces: new Map([["Item", iface.resource.revision]]), + }); +} +test("lifecycle binding and endpoint policies survive formatting and kernel compilation", () => { + const result = compile(); + assert.ok(result.ok, JSON.stringify(result.diagnostics)); + const formatted = compile(formatQx(contract), formatQx(workspace)); + assert.ok(formatted.ok, JSON.stringify(formatted.diagnostics)); + const plan = compileExecutionPlan(result.workspace); + assert.equal(compileExecutionPlan(formatted.workspace).digest, plan.digest); + const kernel = compileKernelProgram(plan) as { + operations: { binding: unknown }[]; + model: { edges: { a: { onDelete: string }; b: { onDelete: string } }[] }; + }; + assert.deepEqual(kernel.operations[0]!.binding, { kind: "lifecycle", primitive: "delete" }); + assert.equal(kernel.model.edges[0]!.a.onDelete, "cascade-other"); + assert.equal(kernel.model.edges[0]!.b.onDelete, "detach"); +}); +test("deletion rejects browser, snapshot, remote, class, payload and unknown primitive promises", () => { + for (const [iface, ws] of [ + [contract.replace("execution transaction", "execution snapshot"), workspace], + [contract.replace("execution transaction", "execution remote"), workspace], + [contract.replace("execution transaction", ""), workspace], + [contract.replace("operation remove", "static operation remove"), workspace], + [contract.replace("unit -> unit", "string -> unit"), workspace], + [contract.replace("unit -> unit", "unit -> string"), workspace], + [contract, workspace.replace("lifecycle.delete", "lifecycle.resurrect")], + ]) { + const result = compile(iface, ws); + assert.equal(result.ok, false, iface + ws); + assert.ok( + result.diagnostics.some((d) => + ["invalid-execution-contract", "invalid-lifecycle-binding", "invalid-package-binding"].includes(d.code), + ), + JSON.stringify(result.diagnostics), + ); + } +}); diff --git a/test/local-first-reader.test.ts b/test/local-first-reader.test.ts new file mode 100644 index 0000000..5bde486 --- /dev/null +++ b/test/local-first-reader.test.ts @@ -0,0 +1,66 @@ +import assert from "node:assert/strict"; +import { test } from "node:test"; +import { compileQuery } from "../src/query/compile.js"; +import { linkQueries } from "../src/query/link.js"; +import { compileExecutionPlan } from "../src/execution-world/plan.js"; +import { loadReaderWorkspace as load, readReaderFixture as read } from "./reader-fixture.js"; +test("complete reader contracts compose factories, portable selectors, CRDT edits, and bounded query scopes", async () => { + const workspace = await load(); + assert.equal(workspace.constructors.length, 0); + assert.equal(workspace.atoms.length, 3); + const linked = linkQueries(workspace); + assert.equal(linked.length, 9); + assert.equal(linked.filter((q) => q.checked.declaration.placement?.kind === "local").length, 4); + const screen = linked.find((q) => q.checked.declaration.displayName === "ReaderScreen")!; + assert.ok(screen.fields.some((f) => f.memberId === "highlight:excerpt" && f.binding.kind === "package")); + assert.ok(screen.fields.some((f) => f.memberId === "comment:content" && f.binding.kind === "state")); + assert.equal( + linked.find((q) => q.checked.declaration.displayName === "HighlightEditorInputs")!.checked.declaration.root.kind, + "object", + ); + assert.equal( + linked.find((q) => q.checked.declaration.displayName === "HighlightScope")!.checked.declaration.root.kind, + "workspace-interface", + ); +}); + +test("ordinary operations allow server fallback; explicitly local projections require local code", async () => { + const serverExcerpt = await load((name, text) => + name === "ReaderRuntime.qx" + ? text.replace(/(operation excerpt[\s\S]*?targets )\[JavaScript, server\]/, "$1[server]") + : text, + ); + assert.throws(() => compileExecutionPlan(serverExcerpt), /NONPORTABLE_QUERY_FIELD/); + for (const pkg of serverExcerpt.packageImports) + pkg.queries = pkg.queries?.filter((q) => q.placement?.kind !== "local"); + const fallback = compileExecutionPlan(serverExcerpt).definition.operations.find( + (op) => op.operationId === "highlight:excerpt:get", + )!; + assert.deepEqual(fallback.availableTargets, ["server"]); + await assert.rejects( + load((name, text) => + name === "ReaderRuntime.qx" + ? text.replace( + "using [sourceRevision.get, content.get];", + "using [sourceRevision.get, content.get, highlights.connect];", + ) + : text, + ), + /Snapshot execution cannot perform a transaction/, + ); +}); + +test("a field can remain server-queryable while failing local query placement", async () => { + const workspace = await load(); + const pkg = workspace.packageImports[0]!; + const highlight = workspace.interfaceImports.find((i) => i.displayName === "HighlightData")!; + const field = highlight.members.find((m) => m.displayName === "color")!; + delete field.operations[0]!.execution; + const admission = pkg.queries!.find((q) => q.displayName === "HighlightScope")!; + const local = pkg.queries!.find((q) => q.displayName === "HighlightList")!; + await compileQuery(admission, workspace.interfaceImports, read); + await assert.rejects( + compileQuery(local, workspace.interfaceImports, read), + /graph snapshot getter for HighlightData.color/, + ); +}); diff --git a/test/modules.test.ts b/test/modules.test.ts new file mode 100644 index 0000000..12d0516 --- /dev/null +++ b/test/modules.test.ts @@ -0,0 +1,238 @@ +import assert from "node:assert/strict"; +import { test } from "node:test"; +import { + compileCapabilityResourceSource, + compileCapabilitySource, + formatQx, +} from "../src/capability-language/index.js"; +import { + validateWorkspaceRevision, + compileWorkspaceRevision, + computeCapabilityClosure, + capabilityId, + type InterfaceRevision, +} from "../src/capability-model/index.js"; +import { compileExecutionPlan, checkExecutionWorld } from "../src/execution-world/index.js"; + +const source = { repository: "https://example.test/modules.git", commit: "a".repeat(40) }; +const interfaces = new Map(); +function define(text: string) { + const result = compileCapabilityResourceSource(text, { + source, + environment: { interfaces, interfaceClosure: [...interfaces.values()] }, + }); + assert.ok(result.ok && result.resource.kind === "interface", JSON.stringify(result.diagnostics)); + interfaces.set(result.resource.revision.displayName, result.resource.revision); +} +define('interface Data id "interface:data" revision "interface:data@1" {}'); +define('interface Other id "interface:other" revision "interface:other@1" {}'); +define(`import interface Data; +interface Row id "interface:row" revision "interface:row@1" { + module render id "member:row:render" contract "org.quixos.react.component/1" subject interface Data; +}`); +define(`interface Standalone id "interface:standalone" revision "interface:standalone@1" { + module render id "member:standalone:render" contract "org.quixos.react.subject-only/1" subject object Subject; +}`); +const packageSource = `import interface Row; import interface Data; import interface Standalone; +package UI id "package:ui" revision "package:ui@1" { + module Row id "module:row" implements Row.render; + module Editor id "module:editor" implements Standalone.render; +}`; +function compile( + body = `conform Item as Data id "owner:data" {} + conform Item as Row id "owner:row" {bind render to UI.Row;} + conform Item as Standalone id "owner:standalone" {bind render to UI.Editor;}`, +) { + const pkg = compileCapabilityResourceSource(packageSource, { + source, + environment: { interfaces, interfaceClosure: [...interfaces.values()] }, + }); + assert.ok(pkg.ok && pkg.resource.kind === "package", JSON.stringify(pkg.diagnostics)); + return compileCapabilitySource( + `workspace Demo id "workspace:demo" revision "workspace:demo@1" commit "${source.commit}" { + import interface Data; import interface Other; import interface Row; import interface Standalone; import package UI; + atom Item id "atom:item"; + ${body} + }`, + "workspace.qx", + { + interfaces, + interfaceClosure: [...interfaces.values()], + packages: new Map([["UI", pkg.resource.revision]]), + }, + ); +} + +test("module members bind independent roles, including closed generic subjects, without operations or storage", () => { + const result = compile(); + assert.ok(result.ok, JSON.stringify(result.diagnostics)); + const pkg = result.workspace.packageImports[0]!; + assert.equal(pkg.exports.length, 0); + assert.equal(pkg.modules?.length, 2); + const roles = result.workspace.conformances.filter((entry) => entry.moduleBindings?.length); + assert.equal(roles.length, 2); + for (const role of roles) { + assert.deepEqual(role.operationBindings, []); + assert.deepEqual(role.privateAttachments, []); + const iface: InterfaceRevision = result.workspace.interfaceImports.find( + (entry) => entry.revisionId === role.interfaceRevisionId, + )!; + assert.equal(iface.members[0]!.kind, "module"); + assert.deepEqual(iface.members[0]!.operations, []); + } +}); + +test("module bindings reject missing, duplicate and wrong nominal roles and unsatisfied subjects", () => { + for (const body of [ + "conform Item as Data {} conform Item as Row {}", + "conform Item as Data {} conform Item as Row {bind render to UI.Editor;}", + "conform Item as Data {} conform Item as Row {bind render to UI.Row; bind render to UI.Row;}", + "conform Item as Other {} conform Item as Row {bind render to UI.Row;}", + "conform Item as Data {} conform Item as Row {bind render to UI.Missing;}", + ]) { + const result = compile(body); + assert.equal(result.ok, false, body); + if (!result.ok) assert.match(JSON.stringify(result.diagnostics), /module/i); + } +}); + +test("module declarations and bindings round-trip through the ordinary formatter", () => { + const formatted = formatQx(packageSource); + assert.equal(formatQx(formatted), formatted); + const result = compileCapabilityResourceSource(formatted, { + source, + environment: { interfaces, interfaceClosure: [...interfaces.values()] }, + }); + assert.ok(result.ok, JSON.stringify(result.diagnostics)); +}); + +test("the old React-specific package declaration is no longer a public language path", () => { + const result = compileCapabilityResourceSource( + 'import interface Data; package UI id "package:ui" revision "package:ui@1" { component Card id "card" subject Data runtime react; }', + { source, environment: { interfaces, interfaceClosure: [...interfaces.values()] } }, + ); + assert.equal(result.ok, false); + assert.ok(result.diagnostics.some((diagnostic) => diagnostic.phase === "syntax")); +}); + +test("module closure retains the provider and subject but no synthetic state", () => { + const result = compile(); + assert.ok(result.ok, JSON.stringify(result.diagnostics)); + const compiled = compileWorkspaceRevision(result.workspace); + assert.ok(compiled.ok); + const closure = computeCapabilityClosure(compiled.plan, [ + { + atomId: capabilityId.atom("atom:item"), + interfaceRevisionId: capabilityId.interfaceRevision("interface:row@1"), + }, + ]); + assert.deepEqual(closure.packageRevisionIds, ["package:ui@1"]); + assert.deepEqual(closure.attachmentIds, []); + assert.deepEqual( + new Set(closure.conformances.map((entry) => entry.interfaceRevisionId)), + new Set(["interface:row@1", "interface:data@1"]), + ); +}); + +test("unknown namespaced module contracts remain metadata and malformed contracts reject", () => { + const result = compile(); + assert.ok(result.ok, JSON.stringify(result.diagnostics)); + const workspace = structuredClone(result.workspace); + const iface: InterfaceRevision = workspace.interfaceImports.find((entry) => entry.revisionId === "interface:row@1")!; + const member = iface.members[0]!; + assert.equal(member.kind, "module"); + if (member.kind !== "module") throw Error("Expected module"); + member.contract = "example.device.firmware/2"; + assert.deepEqual(validateWorkspaceRevision(workspace), []); + member.contract = "react"; + assert.ok(validateWorkspaceRevision(workspace).some((issue) => issue.code === "invalid-module-contract")); +}); + +test("execution plans retain nominal module contracts without synthesizing callable operations", async () => { + const result = compile(); + assert.ok(result.ok, JSON.stringify(result.diagnostics)); + const plan = compileExecutionPlan(result.workspace); + assert.deepEqual(plan.definition.operations, []); + const pkg = plan.definition.packages[0]!; + assert.equal(pkg.modules?.length, 2); + assert.deepEqual(pkg.modules?.find((module) => module.id === "module:editor")?.subject, { + kind: "interface", + interfaceRevisionId: "interface:data@1", + }); + // A language declaration cannot silently pass installation without an artifact consumer. + await assert.rejects( + checkExecutionWorld( + plan, + [ + { + schemaVersion: 1, + packageRevisionId: pkg.revision, + bindingDigest: pkg.bindingDigest, + sourceDigest: pkg.sourceDigest, + versions: plan.definition.versions, + implementations: [], + }, + ], + async () => { + throw Error("Must reject before reading files"); + }, + ), + /Missing component artifact/, + ); +}); + +test("unrecognized module consumers fail before artifact execution, not during interface compilation", async () => { + const result = compile(); + assert.ok(result.ok, JSON.stringify(result.diagnostics)); + const workspace = structuredClone(result.workspace); + const member = workspace.interfaceImports.find((iface) => iface.revisionId === "interface:row@1")!.members[0]!; + assert.equal(member.kind, "module"); + if (member.kind !== "module") throw Error("Expected module"); + member.contract = "example.device.firmware/2"; + const plan = compileExecutionPlan(workspace); + const pkg = plan.definition.packages[0]!; + await assert.rejects( + checkExecutionWorld( + plan, + [ + { + schemaVersion: 1, + packageRevisionId: pkg.revision, + bindingDigest: pkg.bindingDigest, + sourceDigest: pkg.sourceDigest, + versions: plan.definition.versions, + implementations: [], + }, + ], + async () => { + throw Error("Must reject before reading files"); + }, + ), + /No artifact consumer.*example.device.firmware/, + ); +}); + +test("artifact consumers receive checked closed role arguments and prerequisites", async () => { + const { moduleTypeContext } = await import("../src/execution-world/module-contracts.js"); + const { generateInterfaceClientBindings } = await import("../src/execution-world/client-contracts.js"); + const result = compile(); + assert.ok(result.ok, JSON.stringify(result.diagnostics)); + const role = result.workspace.interfaceImports.find((iface) => iface.application)!; + const expected = [{ kind: "object", target: { kind: "interface", interfaceRevisionId: "interface:data@1" } }]; + assert.deepEqual(moduleTypeContext(role), { typeArguments: expected }); + const plan = compileExecutionPlan(result.workspace); + assert.deepEqual( + plan.definition.packages[0]!.modules!.find((module) => module.displayName === "Editor")!.typeArguments, + expected, + ); + assert.match(generateInterfaceClientBindings({ Editor: role }), /"typeArguments":\[/); + assert.deepEqual(moduleTypeContext(interfaces.get("Row")!), {}); + // Prerequisites are public nominal contracts, never private storage inventory. + assert.deepEqual( + moduleTypeContext({ + ...role, + requiredInterfaces: [capabilityId.interfaceRevision("z"), capabilityId.interfaceRevision("a")], + }).requiredInterfaces, + ["a", "z"], + ); +}); diff --git a/test/pin-upgrades.test.ts b/test/pin-upgrades.test.ts index f6e30ef..026a733 100644 --- a/test/pin-upgrades.test.ts +++ b/test/pin-upgrades.test.ts @@ -76,106 +76,112 @@ test( }, ); -test("pin upgrades publish children before parent locks and resume without republishing completed nodes", async (context) => { - const workbench = await fs.mkdtemp(path.join(os.tmpdir(), "qx-upgrade-test-")); - context.after(() => fs.rm(workbench, { recursive: true, force: true })); - const from = "a".repeat(40), - to = "b".repeat(40), - framework = "c".repeat(40); - const sources = [ - { - kind: "workspace" as const, - directory: "root", - source: { repository: "https://example.test/workspace.git", commit: from }, - }, - { - kind: "interface" as const, - directory: "resources/Named", - source: { repository: "https://example.test/named.git", commit: from }, - }, - ]; - const lock = `quixos-lock version 1 { quixos source { repository "https://example.test/quixos.git"; commit "${framework}"; }`; - for (const node of sources) { - const directory = path.join(workbench, node.directory); - await fs.mkdir(directory, { recursive: true }); - await execFile("git", ["-C", directory, "init"]); - await execFile("git", ["-C", directory, "remote", "add", "origin", node.source.repository]); - await fs.writeFile(path.join(directory, ".gitignore"), ".quixos/\n"); +for (const dependencyKind of ["interface", "build"] as const) + test(`${dependencyKind} pin upgrades publish children before parent locks and resume without republishing completed nodes`, async (context) => { + const workbench = await fs.mkdtemp(path.join(os.tmpdir(), "qx-upgrade-test-")); + context.after(() => fs.rm(workbench, { recursive: true, force: true })); + const from = "a".repeat(40), + to = "b".repeat(40), + framework = "c".repeat(40); + const sources = [ + { + kind: "workspace" as const, + directory: "root", + source: { repository: "https://example.test/workspace.git", commit: from }, + }, + { + kind: dependencyKind, + directory: "resources/Named", + source: { repository: "https://example.test/named.git", commit: from }, + }, + ]; + const lock = `quixos-lock version 1 { quixos source { repository "https://example.test/quixos.git"; commit "${framework}"; }`; + for (const node of sources) { + const directory = path.join(workbench, node.directory); + await fs.mkdir(directory, { recursive: true }); + await execFile("git", ["-C", directory, "init"]); + await execFile("git", ["-C", directory, "remote", "add", "origin", node.source.repository]); + await fs.writeFile(path.join(directory, ".gitignore"), ".quixos/\n"); + await fs.writeFile( + path.join(directory, "quixos.lock"), + lock + + (node.kind === "workspace" + ? ` ${dependencyKind} Named source { repository "${sources[1].source.repository}"; commit "${from}"; }` + : "") + + " }", + ); + } await fs.writeFile( - path.join(directory, "quixos.lock"), - lock + - (node.kind === "workspace" - ? ` interface Named source { repository "${sources[1].source.repository}"; commit "${from}"; }` - : "") + - " }", + path.join( + workbench, + dependencyKind === "interface" ? "resources/Named/interface.qx" : "resources/Named/types.ts", + ), + dependencyKind === "interface" + ? 'interface Named id "interface:named" revision "interface:named@1" { value name id "member:name" : string { get id "op:get"; } }' + : "export type Name = string;", ); - } - await fs.writeFile( - path.join(workbench, "resources/Named/interface.qx"), - 'interface Named id "interface:named" revision "interface:named@1" { value name id "member:name" : string { get id "op:get"; } }', - ); - await fs.writeFile( - path.join(workbench, "root/workspace.qx"), - `workspace W id "workspace:w" revision "workspace:w@1" commit "${from}" { import interface Named; atom A id "atom:a"; }`, - ); - const snapshotMap = path.join(workbench, "snapshots.json"); - await fs.writeFile( - snapshotMap, - JSON.stringify({ - resources: [ - { - kind: "interface", - repository: sources[1].source.repository, - commit: to, - directory: path.join(workbench, "resources/Named"), - }, - ], - }), - ); - const oldMap = process.env.QUIXOS_SNAPSHOT_MAP; - process.env.QUIXOS_SNAPSHOT_MAP = snapshotMap; - context.after(() => { - if (oldMap === undefined) delete process.env.QUIXOS_SNAPSHOT_MAP; - else process.env.QUIXOS_SNAPSHOT_MAP = oldMap; + await fs.writeFile( + path.join(workbench, "root/workspace.qx"), + `workspace W id "workspace:w" revision "workspace:w@1" commit "${from}" { ${dependencyKind === "interface" ? "import interface Named;" : ""} atom A id "atom:a"; }`, + ); + const snapshotMap = path.join(workbench, "snapshots.json"); + await fs.writeFile( + snapshotMap, + JSON.stringify({ + resources: [ + { + kind: dependencyKind, + repository: sources[1].source.repository, + commit: to, + directory: path.join(workbench, "resources/Named"), + }, + ], + }), + ); + const oldMap = process.env.QUIXOS_SNAPSHOT_MAP; + process.env.QUIXOS_SNAPSHOT_MAP = snapshotMap; + context.after(() => { + if (oldMap === undefined) delete process.env.QUIXOS_SNAPSHOT_MAP; + else process.env.QUIXOS_SNAPSHOT_MAP = oldMap; + }); + const plan = await planPinUpgrades(workbench, { nodes: sources }); + await fs.mkdir(path.join(workbench, ".quixos"), { recursive: true }); + await fs.writeFile( + path.join(workbench, ".quixos/resource-graph.json"), + JSON.stringify({ resources: [{ ...sources[1], source: { resolver: "git", ...sources[1].source } }] }), + ); + assert.deepEqual( + plan.nodes.map((node) => node.directory), + ["resources/Named", "root"], + ); + let fail = true; + const published: string[] = []; + const effects: UpgradeEffects = { + check: async (node) => { + if (node.kind === "workspace" && fail) throw new Error("refactor required"); + }, + snapshot: async () => to, + publish: async (root) => { + published.push(path.relative(workbench, root)); + }, + }; + await assert.rejects(() => applyPinUpgrades(plan, undefined, effects), /refactor required/); + assert.deepEqual(published, ["resources/Named"]); + assert.match(await fs.readFile(path.join(workbench, "root/quixos.lock"), "utf8"), new RegExp(to)); + const id = (await fs.readdir(path.join(workbench, ".quixos/upgrades"))) + .find((name) => name.endsWith(".json"))! + .slice(0, -5); + fail = false; + await fs.appendFile(path.join(workbench, "root/workspace.qx"), "\n// explicit refactor\n"); + await assert.rejects(() => applyPinUpgrades(plan, id, effects), /accept-edits/); + const result = await applyPinUpgrades(plan, id, effects, { acceptEdits: true }); + assert.equal(result.activated, false); + assert.deepEqual(published, ["resources/Named", "root"]); + const graph = JSON.parse(await fs.readFile(path.join(workbench, ".quixos/resource-graph.json"), "utf8")); + assert.equal(graph.resources.length, 1); + assert.equal(graph.resources[0].source.commit, to); + const next = await planPinUpgrades(workbench, { + nodes: [sources[0], { ...sources[1], source: graph.resources[0].source }], + }); + assert.deepEqual(next.nodes.find((node) => node.kind === "workspace")!.dependencies, ["resources/Named"]); }); - const plan = await planPinUpgrades(workbench, { nodes: sources }); - await fs.mkdir(path.join(workbench, ".quixos"), { recursive: true }); - await fs.writeFile( - path.join(workbench, ".quixos/resource-graph.json"), - JSON.stringify({ resources: [{ ...sources[1], source: { resolver: "git", ...sources[1].source } }] }), - ); - assert.deepEqual( - plan.nodes.map((node) => node.directory), - ["resources/Named", "root"], - ); - let fail = true; - const published: string[] = []; - const effects: UpgradeEffects = { - check: async (node) => { - if (node.kind === "workspace" && fail) throw new Error("refactor required"); - }, - snapshot: async () => to, - publish: async (root) => { - published.push(path.relative(workbench, root)); - }, - }; - await assert.rejects(() => applyPinUpgrades(plan, undefined, effects), /refactor required/); - assert.deepEqual(published, ["resources/Named"]); - assert.match(await fs.readFile(path.join(workbench, "root/quixos.lock"), "utf8"), new RegExp(to)); - const id = (await fs.readdir(path.join(workbench, ".quixos/upgrades"))) - .find((name) => name.endsWith(".json"))! - .slice(0, -5); - fail = false; - await fs.appendFile(path.join(workbench, "root/workspace.qx"), "\n// explicit refactor\n"); - await assert.rejects(() => applyPinUpgrades(plan, id, effects), /accept-edits/); - const result = await applyPinUpgrades(plan, id, effects, { acceptEdits: true }); - assert.equal(result.activated, false); - assert.deepEqual(published, ["resources/Named", "root"]); - const graph = JSON.parse(await fs.readFile(path.join(workbench, ".quixos/resource-graph.json"), "utf8")); - assert.equal(graph.resources.length, 1); - assert.equal(graph.resources[0].source.commit, to); - const next = await planPinUpgrades(workbench, { - nodes: [sources[0], { ...sources[1], source: graph.resources[0].source }], - }); - assert.deepEqual(next.nodes.find((node) => node.kind === "workspace")!.dependencies, ["resources/Named"]); -}); diff --git a/test/portable-bindings.test.ts b/test/portable-bindings.test.ts new file mode 100644 index 0000000..688c3bd --- /dev/null +++ b/test/portable-bindings.test.ts @@ -0,0 +1,150 @@ +import assert from "node:assert/strict"; +import { test } from "node:test"; +import { generatePortableTypeScriptBindings } from "../src/bindings/portable.js"; +import { compileExecutionPlan } from "../src/execution-world/plan.js"; +import { loadReaderWorkspace } from "./reader-fixture.js"; + +test("service bindings depend on their consumed contract, not workspace or source provenance", async () => { + const schema = compileExecutionPlan(await loadReaderWorkspace()).definition.bindingSchema; + const pkg = schema.packages[0]!; + const before = generatePortableTypeScriptBindings(schema, pkg.revisionId, { target: "server" }); + const other = structuredClone(pkg); + other.revisionId = "unrelated@1" as typeof other.revisionId; + other.exports = []; + schema.packages.push(other); + pkg.source.commit = "b".repeat(40); + pkg.revisionId = "same-service-new-source@2" as typeof pkg.revisionId; + assert.equal(generatePortableTypeScriptBindings(schema, pkg.revisionId, { target: "server" }), before); + assert.doesNotMatch(before, /export const (bindingDigest|packageRevision)/); + pkg.exports[0]!.inputType = { kind: "scalar", name: "bool" }; + assert.notEqual(generatePortableTypeScriptBindings(schema, pkg.revisionId, { target: "server" }), before); +}); + +test("reader portable bindings have selected facets, creation inputs and stable keyed calls", async () => { + const schema = compileExecutionPlan(await loadReaderWorkspace()).definition.bindingSchema; + const source = generatePortableTypeScriptBindings(schema, "package:reader-runtime@1", { + target: "target:org.quixos.execution.javascript@1", + }); + assert.match(source, /readonly "sourceRevision.get": \(\) => Promise/); + assert.match( + source, + /readonly "highlights.connect": \(input: ManagedReference<"atom", "atom:reader:highlight">, options: MutationKey\)/, + ); + assert.match(source, /readonly "createDocument"/); + assert.match(source, /readonly "create":/); + assert.match(source, /readonly "comments": ReadonlyArray/); + assert.doesNotMatch(source, /"highlights.resolve"|context\.conform|objectId|"live"/); + assert.equal( + source, + generatePortableTypeScriptBindings(schema, "package:reader-runtime@1", { + target: "target:org.quixos.execution.javascript@1", + }), + ); + const server = generatePortableTypeScriptBindings(schema, "package:reader-runtime@1", { target: "server" }); + assert.equal(source.replace('target = "target:org.quixos.execution.javascript@1"', 'target = "server"'), server); +}); + +test("generation rejects remote dependencies, wrong effects and unsupported shapes explicitly", async () => { + const schema = compileExecutionPlan(await loadReaderWorkspace()).definition.bindingSchema; + const generate = () => + generatePortableTypeScriptBindings(schema, "package:reader-runtime@1", { + target: "target:org.quixos.execution.javascript@1", + }); + const entry = schema.packages[0]!.exports.find((e) => e.displayName === "addHighlight")!; + entry.execution = { kind: "snapshot", targets: ["target:org.quixos.execution.javascript@1"] }; + assert.throws(generate, /Snapshot context cannot expose mutation ports/); + entry.execution = { kind: "transaction", targets: ["target:org.quixos.execution.javascript@1"] }; + const iface = schema.interfaces.find((i) => i.revisionId === "interface:reader:document-data@1")!; + const getter = iface.members[0]!.operations[0]!; + getter.execution = { kind: "remote" }; + assert.throws(generate, /not an object call available in portable target target:org.quixos.execution.javascript@1/); + getter.execution = { kind: "snapshot", targets: ["browser", "server"] }; + entry.dependencyPorts[0]!.requirement = { kind: "reflection", selectors: ["active"] }; + assert.throws(generate, /Only checked presentation metadata is portable/); +}); + +test("closed generic portable handlers have distinct concrete codecs and no universal casts", async () => { + const { portableGenericPlan } = await import("./portable-generic-fixture.js"); + const plan = portableGenericPlan(); + const schema = plan.definition.bindingSchema; + const pkg = schema.packages[0]!; + assert.equal(pkg.exports.length, 2); + const generated = generatePortableTypeScriptBindings(schema, pkg.revisionId, { + target: "target:org.quixos.execution.javascript@1", + }); + assert.match(generated, /readonly input: string/); + assert.match(generated, /readonly input: ReadonlyArray/); + for (const entry of pkg.exports) { + assert.ok(entry.application); + assert.ok(generated.includes(JSON.stringify(entry.id))); + assert.ok(generated.includes(JSON.stringify(entry.displayName))); + } + assert.notEqual(pkg.exports[0]!.displayName, pkg.exports[1]!.displayName); + assert.equal( + generated.replace('target = "target:org.quixos.execution.javascript@1"', 'target = "server"'), + generatePortableTypeScriptBindings(schema, pkg.revisionId, { target: "server" }), + ); +}); + +test("a portable subset of a mixed interface excludes remote dependencies", async () => { + const schema = compileExecutionPlan(await loadReaderWorkspace()).definition.bindingSchema; + const entry = schema.packages.flatMap((p) => p.exports).find((e) => e.displayName === "addHighlight")!; + const iface = schema.interfaces.find((i) => i.revisionId === "interface:reader:document-data@1")!; + const selected = entry.dependencyPorts.find((p) => p.requirement.kind === "interface")!.requirement; + assert.equal(selected.kind, "interface"); + if (selected.kind !== "interface") throw Error("interface required"); + const operation = structuredClone(iface.members[0]!.operations[0]!); + operation.id = "document:remote:get" as typeof operation.id; + operation.execution = { kind: "remote" }; + iface.members.push({ + ...structuredClone(iface.members[0]!), + id: "document:remote" as (typeof iface.members)[0]["id"], + displayName: "remote", + operations: [operation], + }); + const generate = () => + generatePortableTypeScriptBindings(schema, "package:reader-runtime@1", { + target: "target:org.quixos.execution.javascript@1", + }); + assert.doesNotMatch(generate(), /readonly "remote.get"/); + selected.operations!.push(operation.id); + assert.throws(generate, /select a portable subset/); +}); + +test("server projection descriptors are distinct from canonical inputs and have result decoders", async () => { + const { generatePortableClientBindings } = await import("../src/bindings/portable-client.js"); + const plan = compileExecutionPlan(await loadReaderWorkspace()); + const source = generatePortableClientBindings(plan, { target: "target:org.quixos.execution.javascript@1" }); + const projections = source.slice( + source.indexOf("export const serverProjections"), + source.indexOf("export const results"), + ); + assert.match(projections, /ConfirmedColor/); + assert.match(projections, /satisfies ServerProjection/); + assert.match(source.slice(source.indexOf("export const results")), /ConfirmedColor/); + await assert.rejects( + loadReaderWorkspace((name, text) => + name === "queries/confirmed-color.graphql" ? text.replace("color", "color @live") : text, + ), + /@live is retired/, + ); +}); + +test("static interface ports keep unit arguments explicit for generic factory composition", async () => { + const schema = compileExecutionPlan(await loadReaderWorkspace()).definition.bindingSchema; + const entry = schema.packages.flatMap((p) => p.exports).find((e) => e.displayName === "addHighlight")!; + const port = entry.dependencyPorts.find((p) => p.requirement.kind === "interface")!; + if (port.requirement.kind !== "interface") throw Error("interface required"); + const requirement = port.requirement; + const iface = schema.interfaces.find((i) => i.revisionId === requirement.interfaceRevisionId)!; + const op = iface.members.flatMap((m) => m.operations).find((o) => o.id === requirement.operations![0])!; + op.scope = "class"; + op.inputType = { kind: "builtin", name: "unit" }; + port.requirement.operations = [op.id]; + delete port.requirement.receiver; + const generated = generatePortableTypeScriptBindings(schema, "package:reader-runtime@1", { + target: "target:org.quixos.execution.javascript@1", + }); + assert.match(generated, /"explicitInput": true/); + assert.match(generated, /input: null/); +}); diff --git a/test/portable-generic-fixture.ts b/test/portable-generic-fixture.ts new file mode 100644 index 0000000..090b91c --- /dev/null +++ b/test/portable-generic-fixture.ts @@ -0,0 +1,43 @@ +import assert from "node:assert/strict"; +import { compileCapabilityResourceSource, compileCapabilitySource } from "../src/capability-language/parser.js"; +import { compileExecutionPlan } from "../src/execution-world/plan.js"; + +/** Independently versioned generic interface and implementation, closed by a workspace. */ +export function portableGenericPlan() { + const source = { repository: "https://example.test/portable-generics.git", commit: "a".repeat(40) }; + const iface = compileCapabilityResourceSource( + ` + interface Echo id "echo-interface" revision "echo-interface@2" { + operation echo id "echo-member" : T -> T { + call id "echo-call" execution snapshot; + } + }`, + { source }, + ); + assert.ok(iface.ok && iface.resource.kind === "interface", JSON.stringify(iface.diagnostics)); + const pkg = compileCapabilityResourceSource( + `symbol JavaScript id "target:org.quixos.execution.javascript@1"; + + package Generic id "generic" revision "generic@3" { + operation echo id "echo" : T -> T mode call + execution snapshot targets [JavaScript, server] receiver any; + }`, + { source: { ...source, commit: "b".repeat(40) } }, + ); + assert.ok(pkg.ok && pkg.resource.kind === "package", JSON.stringify(pkg.diagnostics)); + const workspace = compileCapabilitySource( + ` + workspace W id "w" revision "w@1" commit "${source.commit}" { + import interface Echo; import package Generic; atom Thing id "thing"; + conform Thing as Echo id "string-echo" { bind echo.call to package Generic.echo; } + conform Thing as Echo> id "list-echo" { bind echo.call to package Generic.echo>; } + }`, + "workspace.qx", + { + interfaces: new Map([["Echo", iface.resource.revision]]), + packages: new Map([["Generic", pkg.resource.revision]]), + }, + ); + assert.ok(workspace.ok, JSON.stringify(workspace.diagnostics)); + return compileExecutionPlan(workspace.workspace); +} diff --git a/test/prepared-presentations.test.ts b/test/prepared-presentations.test.ts new file mode 100644 index 0000000..d456f7a --- /dev/null +++ b/test/prepared-presentations.test.ts @@ -0,0 +1,196 @@ +import assert from "node:assert/strict"; +import { test } from "node:test"; +import { compileCapabilityResourceSource, compileCapabilitySource } from "../src/capability-language/index.js"; +import type { InterfaceRevision, PackageRevision } from "../src/capability-model/types.js"; +import { compileQuery } from "../src/query/compile.js"; +import { compileQueryComposition } from "../src/query/composition.js"; +import { defaultQueryBudgets, type CheckedQuery } from "../src/query/types.js"; +import { + presentationDeclarations, + preparationArtifact, + type ReactPreparationArtifact, +} from "../src/react-build/declarations.js"; +import { completeWorkspace } from "../src/react-build/workspace.js"; + +const source = { repository: "https://example.test/prepared", commit: "a".repeat(40) }; +async function fixture(replacement = "First", change?: (artifact: ReactPreparationArtifact) => void) { + const interfaces = new Map(); + const resource = (text: string) => { + const result = compileCapabilityResourceSource(text, { + source, + environment: { interfaces, interfaceClosure: [...interfaces.values()] }, + }); + assert.ok(result.ok, JSON.stringify(result.diagnostics)); + return result.resource; + }; + for (const text of [ + 'interface Row id "row" revision "row@1" { queryable value title id "title" : string {get id "title:get" execution snapshot;} queryable value detail id "detail" : string {get id "detail:get" execution snapshot;} }', + 'import interface Row; interface Library id "library" revision "library@1" { queryable relation selected id "selected" : optional-one interface Row {resolve id "selected:get" execution snapshot;} }', + 'import interface Row; interface RowRole id "row-role" revision "row-role@1" { module render id "row-render" contract "org.quixos.react.subject-only/1" subject interface Row; }', + 'import interface Library; interface LibraryRole id "library-role" revision "library-role@1" { module render id "library-render" contract "org.quixos.react.subject-only/1" subject interface Library; }', + ]) { + const result = resource(text); + assert.equal(result.kind, "interface"); + if (result.kind === "interface") interfaces.set(result.revision.displayName, result.revision); + } + const result = resource( + 'import interface Row; import interface Library; import interface RowRole; import interface LibraryRole; package UI id "ui" revision "ui@1" { module LibraryCard id "library-card" implements LibraryRole.render; module First id "first" implements RowRole.render; module Second id "second" implements RowRole.render; }', + ); + assert.equal(result.kind, "package"); + if (result.kind !== "package") throw Error("package expected"); + const pkg: PackageRevision = result.revision; + const checked: CheckedQuery[] = []; + for (const [name, iface, fields] of [ + ["LibraryData", "library@1", "selected {_camino {ref atom}}"], + ["Title", "row@1", "title"], + ["Detail", "row@1", "detail"], + ]) { + for (const local of [false, true]) { + const id = name + (local ? "" : "Inputs"); + checked.push( + await compileQuery( + { + id, + displayName: id, + root: { + kind: "object", + interface: [...interfaces.values()].find((entry) => entry.revisionId === iface)!.revisionId, + }, + document: id + ".graphql", + operation: id, + fragments: [], + views: [], + allowances: [], + budgets: { ...defaultQueryBudgets }, + watch: local, + placement: local ? { kind: "local", sourceQueryId: name + "Inputs" } : { kind: "replica" }, + }, + [...interfaces.values()], + async () => `query ${id} {root {${fields}}}`, + ), + ); + } + } + pkg.checkedQueries = checked; + pkg.queries = checked.map((query) => query.declaration); + const input = (query: string) => ({ + name: "data", + query, + root: { from: "input", path: ["subject"] }, + arguments: { record: {} }, + }); + const authored = { + modules: { + LibraryCard: { + inputs: [input("LibraryData")], + children: [ + { + name: "selected", + subject: "Row", + slot: "selected", + with: { from: "result", name: "data", path: ["root", "selected"] }, + root: { from: "item", path: ["_camino", "ref"] }, + role: { interface: "RowRole", member: "render" }, + }, + ], + }, + First: { inputs: [input("Title")] }, + Second: { inputs: [input("Detail")] }, + }, + }; + const declarations = presentationDeclarations(JSON.stringify(authored), pkg, [...interfaces.values()]); + change?.(declarations.artifact); + pkg.buildArtifacts = { [preparationArtifact]: declarations.artifact }; + pkg.queryCompositions = declarations.compositions.map((composition) => + compileQueryComposition(composition, new Map(checked.map((query) => [query.declaration.id, query]))), + ); + const workspace = compileCapabilitySource( + `workspace Example id "example" revision "example@1" commit "${source.commit}" { + import interface Row; import interface Library; import interface RowRole; import interface LibraryRole; import package UI; + atom A id "a"; atom B id "b"; + conform A as Row id "a-row" {private state TitleA id "title-A" on A : string policy register default "title"; private state DetailA id "detail-A" on A : string policy register default "detail"; bind title.get to state TitleA.read; bind detail.get to state DetailA.read;} + conform B as Row id "b-row" {private state TitleB id "title-B" on B : string policy register default "title"; private state DetailB id "detail-B" on B : string policy register default "detail"; bind title.get to state TitleB.read; bind detail.get to state DetailB.read;} + conform A as RowRole id "a-role" {bind render to UI.${replacement};} + conform B as RowRole id "b-role" {bind render to UI.Second;} + }`, + "workspace.qx", + { interfaces, interfaceClosure: [...interfaces.values()], packages: new Map([["UI", pkg]]) }, + ); + assert.ok(workspace.ok, JSON.stringify(workspace.diagnostics)); + await completeWorkspace(workspace.workspace); + return { workspace: workspace.workspace, pkg: workspace.workspace.packageImports[0]!, authored, interfaces }; +} + +test("a local module names its checked role without a synthetic import alias", async () => { + const { pkg, authored, interfaces } = await fixture(); + const source = structuredClone(authored) as any; + source.modules.LibraryCard.children[0].role = { module: "First" }; + const declared = presentationDeclarations(JSON.stringify(source), pkg, [...interfaces.values()]); + assert.deepEqual(declared.artifact.modules[0]!.children[0]!.role, { interface: "row-role@1", member: "row-render" }); + source.modules.LibraryCard.children[0].role = { module: "Missing" }; + assert.throws( + () => presentationDeclarations(JSON.stringify(source), pkg, [...interfaces.values()]), + /local module role/, + ); +}); + +test("workspace expands distinct providers' private inputs without parent field declarations", async () => { + const { pkg } = await fixture(); + const parent = pkg.checkedQueries!.find((query) => query.declaration.id === "react:input:library-card:data")!; + const acquisition = pkg.checkedQueries!.find( + (query) => + query.declaration.id === + (parent.declaration.placement?.kind === "local" ? parent.declaration.placement.sourceQueryId : ""), + )!; + assert.match(acquisition.document, /title/); + assert.match(acquisition.document, /detail/); + const plan = pkg.queryCompositions!.find((composition) => composition.id === "react:inputs:library-card")!; + assert.equal(plan.steps[0]!.query, parent.declaration.id); + assert.equal(Object.keys(parent.fragmentContracts ?? {}).length, 2); + assert(!pkg.queryCompositions!.some((composition) => composition.id.endsWith(":discovery"))); + const original = pkg.checkedQueries!.find((query) => query.declaration.id === "LibraryData")!; + assert.deepEqual(parent.output, original.output, "Acquisition-only child fields are not parent requirements"); + assert(Object.values(parent.fragmentContracts!).every((entry) => entry.mode === "reference")); +}); + +test("eager cycles and wrongly typed child bindings fail before runtime", async () => { + await assert.rejects( + fixture("First", (artifact) => { + artifact.modules + .find((module) => module.module === "first")! + .children.push({ + name: "again", + subject: "row@1", + slot: "again", + root: { from: "input", path: ["subject"] }, + role: { interface: "row-role@1", member: "render" }, + }); + }), + /Eager React preparation cycle/, + ); + await assert.rejects( + fixture("First", (artifact) => { + artifact.modules.find((module) => module.module === "library-card")!.children[0]!.root = { + literal: "not a reference", + }; + }), + /child root must be/, + ); +}); + +test("workspace replacement changes acquisition requirements without changing the parent", async () => { + const { pkg } = await fixture("Second"); + const parent = pkg.checkedQueries!.find((query) => query.declaration.id === "react:input:library-card:data")!; + const acquisition = pkg.checkedQueries!.find( + (query) => + query.declaration.id === + (parent.declaration.placement?.kind === "local" ? parent.declaration.placement.sourceQueryId : ""), + )!; + assert.doesNotMatch(acquisition.document, /\btitle\b/); + assert.match(acquisition.document, /detail/); + assert.equal(Object.keys(parent.fragmentContracts ?? {}).length, 1); + const previous = (await fixture()).pkg.checkedQueries!.find( + (query) => query.declaration.id === parent.declaration.id, + )!; + assert.deepEqual(parent.output, previous.output); +}); diff --git a/test/presentation-mount-fixture.ts b/test/presentation-mount-fixture.ts new file mode 100644 index 0000000..57dc309 --- /dev/null +++ b/test/presentation-mount-fixture.ts @@ -0,0 +1,44 @@ +import assert from "node:assert/strict"; +import { readFile } from "node:fs/promises"; +import { compileCapabilityResourceSource, compileCapabilitySource } from "../src/capability-language/parser.js"; +import { compileExecutionPlan } from "../src/execution-world/plan.js"; +import type { InterfaceRevision } from "../src/capability-model/types.js"; +import { compileQuery } from "../src/query/compile.js"; +export async function presentationMountPlan() { + const read = (name: string) => readFile(`test/fixtures/presentation-mount/${name}.qx`, "utf8"); + const source = { repository: "https://example.test/presentation-mount.git", commit: "a".repeat(40) }; + const interfaces = new Map(); + for (const name of [ + "StateRecord", + "PresentationState", + "PresentationMount", + "StateFactory", + "Presentation", + "PresentationStates", + "ReaderNavigation", + "DocumentNavigation", + ]) { + const result = compileCapabilityResourceSource(await read(name), { source, environment: { interfaces } }); + assert.ok(result.ok && result.resource.kind === "interface", JSON.stringify(result.diagnostics)); + interfaces.set(name, result.resource.revision); + } + const pkg = compileCapabilityResourceSource(await read("PresentationRuntime"), { + source, + environment: { interfaces }, + }); + assert.ok(pkg.ok && pkg.resource.kind === "package", JSON.stringify(pkg.diagnostics)); + const result = compileCapabilitySource(await read("workspace"), "workspace.qx", { + interfaces, + packages: new Map([["PresentationRuntime", pkg.resource.revision]]), + }); + assert.ok(result.ok, JSON.stringify(result.diagnostics)); + const runtime = result.workspace.packageImports[0]!; + runtime.checkedQueries = await Promise.all( + runtime.queries!.map((query) => + compileQuery(query, result.workspace.interfaceImports, (path) => + readFile(`test/fixtures/presentation-mount/${path}`, "utf8"), + ), + ), + ); + return compileExecutionPlan(result.workspace); +} diff --git a/test/presentations.test.ts b/test/presentations.test.ts new file mode 100644 index 0000000..c908779 --- /dev/null +++ b/test/presentations.test.ts @@ -0,0 +1,261 @@ +import assert from "node:assert/strict"; +import { test } from "node:test"; +import { createHash } from "node:crypto"; +import { mkdtemp, mkdir, writeFile, rm } from "node:fs/promises"; +import os from "node:os"; +import path from "node:path"; +import { + compileCapabilityResourceSource, + compileCapabilitySource, + formatQx, + parseQx, + walkSyntax, +} from "../src/capability-language/index.js"; +import { + compileExecutionPlan, + checkExecutionWorld, + componentArtifactDigest, + componentSharedModules, + expectedArtifactReceipt, + checkExecutionInstallation, + clientContractCatalog, + type ComponentArtifact, + type PackageArtifacts, +} from "../src/execution-world/index.js"; +import { validateWorkspaceRevision } from "../src/capability-model/index.js"; +import { presentationDeclarations, preparationArtifact } from "../src/react-build/declarations.js"; +import { compileQueryComposition } from "../src/query/composition.js"; + +const source = { repository: "https://example.test/components.git", commit: "a".repeat(40) }; +const contract = (name: string) => { + const result = compileCapabilityResourceSource( + `interface ${name} id "interface:${name}" revision "interface:${name}@1" { }`, + { source }, + ); + assert.ok(result.ok && result.resource.kind === "interface", JSON.stringify(result.diagnostics)); + return result.resource.revision; +}; +const data = contract("Data"), + other = contract("Other"); +const interfaces = new Map([ + ["Data", data], + ["Other", other], +]); +const role = compileCapabilityResourceSource( + `import interface Data; +interface View id "interface:view" revision "interface:view@1" { + module render id "member:view:render" contract "org.quixos.react.subject-only/1" subject interface Data; +}`, + { source, environment: { interfaces, interfaceClosure: [...interfaces.values()] } }, +); +assert.ok(role.ok && role.resource.kind === "interface", JSON.stringify(role.diagnostics)); +interfaces.set("View", role.resource.revision); +const componentSource = (body = 'module Card id "component:card" implements View.render;') => + `import interface Data; import interface View; package UI id "package:ui" revision "package:ui@1" { ${body} }`; +const compilePackage = (body?: string) => + compileCapabilityResourceSource(componentSource(body), { + source, + environment: { interfaces, interfaceClosure: [...interfaces.values()] }, + }); +const workspace = (presentation = 'presentation Card id "presentation:card" for Data using UI.Card;') => { + const pkg = compilePackage(); + assert.ok(pkg.ok && pkg.resource.kind === "package", JSON.stringify(pkg.diagnostics)); + const preparation = presentationDeclarations('{"modules":{"Card":{"inputs":[]}}}', pkg.resource.revision, [ + ...interfaces.values(), + ]); + pkg.resource.revision.buildArtifacts = { [preparationArtifact]: preparation.artifact }; + pkg.resource.revision.queryCompositions = preparation.compositions.map((entry) => + compileQueryComposition(entry, new Map()), + ); + return compileCapabilitySource( + `workspace Example id "workspace:example" revision "workspace:example@1" commit "${source.commit}" { + import interface Data; import interface Other; import interface View; import package UI; + atom One id "atom:one"; atom Two id "atom:two"; + conform One as Data id "conformance:one:data" { } + conform Two as Data id "conformance:two:data" { } + ${presentation} + }`, + "workspace.qx", + { + interfaces, + interfaceClosure: [...interfaces.values()], + packages: new Map([["UI", pkg.resource.revision]]), + }, + ); +}; + +test("interface-only component declarations serve multiple conforming atoms without capability exports", () => { + const result = workspace(); + assert.ok(result.ok, JSON.stringify(result.diagnostics)); + const plan = compileExecutionPlan(result.workspace); + assert.equal(plan.definition.packages[0]!.exports.length, 0); + assert.deepEqual(plan.definition.packages[0]!.components, [ + { + id: "component:card", + displayName: "Card", + subject: "interface:Data@1", + runtime: "react", + }, + ]); + assert.equal(plan.definition.presentations?.[0]?.componentId, "component:card"); + assert.equal(plan.definition.workspace.conformances.length, 2); + assert.equal(plan.definition.operations.length, 0); +}); + +test("component declarations survive formatting and syntax inspection", () => { + const text = formatQx(componentSource()); + const parsed = parseQx(text); + assert.deepEqual(parsed.diagnostics, []); + assert.ok([...walkSyntax(parsed.root)].some((node) => node.kind === "moduleExport")); + assert.equal(formatQx(text), text); +}); + +test("component and presentation contracts reject wrong subjects, duplicates and unavailable runtimes", () => { + for (const [input, message] of [ + ['module Card id "component:card" implements Missing.render;', /Missing/], + ['module Card id "component:card" implements Data.missing;', /module member/], + [ + 'module Card id "component:card" implements View.render; module Other id "component:card" implements View.render;', + /Duplicate or empty module/, + ], + [ + 'module Card id "component:card" implements View.render; module Card id "component:other" implements View.render;', + /Duplicate or empty module/, + ], + ] as const) { + const result = compilePackage(input); + assert.equal(result.ok, false); + assert.match(JSON.stringify(result.diagnostics), message); + } + for (const [input, message] of [ + ['presentation Card id "presentation:card" for Other using UI.Card;', /Component requires/], + ['presentation Card id "presentation:card" for Data using UI.Missing;', /Unknown component/], + [ + 'presentation Card id "presentation:card" for Data using UI.Card; presentation Duplicate id "presentation:card" for Data using UI.Card;', + /Duplicate presentation/, + ], + ] as const) { + const result = workspace(input); + assert.equal(result.ok, false); + assert.match(JSON.stringify(result.diagnostics), message); + } +}); + +test("model validation rechecks presentation metadata instead of trusting the source parser", () => { + const result = workspace(); + assert.ok(result.ok, JSON.stringify(result.diagnostics)); + result.workspace.presentations![0]!.componentId = "missing"; + assert.ok(validateWorkspaceRevision(result.workspace).some((issue) => issue.code === "invalid-presentation")); + assert.throws(() => compileExecutionPlan(result.workspace), /no installed package component/); +}); + +test("standalone presentation rejects a general component contract that may require extra props", () => { + const result = workspace(); + assert.ok(result.ok, JSON.stringify(result.diagnostics)); + const candidate = structuredClone(result.workspace); + const member = candidate.interfaceImports.find((iface) => iface.revisionId === "interface:view@1")!.members[0]!; + assert.equal(member.kind, "module"); + if (member.kind !== "module") throw Error("Expected module"); + member.contract = "org.quixos.react.component/1"; + assert.ok( + validateWorkspaceRevision(candidate).some((issue) => /supported React presentation contract/.test(issue.message)), + ); +}); + +test("checked component-only artifacts install without a service and reject missing or substituted entries", async () => { + const result = workspace(); + assert.ok(result.ok, JSON.stringify(result.diagnostics)); + const plan = compileExecutionPlan(result.workspace), + pkg = plan.definition.packages[0]!; + const bytes = new TextEncoder().encode("export const Card = () => null;\n"); + const paths = [ + "components/card.mjs", + ...componentSharedModules.map((_name, i) => `share/quixos/shared/runtime-${i}.mjs`), + ]; + const files = paths.map((file) => ({ + path: file, + digest: createHash("sha256").update(bytes).digest("hex"), + bytes: bytes.length, + mediaType: "text/javascript", + })); + const body: Omit = { + id: "component:card", + subject: "interface:Data@1", + compatibility: "org.quixos.react.esm/1", + path: paths[0]!, + exportName: "Card", + files, + sharedModules: Object.fromEntries( + componentSharedModules.map((name, i) => [name, paths[i + 1]!]), + ) as ComponentArtifact["sharedModules"], + }; + const manifest: PackageArtifacts = { + schemaVersion: 1, + packageRevisionId: pkg.revision, + bindingDigest: pkg.bindingDigest, + sourceDigest: pkg.sourceDigest, + versions: plan.definition.versions, + implementations: [], + components: [{ ...body, contentDigest: componentArtifactDigest(body) }], + }; + const world = await checkExecutionWorld(plan, [manifest], async () => bytes); + assert.equal(world.artifacts.length, 1); + assert.equal(world.artifacts[0]!.target, "browser-renderer"); + await assert.rejects( + checkExecutionWorld(plan, [{ ...manifest, components: [] }], async () => bytes), + /Missing component artifact/, + ); + const substitute = structuredClone(manifest); + substitute.components![0]!.subject = "interface:Other@1"; + const { contentDigest: _digest, ...changed } = substitute.components![0]!; + substitute.components![0]!.contentDigest = componentArtifactDigest(changed); + await assert.rejects( + checkExecutionWorld(plan, [substitute], async () => bytes), + /declared subject/, + ); + await assert.rejects( + checkExecutionWorld(plan, [manifest], async () => new Uint8Array(bytes.length)), + /Artifact bytes/, + ); + const missingShared = structuredClone(manifest); + delete (missingShared.components![0]!.sharedModules as Partial).react; + await assert.rejects( + checkExecutionWorld(plan, [missingShared], async () => bytes), + /missing fields/, + ); + + const root = await mkdtemp(path.join(os.tmpdir(), "camino-component-contract-")); + try { + for (const file of files) { + await mkdir(path.dirname(path.join(root, file.path)), { recursive: true }); + await writeFile(path.join(root, file.path), bytes); + } + await mkdir(path.join(root, "share/quixos"), { recursive: true }); + await writeFile(path.join(root, "share/quixos/package-artifacts.json"), JSON.stringify(manifest)); + const generator = "a".repeat(64); + const { receipt, sources } = expectedArtifactReceipt(plan, manifest, generator); + for (const [file, content] of sources) { + await mkdir(path.dirname(path.join(root, file)), { recursive: true }); + await writeFile(path.join(root, file), content); + } + await writeFile(path.join(root, "share/quixos/build-receipt.json"), JSON.stringify(receipt)); + const installation = await checkExecutionInstallation(plan, { [pkg.revision]: root }, generator); + assert.equal(installation.packages[0]!.service, undefined); + assert.equal(installation.components[0]!.id, "component:card"); + assert.ok(installation.routes[installation.components[0]!.url]); + assert.equal(JSON.stringify(installation.components).includes(root), false); + assert.equal(JSON.stringify(installation.components).includes("slot:"), false); + const catalog = clientContractCatalog(plan, installation, [pkg.revision]); + assert.equal(catalog.presentations[0]!.component, "component:card"); + assert.equal(catalog.components[0]!.subject, "interface:Data@1"); + assert.equal(JSON.stringify(catalog).includes(root), false); + assert.equal(JSON.stringify(catalog).includes("bindingSchema"), false); + assert.throws(() => clientContractCatalog(plan, installation, ["absent"]), /UNINSTALLED_PACKAGE/); + assert.throws( + () => clientContractCatalog(plan, { ...installation, plan: "bad" }, [pkg.revision]), + /CATALOG_PLAN_MISMATCH/, + ); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); diff --git a/test/projection-compatibility.test.ts b/test/projection-compatibility.test.ts new file mode 100644 index 0000000..9a73e34 --- /dev/null +++ b/test/projection-compatibility.test.ts @@ -0,0 +1,29 @@ +import assert from "node:assert/strict"; +import { test } from "node:test"; +import { loadReaderWorkspace } from "./reader-fixture.js"; +import { compileExecutionPlan, checkResultProjection, publicOperationKey } from "../src/execution-world/index.js"; +test("factory result observation checks direct replica roots against the result contract", async () => { + const plan = compileExecutionPlan(await loadReaderWorkspace()); + const query = (id: string) => JSON.stringify(["package:reader-runtime@1", id]); + const create = publicOperationKey( + "atom:reader:document", + "interface:reader:document-factory@1", + "document:create:call", + ); + const annotate = publicOperationKey( + "atom:reader:document", + "interface:reader:document-actions@1", + "document:annotate:call", + ); + assert.equal(checkResultProjection(plan, create, query("reader:replica-inputs")).id, query("reader:replica-inputs")); + assert.equal(checkResultProjection(plan, annotate, query("reader:editor-inputs")).id, query("reader:editor-inputs")); + assert.throws(() => checkResultProjection(plan, create, query("reader:editor-inputs")), { + code: "RESULT_PROJECTION_MISMATCH", + }); + assert.throws(() => checkResultProjection(plan, annotate, query("reader:highlight-scope")), { + code: "INVALID_RESULT_PROJECTION", + }); + assert.throws(() => checkResultProjection(plan, annotate, query("reader:editor-view")), { + code: "INVALID_RESULT_PROJECTION", + }); +}); diff --git a/test/query-aggregation.test.ts b/test/query-aggregation.test.ts index 142f503..1ecd2b8 100644 --- a/test/query-aggregation.test.ts +++ b/test/query-aggregation.test.ts @@ -26,7 +26,7 @@ test("aggregate output uses exact count and sum types and nullable reductions", test("explicit expansion and identity distinct retain checked grouped operands", async () => { const checked = await compile(`expand { tags { - distinct(by: {source: {_qx: {ref: true}}, target: {color: true}}) { + distinct(by: {source: {_camino: {ref: true}}, target: {color: true}}) { groups(by: {target: {color: true}}, first: 50, having: {sum: {source: {estimatedHours: {gt: 0}}}}, orderBy: [{sum: {source: {estimatedHours: DESC}}}]) { @@ -55,7 +55,7 @@ test("distinct and grouping reject fields not determined by their selected keys" test("relationship existence predicates compile to typed plans", async () => { const checked = await compile( - `filter(where: {_qx: {relations: {tags: {some: {color: {in: ["red","blue"]}}}}}}) {aggregate {count}}`, + `filter(where: {_camino: {relations: {tags: {some: {color: {in: ["red","blue"]}}}}}}) {aggregate {count}}`, ); const plan = findPlan(checked.selection)!; assert.equal(plan.stages[1]!.operation.kind, "filter"); @@ -88,19 +88,19 @@ test("RPC continuation rejection is local to the affected membership boundary", ); const checked = await compileAggregationDocument(`query Report($after: Cursor) {root { tasks(first: 2, after: $after) {entries {node {cost}}} - _qx {relations {tasks {aggregate {sum {score}}}}} + _camino {relations {tasks {aggregate {sum {score}}}}} }}`); assert.equal(findPlan(checked.selection)!.terminals[0]!.residual, true); }); test("typed reference operands and scalar membership tests reject unbounded literal lists", async () => { const checked = await compile( - `filter(where: {_qx: {ref: {in: $tasks}}}) {aggregate {count}}`, + `filter(where: {_camino: {ref: {in: $tasks}}}) {aggregate {count}}`, "($tasks: [QxRef_Task!]!)", ); assert.match(JSON.stringify(checked.variables), /object-ref/); await assert.rejects( - compile(`filter(where: {_qx: {ref: {eq: "obj:made-up"}}}) {aggregate {count}}`), + compile(`filter(where: {_camino: {ref: {eq: "obj:made-up"}}}) {aggregate {count}}`), /typed variables/, ); await assert.rejects( @@ -108,3 +108,23 @@ test("typed reference operands and scalar membership tests reject unbounded lite /1000 operands/, ); }); + +test("new runtime operator matrix accepts direct count and rejects unsupported relational plans", async () => { + const { unsupportedQueryOperator } = await import("../src/execution-world/query-operators.js"); + assert.equal(unsupportedQueryOperator(await compile("aggregate { count }")), undefined); + assert.equal( + unsupportedQueryOperator( + await compileAggregationDocument( + `query Report($after: Cursor) {root {tasks(first: 30, after: $after, orderBy: [{cost: ASC}]) {entries {cursor node {cost}} pageInfo {hasNextPage endCursor}}}}`, + ), + ), + undefined, + ); + for (const expression of [ + "aggregate { sum { estimatedHours } }", + "filter(where: {estimatedHours: {gt: 0}}) { aggregate { count } }", + "distinct(by: {estimatedHours: true}) { aggregate { count } }", + "aggregate { count @skip(if: true) }", + ]) + assert.ok(unsupportedQueryOperator(await compile(expression)), expression); +}); diff --git a/test/query-compiler.test.ts b/test/query-compiler.test.ts index ca7debf..6ccac88 100644 --- a/test/query-compiler.test.ts +++ b/test/query-compiler.test.ts @@ -4,7 +4,6 @@ import { compileCapabilityResourceSource } from "../src/capability-language/inde import { compileQuery } from "../src/query/compile.js"; import { QueryCompileError } from "../src/query/types.js"; import { checkQueryTemplate } from "../src/query/templates.js"; -import { queryPresentation } from "../src/query/presentation.js"; import { querySelectionToWire } from "../src/query/proto.js"; import type { InterfaceRevision } from "../src/capability-model/types.js"; @@ -37,6 +36,50 @@ interface Tasks id "tasks" revision "tasks@1" { [facts], ); +test("query identity ignores unrelated imports and generic retention provenance", async () => { + const generic = iface(`interface Box id "box" revision "box@1" { + queryable relation item id "box:item" : optional-one object Item {resolve id "box:item:resolve";} + }`); + const compiled = compileCapabilityResourceSource( + `import interface Box; import interface TaskFacts; + package Queries id "queries" revision "queries@1" { + query Row id "row" root Box document "row.graphql" operation "Row" {replica;max rows 10;} + }`, + { + source, + environment: { + interfaces: new Map([ + ["Box", generic], + ["TaskFacts", facts], + ]), + interfaceClosure: [generic, facts], + }, + }, + ); + assert.ok(compiled.ok, JSON.stringify(compiled.diagnostics)); + if (compiled.resource.kind !== "package") throw new Error("package required"); + const declaration = compiled.resource.revision.queries![0]!; + const closed = compiled.resource.specializations![0]!; + const read = async () => "query Row {root {item {title}}}"; + const before = await compileQuery(declaration, [facts, closed], read); + const retained = structuredClone(closed); + retained.source = { repository: "https://local.test/box.git", commit: "b".repeat(40) }; + retained.application!.source = retained.source; + const unrelated = iface(`interface Unrelated id "unrelated" revision "unrelated@1" { + queryable value label id "label" : string {get id "label:get";} + }`); + assert.equal( + (await compileQuery(declaration, [unrelated, generic, retained, facts], read)).definitionDigest, + before.definitionDigest, + ); + const changed = structuredClone(facts); + changed.members[0]!.operations[0]!.id = "changed:title:get" as (typeof changed.members)[0]["operations"][0]["id"]; + assert.notEqual( + (await compileQuery(declaration, [changed, retained], read)).definitionDigest, + before.definitionDigest, + ); +}); + test("generic query templates check declared bounds before closed specialization", async () => { const generic = iface( `import interface TaskFacts; @@ -45,11 +88,11 @@ test("generic query templates check declared bounds before closed specialization }`, [facts], ); - const build = (bound: string) => + const build = (bound: string, workspace = false) => compileCapabilityResourceSource( `import interface Collection; import interface TaskFacts; package GenericQueries id "generic-queries" revision "generic-queries@1" { - query Rows id "rows-template" root Collection + query Rows id "rows-template" root ${workspace ? "workspace " : ""}Collection document "rows.graphql" operation "Rows" { view object Item as TaskFacts; max rows 30; } query TaskRows id "task-rows" specialize Rows; }`, @@ -93,12 +136,33 @@ test("generic query templates check declared bounds before closed specialization checkQueryTemplate(invalid.resource.revision.queryTemplates![0]!, [generic, facts], read), /bound|guarantee|implement/i, ); + const extent = build("implements TaskFacts", true); + assert.ok(extent.ok, JSON.stringify(extent.diagnostics)); + if (extent.resource.kind !== "package") throw new Error("package expected"); + const extentPackage = extent.resource.revision; + const readExtent = async () => + `query Rows {root {objects(first: 2) {entries {node {items(first: 3) {entries {node {title}}}}}}}}`; + await checkQueryTemplate(extentPackage.queryTemplates![0]!, [generic, facts], readExtent); + const closed = await compileQuery( + extentPackage.queries![0]!, + [generic, facts, ...(extent.resource.specializations ?? [])], + readExtent, + ); + assert.equal(closed.declaration.root.kind, "workspace-interface"); + assert.equal(closed.declaration.root.interface, specialized.declaration.root.interface); + const invalidExtent = build("", true); + assert.ok(invalidExtent.ok, JSON.stringify(invalidExtent.diagnostics)); + if (invalidExtent.resource.kind !== "package") throw new Error("package expected"); + await assert.rejects( + checkQueryTemplate(invalidExtent.resource.revision.queryTemplates![0]!, [generic, facts], readExtent), + /bound|guarantee|implement/i, + ); }); -function fixture(clauses = "") { +function fixture(clauses = "", root = "Tasks") { const result = compileCapabilityResourceSource( `import interface Tasks; import interface TaskFacts; package Queries id "queries" revision "queries@1" { - query Upcoming id "upcoming" root Tasks document "queries/upcoming.graphql" operation "Upcoming" { + query Upcoming id "upcoming" root ${root} document "queries/upcoming.graphql" operation "Upcoming" { fragments "queries/row.graphql"; max rows 30; ${clauses} } }`, @@ -119,56 +183,183 @@ package Queries id "queries" revision "queries@1" { assert.equal(result.resource.revision.exports.length, 0); return result.resource.revision.queries![0]!; } +test("workspace roots expose a bounded interface extent, not a root object or writable relationship", async () => { + const declaration = { ...fixture("watch;", "workspace TaskFacts"), fragments: [] }; + assert.deepEqual(declaration.root, { kind: "workspace-interface", interface: "facts@1" }); + const checked = await compileQuery( + declaration, + [facts, collection], + async () => `query Upcoming { + root { + objects(first: 3, where: {done: {eq: false}}, orderBy: {title: ASC}) { + entries {key node {_camino {ref} title}} pageInfo {hasNextPage endCursor} + } + _camino {relations {objects {aggregate {count sum {due}}}}} + } + }`, + ); + assert.deepEqual(new Set(checked.effects.map((effect) => effect.memberId)), new Set(["done", "title", "due"])); + assert.ok(!checked.effects.some((effect) => effect.memberId.includes("workspace-objects"))); + assert.ok(JSON.stringify(checked.selection).includes("qx-internal-workspace-objects")); + await assert.rejects( + compileQuery(declaration, [facts], async () => `query Upcoming {root {_camino {ref}}}`), + /QUERY_VALIDATION/, + ); + await assert.rejects( + compileQuery(declaration, [facts], async () => `query Upcoming {root {objects {entries {key}}}}`), + /QUERY_ROW_LIMIT/, + ); +}); const document = `query Upcoming($first: Int!, $before: Int64!) { root { items(first: $first, where: {done: {eq: false}, due: {lt: $before}}, orderBy: [{due: ASC}]) { - entries { key node { _qx { ref } ...Row } } pageInfo { hasNextPage endCursor } + entries { key node { _camino { ref } ...Row } } pageInfo { hasNextPage endCursor } } } }`; const compile = (query = document, row = "fragment Row on TaskFacts { title due }", clauses = "") => compileQuery(fixture(clauses), [collection, facts], async (name) => (name.endsWith("row.graphql") ? row : query)); -test("live selections preserve aliases, nullable values, fragment conditions and wire identity", async () => { +test("ordinary selections preserve aliases, fragment conditions and wire identity", async () => { const checked = await compile( `query Upcoming($show: Boolean!) {root {items(first: 3) {entries {node {plain: title ...Row @include(if: $show)}}}}}`, - `fragment Row on TaskFacts {label: title @live due @live}`, + `fragment Row on TaskFacts {label: title due}`, ); - const fields = queryPresentation(checked, [collection, facts]); + const node = checked.selection[0]!.selection[0]!.selection[0]!.selection[0]!; assert.deepEqual( - fields.map((f) => f.path), - [ - ["root", "items", "entries", "*", "node", "label"], - ["root", "items", "entries", "*", "node", "due"], - ], + node.selection.map((s) => s.key), + ["plain", "label", "due", "_camino"], ); - assert.equal(fields[0]!.getOperationId, "title:get"); - assert.equal(fields[0]!.setOperationId, undefined); - assert.equal(fields[1]!.valueType.kind, "optional"); - assert.equal(fields[0]!.conditions.length, 1); - assert.ok(JSON.stringify(checked.selection.map(querySelectionToWire)).includes(fields[0]!.selectionId)); - const defaults = await compile( - `query Upcoming($show: Boolean! = true) {root {items(first: 3) {entries {node {...Row @include(if: $show)}}}}}`, - "fragment Row on TaskFacts {title @live}", - ); - assert.equal(queryPresentation(defaults, [collection, facts])[0]!.conditions[0]!.defaultValue, true); + assert.equal(node.selection[1]!.conditions.length, 1); + assert.ok(JSON.stringify(checked.selection.map(querySelectionToWire)).includes("label")); }); - -test("live selections reject RPC getters, synthetic selections and conflicting presentations", async () => { - for (const [selected, code] of [ - ["score @live", "QUERY_LIVE_UNSUPPORTED"], - ["_qx @live {ref}", "QUERY_LIVE_UNSUPPORTED"], - ["title @live title", "QUERY_LIVE_CONFLICT"], - ["title @live(unchecked: true)", "QUERY_VALIDATION"], - ]) +test("object references are unconditional, canonical and idempotent through fragments", async () => { + const checked = await compile( + `query Upcoming($show: Boolean!) {root {items(first: 3) {entries {node {...Row @include(if: $show)}}}}}`, + `fragment Row on TaskFacts {label: title identity: _camino {alias: ref} _camino @include(if: $show) {ref}}`, + ); + const root = checked.selection[0]!; + const node = root.selection.find((s) => s.name === "items")!.selection[0]!.selection[0]!; + for (const object of [root, node]) { + const metadata = object.selection.filter((s) => s.key === "_camino"); + assert.equal(metadata.length, 1); + assert.deepEqual(metadata[0]!.conditions, []); + assert.deepEqual( + metadata[0]!.selection.filter((s) => s.key === "ref").map((s) => s.conditions), + [[]], + ); + } + assert.equal(node.selection.find((s) => s.key === "identity")!.conditions.length, 1); + const recompiled = await compileQuery( + { ...checked.declaration, fragments: [] }, + [collection, facts], + async () => checked.document, + ); + assert.deepEqual(recompiled.selection, checked.selection); + assert.deepEqual(recompiled.output, checked.output); +}); +test("retired live syntax fails with migration guidance", async () => { + for (const selected of ["score @live", "_camino @live {ref}", "title @live title", "title @live(unchecked: true)"]) await assert.rejects( compile( `query Upcoming {root {items(first: 3) {entries {node {...Row}}}}}`, `fragment Row on TaskFacts {${selected}}`, 'allow TaskFacts.score select "bounded";', ), - (error: unknown) => error instanceof QueryCompileError && error.code === code, + (error: unknown) => error instanceof QueryCompileError && error.code === "QUERY_RETIRED_DIRECTIVE", ); }); +test("implicit references reject aliases which occupy the canonical metadata path", async () => { + for (const invalid of ["_camino: title", "_camino {ref: __typename}"]) + await assert.rejects( + compile( + `query Upcoming {root {items(first: 3) {entries {node {...Row}}}}}`, + `fragment Row on TaskFacts {${invalid}}`, + ), + /QUERY_VALIDATION|Introspection/, + ); +}); + +test("implicit reference merges with conditional aggregate metadata", async () => { + const checked = await compileQuery( + { ...fixture(""), fragments: [] }, + [collection, facts], + async () => + `query Upcoming($show: Boolean!) {root {_camino @include(if: $show) {relations {items {aggregate {count}}}}}}`, + ); + const metadata = checked.selection[0]!.selection.filter((field) => field.key === "_camino"); + assert.equal(metadata.length, 1); + assert.deepEqual(metadata[0]!.conditions, []); + assert.equal(metadata[0]!.selection.find((field) => field.name === "relations")!.conditions.length, 1); + assert.deepEqual(metadata[0]!.selection.find((field) => field.name === "ref")!.conditions, []); + assert.equal( + metadata[0]!.selection + .find((field) => field.name === "relations")! + .selection[0]!.selection[0]!.selection.some((field) => field.name === "_camino"), + false, + ); +}); + +test("qualified interface predicates and guaranteed inline projections retain nominal reads", async () => { + const owner = iface( + `interface Owner id "owner" revision "owner@1" { queryable value name id "name" : string {get id "name:get";} }`, + ); + const owned = iface( + `import interface Owner; + interface Owned id "owned" revision "owned@1" { + queryable relation owner id "owner" : optional-one interface Owner {resolve id "owner:resolve";} + queryable value label id "owned:label" : string {get id "owned:label:get";} + }`, + [owner], + ); + const item = iface( + `import interface Owned; + interface Item id "item" revision "item@1" requires Owned { + queryable value label id "item:label" : string {get id "item:label:get";} + }`, + [owned, owner], + ); + const declaration = { + ...fixture(""), + root: { kind: "workspace-interface" as const, interface: item.revisionId }, + fragments: [], + }; + const document = `query Upcoming($owner: QxRef_Owner!) {root {objects(all: 10, where: {_camino: {as: {Owned: {owner: {eq: $owner}}}}}) {entries {node {label ... on Owned {owner {name} ownerLabel: label}}}}}}`; + const checked = await compileQuery(declaration, [item, owned, owner], async () => document); + assert.ok( + checked.effects.some( + (effect) => + effect.interfaceRevisionId === owned.revisionId && + effect.memberId === "owner" && + effect.uses.includes("predicate"), + ), + ); + const objects = checked.selection[0]!.selection[0]!; + assert.equal(objects.predicate?.kind, "and"); + assert.ok(JSON.stringify(objects.predicate).includes('"kind":"as"')); + const node = objects.selection[0]!.selection[0]!; + assert.equal(node.selection.find((s) => s.key === "ownerLabel")!.interfaceRevisionId, owned.revisionId); + assert.equal(node.selection.filter((s) => s.key === "_camino").length, 1); + await assert.rejects( + compileQuery(declaration, [item, owned, owner], async () => + document.replace("ownerLabel: label", "ownerLabel: label _camino {ref}"), + ), + /QUERY_VALIDATION/, + ); + const explicitProjectionRef = await compileQuery(declaration, [item, owned, owner], async () => + document.replace("ownerLabel: label", "ownerLabel: label ownedIdentity: _camino {ref}"), + ); + const projectedNode = explicitProjectionRef.selection[0]!.selection[0]!.selection[0]!.selection[0]!; + assert.equal( + projectedNode.selection.find((field) => field.key === "_camino")!.selection.find((field) => field.key === "ref")! + .targetInterfaceRevisionId, + item.revisionId, + ); + await assert.rejects( + compileQuery(declaration, [item, owned, owner], async () => document.replace("ownerLabel: label", "label")), + /QUERY_PROJECTION_CONFLICT|QUERY_VALIDATION/, + ); +}); + test("query dependency ports resolve exact exports without declaration ordering constraints", () => { const result = compileCapabilityResourceSource( `import interface Tasks; @@ -228,6 +419,10 @@ test("fixed GraphQL yields exact effects, typed references and distinct query ar test("query allowlist and two-sided RPC effects fail during compilation", async () => { const rejects = (promise: Promise, code: string) => assert.rejects(promise, (error: unknown) => error instanceof QueryCompileError && error.code === code); + const substring = (field: string) => + `query Upcoming {root {items(first: 3, where: {${field}: {containsInsensitive: "TASK"}}) {entries {node {...Row}}}}}`; + await compile(substring("title")); + await rejects(compile(substring("due")), "QUERY_VALIDATION"); await rejects(compile(document, "fragment Row on TaskFacts { hidden }"), "QUERY_VALIDATION"); await rejects(compile(document, "fragment Row on TaskFacts { score }"), "QUERY_RPC_CONSUMER_REASON"); await compile( @@ -248,3 +443,98 @@ test("query allowlist and two-sided RPC effects fail during compilation", async await rejects(compile(document.replace("...Row", "__typename ...Row")), "QUERY_UNSUPPORTED_FEATURE"); await rejects(compile(document, "fragment Row on TaskFacts { ...Row }"), "QUERY_VALIDATION"); }); + +test("unselected atom relationships do not require views on reference-only targets", async () => { + const target = iface(`external atom Detail id "detail"; + interface Referenced id "referenced" revision "referenced@1" { + queryable relation details id "details" : many atom Detail {resolve id "details:resolve";} + }`); + const subject = iface( + `external atom Detail id "detail"; import interface Referenced; + interface Subject id "subject" revision "subject@1" { + queryable relation selected id "selected" : optional-one interface Referenced {resolve id "selected:resolve";} + }`, + [target], + ); + const declaration = { + ...fixture(""), + root: { kind: "object" as const, interface: subject.revisionId }, + fragments: [], + }; + const document = "query Upcoming {root {selected {_camino {ref}}}}"; + const checked = await compileQuery(declaration, [subject, target], async () => document); + assert.match(JSON.stringify(checked.output), /referenced@1/); + await assert.rejects( + compileQuery( + declaration, + [subject, target], + async () => "query Upcoming {root {selected {details(all: 3) {entries {node {_camino {ref}}}}}}}", + ), + /QUERY_VIEW_REQUIRED/, + ); +}); + +test("React tooling generates checked owner queries from imported closed navigation fields", async () => { + const { presentationQueries } = await import("../src/bindings/presentation-queries.js"); + const mount = iface('interface PresentationMount id "mount" revision "mount@1" {}'); + const subject = iface('interface Subject id "subject" revision "subject@1" {}'); + const state = iface( + `import interface PresentationMount; import interface Subject; + interface PresentationState id "state" revision "state@1" { + queryable value occurrence id "occurrence" : string {get id "occurrence:get" execution snapshot;} + queryable relation presentationMount id "mount" : optional-one interface PresentationMount {resolve id "mount:resolve" execution snapshot;} + queryable relation parent id "parent" : optional-one interface PresentationState {resolve id "parent:resolve" execution snapshot;} + queryable relation source id "source" : exactly-one interface Subject {resolve id "source:resolve" execution snapshot;} + }`, + [mount, subject], + ); + const navigation = iface( + `import interface PresentationState; + interface Navigation id "navigation" revision "navigation@1" requires PresentationState { + queryable value custom id "custom" : T {get id "custom:get" execution snapshot;} + }`, + [mount, subject, state], + ); + const react = iface( + `import interface PresentationState; + interface ReactComponent id "react" revision "react@1" { + module render id "render" contract "org.quixos.react.component/2" subject object Subject; + }`, + [mount, subject, state], + ); + const dependencies = [mount, subject, state, navigation, react]; + const parsed = compileCapabilityResourceSource( + `import interface ReactComponent; import interface Navigation; import interface Subject; + package Example id "example" revision "example@1" { + module Screen id "screen" implements ReactComponent>.render; + }`, + { + source, + environment: { + interfaces: new Map(dependencies.map((entry) => [entry.displayName, entry])), + interfaceClosure: dependencies, + }, + }, + ); + assert.ok(parsed.ok, JSON.stringify(parsed.diagnostics)); + if (parsed.resource.kind !== "package") throw Error("package required"); + const contracts = [...dependencies, ...(parsed.resource.specializations ?? [])]; + const documents = presentationQueries(parsed.resource.revision, contracts); + assert.equal(documents.size, 2); + const checked = await Promise.all( + parsed.resource.revision.queries!.map((query) => + compileQuery(query, contracts, async (name) => documents.get(name)!), + ), + ); + assert.deepEqual( + checked.map((query) => query.declaration.placement?.kind), + ["replica", "local"], + ); + assert.match(JSON.stringify(checked[0]!.output), /"custom":\{"kind":"record","fields":\{"label"/); + assert.match(JSON.stringify(checked[0]!.selection), /"kind":"as"/); + assert( + checked.every((query) => + query.effects.some((effect) => effect.memberId === "mount" && effect.uses.includes("predicate")), + ), + ); +}); diff --git a/test/query-composition.test.ts b/test/query-composition.test.ts new file mode 100644 index 0000000..6f9d38b --- /dev/null +++ b/test/query-composition.test.ts @@ -0,0 +1,117 @@ +import assert from "node:assert/strict"; +import { test } from "node:test"; +import { compileCapabilityResourceSource } from "../src/capability-language/index.js"; +import type { InterfaceRevision } from "../src/capability-model/types.js"; +import { valueType } from "../src/capability-model/types.js"; +import { compileQuery } from "../src/query/compile.js"; +import { compileQueryComposition, type QueryComposition } from "../src/query/composition.js"; +import { defaultQueryBudgets } from "../src/query/types.js"; + +const iface = (text: string, dependencies: InterfaceRevision[] = []) => { + const result = compileCapabilityResourceSource(text, { + source: { repository: "https://example.test/devices", commit: "a".repeat(40) }, + environment: { + interfaces: new Map(dependencies.map((entry) => [entry.displayName, entry])), + interfaceClosure: dependencies, + }, + }); + assert.ok(result.ok, JSON.stringify(result.diagnostics)); + if (result.resource.kind !== "interface") throw Error("Expected interface"); + return result.resource.revision; +}; + +test("dependent query roots and arguments are checked without a presentation library", async () => { + const sensor = iface(`interface Sensor id "sensor" revision "sensor@1" { + queryable value reading id "reading" : int64 { get id "reading:get"; } + }`); + const control = iface( + `import interface Sensor; + interface Control id "control" revision "control@1" { + queryable value enabled id "enabled" : bool { get id "enabled:get"; } + queryable relation selected id "selected" : optional-one interface Sensor { resolve id "selected:get"; } + }`, + [sensor], + ); + const query = (id: string, root: InterfaceRevision["revisionId"], text: string) => + compileQuery( + { + id, + displayName: id, + document: `${id}.graphql`, + operation: id, + root: { kind: "object", interface: root }, + fragments: [], + views: [], + allowances: [], + budgets: { ...defaultQueryBudgets }, + watch: false, + placement: { kind: "replica" }, + }, + [control, sensor], + async () => text, + ); + const selection = await query( + "Selection", + control.revisionId, + "query Selection { root { enabled selected { _camino { ref atom } } } }", + ); + const reading = await query( + "Reading", + sensor.revisionId, + "query Reading($enabled: Boolean!) { root { reading @include(if: $enabled) } }", + ); + const queries = new Map([ + ["Selection", selection], + ["Reading", reading], + ]); + const source: QueryComposition = { + id: "device-display", + input: { kind: "record", fields: { controller: valueType.interfaceRef(control.revisionId) } }, + maxQueries: 2, + steps: [ + { + name: "selection", + query: "Selection", + root: { from: "input", path: ["controller"] }, + arguments: { record: {} }, + }, + { + name: "reading", + query: "Reading", + with: { from: "result", name: "selection", path: ["root", "selected"] }, + root: { from: "item", path: ["_camino", "ref"] }, + arguments: { record: { enabled: { from: "result", name: "selection", path: ["root", "enabled"] } } }, + }, + ], + }; + const checked = compileQueryComposition(source, queries); + assert.equal(checked.output.kind, "record"); + assert.equal(checked.dependencies.length, 2); + assert.ok(checked.definitionDigest); + const changed = new Map(queries); + changed.set("Reading", { ...reading, definitionDigest: "different-source" }); + assert.notEqual(compileQueryComposition(source, changed).definitionDigest, checked.definitionDigest); + assert.throws(() => compileQueryComposition({ ...source, maxQueries: 1 }, queries), /exceed maxQueries/); + assert.throws( + () => compileQueryComposition({ ...source, steps: [...source.steps].reverse() }, queries), + /earlier step/, + ); + const wrongArguments = structuredClone(source); + wrongArguments.steps[1]!.arguments = { record: { enabled: { from: "input", path: ["controller"] } } }; + assert.throws(() => compileQueryComposition(wrongArguments, queries), /arguments do not match/); + const wrongRoot = structuredClone(source); + wrongRoot.steps[1]!.root = { from: "input", path: ["controller"] }; + assert.throws(() => compileQueryComposition(wrongRoot, queries), /reference to sensor@1/); + const nullableTraversal = structuredClone(source); + nullableTraversal.steps[1]!.root = { + from: "result", + name: "selection", + path: ["root", "selected", "_camino", "ref"], + }; + assert.throws(() => compileQueryComposition(nullableTraversal, queries), /explicit boundary/); + const branch = structuredClone(source); + branch.steps[1]!.when = { equal: [{ from: "item", path: ["_camino", "atom"] }, { literal: "thermometer" }] }; + assert.ok(compileQueryComposition(branch, queries).definitionDigest); + branch.steps[1]!.when = { literal: "not a boolean" }; + assert.throws(() => compileQueryComposition(branch, queries), /when requires a boolean/); +}); diff --git a/test/query-contracts.test.ts b/test/query-contracts.test.ts index c8f15f2..0068437 100644 --- a/test/query-contracts.test.ts +++ b/test/query-contracts.test.ts @@ -34,7 +34,7 @@ test("queryable fields need one authoritative getter and supported values", () = assert.equal(compile('queryable value title id "title" : string { get id "title:get"; }').ok, true); for (const member of [ 'queryable value title id "title" : string { set id "title:set"; }', - 'queryable value title id "title" : list { get id "title:get"; }', + 'queryable value title id "title" : list { get id "title:get"; }', 'queryable value title id "title" : string { get id "title:get"; get id "other:get"; }', ]) assert.equal(compile(member).ok, false, member); diff --git a/test/query-cursor-validation.test.ts b/test/query-cursor-validation.test.ts new file mode 100644 index 0000000..2e23986 --- /dev/null +++ b/test/query-cursor-validation.test.ts @@ -0,0 +1,34 @@ +import assert from "node:assert/strict"; +import { test } from "node:test"; +import { loadReaderWorkspace } from "./reader-fixture.js"; +import { compileExecutionPlan } from "../src/execution-world/index.js"; +test("local relationship pages may reuse exactly the confirmed source window", async () => { + const workspace = await loadReaderWorkspace((name, text) => { + if (name === "queries/replica-inputs.graphql") + return text.replace("highlights(all: 1000)", "highlights(first: 10)"); + if (name === "queries/reader-screen.graphql") + return text + .replace("ReaderScreen($color: String!)", "ReaderScreen") + .replace( + "highlights(all: 1000, where: {color: {eq: $color}}, orderBy: [{range: {start: ASC}}])", + "highlights(first: 10)", + ) + .replace("_camino { relations { highlights { aggregate { count } } } }", ""); + return text; + }); + assert.doesNotThrow(() => compileExecutionPlan(workspace)); +}); + +test("local workspace objects reads the exact server window without adding local candidates", async () => { + const load = (first: number) => + loadReaderWorkspace((name, text) => { + if (name === "ReaderRuntime.qx") return text.replace(/^.*query HighlightList id.*$/m, ""); + if (name === "queries/highlight-list.graphql") + return text.replace("availableObjects(all: 200", `objects(first: ${first}`); + return text; + }); + const workspace = await load(2); + assert.doesNotThrow(() => compileExecutionPlan(workspace)); + const wrong = await load(3); + assert.throws(() => compileExecutionPlan(wrong), /REPLICA_WINDOW_MISMATCH/); +}); diff --git a/test/query-fragments.test.ts b/test/query-fragments.test.ts new file mode 100644 index 0000000..6345401 --- /dev/null +++ b/test/query-fragments.test.ts @@ -0,0 +1,232 @@ +import assert from "node:assert/strict"; +import test from "node:test"; +import { compileCapabilityResourceSource } from "../src/capability-language/parser.js"; +import { compileQuery } from "../src/query/compile.js"; +import type { PackageRevision } from "../src/capability-model/types.js"; +const source = { repository: "https://example.test/fragments", commit: "a".repeat(40) }; +const compiled = compileCapabilityResourceSource( + `interface Item id "item" revision "item@1" { + queryable value title id "title" : string {get id "title:get" execution snapshot;} + queryable rpc value remote id "remote" : string {get id "remote:get";} +}`, + { source }, +); +if (!compiled.ok || compiled.resource.kind !== "interface") throw Error(JSON.stringify(compiled.diagnostics)); +const item = compiled.resource.revision; +function pkg(body: string, imports: PackageRevision[] = [], name = "Views") { + const result = compileCapabilityResourceSource( + `import interface Item; ${imports.map((p) => `import package ${p.displayName};`).join(" ")} package ${name} id "${name}" revision "${name}@1" {${body}}`, + { + source, + environment: { + interfaces: new Map([["Item", item]]), + interfaceClosure: [item], + packages: new Map(imports.map((p) => [p.displayName, p])), + }, + }, + ); + assert.ok(result.ok, JSON.stringify(result.diagnostics)); + if (result.resource.kind !== "package") throw Error("package required"); + return result.resource.revision; +} +test("exported fragments generate local projection and direct admission", async () => { + const p = pkg('query fragment Row id "row" root Item document "row.graphql" fragment "Row";'); + assert.equal(p.queries!.length, 2); + const local = p.queries!.find((q) => !q.fragment?.admission)!; + assert.equal(local.placement?.kind, "local"); + const checked = await compileQuery(local, [item], async () => "fragment Row on Item {title}"); + assert.equal(checked.output.kind, "record"); + assert.match(checked.document, /query QuixosFragment/); + await assert.rejects( + compileQuery(local, [item], async () => "fragment Row on Item {remote}"), + /QUERY_LOCAL_DEPENDENCY/, + ); +}); +test("imported fragments retain checked occurrences and require exact roots", async () => { + const provider = pkg('query fragment Row id "row" root Item document "row.graphql" fragment "Row";', [], "Provider"); + const checked = await compileQuery( + provider.queries!.find((q) => !q.fragment?.admission)!, + [item], + async () => "fragment Row on Item {title}", + ); + const consumer = pkg( + 'query Screen id "screen" root Item document "screen.graphql" operation "Screen" {replica; fragments Provider.Row;}', + [provider], + ); + const result = await compileQuery( + consumer.queries![0]!, + [item], + async () => "query Screen {root {...Row}}", + async () => checked, + ); + assert.equal(result.selection[0]!.selection[0]!.fragmentSpread?.digest, checked.definitionDigest); + assert.equal(result.selection[0]!.selection[0]!.fragmentSpread?.root, item.revisionId); + assert.ok(result.effects.some((e) => e.memberId === "title")); + const bound = await compileQuery( + { + ...consumer.queries![0]!, + importedFragments: consumer.queries![0]!.importedFragments!.map((entry) => ({ ...entry, mode: "reference" })), + }, + [item], + async () => "query Screen {root {...Row}}", + async () => checked, + ); + assert.equal(bound.selection[0]!.selection.find((entry) => entry.fragmentSpread)?.fragmentSpread?.mode, "reference"); + assert( + !bound.selection[0]!.selection.some((entry) => entry.memberId === "title"), + "A fragment binding does not evaluate its fields in the parent", + ); + assert(!JSON.stringify(bound.output).includes('"title"'), "Parent output excludes separately observed fields"); + const wrong = { + ...checked, + declaration: { + ...checked.declaration, + root: { ...checked.declaration.root, interface: "different@1" as typeof item.revisionId }, + }, + }; + await assert.rejects( + compileQuery( + consumer.queries![0]!, + [item], + async () => "query Screen {root {...Row}}", + async () => wrong, + ), + /FRAGMENT_ROOT_MISMATCH/, + ); +}); +test("fragment variable mappings are explicit and checked", async () => { + const provider = pkg( + 'query fragment Row id "row" root Item document "row.graphql" fragment "Row" operation "Inputs";', + [], + "Provider", + ); + const checked = await compileQuery( + provider.queries!.find((q) => !q.fragment?.admission)!, + [item], + async () => + "query Inputs($show: Boolean!) {root {...Row}} fragment Row on Item {_camino {ref} title @include(if:$show)}", + ); + const build = (mapping: string) => + pkg( + `query Screen id "screen" root Item document "screen.graphql" operation "Screen" {replica; fragments Provider.Row ${mapping};}`, + [provider], + ).queries![0]!; + const document = "query Screen($visible: Boolean!) {root {...Row}}"; + await assert.rejects( + compileQuery( + build(""), + [item], + async () => document, + async () => checked, + ), + /FRAGMENT_VARIABLE_MAPPING/, + ); + const result = await compileQuery( + build("with {show to visible;}"), + [item], + async () => document, + async () => checked, + ); + assert.deepEqual(result.selection[0]!.selection[0]!.fragmentSpread?.variables, { show: "visible" }); + const referenceDeclaration = build("with {show to visible;}"); + referenceDeclaration.importedFragments = referenceDeclaration.importedFragments!.map((entry) => ({ + ...entry, + mode: "reference", + })); + const binding = await compileQuery( + referenceDeclaration, + [item], + async () => document, + async () => checked, + ); + assert.deepEqual(binding.selection[0]!.selection[0]!.fragmentSpread?.variables, { show: "visible" }); + assert.notEqual(binding.definitionDigest, result.definitionDigest); + await assert.rejects( + compileQuery( + referenceDeclaration, + [item], + async () => document.replace("$visible: Boolean!", "$visible: Boolean!, $unused: Boolean!"), + async () => checked, + ), + /never used/, + ); + const changedDigest = await compileQuery( + referenceDeclaration, + [item], + async () => document, + async () => ({ ...checked, definitionDigest: "b".repeat(64) }), + ); + assert.notEqual( + changedDigest.definitionDigest, + binding.definitionDigest, + "A bound fragment's identity remains in the parent contract digest", + ); + await assert.rejects( + compileQuery( + build("with {show to visible;}"), + [item], + async () => document.replace("Boolean!", "Boolean"), + async () => checked, + ), + /FRAGMENT_VARIABLE_TYPE/, + ); +}); + +test("independent fragment package survives execution lowering and typed client generation", async () => { + const { loadReaderWorkspace, readReaderFixture } = await import("./reader-fixture.js"); + const { compileExecutionPlan } = await import("../src/execution-world/plan.js"); + const { compileQueryProgram } = await import("../src/execution-world/queries.js"); + const { generatePackageQueryClientBindings } = await import("../src/execution-world/client-contracts.js"); + const workspace = await loadReaderWorkspace(); + const comment = workspace.interfaceImports.find((i) => i.displayName === "CommentData")!; + const parsed = compileCapabilityResourceSource( + `import interface CommentData; package CommentRows id "comment-rows" revision "comment-rows@1" { + query fragment Row id "row" root CommentData document "row.graphql" fragment "CommentRow"; + }`, + { source, environment: { interfaces: new Map([["CommentData", comment]]), interfaceClosure: [comment] } }, + ); + assert.ok(parsed.ok, JSON.stringify(parsed.diagnostics)); + if (parsed.resource.kind !== "package") throw Error("expected package"); + const provider = parsed.resource.revision; + provider.checkedQueries = await Promise.all( + provider.queries!.map((q) => + compileQuery( + q, + workspace.interfaceImports, + async () => "fragment CommentRow on CommentData {content {text tags}}", + ), + ), + ); + workspace.packageImports.push(provider); + const consumer = workspace.packageImports.find((p) => p.displayName === "ReaderRuntime")!; + const declaration = consumer.queries!.find((q) => q.displayName === "ReaderScreen")!; + declaration.importedFragments = [ + { packageRevisionId: provider.revisionId, packageAlias: "CommentRows", fragmentId: "row", variables: {} }, + ]; + const document = (await readReaderFixture("queries/reader-screen.graphql")).replace( + "_camino { ref } content { text tags }", + "...CommentRow", + ); + const checked = await compileQuery( + declaration, + workspace.interfaceImports, + async () => document, + async () => provider.checkedQueries!.find((q) => q.declaration.id === "row")!, + ); + consumer.checkedQueries = consumer.checkedQueries!.map((q) => (q.declaration.id === declaration.id ? checked : q)); + const plan = compileExecutionPlan(workspace); + const program = compileQueryProgram(plan) as { + queries: { id: string; fragment?: { package: string; id: string }; selection: unknown }[]; + }; + assert.deepEqual(program.queries.find((q) => q.fragment)?.fragment, { + package: provider.revisionId, + id: "row", + digest: provider.checkedQueries!.find((q) => q.declaration.id === "row")!.definitionDigest, + }); + const screen = program.queries.find((q) => q.id === JSON.stringify([consumer.revisionId, declaration.id]))!; + assert.match(JSON.stringify(screen.selection), /fragmentSpread/); + const generated = generatePackageQueryClientBindings(consumer, undefined, workspace.packageImports); + assert.match(generated, /"CommentRows": \{"Row": definePackageFragment/); + assert.ok(generated.includes(JSON.stringify(comment.revisionId))); + assert.ok(!generated.includes('"Row$inputs": definePackageQuery')); +}); diff --git a/test/query-source-generation.test.ts b/test/query-source-generation.test.ts new file mode 100644 index 0000000..b2b38dc --- /dev/null +++ b/test/query-source-generation.test.ts @@ -0,0 +1,159 @@ +import assert from "node:assert/strict"; +import { mkdtemp, mkdir, writeFile, rm } from "node:fs/promises"; +import os from "node:os"; +import path from "node:path"; +import test from "node:test"; +import { + compileCapabilityResourceRepository, + type PackageQueryGenerator, +} from "../src/capability-language/assembly.js"; +import { defaultQueryBudgets } from "../src/query/types.js"; + +test("an explicit non-React frontend emits checked queries for its own selection model", async (context) => { + const directory = await mkdtemp(path.join(os.tmpdir(), "qx-query-generator-")); + context.after(() => rm(directory, { recursive: true, force: true })); + const commit = "a".repeat(40); + const source = (name: string) => ({ + resolver: "git" as const, + repository: `https://example.test/${name}.git`, + commit, + }); + const lock = (imports: string) => `quixos-lock version 1 { + quixos source { repository "https://example.test/quixos.git"; commit "${commit}"; } + ${imports} + }`; + const dependency = (name: string) => + `interface ${name} source { repository "${source(name).repository}"; commit "${commit}"; }`; + for (const name of ["Sensor", "DeviceSelection", "Device"]) await mkdir(path.join(directory, name)); + await writeFile(path.join(directory, "Sensor", "quixos.lock"), lock("")); + await writeFile( + path.join(directory, "Sensor", "interface.qx"), + ` + interface Sensor id "sensor" revision "sensor@1" { + queryable value reading id "reading" : int64 { get id "reading:get"; } + } + `, + ); + await writeFile(path.join(directory, "DeviceSelection", "quixos.lock"), lock(dependency("Sensor"))); + await writeFile( + path.join(directory, "DeviceSelection", "interface.qx"), + ` + import interface Sensor; + interface DeviceSelection id "device-selection" revision "device-selection@1" { + queryable relation selectedSensor id "selected-sensor" : optional-one interface Sensor { + resolve id "selected-sensor:resolve"; + } + } + `, + ); + await writeFile(path.join(directory, "Device", "quixos.lock"), lock(dependency("DeviceSelection"))); + await writeFile( + path.join(directory, "Device", "package.qx"), + ` + import interface DeviceSelection; + package Device id "device" revision "device@1" {} + `, + ); + await writeFile( + path.join(directory, "Device", "inputs.graphql"), + `query Selected { root { selectedSensor { reading } } }`, + ); + const options = { + rootDirectory: path.join(directory, "Device"), + kind: "package" as const, + source: source("Device"), + resolveResource: async (ref: { repository: string }) => ({ + directory: path.join(directory, new URL(ref.repository).pathname.slice(1, -4)), + }), + }; + const plain = await compileCapabilityResourceRepository(options); + assert.equal(plain.resource.kind, "package"); + if (plain.resource.kind !== "package") throw Error("Expected package"); + assert.equal(plain.resource.revision.queries?.length ?? 0, 0); + + const generateQueries: PackageQueryGenerator = async ({ interfaces, readSource }) => ({ + queries: [ + { + id: "selected-inputs", + displayName: "SelectedInputs", + root: { + kind: "object", + interface: interfaces.find((iface) => iface.displayName === "DeviceSelection")!.revisionId, + }, + document: "generated/selected.graphql", + operation: "Selected", + fragments: [], + views: [], + allowances: [], + budgets: { ...defaultQueryBudgets }, + watch: false, + placement: { kind: "replica" }, + }, + ], + documents: new Map([["generated/selected.graphql", await readSource("inputs.graphql")]]), + compositions: [ + { + id: "display", + maxQueries: 1, + input: { + kind: "record", + fields: { + device: { + kind: "object-ref", + expectation: { + kind: "interface", + interfaceRevisionId: interfaces.find((iface) => iface.displayName === "DeviceSelection")!.revisionId, + }, + }, + }, + }, + steps: [ + { + name: "selection", + query: "selected-inputs", + root: { from: "input", path: ["device"] }, + arguments: { record: {} }, + }, + ], + }, + ], + }); + const built = await compileCapabilityResourceRepository({ ...options, generateQueries }); + assert.equal(built.resource.kind, "package"); + if (built.resource.kind !== "package") throw Error("Expected package"); + assert.equal(built.resource.revision.checkedQueries?.length, 1); + const checked = built.resource.revision.checkedQueries![0]!; + assert.equal(checked.declaration.root.interface, "device-selection@1"); + assert.ok(checked.effects.some((effect) => effect.memberId === "reading")); + assert.ok(checked.effects.some((effect) => effect.memberId === "selected-sensor")); + assert.ok(checked.definitionDigest); + assert.equal(built.resource.revision.queryCompositions?.[0]?.dependencies[0]?.digest, checked.definitionDigest); + assert.equal( + built.resources.some((resource) => /React|PresentationMount/.test(resource.key)), + false, + ); + + await assert.rejects( + compileCapabilityResourceRepository({ + ...options, + generateQueries: async (input) => { + const result = await generateQueries(input); + return { + ...result, + documents: new Map([["generated/selected.graphql", "query Selected { root { invented } }"]]), + }; + }, + }), + /invented/, + ); + await assert.rejects( + compileCapabilityResourceRepository({ + ...options, + generateQueries: async (input) => { + const result = await generateQueries(input); + return { ...result, queries: [...result.queries, ...result.queries] }; + }, + }), + /Generated query conflicts/, + ); +}); diff --git a/test/query-structured.test.ts b/test/query-structured.test.ts new file mode 100644 index 0000000..e8856bd --- /dev/null +++ b/test/query-structured.test.ts @@ -0,0 +1,121 @@ +import assert from "node:assert/strict"; +import { test } from "node:test"; +import { compileCapabilityResourceSource } from "../src/capability-language/index.js"; +import { compileQuery } from "../src/query/compile.js"; +import { querySelectionToWire } from "../src/query/proto.js"; +import { defaultQueryBudgets, type QueryDeclaration } from "../src/query/types.js"; +const source = { repository: "https://example.test/structured.git", commit: "a".repeat(40) }; +const result = compileCapabilityResourceSource( + `interface Highlight id "highlight" revision "highlight@1" { + queryable value range id "range" : record { sourceRevision:string; start:int32; end:int32; } { get id "range:get" execution snapshot; } + queryable value content id "content" : record { text:string; tags:list; metadata:optional; } { get id "content:get" execution snapshot; } +}`, + { source }, +); +assert.ok(result.ok && result.resource.kind === "interface", JSON.stringify(result.diagnostics)); +const iface = result.resource.revision; +const declaration: QueryDeclaration = { + id: "rows", + displayName: "Rows", + root: { kind: "workspace-interface", interface: iface.revisionId }, + document: "rows.graphql", + operation: "Rows", + fragments: [], + views: [], + allowances: [], + budgets: { ...defaultQueryBudgets }, + watch: false, +}; +const compile = (selection: string) => + compileQuery( + declaration, + [iface], + async () => + `query Rows {root {objects(first:10, ${selection}) {entries {node {position:range {begin:start end} content {text tags metadata {score}}}}}}}`, + ); +test("record/list query values preserve nested projections and leaf predicates", async () => { + const checked = await compile( + "where:{range:{start:{gte:4}},content:{metadata:{score:{gt:2}}}}, orderBy:{range:{start:ASC}}", + ); + const extent = checked.selection[0]!.selection[0]!; + assert.equal(extent.predicate?.kind, "and"); + if (extent.predicate?.kind !== "and") return; + const expressions = extent.predicate.children.map((p) => (p.kind === "compare" ? p.expression : undefined)); + assert.deepEqual( + expressions.map((e) => e?.leaf), + [ + { kind: "field", interfaceRevisionId: "highlight@1", memberId: "range", valuePath: ["start"] }, + { kind: "field", interfaceRevisionId: "highlight@1", memberId: "content", valuePath: ["metadata", "score"] }, + ], + ); + assert.deepEqual(expressions[1]?.type, { kind: "optional", value: { kind: "scalar", name: "int32" } }); + const wire = querySelectionToWire(extent); + assert.equal(wire.predicate?.predicate.case, "and"); + if (wire.predicate?.predicate.case === "and") { + const child = wire.predicate.predicate.value.children[0]!; + assert.equal(child.predicate.case, "compare"); + if (child.predicate.case === "compare") { + const operand = child.predicate.value.operand?.operand; + assert.ok(operand?.case === "expression" && operand.value.leaf.case === "field"); + assert.deepEqual(operand.value.leaf.value.valuePath, ["start"]); + } + } + const raw = JSON.stringify(checked.output); + const node = extent.selection[0]!.selection[0]!; + assert.equal(node.selection.filter((field) => field.key === "_camino").length, 1); + const assertValuesOnly = (fields: typeof node.selection): void => { + for (const field of fields) { + assert.notEqual(field.name, "_camino", "Structured values do not acquire graph identity"); + assertValuesOnly(field.selection); + } + }; + assertValuesOnly(node.selection.filter((field) => field.key !== "_camino")); + assert.equal( + checked.selection[0]!.selection.some((field) => field.key === "_camino"), + false, + ); + assert.equal( + extent.selection.some((field) => field.key === "_camino"), + false, + ); + assert.match(raw, /"begin"/); + assert.match(raw, /"tags":\{"kind":"list"/); + assert.equal(checked.effects.filter((e) => e.memberId === "range").length, 1); +}); +test("structured predicates reject whole-record scalar comparisons and implicit list expansion", async () => { + await assert.rejects(compile("where:{range:{eq:4}}"), /not defined/); + await assert.rejects(compile('where:{content:{tags:{eq:"x"}}}'), /not defined/); +}); + +test("explicit budgets can exceed defaults without exceeding wire bounds", () => { + const compile = (rows: string) => + compileCapabilityResourceSource( + `import interface Highlight; + package P id "p" revision "p@1" { + query Rows id "rows" root Highlight document "rows.graphql" operation "Rows" { replica; max rows ${rows}; max result-bytes 8388608; } + }`, + { source, environment: { interfaces: new Map([["Highlight", iface]]), interfaceClosure: [iface] } }, + ); + const result = compile("5000"); + assert.ok(result.ok && result.resource.kind === "package", JSON.stringify(result.diagnostics)); + assert.equal(result.resource.revision.queries![0]!.budgets.rows, 5000); + assert.equal(compile("4294967296").ok, false); +}); + +test("query identity follows public contracts, not source retention commits", async () => { + const document = async () => "query Rows {root {objects(first:10) {entries {node {range {start}}}}}}"; + const before = await compileQuery(declaration, [iface], document); + const retained = { + ...iface, + source: { ...iface.source, repository: "https://other.test/interfaces.git", commit: "b".repeat(40) }, + }; + assert.equal((await compileQuery(declaration, [retained], document)).definitionDigest, before.definitionDigest); + const changed = structuredClone(retained); + changed.members[0]!.operations[0]!.id = "changed:get" as (typeof changed.members)[0]["operations"][0]["id"]; + assert.notEqual((await compileQuery(declaration, [changed], document)).definitionDigest, before.definitionDigest); + assert.notEqual( + (await compileQuery({ ...declaration, budgets: { ...declaration.budgets, rows: 17 } }, [retained], document)) + .definitionDigest, + before.definitionDigest, + ); +}); diff --git a/test/query-workspace.test.ts b/test/query-workspace.test.ts index bdbf8f4..f04df42 100644 --- a/test/query-workspace.test.ts +++ b/test/query-workspace.test.ts @@ -2,6 +2,7 @@ import assert from "node:assert/strict"; import test from "node:test"; import { queryWorkspaceFixture } from "./fixtures/query-workspace.js"; import { linkQueries } from "../src/query/link.js"; +import { compileQuery } from "../src/query/compile.js"; test("query fixture links generic collections, both implementations, and native getter/RPC setter", async () => { const { workspace } = await queryWorkspaceFixture(); @@ -27,3 +28,27 @@ test("query fixture links generic collections, both implementations, and native const totals = workspace.linkedQueries.find((q) => q.id.endsWith(":totals"))!; assert.equal(totals.fields.filter((f) => f.memberId === "rank").length, 2); }); + +test("reference-only workspace queries hash every eligible conformance, including empty atoms", async () => { + const { workspace } = await queryWorkspaceFixture(); + const pkg = workspace.packageImports.find((pkg) => pkg.queries?.some((q) => q.id === "upcoming"))!; + const query = pkg.queries!.find((q) => q.id === "upcoming")!; + const contract = workspace.interfaceImports.find((iface) => iface.displayName === "TaskFacts")!; + query.root = { kind: "workspace-interface", interface: contract.revisionId }; + query.fragments = []; + const checked = await compileQuery( + query, + workspace.interfaceImports, + async () => `query ${query.operation} {root {objects(first: 10) {entries {node {_camino {ref}}}}}}`, + ); + pkg.checkedQueries = pkg.checkedQueries!.map((q) => (q.declaration.id === query.id ? checked : q)); + const linked = linkQueries(workspace).find((q) => q.id.endsWith(":upcoming"))!; + assert.equal(linked.fields.length, 0); + assert.equal(linked.rootAtoms.length, 2); + workspace.conformances = workspace.conformances.filter( + (c) => !(c.atomId === linked.rootAtoms[0] && c.interfaceRevisionId === contract.revisionId), + ); + const changed = linkQueries(workspace).find((q) => q.id === linked.id)!; + assert.equal(changed.rootAtoms.length, 1); + assert.notEqual(linked.bindingDigest, changed.bindingDigest); +}); diff --git a/test/qx-source.test.ts b/test/qx-source.test.ts index 9b9d2db..8a81ce2 100644 --- a/test/qx-source.test.ts +++ b/test/qx-source.test.ts @@ -1,3 +1,4 @@ +import { qxSourceManifest } from "../src/capability-language/source-loader.js"; import assert from "node:assert/strict"; import test from "node:test"; import { mkdtemp, writeFile, readFile, rm, symlink } from "node:fs/promises"; @@ -119,3 +120,21 @@ test("lowering diagnostics map to the imported file", async () => { assert.equal(position.fileName, "bad.qx"); assert.equal(position.line, 2); }); + +test("source manifests include resource imports in deduplicated workspace fragments", async (t) => { + const root = await mkdtemp(path.join(os.tmpdir(), "qx-source-manifest-")); + t.after(() => rm(root, { recursive: true, force: true })); + await writeFile( + path.join(root, "workspace.qx"), + addWorkspaceImport(addWorkspaceImport(workspace, "left.qx"), "right.qx"), + ); + await writeFile(path.join(root, "left.qx"), 'fragment {import "rules.qx";}'); + await writeFile(path.join(root, "right.qx"), 'fragment {import "rules.qx";}'); + await writeFile(path.join(root, "rules.qx"), "fragment {import interface Person; import package Runtime;}"); + const manifest = await qxSourceManifest(root); + assert.deepEqual(manifest.imports, [ + { kind: "interface", binding: "Person" }, + { kind: "package", binding: "Runtime" }, + ]); + assert.deepEqual([...manifest.sourceFiles].sort(), ["left.qx", "right.qx", "rules.qx", "workspace.qx"]); +}); diff --git a/test/react-fields.test.ts b/test/react-fields.test.ts index 835c3f8..767f733 100644 --- a/test/react-fields.test.ts +++ b/test/react-fields.test.ts @@ -33,7 +33,7 @@ test("React bindings preserve read-only, writable and nested reference contracts await compileQuery( pkg.resource.revision.queries![0]!, [iface.resource.revision], - async () => "query Editor {root {title @live summary @live plain: title}}", + async () => "query Editor {root {title summary plain: title}}", ), ]; const schema = { @@ -75,15 +75,14 @@ test("React bindings preserve read-only, writable and nested reference contracts ); await fs.writeFile( path.join(root, "consumer.ts"), - `import {useLiveField, tryConform, type ReadableField, type WritableField} from "@quixos/web-studio-react-runtime"; + `import {useLiveField, tryConform, detachField, type ReadableField, type WritableField} from "@quixos/web-studio-react-runtime"; import {reactInterfaces} from "./react-props.gen.js"; import type {ReactResults, QueryResults} from "./react-props.gen.js"; declare const query: QueryResults["Editor"]; -useLiveField(query.root.title).set("new"); -// @ts-expect-error live query fields retain exact setter types -useLiveField(query.root.title).set(123); -// @ts-expect-error readonly query fields do not acquire a setter -useLiveField(query.root.summary).set("no"); +const queryTitle: string = query.root.title; +const querySummary: string = query.root.summary; +// @ts-expect-error query values are not field mutation handles +query.root.title.set("new"); const plain: string = query.root.plain; async function lookup() { const view = await tryConform("object", reactInterfaces.Fields); diff --git a/test/reader-fixture.ts b/test/reader-fixture.ts new file mode 100644 index 0000000..7664e55 --- /dev/null +++ b/test/reader-fixture.ts @@ -0,0 +1,44 @@ +import assert from "node:assert/strict"; +import { readFile } from "node:fs/promises"; +import { resolve } from "node:path"; +import { + compileCapabilityResourceSource, + compileCapabilitySource, + formatQx, +} from "../src/capability-language/index.js"; +import type { InterfaceRevision } from "../src/capability-model/types.js"; +import { compileQuery } from "../src/query/compile.js"; +const directory = resolve("test/fixtures/local-first-reader"); +export const readReaderFixture = (name: string) => readFile(resolve(directory, name), "utf8"); +const source = { repository: "https://example.test/reader.git", commit: "a".repeat(40) }; +export const loadReaderWorkspace = async (transform = (name: string, text: string) => text) => { + const interfaces = new Map(); + for (const name of ["DocumentData", "HighlightData", "CommentData", "DocumentActions", "DocumentFactory"]) { + const text = transform(`${name}.qx`, await readReaderFixture(`${name}.qx`)); + const result = compileCapabilityResourceSource(formatQx(text), { source }); + assert.ok(result.ok && result.resource.kind === "interface", `${name}: ${JSON.stringify(result.diagnostics)}`); + interfaces.set(name, result.resource.revision); + } + const environment = { interfaces, interfaceClosure: [...interfaces.values()] }; + const pkg = compileCapabilityResourceSource( + formatQx(transform("ReaderRuntime.qx", await readReaderFixture("ReaderRuntime.qx"))), + { + source, + environment, + }, + ); + assert.ok(pkg.ok && pkg.resource.kind === "package", JSON.stringify(pkg.diagnostics)); + const revision = pkg.resource.revision; + revision.checkedQueries = await Promise.all( + revision.queries!.map((query) => + compileQuery(query, [...interfaces.values()], async (name) => transform(name, await readReaderFixture(name))), + ), + ); + const result = compileCapabilitySource(formatQx(await readReaderFixture("workspace.qx")), "workspace.qx", { + ...environment, + packages: new Map([["ReaderRuntime", revision]]), + packageClosure: [revision], + }); + assert.ok(result.ok, JSON.stringify(result.diagnostics)); + return result.workspace; +}; diff --git a/test/recurring-fixture.ts b/test/recurring-fixture.ts new file mode 100644 index 0000000..2f2c04e --- /dev/null +++ b/test/recurring-fixture.ts @@ -0,0 +1,19 @@ +import assert from "node:assert/strict"; +import { readFile } from "node:fs/promises"; +import { compileCapabilityResourceSource, compileCapabilitySource } from "../src/capability-language/parser.js"; +import { compileExecutionPlan } from "../src/execution-world/plan.js"; + +export async function recurringTaskPlan() { + const read = (name: string) => readFile(`test/fixtures/recurring-task/${name}.qx`, "utf8"); + const source = { repository: "https://example.test/recurring.git", commit: "a".repeat(40) }; + const task = compileCapabilityResourceSource(await read("Task"), { source }); + assert.ok(task.ok && task.resource.kind === "interface", JSON.stringify(task.diagnostics)); + const runtime = compileCapabilityResourceSource(await read("TaskRuntime"), { source }); + assert.ok(runtime.ok && runtime.resource.kind === "package", JSON.stringify(runtime.diagnostics)); + const result = compileCapabilitySource(await read("workspace"), "workspace.qx", { + interfaces: new Map([["Task", task.resource.revision]]), + packages: new Map([["TaskRuntime", runtime.resource.revision]]), + }); + assert.ok(result.ok, JSON.stringify(result.diagnostics)); + return compileExecutionPlan(result.workspace); +} diff --git a/test/reference-dependencies.test.ts b/test/reference-dependencies.test.ts new file mode 100644 index 0000000..dd4c5c9 --- /dev/null +++ b/test/reference-dependencies.test.ts @@ -0,0 +1,137 @@ +import assert from "node:assert/strict"; +import { test } from "node:test"; +import { compileCapabilityResourceSource, compileCapabilitySource } from "../src/capability-language/parser.js"; +import { compileExecutionPlan, compileKernelProgram } from "../src/execution-world/index.js"; +import { generatePortableTypeScriptBindings } from "../src/bindings/portable.js"; +import { formatQx } from "../src/capability-language/source.js"; +const source = { repository: "https://example.test/reference.git", commit: "a".repeat(40) }; +const iface = `interface Data id "data" revision "data@1" { + value content id "content" : string { get id "content:get" execution snapshot; } + operation peek id "peek" : interface-ref -> string { call id "peek:call" execution snapshot; } +}`; +const pkg = `symbol JavaScript id "target:org.quixos.execution.javascript@1"; +external atom Item id "item"; +import interface Data; +package Runtime id "runtime" revision "runtime@1" { + operation peek id "runtime:peek" : interface-ref -> string mode call execution snapshot targets [server] + receiver atom Item requires { interface peers id "peers" : Data using [content.get] from input; }; +}`; +const workspace = `workspace Test id "workspace:test" revision "workspace:test@1" commit "${"a".repeat(40)}" { + import interface Data; import package Runtime; atom Item id "item"; + conform Item as Data id "item:data" { + private state Content id "content" on Item : string policy register default "hello"; + bind content.get to state Content.read; + bind peek.call to package Runtime.peek with { peers to interface Data from input; }; + } +}`; +function compile(packageSource = pkg, workspaceSource = workspace, interfaceSource = iface) { + const contract = compileCapabilityResourceSource(interfaceSource, { source }); + assert.ok(contract.ok && contract.resource.kind === "interface", JSON.stringify(contract.diagnostics)); + const interfaces = new Map([["Data", contract.resource.revision]]); + const runtime = compileCapabilityResourceSource(packageSource, { source, environment: { interfaces } }); + if (!runtime.ok) return runtime; + assert.equal(runtime.resource.kind, "package"); + if (runtime.resource.kind !== "package") throw Error("Expected package"); + return compileCapabilitySource(workspaceSource, "workspace.qx", { + interfaces, + packages: new Map([["Runtime", runtime.resource.revision]]), + }); +} +test("reference-selected dependencies round trip and do not require a recursive receiver conformance", () => { + const result = compile(formatQx(pkg), formatQx(workspace)); + assert.ok(result.ok, JSON.stringify(result.diagnostics)); + const plan = compileExecutionPlan(result.workspace); + const kernel = compileKernelProgram(plan) as { + operations: { operation: string; binding: { ports: { receiver?: string }[] } }[]; + }; + assert.equal(kernel.operations.find((op) => op.operation === "peek:call")!.binding.ports[0]!.receiver, "input"); + const peek = plan.definition.operations.find((op) => op.operationId === "peek:call")!; + assert.ok( + peek.dependencies.some( + (dependency) => dependency.kind === "operation" && dependency.operation.includes("content:get"), + ), + ); + const generated = generatePortableTypeScriptBindings(plan.definition.bindingSchema, "runtime@1", { + target: "server", + }); + assert.ok(generated.includes('ManagedReference<"interface", "data@1">')); + assert.ok(generated.includes('"receiver"')); +}); +test("both sides explicitly grant reference selection and remote execution is excluded", () => { + assert.equal(compile(pkg, workspace.replace("Data from input", "Data")).ok, false); + assert.equal(compile(pkg.replace(" from input", ""), workspace).ok, false); + assert.equal(compile(pkg.replace("execution snapshot targets [server]", "execution remote"), workspace).ok, false); + assert.equal(compile(pkg.replace("using [content.get]", "")).ok, false); +}); +test("closed generic specialization preserves reference-selected interface ports", () => { + const result = compile( + pkg.replace("operation peek id", "operation peek id"), + workspace.replace("Runtime.peek with", "Runtime.peek with"), + ); + assert.ok(result.ok, JSON.stringify(result.diagnostics)); + const plan = compileExecutionPlan(result.workspace); + const entry = plan.definition.bindingSchema.packages[0]!.exports[0]!; + assert.ok( + entry.dependencyPorts.some( + (port) => port.requirement.kind === "interface" && port.requirement.receiver === "input", + ), + ); + assert.ok( + generatePortableTypeScriptBindings(plan.definition.bindingSchema, "runtime@1", { target: "server" }).includes( + '"receiver"', + ), + ); +}); + +test("portable input-selected reads use installed support beyond the public minimum", () => { + const portable = pkg.replaceAll("targets [server]", "targets [JavaScript, server]"); + const optional = compile(portable); + assert.ok(optional.ok, JSON.stringify(optional.diagnostics)); + const plan = compileExecutionPlan(optional.workspace); + assert.deepEqual(plan.definition.operations.find((op) => op.operationId === "peek:call")!.availableTargets, [ + "server", + "target:org.quixos.execution.javascript@1", + ]); + const result = compile(portable, workspace, iface); + assert.ok(result.ok, JSON.stringify(result.diagnostics)); +}); + +test("input-selected transactions compose only within a compatible transaction contract", () => { + const txInterface = iface + .replace('get id "content:get" execution snapshot', 'get id "content:get" execution transaction') + .replace('call id "peek:call" execution snapshot', 'call id "peek:call" execution transaction'); + const txPackage = pkg.replace("execution snapshot targets [server]", "execution transaction targets [server]"); + // Use an operation for the selected write; value getters themselves must remain snapshots. + const contract = txInterface.replace( + 'value content id "content" : string { get id "content:get" execution transaction; }', + 'operation update id "content" : unit -> string { call id "content:get" execution transaction; }', + ); + const implementation = txPackage.replace("using [content.get]", "using [update.call]"); + const model = workspace.replace( + "bind content.get to state Content.read;", + "bind update.call to package Runtime.update with { data to state Content; };", + ); + const sourcePackage = implementation.replace( + /\n}$/, + '\n operation update id "runtime:update" : unit -> string mode call execution transaction targets [server] receiver atom Item requires {state data id "data" : string [read,write];};\n}', + ); + const result = compile(sourcePackage, model, contract); + assert.ok(result.ok, JSON.stringify(result.diagnostics)); + const plan = compileExecutionPlan(result.workspace); + const generated = generatePortableTypeScriptBindings(plan.definition.bindingSchema, "runtime@1", { + target: "server", + }); + assert.ok(generated.includes('"receiver"')); + assert.ok(generated.includes('"update.call"')); + assert.equal( + compile( + sourcePackage.replace( + 'operation peek id "runtime:peek" : interface-ref -> string mode call execution transaction', + 'operation peek id "runtime:peek" : interface-ref -> string mode call execution snapshot', + ), + model, + contract, + ).ok, + false, + ); +}); diff --git a/test/reflection.test.ts b/test/reflection.test.ts new file mode 100644 index 0000000..2a24d2a --- /dev/null +++ b/test/reflection.test.ts @@ -0,0 +1,48 @@ +import assert from "node:assert/strict"; +import test from "node:test"; +import { compileCapabilityResourceSource, compileCapabilitySource } from "../src/capability-language/parser.js"; +import { generateTypeScriptBindings } from "../src/bindings/index.js"; +const source = { repository: "https://example.test/meta.git", commit: "a".repeat(40) }; + +test("reflection authority is an explicit checked workspace binding, including generic exports", () => { + const iface = compileCapabilityResourceSource( + 'interface Inspect id "inspect" revision "inspect@1" {operation run id "run" : unit -> unit {call id "call";}}', + { source }, + ); + assert.ok(iface.ok && iface.resource.kind === "interface"); + if (!iface.ok || iface.resource.kind !== "interface") throw Error("interface"); + const interfaceRevision = iface.resource.revision; + for (const generic of [false, true]) { + const pkg = compileCapabilityResourceSource( + `package Inspector id "inspector" revision "inspector@1" {operation run${generic ? "" : ""} id "run" : unit -> unit mode call receiver any requires {reflection meta id "meta" [atoms, interfaces];};}`, + { source }, + ); + assert.ok(pkg.ok && pkg.resource.kind === "package", JSON.stringify(pkg.diagnostics)); + if (!pkg.ok || pkg.resource.kind !== "package") throw Error("package"); + const packageRevision = pkg.resource.revision; + const compile = (selectors: string) => + compileCapabilitySource( + `workspace W id "ws" revision "ws@1" commit "${source.commit}" { + import interface Inspect;import package Inspector;atom Thing id "thing"; + conform Thing as Inspect id "inspection" {bind run.call to package Inspector.run${generic ? "" : ""} with {meta to workspace reflection [${selectors}];};} + }`, + "workspace.qx", + { interfaces: new Map([["Inspect", interfaceRevision]]), packages: new Map([["Inspector", packageRevision]]) }, + ); + const result = compile("atoms, interfaces"); + assert.ok(result.ok, JSON.stringify(result.diagnostics)); + assert.equal(compile("atoms").ok, false, "binding cannot narrow or expand the declared contract silently"); + assert.equal(compile("atoms, interfaces, queries").ok, false); + if (!result.ok) throw Error("workspace"); + const generated = generateTypeScriptBindings( + { + format: "quixos-bindings", + version: 1, + interfaces: result.workspace.interfaceImports, + packages: result.workspace.packageImports, + }, + "inspector@1", + ); + assert.match(generated, /QxReflectionPort/); + } +}); diff --git a/test/remote-bindings.test.ts b/test/remote-bindings.test.ts new file mode 100644 index 0000000..aad99fd --- /dev/null +++ b/test/remote-bindings.test.ts @@ -0,0 +1,29 @@ +import assert from "node:assert/strict"; +import { test } from "node:test"; +import { asyncObjectPlan } from "./async-fixture.js"; +import { generateRemoteTypeScriptBindings, generatePortableTypeScriptBindings } from "../src/bindings/portable.js"; +test("remote adapters expose selected typed calls, lifetimes and explicit transaction identities", async () => { + const plan = await asyncObjectPlan(); + const schema = plan.definition.bindingSchema; + const source = generateRemoteTypeScriptBindings(schema, "async:controller-runtime@1"); + assert.match(source, /readonly signal: AbortSignal/); + assert.match(source, /readonly "summary.set":.*options: ServerMutationOptions/); + assert.match(source, /readonly "summarize.call": \(input: string\) => Promise/); + assert.doesNotMatch(source, /readonly "summary.get"|context\.conform|readonly snapshot:/); + assert.doesNotMatch( + generatePortableTypeScriptBindings(schema, "async:controller-runtime@1", { target: "browser" }), + /readonly "refresh"/, + ); + const entry = schema.packages + .find((p) => p.revisionId === "async:controller-runtime@1")! + .exports.find((entry) => entry.id === "async:controller:refresh")!; + entry.dependencyPorts[0]!.requirement = { + kind: "state", + valueType: { kind: "scalar", name: "string" }, + primitives: ["write"], + }; + assert.throws( + () => generateRemoteTypeScriptBindings(schema, "async:controller-runtime@1"), + /named transaction capability/, + ); +}); diff --git a/test/scaffold-recipes.test.ts b/test/scaffold-recipes.test.ts index ba51e34..5755afc 100644 --- a/test/scaffold-recipes.test.ts +++ b/test/scaffold-recipes.test.ts @@ -7,118 +7,107 @@ import { execFile as callback } from "node:child_process"; import { promisify } from "node:util"; import { scaffoldRecipe } from "../src/capability-language/scaffold-recipes.js"; import { planStructure, applyStructure } from "../src/capability-language/structural-plan.js"; -import { contentDigest } from "../src/capability-model/evolution.js"; -import { sealMigrations } from "../src/capability-language/migration-seal.js"; import { reactPlatformTypes } from "../src/bindings/react-platform.js"; const execFile = promisify(callback); -test("React preset applies its browser build script and shared-platform imports", async (context) => { - const root = await fs.mkdtemp(path.join(os.tmpdir(), "qx-react-recipe-")); +test("execution scaffolds route typed handlers by declared effect and target without legacy lifecycle files", async (context) => { + const root = await fs.mkdtemp(path.join(os.tmpdir(), "qx-execution-recipe-")); context.after(() => fs.rm(root, { recursive: true, force: true })); await execFile("git", ["-C", root, "init"]); - const source = { repository: "https://example.test/react.git", commit: "a".repeat(40) }; - const request = await scaffoldRecipe(root, "package", { - source, - name: "React", - id: "package:react", - revision: "package:react@1", - template: "typescript-react", + const source = { repository: "https://example.test/execution.git", commit: "a".repeat(40) }; + const apply = async ( + command: Parameters[1], + spec: Partial[2]>, + ) => applyStructure(await planStructure(root, await scaffoldRecipe(root, command, { source, ...spec }))); + await apply("package", { + name: "Execution", + id: "package:execution", + revision: "package:execution@1", + template: "typescript-execution", tools: { quixos: source, protocol: source, helpers: source, sdk: source }, }); - await applyStructure(await planStructure(root, request)); - assert.match(await fs.readFile(path.join(root, "src/impl/sourceGet.ts"), "utf8"), /component.js\?browser-source/); - assert.match(await fs.readFile(path.join(root, "flake.nix"), "utf8"), /browserSources = true/); - assert.equal(await fs.readFile(path.join(root, "src/gen/web-studio-react-runtime.d.ts"), "utf8"), reactPlatformTypes); - assert.match(await fs.readFile(path.join(root, "src/browser-assets.d.ts"), "utf8"), /declare module "\*\.css"/); - assert.deepEqual( - JSON.parse(await fs.readFile(path.join(root, "quixos.check.json"), "utf8")).options.react.propsExports, - [], - ); - await assert.rejects(fs.access(path.join(root, "quixos.scaffold.json"))); - assert.equal( - JSON.parse(await fs.readFile(path.join(root, "tsconfig.json"), "utf8")).compilerOptions.jsx, - "react-jsx", - ); + for (const file of ["quixos.migrations.json", "src/migrate.ts", "descriptor.quixos-package.txtpb"]) + await assert.rejects(fs.access(path.join(root, file))); + await fs.appendFile(path.join(root, "src/server.ts"), "\n// Preserve authored code\n"); + const add = (name: string, execution: string) => + apply("function", { + name, + id: "export:" + name, + declaration: `function ${name} id "export:${name}" : unit -> unit execution ${execution};`, + }); + await add("both", "transaction targets [JavaScript, server]"); + await add("serverOnly", "snapshot targets [server]"); + await add("asyncWork", "remote"); + const read = (file: string) => fs.readFile(path.join(root, file), "utf8"); + assert.match(await read("src/browser.ts"), /"both":/); + assert.doesNotMatch(await read("src/browser.ts"), /serverOnly|asyncWork/); + assert.match(await read("src/server.ts"), /"serverOnly":/); + assert.match(await read("src/server.ts"), /Preserve authored code/); + assert.match(await read("src/remote.ts"), /"asyncWork":/); + assert.match(await read("src/impl/asyncWork.ts"), /RemoteImplementation/); + assert.match(await read("src/impl/both.ts"), /PortableImplementation/); + assert.match(await read("flake.nix"), /checkedArtifacts/); + assert.match(await read("flake.nix"), /entries.browser/); + await assert.rejects(add("both", "transaction targets [JavaScript, server]"), /unique/); + await assert.rejects(add("invalid", "snapshot targets [remote]"), /TypeScript scaffold supports/); + await assert.rejects(apply("function", { name: "missing", id: "export:missing" }), /explicit execution/); + await assert.rejects(apply("migration", { name: "migration", id: "export:migration" }), /retired/); }); -test("imperative scaffolds preserve authored wiring; migration sealing is explicit and separate", async (context) => { - const root = await fs.mkdtemp(path.join(os.tmpdir(), "qx-recipes-")); +test("query scaffold preserves placement and inserts its named GraphQL document atomically", async (context) => { + const root = await fs.mkdtemp(path.join(os.tmpdir(), "qx-query-recipe-")); context.after(() => fs.rm(root, { recursive: true, force: true })); await execFile("git", ["-C", root, "init"]); - await fs.writeFile(path.join(root, ".gitignore"), ".quixos/\n"); - const source = { repository: "https://example.test/chess.git", commit: "a".repeat(40) }; - const base = { source, directory: "packages/Chess" }; - const apply = async (command: Parameters[1], input: Parameters[2]) => - applyStructure(await planStructure(root, await scaffoldRecipe(root, command, input))); - await apply("package", { - ...base, - name: "Chess", - id: "package:chess", - revision: "package:chess@1", - tools: { quixos: source, protocol: source, helpers: source, sdk: source }, - }); - await apply("function", { ...base, name: "play", id: "export:play" }); - const filename = path.join(root, base.directory, "src/impl/play.ts"); - const edited = - 'import type {Implementation} from "../gen/qx.js";\nexport const handler: Implementation["play"] = async () => null;\n'; - await fs.writeFile(filename, edited); - const old = { version: 1 }, - next = { version: 2 }, - from = contentDigest(old), - to = contentDigest(next); - await apply("migration", { - ...base, - name: "upgrade", - id: "export:upgrade", - migration: { - id: "upgrade-v2", - scopeId: "board", - from, - to, - predecessors: [], - ports: [], - contracts: { [from]: old, [to]: next }, - }, - }); - const migrationFile = path.join(root, base.directory, "src/migrations/upgrade.ts"); - await fs.appendFile(migrationFile, "\n// authored migration change\n"); - await sealMigrations(path.join(root, base.directory)); - assert.equal(await fs.readFile(filename, "utf8"), edited); - const catalog = JSON.parse(await fs.readFile(path.join(root, base.directory, "quixos.migrations.json"), "utf8")); - assert.equal(catalog.migrations[0].implementation.digest, contentDigest(await fs.readFile(migrationFile, "utf8"))); - assert.match(await fs.readFile(path.join(root, base.directory, "src/migrate.ts"), "utf8"), /export:upgrade/); - await assert.rejects(() => apply("function", { ...base, name: "play", id: "export:play" }), /unique/); - const declarations = path.join(root, base.directory, "package.qx"); + const source = { repository: "https://example.test/query.git", commit: "a".repeat(40) }; await fs.writeFile( - declarations, - (await fs.readFile(declarations, "utf8")).replace( - /}\s*$/, - ' function authored id "export:authored" : unit -> unit;\n}\n', - ), + path.join(root, "package.qx"), + 'import interface Task;\npackage Tasks id "tasks" revision "tasks@1" {}\n', ); - const server = path.join(root, base.directory, "src/server.ts"); - await fs.appendFile(server, "\n// authored comment must survive\n"); - await apply("function", { ...base, name: "another", id: "export:another" }); - assert.match(await fs.readFile(server, "utf8"), /authored comment must survive/); - await assert.rejects(() => apply("refresh", base), /removed/); - await assert.rejects(fs.access(path.join(root, base.directory, "src/impl/authored.ts"))); - assert.equal(await fs.readFile(filename, "utf8"), edited); - await planStructure(root, { - kind: "package", + await fs.writeFile(path.join(root, "quixos.check.json"), JSON.stringify({ backend: "execution" })); + const request = (name: string, placement: string) => ({ source, - resourceRoot: base.directory, - validation: "syntax", - files: [ - { - file: `${base.directory}/package.qx`, - edits: [ - { - operation: "replace", - target: { kind: "packageResourceDecl", id: "package:chess" }, - source: 'package Other id "package:other" revision "package:other@1" {}', - }, - ], - }, - ], + name, + id: `tasks:${name}`, + declaration: `query ${name} id "tasks:${name}" root Task document "queries/${name}.graphql" operation "${name}" { ${placement} max rows 100; }`, + queryDocument: `query ${name} { root { title } }\n`, }); + for (const [name, placement] of [ + ["Inputs", "replica;"], + ["Screen", "local from Inputs;"], + ["Confirmed", ""], + ]) { + const spec = request(name, placement); + const recipe = await scaffoldRecipe(root, "query", spec); + const planned = await planStructure(root, recipe); + await assert.rejects(fs.access(path.join(root, `queries/${name}.graphql`)), "Preview must not write files"); + await applyStructure(planned); + assert.equal(await fs.readFile(path.join(root, `queries/${name}.graphql`), "utf8"), spec.queryDocument); + assert.ok((await fs.readFile(path.join(root, "package.qx"), "utf8")).includes(placement)); + await assert.rejects(scaffoldRecipe(root, "query", spec), /unique/); + } + const bad = request("Bad", "replica;"); + await assert.rejects( + scaffoldRecipe(root, "query", { + ...bad, + declaration: bad.declaration.replace("queries/Bad.graphql", "../Bad.graphql"), + }), + /contained/, + ); + await assert.rejects( + scaffoldRecipe(root, "query", { ...bad, queryDocument: "query Other { root { title } }" }), + /declared named query/, + ); + await assert.rejects( + scaffoldRecipe(root, "query", { ...bad, queryDocument: "mutation Bad { title }" }), + /declared named query/, + ); + await assert.rejects( + scaffoldRecipe(root, "query", { ...bad, declaration: bad.declaration + ' function x id "x" : unit -> unit;' }), + /one QX query/, + ); + await fs.mkdir(path.join(root, "queries"), { recursive: true }); + await fs.writeFile(path.join(root, "queries/Bad.graphql"), "# authored content\n"); + const before = await fs.readFile(path.join(root, "package.qx"), "utf8"); + await assert.rejects(planStructure(root, await scaffoldRecipe(root, "query", bad)), /existing file/); + assert.equal(await fs.readFile(path.join(root, "package.qx"), "utf8"), before); }); diff --git a/test/structural-edits.test.ts b/test/structural-edits.test.ts index a8e582d..42ced41 100644 --- a/test/structural-edits.test.ts +++ b/test/structural-edits.test.ts @@ -82,7 +82,7 @@ test("dependency scaffolding validates exact sources and preserves unrelated loc }); test("conformance enrollment, major edits, imports, and private attachment removal preserve valid structure", () => { - const source = `fragment { conform Game as Playable { private state Board id "slot:board" on Game : string policy optimistic-register; } }`; + const source = `fragment { conform Game as Playable { private state Board id "slot:board" on Game : string policy register; } }`; const selector = { kind: "conformanceDecl", names: ["Game", "Playable"] }; const enrolled = editStructure(source, { operation: "conformance-id", target: selector, id: "conformance:playable" }); assert.ok(enrolled.includes('as Playable id "conformance:playable"')); @@ -101,3 +101,17 @@ test("conformance enrollment, major edits, imports, and private attachment remov const imported = editStructure(removed, { operation: "import", kind: "interface", name: "Playable" }); assert.equal(editStructure(imported, { operation: "import", kind: "interface", name: "Playable" }), imported); }); + +test("enrolling a generic conformance or rule places its identity after target arguments", () => { + for (const source of [ + "fragment { conform Game as View {} }", + "fragment { conform Source as View {} }", + ]) { + const enrolled = editStructure(source, { + operation: "conformance-id", + target: { kind: "conformanceDecl" }, + id: "rule:view", + }); + assert.match(enrolled, /as View<[^>]+> id "rule:view"/); + } +}); diff --git a/test/structural-plan.test.ts b/test/structural-plan.test.ts index d57c6a5..8ecaedc 100644 --- a/test/structural-plan.test.ts +++ b/test/structural-plan.test.ts @@ -61,4 +61,28 @@ test("structural plans validate the graph, journal originals, and reject stale e await applyStructure(draft); assert.match(await fs.readFile(path.join(root, "workspace.qx"), "utf8"), /import interface NotImplementedYet/); await assert.rejects(() => planStructure(root, { ...provisional, validation: "resource-graph" })); + const plugin = ".yarn/plugins/yarn-plugin-nixify.cjs"; + await applyStructure( + await planStructure(root, { + kind: "workspace", + validation: "syntax", + files: [ + { file: plugin, create: "module.exports = {};\n" }, + { file: "yarn.lock", create: "__metadata:\n version: 8\n" }, + { file: "flake.lock", create: '{"version":7}\n' }, + ], + }), + ); + assert.equal(await fs.readFile(path.join(root, plugin), "utf8"), "module.exports = {};\n"); + for (const file of [".yarn/../escape.cjs", ".yarn/cache/entry.cjs", ".git/hooks/hook.cjs"]) { + await assert.rejects( + () => + planStructure(root, { + kind: "workspace", + validation: "syntax", + files: [{ file, create: "" }], + }), + /Unsafe scaffold path/, + ); + } }); diff --git a/test/tree-fixture.ts b/test/tree-fixture.ts new file mode 100644 index 0000000..11d30c4 --- /dev/null +++ b/test/tree-fixture.ts @@ -0,0 +1,23 @@ +import assert from "node:assert/strict"; +import { readFile } from "node:fs/promises"; +import { compileCapabilityResourceSource, compileCapabilitySource } from "../src/capability-language/parser.js"; +import { compileExecutionPlan } from "../src/execution-world/plan.js"; +import type { InterfaceRevision } from "../src/capability-model/types.js"; +export async function treePlan() { + const read = (name: string) => readFile(`test/fixtures/tree/${name}.qx`, "utf8"); + const source = { repository: "https://example.test/tree.git", commit: "a".repeat(40) }; + const interfaces = new Map(); + for (const name of ["TreeData"]) { + const result = compileCapabilityResourceSource(await read(name), { source, environment: { interfaces } }); + assert.ok(result.ok && result.resource.kind === "interface", JSON.stringify(result.diagnostics)); + interfaces.set(name, result.resource.revision); + } + const pkg = compileCapabilityResourceSource(await read("TreeRuntime"), { source, environment: { interfaces } }); + assert.ok(pkg.ok && pkg.resource.kind === "package", JSON.stringify(pkg.diagnostics)); + const result = compileCapabilitySource(await read("workspace"), "workspace.qx", { + interfaces, + packages: new Map([["TreeRuntime", pkg.resource.revision]]), + }); + assert.ok(result.ok, JSON.stringify(result.diagnostics)); + return compileExecutionPlan(result.workspace); +} diff --git a/test/variants.test.ts b/test/variants.test.ts new file mode 100644 index 0000000..c1873b4 --- /dev/null +++ b/test/variants.test.ts @@ -0,0 +1,82 @@ +import { readFileSync } from "node:fs"; +import assert from "node:assert/strict"; +import { test } from "node:test"; +import { compileCapabilityResourceSource } from "../src/capability-language/index.js"; +import { compileQuery } from "../src/query/compile.js"; +import { defaultQueryBudgets } from "../src/query/types.js"; +import { portableValueSchema, tsType } from "../src/bindings/portable.js"; +import { isStorableType, TypeSubstitution } from "../src/capability-model/index.js"; + +const source = { repository: "https://example.test/variants.git", commit: "a".repeat(40) }; +const declaration = readFileSync("test/fixtures/canvas-content.qx", "utf8"); +const result = compileCapabilityResourceSource(declaration, { source }); +assert.ok(result.ok, JSON.stringify(result.diagnostics)); +if (result.resource.kind !== "interface") throw Error("Expected interface"); +const iface = result.resource.revision; +const entries = iface.members[0]; +if (entries.kind !== "value") throw Error("Expected value"); + +test("native variant aliases lower into nominal cases with typed payloads", () => { + assert.equal(entries.valueType.kind, "list"); + assert.ok(isStorableType(entries.valueType)); + const schema = portableValueSchema(entries.valueType) as any; + assert.equal(schema.value.id, "canvas:entry@1"); + assert.equal(schema.value.cases.stroke.id, "canvas:stroke"); + assert.match(tsType(entries.valueType), /kind: "stroke"/); + assert.match(tsType(entries.valueType), /ReadonlyArray<.*"x": number/); + const substitution = new TypeSubstitution({ + arguments: new Map([["T", { kind: "value", type: { kind: "scalar", name: "string" } }]]), + applyInterface: () => { + throw Error("unused"); + }, + implementsInterface: () => false, + }); + assert.deepEqual( + substitution.value({ + kind: "variant", + id: "box", + cases: { some: { id: "box:some", value: { kind: "parameter", parameterId: "T" } } }, + }), + { kind: "variant", id: "box", cases: { some: { id: "box:some", value: { kind: "scalar", name: "string" } } } }, + ); +}); +test("variant query scalars keep their checked payload type and reject branch selection", async () => { + const query = { + id: "canvas-query", + displayName: "Canvas", + root: { kind: "object" as const, interface: iface.revisionId }, + document: "canvas.graphql", + operation: "Canvas", + fragments: [], + views: [], + allowances: [], + budgets: { ...defaultQueryBudgets }, + watch: true, + }; + const checked = await compileQuery(query, [iface], async () => `query Canvas { root { entries } }`); + assert.equal(checked.output.kind, "record"); + if (checked.output.kind !== "record") return; + const root = checked.output.fields.root; + assert.equal(root.kind, "record"); + if (root.kind !== "record") return; + assert.deepEqual(root.fields.entries, entries.valueType); + await assert.rejects(compileQuery(query, [iface], async () => `query Canvas { root { entries { kind value } } }`)); +}); +test("unknown and duplicate cases fail; reference payloads remain nonstorable", () => { + const duplicate = compileCapabilityResourceSource(declaration.replace('"canvas:image"', '"canvas:stroke"'), { + source, + }); + assert.equal(duplicate.ok, false); + assert.ok( + !isStorableType({ + kind: "variant", + id: "transport", + cases: { + ref: { + id: "ref", + value: { kind: "object-ref", expectation: { kind: "interface", interfaceRevisionId: iface.revisionId } }, + }, + }, + }), + ); +}); diff --git a/yarn.lock b/yarn.lock index fb471a2..5ebe829 100644 --- a/yarn.lock +++ b/yarn.lock @@ -89,6 +89,7 @@ __metadata: typescript: "npm:^7.0.2" bin: quixos-capability-compile: dist/src/capability-language/cli.js + quixos-codegen-client: dist/src/bindings/portable-client-cli.js quixos-codegen-ts: dist/src/bindings/cli.js quixos-descriptor-check: dist/src/descriptor-check.js quixos-lock-check: dist/src/resource-lock/cli.js