Make local graph execution optional across composed packages

This commit is contained in:
Timothy J. Aveni
2026-09-28 21:05:42 -07:00
parent 1e72713ae2
commit e9c15fe40f
87 changed files with 3703 additions and 3400 deletions
+7 -1
View File
@@ -44,10 +44,15 @@ externalInterfaceDecl
: EXTERNAL INTERFACE identifier REVISION stringLiteral SEMI
;
symbolDecl
: SYMBOL identifier ID stringLiteral SEMI
;
resourcePreamble
: resourceImportDecl
| externalAtomDecl
| externalInterfaceDecl
| symbolDecl
| typeAliasDecl
;
@@ -207,7 +212,7 @@ preparationClause
executionClause
: EXECUTION REMOTE
| EXECUTION (SNAPSHOT | TRANSACTION) TARGETS LBRACK identifierList RBRACK
| EXECUTION (SNAPSHOT | TRANSACTION) (TARGETS LBRACK identifierList RBRACK)?
;
selectedOperations
@@ -571,6 +576,7 @@ REF: 'ref';
FRAGMENT: 'fragment';
IMPORT: 'import';
EXTERNAL: 'external';
SYMBOL: 'symbol';
ATOM: 'atom';
INTERFACE: 'interface';
INTERFACES: 'interfaces';
+1 -1
View File
@@ -2,7 +2,7 @@
import { readFile, writeFile } from "node:fs/promises";
import { generatePortableClientBindings } from "./portable-client.js";
async function main() {
const [plan, output, target = "browser", ...rest] = process.argv.slice(2);
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 }));
+1 -1
View File
@@ -17,7 +17,7 @@ export function generatePortableClientBindings(
unavailable: { key: string; reason: string }[] = [];
const seen = new Set<string>();
for (const operation of plan.definition.operations) {
if (operation.execution.kind === "remote" || !operation.execution.targets.includes(options.target)) {
if (operation.execution.kind === "remote" || !operation.availableTargets.includes(options.target)) {
unavailable.push({ key: operation.key, reason: "OPERATION_NOT_PORTABLE" });
continue;
}
+11 -6
View File
@@ -5,6 +5,7 @@ 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("${", "\\${");
@@ -31,7 +32,10 @@ export async function executionScaffold(
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", `package ${spec.name} id ${q(spec.id)} revision ${q(spec.revision)} {\n}\n`);
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: [] }),
@@ -90,7 +94,7 @@ in camino.assembleCaminoPackage {
pkgs.lib.optional (portable != []) (camino.mkCaminoTypeScriptPackage {
inherit pkgs context dependencies; src = ./.;
entry = "src/server.ts"; entries.browser = "src/browser.ts";
targets = pkgs.lib.unique (pkgs.lib.concatMap (entry: entry.execution.targets) portable);
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 {
@@ -134,15 +138,16 @@ in camino.assembleCaminoPackage {
.filter((token) => !token.trivia && token.start >= execution.start && token.end <= execution.end)
.map((token) => token.text);
const remote = tokens[1] === "remote";
const targets = 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"] : ["browser", "server"]).includes(target))
targets.some((target) => !(remote ? ["remote"] : ["JavaScript", "server"]).includes(target))
)
throw new Error("Execution scaffold supports browser/server portable targets and remote service handlers");
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");
@@ -174,7 +179,7 @@ in camino.assembleCaminoPackage {
`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}.ts`;
const file = prefix + `src/${target === "JavaScript" ? "browser" : target}.ts`;
const before = await fs.readFile(path.join(root, file), "utf8");
files.push({
file,
File diff suppressed because one or more lines are too long
@@ -42,120 +42,121 @@ REF=41
FRAGMENT=42
IMPORT=43
EXTERNAL=44
ATOM=45
INTERFACE=46
INTERFACES=47
PACKAGE=48
VALUE=49
FIELD=50
RELATION=51
OPERATION=52
FUNCTION=53
CONSTRUCTOR=54
CONSTRUCTS=55
INPUT=56
CONFORM=57
AS=58
BIND=59
STATIC=60
TO=61
PRIVATE=62
SHARED=63
STATE=64
EDGE=65
PROJECTION=66
WITH=67
USING=68
ACCEPT=69
VIA=70
MATERIALIZE=71
IF=72
ABSENT=73
ON=74
POLICY=75
DEFAULT=76
SOURCE=77
REPOSITORY=78
COMMIT=79
REVISION=80
SEMANTIC_MAJOR=81
ON_DELETE=82
LIFECYCLE=83
JOURNAL=84
SUBSCRIPTION=85
RETAIN_OTHER=86
KEYED=87
PUBLIC_TRAVERSAL=88
ID=89
DOC=90
MODE=91
EMITS=92
RECEIVER=93
REQUIRES=94
ANY=95
GET=96
SET=97
WATCH=98
START=99
STOP=100
READ=101
WRITE=102
RESOLVE=103
CONNECT=104
DISCONNECT=105
CALL=106
WATCH_START=107
WATCH_STOP=108
SUBSCRIBE=109
UNSUBSCRIBE=110
REGISTER=111
CRDT=112
OPTIONAL_ONE=113
EXACTLY_ONE=114
MANY_UNIQUE=115
MANY=116
ORDERED=117
UNIT=118
WATCH_HANDLE=119
MESSAGE=120
ATOM_REF=121
INTERFACE_REF=122
OPTIONAL=123
LIST=124
RECORD=125
BOOL=126
BYTES=127
DOUBLE=128
INT32=129
INT64=130
STRING=131
UINT32=132
UINT64=133
TRUE=134
FALSE=135
NULL=136
ARROW=137
COLON=138
SEMI=139
COMMA=140
DOT=141
LBRACE=142
RBRACE=143
LBRACK=144
RBRACK=145
LPAREN=146
RPAREN=147
LT=148
GT=149
AMP=150
EQUAL=151
INTEGER=152
JSON_NUMBER=153
IDENTIFIER=154
STRING_LITERAL=155
LINE_COMMENT=156
BLOCK_COMMENT=157
WS=158
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
@@ -200,110 +201,111 @@ WS=158
'fragment'=42
'import'=43
'external'=44
'atom'=45
'interface'=46
'interfaces'=47
'package'=48
'value'=49
'field'=50
'relation'=51
'operation'=52
'function'=53
'constructor'=54
'constructs'=55
'input'=56
'conform'=57
'as'=58
'bind'=59
'static'=60
'to'=61
'private'=62
'shared'=63
'state'=64
'edge'=65
'projection'=66
'with'=67
'using'=68
'accept'=69
'via'=70
'materialize'=71
'if'=72
'absent'=73
'on'=74
'policy'=75
'default'=76
'source'=77
'repository'=78
'commit'=79
'revision'=80
'semantic-major'=81
'on-delete'=82
'lifecycle'=83
'journal'=84
'subscription'=85
'retain-other'=86
'keyed'=87
'public-traversal'=88
'id'=89
'doc'=90
'mode'=91
'emits'=92
'receiver'=93
'requires'=94
'any'=95
'get'=96
'set'=97
'watch'=98
'start'=99
'stop'=100
'read'=101
'write'=102
'resolve'=103
'connect'=104
'disconnect'=105
'call'=106
'watch-start'=107
'watch-stop'=108
'subscribe'=109
'unsubscribe'=110
'register'=111
'crdt'=112
'optional-one'=113
'exactly-one'=114
'many-unique'=115
'many'=116
'ordered'=117
'unit'=118
'watch-handle'=119
'message'=120
'atom-ref'=121
'interface-ref'=122
'optional'=123
'list'=124
'record'=125
'bool'=126
'bytes'=127
'double'=128
'int32'=129
'int64'=130
'string'=131
'uint32'=132
'uint64'=133
'true'=134
'false'=135
'null'=136
'->'=137
':'=138
';'=139
','=140
'.'=141
'{'=142
'}'=143
'['=144
']'=145
'('=146
')'=147
'<'=148
'>'=149
'&'=150
'='=151
'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
File diff suppressed because one or more lines are too long
@@ -42,120 +42,121 @@ REF=41
FRAGMENT=42
IMPORT=43
EXTERNAL=44
ATOM=45
INTERFACE=46
INTERFACES=47
PACKAGE=48
VALUE=49
FIELD=50
RELATION=51
OPERATION=52
FUNCTION=53
CONSTRUCTOR=54
CONSTRUCTS=55
INPUT=56
CONFORM=57
AS=58
BIND=59
STATIC=60
TO=61
PRIVATE=62
SHARED=63
STATE=64
EDGE=65
PROJECTION=66
WITH=67
USING=68
ACCEPT=69
VIA=70
MATERIALIZE=71
IF=72
ABSENT=73
ON=74
POLICY=75
DEFAULT=76
SOURCE=77
REPOSITORY=78
COMMIT=79
REVISION=80
SEMANTIC_MAJOR=81
ON_DELETE=82
LIFECYCLE=83
JOURNAL=84
SUBSCRIPTION=85
RETAIN_OTHER=86
KEYED=87
PUBLIC_TRAVERSAL=88
ID=89
DOC=90
MODE=91
EMITS=92
RECEIVER=93
REQUIRES=94
ANY=95
GET=96
SET=97
WATCH=98
START=99
STOP=100
READ=101
WRITE=102
RESOLVE=103
CONNECT=104
DISCONNECT=105
CALL=106
WATCH_START=107
WATCH_STOP=108
SUBSCRIBE=109
UNSUBSCRIBE=110
REGISTER=111
CRDT=112
OPTIONAL_ONE=113
EXACTLY_ONE=114
MANY_UNIQUE=115
MANY=116
ORDERED=117
UNIT=118
WATCH_HANDLE=119
MESSAGE=120
ATOM_REF=121
INTERFACE_REF=122
OPTIONAL=123
LIST=124
RECORD=125
BOOL=126
BYTES=127
DOUBLE=128
INT32=129
INT64=130
STRING=131
UINT32=132
UINT64=133
TRUE=134
FALSE=135
NULL=136
ARROW=137
COLON=138
SEMI=139
COMMA=140
DOT=141
LBRACE=142
RBRACE=143
LBRACK=144
RBRACK=145
LPAREN=146
RPAREN=147
LT=148
GT=149
AMP=150
EQUAL=151
INTEGER=152
JSON_NUMBER=153
IDENTIFIER=154
STRING_LITERAL=155
LINE_COMMENT=156
BLOCK_COMMENT=157
WS=158
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
@@ -200,110 +201,111 @@ WS=158
'fragment'=42
'import'=43
'external'=44
'atom'=45
'interface'=46
'interfaces'=47
'package'=48
'value'=49
'field'=50
'relation'=51
'operation'=52
'function'=53
'constructor'=54
'constructs'=55
'input'=56
'conform'=57
'as'=58
'bind'=59
'static'=60
'to'=61
'private'=62
'shared'=63
'state'=64
'edge'=65
'projection'=66
'with'=67
'using'=68
'accept'=69
'via'=70
'materialize'=71
'if'=72
'absent'=73
'on'=74
'policy'=75
'default'=76
'source'=77
'repository'=78
'commit'=79
'revision'=80
'semantic-major'=81
'on-delete'=82
'lifecycle'=83
'journal'=84
'subscription'=85
'retain-other'=86
'keyed'=87
'public-traversal'=88
'id'=89
'doc'=90
'mode'=91
'emits'=92
'receiver'=93
'requires'=94
'any'=95
'get'=96
'set'=97
'watch'=98
'start'=99
'stop'=100
'read'=101
'write'=102
'resolve'=103
'connect'=104
'disconnect'=105
'call'=106
'watch-start'=107
'watch-stop'=108
'subscribe'=109
'unsubscribe'=110
'register'=111
'crdt'=112
'optional-one'=113
'exactly-one'=114
'many-unique'=115
'many'=116
'ordered'=117
'unit'=118
'watch-handle'=119
'message'=120
'atom-ref'=121
'interface-ref'=122
'optional'=123
'list'=124
'record'=125
'bool'=126
'bytes'=127
'double'=128
'int32'=129
'int64'=130
'string'=131
'uint32'=132
'uint64'=133
'true'=134
'false'=135
'null'=136
'->'=137
':'=138
';'=139
','=140
'.'=141
'{'=142
'}'=143
'['=144
']'=145
'('=146
')'=147
'<'=148
'>'=149
'&'=150
'='=151
'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
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -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";
@@ -145,6 +146,12 @@ export class QuixosCapabilityVisitor<Result> extends AbstractParseTreeVisitor<Re
* @return the visitor result
*/
visitExternalInterfaceDecl?: (ctx: ExternalInterfaceDeclContext) => 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
+60 -16
View File
@@ -205,6 +205,7 @@ interface LoweringState {
fileName: string;
diagnostics: CapabilitySourceDiagnostic[];
atoms: Map<string, AtomId>;
symbols: Map<string, string>;
interfaces: Map<string, InterfaceSymbol>;
packages: Map<string, PackageSymbol>;
attachments: Map<string, AttachmentSymbol>;
@@ -387,14 +388,33 @@ const lowerSelectedOperations = (
return { operations };
};
const lowerExecution = (context: ExecutionClauseContext | null): { execution?: ExecutionContract } => {
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: context.identifierList()!.identifier().map(identifier),
targets: implementation ? targets : ["server"],
},
};
};
@@ -508,17 +528,17 @@ const lowerValueMember = (state: LoweringState, context: ValueMemberContext): In
if (operation.GET()) {
operations.push({
...valueOperation("get", capabilityId.operation(stringValue(operation.stringLiteral(0))), memberValueType),
...lowerExecution(operation.executionClause()),
...lowerExecution(state, operation.executionClause()),
});
} else if (operation.EDIT()) {
operations.push({
...valueOperation("edit", capabilityId.operation(stringValue(operation.stringLiteral(0))), memberValueType),
...lowerExecution(operation.executionClause()),
...lowerExecution(state, operation.executionClause()),
});
} else if (operation.SET()) {
operations.push({
...valueOperation("set", capabilityId.operation(stringValue(operation.stringLiteral(0))), memberValueType),
...lowerExecution(operation.executionClause()),
...lowerExecution(state, operation.executionClause()),
});
} else {
operations.push(
@@ -611,7 +631,7 @@ const lowerRelationshipMember = (
);
}
if (operation.executionClause())
Object.assign(operations[operations.length - 1]!, lowerExecution(operation.executionClause()));
Object.assign(operations[operations.length - 1]!, lowerExecution(state, operation.executionClause()));
}
return {
kind: "relationship",
@@ -653,7 +673,7 @@ const lowerOperationMember = (state: LoweringState, context: OperationMemberCont
outputType,
mode: "call",
...(context.STATIC() ? { scope: "class" as const } : {}),
...lowerExecution(context.executionClause()),
...lowerExecution(state, context.executionClause()),
},
],
};
@@ -814,18 +834,24 @@ const lowerInterfaceTemplate = (
const id = capabilityId.operation(stringValue(operation.stringLiteral(0)));
if (operation.GET())
return [
{ ...signature("get", id, valueType.unit, type), ...lowerExecution(operation.executionClause()) },
{
...signature("get", id, valueType.unit, type),
...lowerExecution(state, operation.executionClause()),
},
];
if (operation.EDIT())
return [
{
...signature("edit", id, { kind: "change", value: type }, valueType.unit),
...lowerExecution(operation.executionClause()),
...lowerExecution(state, operation.executionClause()),
},
];
if (operation.SET())
return [
{ ...signature("set", id, type, valueType.unit), ...lowerExecution(operation.executionClause()) },
{
...signature("set", id, type, valueType.unit),
...lowerExecution(state, operation.executionClause()),
},
];
return [
signature("watch-start", id, valueType.unit, valueType.watchHandle, "watch-start", type),
@@ -859,7 +885,7 @@ const lowerInterfaceTemplate = (
outputType,
),
...(operation.STATIC() ? { scope: "class" as const } : {}),
...lowerExecution(operation.executionClause()),
...lowerExecution(state, operation.executionClause()),
},
],
};
@@ -907,7 +933,10 @@ const lowerInterfaceTemplate = (
const id = capabilityId.operation(stringValue(operation.stringLiteral(0)));
if (operation.RESOLVE())
return [
{ ...signature("resolve", id, valueType.unit, resolved), ...lowerExecution(operation.executionClause()) },
{
...signature("resolve", id, valueType.unit, resolved),
...lowerExecution(state, operation.executionClause()),
},
];
if (operation.CONNECT() || operation.DISCONNECT())
return [
@@ -918,7 +947,7 @@ const lowerInterfaceTemplate = (
operation.CONNECT() ? keyedTarget : targetType,
valueType.unit,
),
...lowerExecution(operation.executionClause()),
...lowerExecution(state, operation.executionClause()),
},
];
return [
@@ -1196,7 +1225,7 @@ const lowerPackageOperation = (
inputType: lowerValueType(state, context.valueType(0)),
outputType: lowerValueType(state, context.valueType(1)),
mode: text(context.operationMode()) as InterfaceOperationMode,
...lowerExecution(context.executionClause()),
...lowerExecution(state, context.executionClause(), true),
eventType: event ? lowerValueType(state, event.valueType()) : undefined,
receiverRequirement: lowerReceiver(state, context.receiverRequirement()),
dependencyPorts: ports,
@@ -1213,7 +1242,7 @@ const lowerPackageFunction = (
const alias = identifier(context.identifier());
const entry: PackageExport = {
kind: "function",
...lowerExecution(context.executionClause()),
...lowerExecution(state, context.executionClause(), true),
id: capabilityId.packageExport(stringValue(context.stringLiteral())),
displayName: alias,
inputType: lowerValueType(state, context.valueType(0)),
@@ -1355,7 +1384,7 @@ const lowerGenericPackageExport = (
displayName: identifier(context.identifier()),
parameters,
kind: operation ? "operation" : "function",
...lowerExecution(context.executionClause()),
...lowerExecution(state, context.executionClause(), true),
inputType: state.types.expression(context.valueType(0)!),
outputType: state.types.expression(context.valueType(1)!),
dependencyPorts,
@@ -3092,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(),
};
@@ -3240,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;
+1 -8
View File
@@ -25,7 +25,6 @@ export function validateExecutionContracts(workspace: WorkspaceRevision): Capabi
requested: ExecutionContract | undefined,
provided: ExecutionContract | undefined,
path: string,
requireTargets = true,
) => {
if (!requested || requested.kind === "remote") return;
if (!provided || provided.kind === "remote") {
@@ -34,9 +33,6 @@ export function validateExecutionContracts(workspace: WorkspaceRevision): Capabi
}
if (requested.kind === "snapshot" && provided.kind !== "snapshot")
fail(path, "Snapshot execution cannot perform a transaction");
if (requireTargets)
for (const target of requested.targets ?? [])
if (!provided.targets?.includes(target)) fail(path, `Dependency does not provide execution target ${target}`);
};
for (const revision of workspace.interfaceImports)
for (const member of revision.template?.members ?? revision.members) {
@@ -84,10 +80,7 @@ export function validateExecutionContracts(workspace: WorkspaceRevision): Capabi
if (entry.execution && entry.execution.kind !== "remote") {
if (!selected) fail(portPath, "Portable interface ports must select their required operations");
for (const id of selected ?? [])
// Package targets describe available code. Required public target
// guarantees are checked over the installed operation closure;
// an optional implementation target must not strengthen a dependency.
if (contract) supports(entry.execution, operations(id, contract)?.execution, portPath, false);
if (contract) supports(entry.execution, operations(id, contract)?.execution, portPath);
}
}
if (!entry.execution || entry.execution.kind === "remote") continue;
+1 -4
View File
@@ -32,10 +32,7 @@ export function publicOperationContract(
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" &&
execution.targets.includes("browser")
member.kind === "value" && operation.scope !== "class" && execution.kind === "transaction"
? operation.displayName === "edit"
? "crdt"
: member.nativeField && operation.displayName === "set"
+5 -1
View File
@@ -117,7 +117,11 @@ export class BrowserArtifactGateway {
const entries: BrowserArtifact[] = [];
for (const manifest of loaded.manifests)
for (const implementation of manifest.implementations) {
if (implementation.target !== "browser" || implementation.compatibility !== "quixos-js-module-v1") continue;
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");
+1
View File
@@ -1,4 +1,5 @@
export * from "./plan.js";
export * from "./native-backends.js";
export * from "./artifacts.js";
export * from "./ports.js";
export * from "./files.js";
+3 -1
View File
@@ -81,7 +81,9 @@ export function compileInspectionTemplates(plan: ExecutionPlan) {
),
);
if (providers.some((op) => !op || op.unsupported || !["snapshot", "remote"].includes(op.effect))) continue;
const portable = providers.every((op) => op!.effect === "snapshot" && op!.targets.includes("browser"));
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;
+4 -2
View File
@@ -250,7 +250,9 @@ function assembleExecutionInstallation(
})
: undefined;
const browserExports = exports.filter(
(entry) => entry.execution.kind !== "remote" && entry.execution.targets.includes("browser"),
(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");
@@ -259,7 +261,7 @@ function assembleExecutionInstallation(
}
const browser = selectArtifactImplementation(manifest, {
...selection.browser,
target: "browser",
target: "target:org.quixos.execution.javascript@1",
compatibility: "quixos-js-module-v1",
exports: browserExports.map((entry) => entry.id),
});
+21
View File
@@ -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<Binding, { kind: "interface" }>) => 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"),
},
];
+19 -14
View File
@@ -1,3 +1,4 @@
import { standardNativeBackends, type NativeExecutionBackend } from "./native-backends.js";
import type {
WorkspaceRevision,
ExecutionContract,
@@ -45,7 +46,7 @@ export interface PlannedOperation {
operationId: string;
scope: "object" | "class";
execution: ExecutionContract;
/** Installed implementation closure; the public execution contract above remains required. */
/** Targets available for this complete installed implementation/dependency closure. */
availableTargets: string[];
input: ValueType;
output: ValueType;
@@ -171,7 +172,10 @@ function checkValue(type: ValueType, path: string, allowChange = false): void {
}
/** Compile declarations into a closed dispatch/dependency graph before artifact selection. */
export function compileExecutionPlan(input: WorkspaceRevision): ExecutionPlan {
export function compileExecutionPlan(
input: WorkspaceRevision,
backends: readonly NativeExecutionBackend[] = standardNativeBackends,
): ExecutionPlan {
const workspace = structuredClone(input);
const issues = validateWorkspaceRevision(workspace);
if (issues.length)
@@ -311,13 +315,6 @@ export function compileExecutionPlan(input: WorkspaceRevision): ExecutionPlan {
path.join(" -> "),
"Execution effect is not allowed by the root contract",
);
for (const target of requested.targets)
if (!node.execution.targets.includes(target))
throw new ExecutionPlanError(
"MISSING_EXECUTION_TARGET",
path.join(" -> "),
`Dependency does not provide ${target}`,
);
for (const dependency of node.dependencies) {
if (dependency.kind === "operation")
walk(dependency.operation, [...path, dependency.port, dependency.operation]);
@@ -352,9 +349,10 @@ export function compileExecutionPlan(input: WorkspaceRevision): ExecutionPlan {
const provider =
binding.kind === "package"
? packages.get(binding.packageRevisionId)!.exports.find((entry) => entry.id === binding.exportId)!.execution
: binding.kind === "lifecycle" || (binding.kind === "subscription" && binding.primitive !== "status")
? { kind: "transaction" as const, targets: ["server" as const] }
: { kind: node.execution.kind, targets: ["browser" as const, "server" as const] };
: {
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;
@@ -382,6 +380,13 @@ export function compileExecutionPlan(input: WorkspaceRevision): ExecutionPlan {
}
}
}
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,
@@ -515,11 +520,11 @@ export function compileExecutionPlan(input: WorkspaceRevision): ExecutionPlan {
source.admittedOperations.push(...query.operations);
for (const operation of query.operations) {
const node = operationMap.get(operation)!;
if (node.execution.kind !== "snapshot" || !node.execution.targets.includes("browser"))
if (node.execution.kind !== "snapshot" || !node.availableTargets.some((target) => target !== "server"))
throw new ExecutionPlanError(
"NONPORTABLE_QUERY_FIELD",
`${query.id}/${operation}`,
"Local query getter must provide browser snapshot execution",
"Local query getter needs an installed local snapshot implementation",
);
}
}
+3 -2
View File
@@ -15,6 +15,8 @@ export interface ArtifactReceipt {
generated: { target: string; path: string; digest: string; bytes: number }[];
}
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(
@@ -56,8 +58,7 @@ export function expectedArtifactReceipt(
.sort();
const sources = new Map<string, Uint8Array>();
const generated = targets.map((target) => {
if (!/^[a-zA-Z0-9_-]+$/.test(target)) throw new Error("Unsupported generated target filename");
const path = `share/quixos/generated/${target}.ts`;
const path = generatedBindingPath(target);
const bytes = new TextEncoder().encode(
generatePortableTypeScriptBindings(plan.definition.bindingSchema, pkg.revision, { target }),
);
+2 -2
View File
@@ -250,10 +250,10 @@ export async function compileQuery(
const getter = member.operations.find(
(operation) => operation.displayName === (member.kind === "value" ? "get" : "resolve"),
);
if (getter?.execution?.kind !== "snapshot" || !getter.execution.targets.includes("browser"))
if (getter?.execution?.kind !== "snapshot")
fail(
"QUERY_LOCAL_DEPENDENCY",
`Local query requires a browser snapshot getter for ${contract.displayName}.${name}; split the remote field or move the query to the server`,
`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")
+4 -3
View File
@@ -2,12 +2,13 @@ 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 targets [browser, server];
call id "change:call" execution transaction;
}
}`;
export const runtime = `external atom Item id "item";
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 [browser, server]
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 {
+4 -4
View File
@@ -28,9 +28,9 @@ test("acceptance syntax retains separate final-state ports through formatting, p
]);
assert.equal(kernel.packages[0]!.exports.find((e) => e.id === "runtime:change")!.acceptance, "runtime:check");
assert.ok(
!generatePortableTypeScriptBindings(plan.definition.bindingSchema, "runtime@1", { target: "browser" }).includes(
'"runtime:check"',
),
!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(
@@ -43,7 +43,7 @@ test("acceptance syntax retains separate final-state ports through formatting, p
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 [browser, 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,
+3 -1
View File
@@ -46,7 +46,9 @@ test("gateway serves only declared browser assets with correct Wasm MIME and con
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 === "browser")!;
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,
+2 -2
View File
@@ -13,7 +13,7 @@ export const buildArtifactFixture = (plan: ExecutionPlan, revision = plan.defini
target: string,
compatibility: ArtifactImplementation["compatibility"],
): ArtifactImplementation => {
const path = compatibility === "quixos-service-v1" ? "bin/service" : `portable/${target}/module.mjs`;
const path = compatibility === "quixos-service-v1" ? "bin/service" : `portable/${id}/module.mjs`;
const value = {
id,
target,
@@ -44,7 +44,7 @@ export const buildArtifactFixture = (plan: ExecutionPlan, revision = plan.defini
implementations: [
implementation("service", "server", "quixos-service-v1"),
implementation("server", "server", "quixos-js-module-v1"),
implementation("browser", "browser", "quixos-js-module-v1"),
implementation("browser", "target:org.quixos.execution.javascript@1", "quixos-js-module-v1"),
],
};
return { manifest, bytes, read: async () => bytes };
+1 -1
View File
@@ -229,7 +229,7 @@ test("resource-owned fragment imports compile through the locked package closure
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 targets [browser,server];}}`,
`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(
+9 -8
View File
@@ -14,7 +14,7 @@ import { parseControllerCatalog, validateControllers } from "../src/capability-m
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 targets [browser, server]; }
queryable portable value label id "label" : string { get id "label:get" execution snapshot; }
operation publish id "publish" : string -> unit { call id "publish:call"; }
}`,
{ source },
@@ -26,20 +26,20 @@ test("native fields certify one storage slot; only browser writes get live descr
const parsed = compileCapabilityResourceSource(
`interface Fields id "fields" revision "fields@1" {
queryable field position id "position" : string {
get id "position:get" execution snapshot targets [browser, server];
set id "position:set" execution transaction targets [browser, server];
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 targets [browser,server];
set id "arbitrary:set" execution transaction targets [browser, server];
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 targets [browser, server];
edit id "text:edit" execution transaction;
}
}`,
{ source },
@@ -92,8 +92,9 @@ test("native fields certify one storage slot; only browser writes get live descr
const goodForward = compileCapabilitySource(forwarded, "workspace.qx", env);
assert.ok(goodForward.ok, JSON.stringify(goodForward.diagnostics));
const pkg = compileCapabilityResourceSource(
`package Setter id "setter" revision "setter@1" {
operation replaceValue id "write" : string -> unit mode call execution transaction targets [browser,server] receiver any;
`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 },
);
+1 -1
View File
@@ -52,7 +52,7 @@ test("checked component scaffolding composes with execution handlers without rep
await apply("function", {
name: "echo",
id: "echo",
declaration: 'function echo id "echo" : string -> string execution snapshot targets [browser,server];',
declaration: 'function echo id "echo" : string -> string execution snapshot targets [JavaScript,server];',
});
await apply("component", {
name: "Details",
+4 -3
View File
@@ -24,7 +24,7 @@ test("compound captured changes survive planning, transport lowering and typed e
]);
assert.deepEqual(paths.find((op) => op.operation === "compound:content:edit")!.inputChanges, [[]]);
const code = generatePortableTypeScriptBindings(plan.definition.bindingSchema, "compound:runtime@1", {
target: "browser",
target: "target:org.quixos.execution.javascript@1",
});
assert.ok(code.includes("PreparedChange<"));
assert.ok(code.includes('"edit"'));
@@ -33,8 +33,9 @@ test("compound captured changes survive planning, transport lowering and typed e
test("prepared changes require one storable materialized type", () => {
for (const shape of ["change<string, bool>", "change<atom N>", "change<atom-ref<N>>", "change<change<string>>"]) {
const result = compileCapabilityResourceSource(
`external atom N id "n"; package P id "p" revision "p@1" {
function f id "f" : ${shape} -> unit execution transaction targets [browser, server];
`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 },
);
+3 -3
View File
@@ -192,9 +192,9 @@ test("rule private edges substitute Self and captured endpoints with stable proj
test("rule creation ports close captured atom parameters and retain initialization checking", () => {
const compile = fixture([
'interface Factory<object T> id "factory" revision "factory@1" {operation create id "create" : unit -> ref<T> {call id "call" execution transaction targets [browser,server];}}',
'interface Factory<object T> id "factory" revision "factory@1" {operation create id "create" : unit -> ref<T> {call id "call" execution transaction;}}',
'interface Creates<object T> id "creates" revision "creates@1" {}',
'package Runtime id "runtime" revision "runtime@1" {operation create<object T> id "create" : unit -> ref<T> mode call execution transaction targets [browser,server] receiver any requires {creation result id "result" : T input unit;};}',
'symbol JavaScript id "target:org.quixos.execution.javascript@1";\npackage Runtime id "runtime" revision "runtime@1" {operation create<object T> id "create" : unit -> ref<T> mode call execution transaction targets [JavaScript,server] receiver any requires {creation result id "result" : T input unit;};}',
]);
const rule = `conform<object T> Creates<T> as Factory<T> id "factory-rule" {
bind create.call to package Runtime.create<T> with {result to creation T with {};};
@@ -324,7 +324,7 @@ 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 targets [browser,server];}}',
'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";
+3 -3
View File
@@ -6,8 +6,8 @@ const content = "record { text:string; tags:list<string>; }";
const iface = compileCapabilityResourceSource(
`interface Comment id "comment" revision "comment@1" {
value content id "content" : ${content} {
get id "content:get" execution snapshot targets [browser, server];
edit id "content:edit" execution transaction targets [browser, server];
get id "content:get" execution snapshot;
edit id "content:edit" execution transaction;
}
}`,
{ source },
@@ -68,7 +68,7 @@ test("authored prepared changes retain their materialized type as transport valu
test("generic edit retains its materialized type through specialization", () => {
const generic = compileCapabilityResourceSource(
`interface Cell<value T : storable> id "cell" revision "cell@1" {
value content id "content" : T { get id "get"; edit id "edit" execution transaction targets [browser, server]; }
value content id "content" : T { get id "get"; edit id "edit" execution transaction; }
}`,
{ source },
);
+11 -9
View File
@@ -14,23 +14,24 @@ const iface = (text: string): InterfaceRevision => {
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 targets [browser, server]; }
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<string> { get id "tags:get" execution snapshot targets [browser, server]; }
value tags id "tags" : list<string> { 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<Note> { call id "create:call" execution transaction targets [browser, server]; }
static operation importNote id "import" : ${seed} -> atom-ref<Note> { call id "import:call" execution transaction targets [browser, server]; }
static operation create id "create" : ${seed} -> atom-ref<Note> { call id "create:call" execution transaction; }
static operation importNote id "import" : ${seed} -> atom-ref<Note> { call id "import:call" execution transaction; }
}`);
const runtime = compileCapabilityResourceSource(
`external atom Note id "note";
`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<Note> execution transaction targets [browser, server] requires {
function createNote id "create-note" : ${seed} -> atom-ref<Note> execution transaction targets [JavaScript, server] requires {
creation note id "new-note" : Note input ${seed};
};
function importNote id "import-note" : ${seed} -> atom-ref<Note> execution transaction targets [browser, server] requires {
function importNote id "import-note" : ${seed} -> atom-ref<Note> execution transaction targets [JavaScript, server] requires {
creation note id "new-note" : Note input ${seed};
};
}`,
@@ -137,9 +138,10 @@ test("defaults supply unmapped state and seeds cannot invoke an undeclared owner
test("creation ports cannot be used in snapshot execution", () => {
const result = compileCapabilityResourceSource(
`external atom Note id "note";
`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<Note> execution snapshot targets [browser, server] requires {
function createNote id "create" : unit -> atom-ref<Note> execution snapshot targets [JavaScript, server] requires {
creation note id "note" : Note input record { title:string; };
};
}`,
+16 -14
View File
@@ -15,7 +15,7 @@ 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<Entry> {
call id "entry:create:call" execution transaction targets [server];
call id "entry:create:call" execution transaction;
}
}`,
{ source },
@@ -23,12 +23,12 @@ interface EntryFactory id "interface:entry-factory" revision "interface:entry-fa
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 targets [browser, server]; edit id "body:edit" execution transaction targets [browser, server]; }
queryable value notes id "notes" : string { get id "notes:get" execution snapshot targets [browser, server]; edit id "notes:edit" execution transaction targets [browser, server]; }
operation revise id "revise" : record {body:change<string>; notes:change<string>; reject:bool;} -> unit {call id "revise:call" execution transaction targets [browser, server];}
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<string>; notes:change<string>; reject:bool;} -> unit {call id "revise:call" execution transaction;}
queryable value label id "label" : string {
get id "label:get" execution snapshot targets [browser, server];
set id "label:set" execution transaction targets [browser, server];
get id "label:get" execution snapshot;
set id "label:set" execution transaction;
}
}`,
{ source },
@@ -36,8 +36,8 @@ const data = compileCapabilityResourceSource(
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<Entry> { call id "create-one:call" execution transaction targets [browser, server]; }
static operation createTwo id "create-two" : unit -> atom-ref<Independent> { call id "create-two:call" execution transaction targets [browser, server]; }
static operation createOne id "create-one" : string -> atom-ref<Entry> { call id "create-one:call" execution transaction; }
static operation createTwo id "create-two" : unit -> atom-ref<Independent> { call id "create-two:call" execution transaction; }
}`,
{ source },
);
@@ -70,7 +70,8 @@ for (const variant of ["base", "added", "gateway", "gateway-next", "session"]) {
gateway = session || variant.startsWith("gateway"),
commit = { base: "a", added: "b", gateway: "c", "gateway-next": "d", session: "e" }[variant]!.repeat(40);
const pkg = compileCapabilityResourceSource(
`${gateway ? 'external atom Entry id "atom:entry";' : ""}
`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;
@@ -78,9 +79,9 @@ package Owner id "package:owner" revision "package:owner@1" {
${
session
? `module Editor id "component:owner-editor" implements View.render;
function createOne id "create-one" : string -> atom-ref<Entry> execution transaction targets [browser, server] requires { creation entry id "one:birth" : Entry input unit; };
function createTwo id "create-two" : unit -> atom-ref<Independent> execution transaction targets [browser, server] requires { creation entry id "two:birth" : Independent input unit; };
operation revise id "revise" : record {body:change<string>; notes:change<string>; reject:bool;} -> unit mode call execution transaction targets [browser, server] receiver any requires {
function createOne id "create-one" : string -> atom-ref<Entry> execution transaction targets [JavaScript, server] requires { creation entry id "one:birth" : Entry input unit; };
function createTwo id "create-two" : unit -> atom-ref<Independent> execution transaction targets [JavaScript, server] requires { creation entry id "two:birth" : Independent input unit; };
operation revise id "revise" : record {body:change<string>; notes:change<string>; 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; }
@@ -202,9 +203,10 @@ 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(
`package Owner id "package:owner" revision "package:owner@1" {
`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 [browser,server]" : "remote"};
execution ${kind === "portable" ? "snapshot targets [JavaScript,server]" : "remote"};
}`,
{ source },
);
+13 -5
View File
@@ -7,17 +7,21 @@ 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 }),
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: "browser" }));
await writeFile("dist/portable-reader/client.ts", generatePortableClientBindings(plan, { target: javascriptTarget }));
const serviceWorkspace = structuredClone(plan.definition.workspace);
serviceWorkspace.atoms = [];
serviceWorkspace.conformances = [];
@@ -49,7 +53,9 @@ for (const target of ["browser", "server"])
] as const)
await writeFile(
`dist/portable-reader/composite/${name}-${target}.ts`,
generatePortableTypeScriptBindings(composite.definition.bindingSchema, revision, { target }),
generatePortableTypeScriptBindings(composite.definition.bindingSchema, revision, {
target: executionTarget(target),
}),
);
const { portableGenericPlan } = await import("./portable-generic-fixture.js");
@@ -59,7 +65,9 @@ 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 }),
generatePortableTypeScriptBindings(generic.definition.bindingSchema, genericPackage.revisionId, {
target: executionTarget(target),
}),
);
}
const stringEntry = genericPackage.exports.find((e) => e.inputType.kind === "scalar")!;
@@ -141,7 +149,7 @@ await writeFile(
await writeFile(
"dist/portable-reader/presentation-mount/browser.ts",
generatePortableTypeScriptBindings(presentationMount.definition.bindingSchema, "presentation-runtime@1", {
target: "browser",
target: javascriptTarget,
}),
);
+43 -19
View File
@@ -3,7 +3,7 @@ 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 targets [browser, server]") =>
const data = (execution = "snapshot") =>
compileCapabilityResourceSource(
`
interface Data id "interface:data" revision "interface:data@1" {
@@ -14,13 +14,14 @@ interface Data id "interface:data" revision "interface:data@1" {
}`,
{ source },
);
const program = (selection: string, execution = "snapshot targets [browser, server]") => {
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 {
@@ -43,7 +44,10 @@ test("portable contracts preserve exact selected operations and targets", () =>
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: ["browser", "server"] });
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",
@@ -53,10 +57,10 @@ test("portable contracts preserve exact selected operations and targets", () =>
test("portable dependency diagnostics reject remote defaults and broad acquisition", () => {
for (const [selection, execution, message] of [
["using [refresh.call]", "snapshot targets [browser]", "remote-only"],
["", "snapshot targets [browser]", "select"],
["using [label.get, label.get]", "snapshot targets [browser]", "duplicates"],
["using [label.set]", "snapshot targets [browser]", "Unknown"],
["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);
@@ -64,11 +68,26 @@ test("portable dependency diagnostics reject remote defaults and broad acquisiti
}
});
test("package code targets may exceed a dependency's public minimum", () => {
test("execution targets must resolve nominal declarations", () => {
const result = program("using [label.get]", "snapshot targets [arduino]");
assert.ok(result.ok, JSON.stringify(result.diagnostics));
// A workspace still has to provide the full installed closure before an
// operation can promise this target; package code alone is insufficient.
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", () => {
@@ -80,9 +99,10 @@ test("portable query getters and target declarations are checked", () => {
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 [browser, server] requires {
function update id "export:update" : string -> unit execution ${effect} targets [JavaScript, server] requires {
state storage id "port:value" : string [read, write];
};
}`,
@@ -137,7 +157,7 @@ package Reader id "package:reader" revision "package:reader@1" {
delete remote.members[0]!.operations[0]!.execution;
await assert.rejects(
compileQuery(local, [remote], async () => document("Presentation", "availableObjects")),
/browser snapshot getter/,
/graph snapshot getter/,
);
const { capabilityId } = await import("../src/capability-model/index.js");
const linked = linkQueries({
@@ -200,15 +220,16 @@ test("generic selected ports preserve operation identities and execution through
const { specializePackageExport, capabilityId } = await import("../src/capability-model/index.js");
const iface = compileCapabilityResourceSource(
`interface Cell<value T> id "cell" revision "cell@1" {
value content id "content" : T { get id "content:get" execution snapshot targets [browser, server]; }
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(
`import interface Cell;
`symbol JavaScript id "target:org.quixos.execution.javascript@1";
import interface Cell;
package P id "p" revision "p@1" {
function load<value T> id "read" : unit -> T execution snapshot targets [browser, server] requires {
function load<value T> id "read" : unit -> T execution snapshot targets [JavaScript, server] requires {
interface cell id "cell" : Cell<T> using [content.get];
};
}`,
@@ -236,7 +257,10 @@ test("generic selected ports preserve operation identities and execution through
implementsInterface: () => false,
},
);
assert.deepEqual(specialized.execution, { kind: "snapshot", targets: ["browser", "server"] });
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")
+1 -1
View File
@@ -88,7 +88,7 @@ test("outbound provider changes invalidate consumers while caller changes preser
mode: "call",
inputType: { kind: "builtin", name: "unit" },
outputType: { kind: "scalar", name: "string" },
execution: { kind: "snapshot", targets: ["browser", "server"] },
execution: { kind: "snapshot", targets: ["target:org.quixos.execution.javascript@1", "server"] },
receiverRequirement: { kind: "exact-atom", atomId: id.atom("atom:reader:document") },
dependencyPorts: [
{
+1 -1
View File
@@ -24,7 +24,7 @@ export async function emitExecutionScaffold(root: string) {
tools: { quixos: source, protocol: source, helpers: source, sdk: source },
});
for (const [name, execution] of [
["echo", "snapshot targets [browser, server]"],
["echo", "snapshot targets [JavaScript, server]"],
["serverEcho", "snapshot targets [server]"],
["remoteEcho", "remote"],
]) {
+54 -15
View File
@@ -17,23 +17,63 @@ import {
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 can exceed the public requirement without changing that contract", async () => {
const workspace = await loadReaderWorkspace((name, text) =>
name === "DocumentFactory.qx" ? text.replaceAll("targets [browser, server]", "targets [server]") : text,
);
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, ["browser", "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, ["browser", "server"]);
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)
@@ -102,8 +142,8 @@ test("artifact closure checks bytes, exact source/schema, ABI, exports and targe
(m.versions as unknown as Record<string, string>).value = "v2";
}, /Incompatible runtime codecs/);
await negative((m) => {
m.implementations = m.implementations.filter((i) => i.target !== "browser");
}, /Missing browser implementation/);
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/);
@@ -211,7 +251,7 @@ test("closed generic applications retain distinct identities and codec shapes",
const source = { repository: "https://example.test/generic.git", commit: "a".repeat(40) };
const definition = compileCapabilityResourceSource(
`interface Cell<value V : storable> id "cell" revision "cell@1" {
value content id "value" : V { get id "get" execution snapshot targets [browser, server]; }
value content id "value" : V { get id "get" execution snapshot; }
}`,
{ source },
);
@@ -269,11 +309,10 @@ test("query dependencies propagate target requirements through selected getters"
binding: { kind: "query", packageRevisionId: pkg.revisionId, queryId: query.id, scope: "workspace" },
});
compileExecutionPlan(workspace);
const field = workspace.interfaceImports
.find((i) => i.displayName === "HighlightData")!
.members.find((m) => m.displayName === "color")!;
field.operations.find((o) => o.displayName === "get")!.execution = { kind: "snapshot", targets: ["browser"] };
assert.throws(() => compileExecutionPlan(workspace), /MISSING_EXECUTION_TARGET.*test:selector-query.*server/);
// 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 () => {
@@ -281,7 +320,7 @@ test("implementation selection is explicit, target checked and independent of ma
const plan = compileExecutionPlan(await loadReaderWorkspace());
const { manifest, read } = buildFixture(plan);
const selection = {
target: "browser",
target: "target:org.quixos.execution.javascript@1",
compatibility: "quixos-js-module-v1" as const,
exports: [manifest.implementations[0]!.exports[0]!],
};
+6 -6
View File
@@ -1,17 +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 targets [browser, server];
get id "async:content:get" execution snapshot;
}
queryable value summary id "async:summary" : Summary {
get id "async:summary:get" execution snapshot targets [browser, server];
set id "async:summary:set" execution transaction targets [browser, server];
get id "async:summary:get" execution snapshot;
set id "async:summary:set" execution transaction;
}
queryable value pendingJob id "async:pending-job" : optional<record { operationId: string; }> {
get id "async:pending-job:get" execution snapshot targets [server];
set id "async:pending-job:set" execution transaction targets [server];
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 targets [server];
call id "async:record-summary:call" execution transaction;
}
}
+2 -2
View File
@@ -6,7 +6,7 @@ type Entry = variant id "canvas:entry@1" {
};
interface CanvasContent id "canvas" revision "canvas@1" {
queryable value entries id "entries" : list<Entry> {
get id "entries:get" execution snapshot targets [browser,server];
edit id "entries:edit" execution transaction targets [browser,server];
get id "entries:get" execution snapshot;
edit id "entries:edit" execution transaction;
}
}
+4 -3
View File
@@ -1,17 +1,18 @@
symbol JavaScript id "target:org.quixos.execution.javascript@1";
type Replacement = record { expected: list<string>; targets: list<atom-ref<Node>>; };
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<string>
mode call execution snapshot targets [browser, server] receiver atom Node requires {
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 [browser, server] receiver atom Node requires {
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<Node>
mode call execution transaction targets [browser, server] receiver atom Node requires {
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;
};
+8 -8
View File
@@ -2,25 +2,25 @@ type Replacement = record { expected: list<string>; targets: list<atom-ref<Node>
external atom Node id "collection:node";
interface NodeData id "collection:data" revision "collection:data@1" {
queryable portable value version id "collection:version" : list<string> {
get id "collection:version:get" execution snapshot targets [browser, server];
get id "collection:version:get" execution snapshot;
}
operation replace id "collection:replace" : Replacement -> unit {
call id "collection:replace:call" execution transaction targets [browser, server];
call id "collection:replace:call" execution transaction;
}
operation ensure id "collection:ensure" : unit -> atom-ref<Node> {
call id "collection:ensure:call" execution transaction targets [browser, server];
call id "collection:ensure:call" execution transaction;
}
queryable value label id "collection:label" : string {
get id "collection:label:get" execution snapshot targets [browser, server];
set id "collection:label:set" execution transaction targets [browser, server];
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 targets [browser, server];
resolve id "collection:items:resolve" execution snapshot;
}
queryable relation containers id "collection:containers" : many atom Node {
resolve id "collection:containers:resolve" execution snapshot targets [browser, server];
resolve id "collection:containers:resolve" execution snapshot;
}
queryable relation detail id "collection:detail" : optional-one atom Node {
resolve id "collection:detail:resolve" execution snapshot targets [browser, server];
resolve id "collection:detail:resolve" execution snapshot;
}
}
+3 -2
View File
@@ -1,9 +1,10 @@
symbol JavaScript id "target:org.quixos.execution.javascript@1";
external atom Note id "compound:note";
type Content = record { text: string; tags: list<string>; };
type Edit = record { change: change<Content>; resolved: bool; };
package EditRuntime id "compound:runtime" revision "compound:runtime@1" {
operation batch id "compound:runtime:batch" : record { edits: list<Edit>; } -> unit
mode call execution transaction targets [browser, server] receiver atom Note requires {
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;
@@ -12,7 +13,7 @@ package EditRuntime id "compound:runtime" revision "compound:runtime@1" {
state allowed id "compound:batch:allowed" : bool [read];
};
operation update id "compound:runtime:update" : Edit -> unit
mode call execution transaction targets [browser, server] receiver atom Note requires {
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;
+7 -7
View File
@@ -2,20 +2,20 @@ type Content = record { text: string; tags: list<string>; };
type Edit = record { change: change<Content>; resolved: bool; };
interface NoteData id "compound:data" revision "compound:data@1" {
operation batch id "compound:batch" : record { edits: list<Edit>; } -> unit {
call id "compound:batch:call" execution transaction targets [browser, server];
call id "compound:batch:call" execution transaction;
}
operation update id "compound:update" : Edit -> unit {
call id "compound:update:call" execution transaction targets [browser, server];
call id "compound:update:call" execution transaction;
}
queryable value content id "compound:content" : Content {
get id "compound:content:get" execution snapshot targets [browser, server];
edit id "compound:content:edit" execution transaction targets [browser, server];
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 targets [browser, server];
get id "compound:resolved:get" execution snapshot;
}
queryable value title id "compound:title" : string {
get id "compound:title:get" execution snapshot targets [browser, server];
set id "compound:title:set" execution transaction targets [browser, server];
get id "compound:title:get" execution snapshot;
set id "compound:title:set" execution transaction;
}
}
+7 -7
View File
@@ -1,11 +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<Display> {call id "device:create:call" execution transaction targets [browser,server];}
queryable value enabled id "device:enabled" : bool {get id "device:enabled:get" execution snapshot targets [browser,server];set id "device:enabled:set" execution transaction targets [browser,server];}
queryable value requested id "device:requested" : Request {get id "device:requested:get" execution snapshot targets [browser,server];}
queryable value observed id "device:observed" : optional<Request> {get id "device:observed:get" execution snapshot targets [browser,server];}
operation requestText id "device:request" : string -> unit {call id "device:request:call" execution transaction targets [browser,server];}
operation recordObservation id "device:record" : Request -> unit {call id "device:record:call" execution transaction targets [server];}
operation remove id "device:remove" : unit -> unit {call id "device:remove:call" execution transaction targets [server];}
static operation create id "device:create" : string -> atom-ref<Display> {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<Request> {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;}
}
+2 -2
View File
@@ -1,6 +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 targets [server];}
operation snapshot id "device:snapshot" : unit -> optional<Request> {call id "device:snapshot:call" execution snapshot targets [server];}
queryable portable value enabled id "controller:enabled" : bool {get id "controller:enabled:get" execution snapshot;}
operation snapshot id "device:snapshot" : unit -> optional<Request> {call id "device:snapshot:call" execution snapshot;}
operation apply id "device:apply" : optional<Request> -> unit {call id "device:apply:call" execution remote;}
}
+3 -2
View File
@@ -1,10 +1,11 @@
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<Display> execution transaction targets [browser,server] requires {creation device id "device" : Display input record {enabled:bool;requested:Request;observed:optional<Request>;};};
operation request id "driver:request" : string -> unit mode call execution transaction targets [browser,server] receiver any requires {state requested id "requested" : Request [read,write];};
function create id "driver:create" : string -> atom-ref<Display> execution transaction targets [JavaScript,server] requires {creation device id "device" : Display input record {enabled:bool;requested:Request;observed:optional<Request>;};};
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<Request> 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<Request> [write];};
operation apply id "driver:apply" : optional<Request> -> unit mode call execution remote receiver any requires {interface device id "device" : Device using [recordObservation.call];};
+1 -1
View File
@@ -1,5 +1,5 @@
interface EventHandler<value Payload,value Parameters> 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 targets [server];
call id "events:handle:call" execution transaction;
}
}
+3 -3
View File
@@ -1,13 +1,13 @@
// Required server support; a checked installed implementation may additionally predict locally.
interface EventStream<value Payload> 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 targets [server];
call id "events:publish:call" execution transaction;
}
queryable value head id "events:head" : optional<uint64> {
get id "events:head:get" execution snapshot targets [server];
get id "events:head:get" execution snapshot;
}
operation readPage id "events:read-page" : record {after:optional<string>;limit:uint32;}
-> record {entries:list<record {publication:string;position:uint64;payload:Payload;}>;next:optional<string>;asOfHead:optional<uint64>;} {
call id "events:read-page:call" execution snapshot targets [server];
call id "events:read-page:call" execution snapshot;
}
}
+5 -5
View File
@@ -7,12 +7,12 @@ type StartPosition = variant id "org.quixos.events:start@1" {
};
interface EventSubscription<value Payload,value Parameters> id "interface:org.quixos.events:subscription" revision "interface:org.quixos.events:subscription@1" {
operation attach id "events:attach" : record {source:interface-ref<EventStream<Payload>>;handler:interface-ref<EventHandler<Payload,Parameters>>;parameters:Parameters;start:StartPosition;} -> unit {
call id "events:attach:call" execution transaction targets [server];
call id "events:attach:call" execution transaction;
}
queryable value status id "events:status" : record {status:string;generation:uint64;cursor:uint64;failure:optional<record {publication:string;code:string;message:string;}>;} {
get id "events:status:get" execution snapshot targets [server];
get id "events:status:get" execution snapshot;
}
operation pause id "events:pause" : unit -> unit {call id "events:pause:call" execution transaction targets [server];}
operation resume id "events:resume" : unit -> unit {call id "events:resume:call" execution transaction targets [server];}
operation retire id "events:retire" : unit -> unit {call id "events:retire:call" execution transaction targets [server];}
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;}
}
+1 -1
View File
@@ -2,6 +2,6 @@ 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<Comment> {
call id "document:prepare-comment:call" execution transaction targets [browser, server];
call id "document:prepare-comment:call" execution transaction;
}
}
+2 -1
View File
@@ -1,10 +1,11 @@
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<string>; }; };
package CommentRuntime id "package:comment-runtime" revision "package:comment-runtime@1" {
operation prepareComment id "comment:prepare"
: bool -> atom-ref<Comment> mode call execution transaction targets [browser, server]
: bool -> atom-ref<Comment> mode call execution transaction targets [JavaScript, server]
receiver atom Document requires {
creation comment id "comment:prepare:comment" : Comment input CommentSeed;
};
+1 -1
View File
@@ -20,6 +20,6 @@ interface DocumentFactory id "interface:reader:document-factory"
revision "interface:reader:document-factory@1" {
static operation create id "document:create" : DocumentSeed -> atom-ref<Document> {
call id "document:create:call"
execution transaction targets [browser, server];
execution transaction;
}
}
+1 -1
View File
@@ -1,6 +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 targets [browser, server];
get id "document:view-title:get" execution snapshot;
}
}
+1 -1
View File
@@ -1,6 +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 targets [browser, server];
get id "document:label:get" execution snapshot;
}
}
+5 -4
View File
@@ -1,3 +1,4 @@
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";
@@ -30,7 +31,7 @@ package ReaderRuntime id "package:reader-runtime"
revision "package:reader-runtime@1" {
operation addHighlight id "reader:add-highlight"
: SourceRange -> atom-ref<Highlight>
mode call execution transaction targets [browser, server]
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];
@@ -39,7 +40,7 @@ revision "package:reader-runtime@1" {
};
operation excerpt id "reader:excerpt" : unit -> string
mode call execution snapshot targets [browser, server]
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
@@ -49,11 +50,11 @@ revision "package:reader-runtime@1" {
// A receiverless function needs no synthetic service object.
function validateRange id "reader:validate-range"
: record { range: SourceRange; content: string; } -> bool
execution snapshot targets [browser, server];
execution snapshot targets [JavaScript, server];
function createDocument id "reader:create-document"
: DocumentSeed -> atom-ref<Document>
execution transaction targets [browser, server] requires {
execution transaction targets [JavaScript, server] requires {
creation document id "reader:create:document" : Document input DocumentSeed;
};
query ReplicaInputs id "reader:replica-inputs" root DocumentData
+2 -2
View File
@@ -20,8 +20,8 @@ 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 targets [browser, server];
execution snapshot;
edit id "comment:content:edit"
execution transaction targets [browser, server];
execution transaction;
}
}
+1 -1
View File
@@ -20,6 +20,6 @@ interface DocumentActions id "interface:reader:document-actions"
revision "interface:reader:document-actions@1" {
operation annotate id "document:annotate" : SourceRange -> atom-ref<Highlight> {
call id "document:annotate:call"
execution transaction targets [browser, server];
execution transaction;
}
}
+4 -4
View File
@@ -20,17 +20,17 @@ 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 targets [browser, server];
execution snapshot;
}
queryable value content id "document:content" : string {
get id "document:content:get"
execution snapshot targets [browser, server];
execution snapshot;
}
queryable relation highlights id "document:highlights"
: many-unique atom Highlight {
resolve id "document:highlights:resolve"
execution snapshot targets [browser, server];
execution snapshot;
connect id "document:highlights:connect"
execution transaction targets [browser, server];
execution transaction;
}
}
+1 -1
View File
@@ -20,6 +20,6 @@ interface DocumentFactory id "interface:reader:document-factory"
revision "interface:reader:document-factory@1" {
static operation create id "document:create" : DocumentSeed -> atom-ref<Document> {
call id "document:create:call"
execution transaction targets [browser, server];
execution transaction;
}
}
+5 -5
View File
@@ -20,20 +20,20 @@ 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 targets [browser, server];
execution snapshot;
}
queryable value color id "highlight:color" : string {
get id "highlight:color:get"
execution snapshot targets [browser, server];
execution snapshot;
set id "highlight:color:set"
execution transaction targets [browser, server];
execution transaction;
}
queryable portable value excerpt id "highlight:excerpt" : string {
get id "highlight:excerpt:get"
execution snapshot targets [browser, server];
execution snapshot;
}
queryable relation comments id "highlight:comments" : many-unique atom Comment {
resolve id "highlight:comments:resolve"
execution snapshot targets [browser, server];
execution snapshot;
}
}
+5 -4
View File
@@ -1,3 +1,4 @@
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";
@@ -29,7 +30,7 @@ package ReaderRuntime id "package:reader-runtime"
revision "package:reader-runtime@1" {
operation addHighlight id "reader:add-highlight"
: SourceRange -> atom-ref<Highlight>
mode call execution transaction targets [browser, server]
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];
@@ -38,7 +39,7 @@ revision "package:reader-runtime@1" {
};
operation excerpt id "reader:excerpt" : unit -> string
mode call execution snapshot targets [browser, server]
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
@@ -48,11 +49,11 @@ revision "package:reader-runtime@1" {
// A receiverless function needs no synthetic service object.
function validateRange id "reader:validate-range"
: record { range: SourceRange; content: string; } -> bool
execution snapshot targets [browser, server];
execution snapshot targets [JavaScript, server];
function createDocument id "reader:create-document"
: DocumentSeed -> atom-ref<Document>
execution transaction targets [browser, server] requires {
execution transaction targets [JavaScript, server] requires {
creation document id "reader:create:document" : Document input DocumentSeed;
};
query ReplicaInputs id "reader:replica-inputs" root DocumentData
+2 -2
View File
@@ -1,7 +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 targets [browser,server];
set id "document-navigation:compact:set" execution transaction targets [browser,server];
get id "document-navigation:compact:get" execution snapshot;
set id "document-navigation:compact:set" execution transaction;
}
}
+4 -4
View File
@@ -1,15 +1,15 @@
import interface PresentationState;
interface PresentationMount id "presentation-mount" revision "presentation-mount@1" {
static operation create id "presentation-mount:create" : unit -> interface-ref<PresentationMount> {
call id "presentation-mount:create:call" execution transaction targets [browser,server];
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 targets [browser,server];
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 targets [browser,server];
resolve id "presentation-mount:root:resolve" execution snapshot;
}
operation attach id "presentation-mount:attach" : interface-ref<PresentationState> -> unit {
call id "presentation-mount:attach:call" execution transaction targets [browser,server];
call id "presentation-mount:attach:call" execution transaction;
}
}
+6 -5
View File
@@ -1,3 +1,4 @@
symbol JavaScript id "target:org.quixos.execution.javascript@1";
import interface PresentationStates;
import interface ReaderNavigation;
external atom ReaderNavigationRecord id "reader-navigation-record";
@@ -6,24 +7,24 @@ 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<PresentationState>>;} -> interface-ref<ReaderNavigation> execution transaction targets [browser,server] requires {
function createReaderState id "presentation-runtime:create-reader-state" : record {occurrence:string;parent:optional<interface-ref<PresentationState>>;} -> interface-ref<ReaderNavigation> execution transaction targets [JavaScript,server] requires {
creation navigation id "navigation" : ReaderNavigationRecord input record {occurrence:string;parent:optional<interface-ref<PresentationState>>;};
};
function createPresentationMount id "presentation-runtime:create" : unit -> interface-ref<PresentationMount> execution transaction targets [browser,server] requires {
function createPresentationMount id "presentation-runtime:create" : unit -> interface-ref<PresentationMount> execution transaction targets [JavaScript,server] requires {
creation presentationMount id "presentationMount" : PresentationMountRecord input unit;
};
operation attach id "presentation-runtime:attach" : interface-ref<PresentationState> -> unit mode call execution transaction targets [browser,server] receiver any requires {
operation attach id "presentation-runtime:attach" : interface-ref<PresentationState> -> 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<object Navigation implements PresentationState> id "presentation-runtime:ensure" : record {presentationMount:interface-ref<PresentationMount>;occurrence:string;parent:optional<interface-ref<PresentationState>>;} -> ref<Navigation> mode call execution transaction targets [browser,server] receiver any requires {
operation ensure<object Navigation implements PresentationState> id "presentation-runtime:ensure" : record {presentationMount:interface-ref<PresentationMount>;occurrence:string;parent:optional<interface-ref<PresentationState>>;} -> ref<Navigation> 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<Navigation> using [create.call];
};
function createState<object Navigation> id "presentation-runtime:create-state" : record {occurrence:string;parent:optional<interface-ref<PresentationState>>;} -> ref<Navigation> execution transaction targets [browser,server] requires {
function createState<object Navigation> id "presentation-runtime:create-state" : record {occurrence:string;parent:optional<interface-ref<PresentationState>>;} -> ref<Navigation> execution transaction targets [JavaScript,server] requires {
creation navigation id "navigation" : Navigation input record {occurrence:string;parent:optional<interface-ref<PresentationState>>;};
};
query ReaderStateInputs id "reader-state:inputs" root PresentationStates<atom ReaderNavigationRecord> document "queries/reader-inputs.graphql" operation "ReaderState" { replica; view ReaderNavigationRecord as ReaderNavigation; max rows 100; }
+3 -3
View File
@@ -1,12 +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 targets [browser,server];
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 targets [browser,server];
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 targets [browser,server];
resolve id "state:parent:resolve" execution snapshot;
}
}
+2 -2
View File
@@ -2,9 +2,9 @@ import interface PresentationState;
import interface PresentationMount;
interface PresentationStates<object Navigation implements PresentationState> 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 targets [browser,server];
resolve id "presentation-states:states:resolve" execution snapshot;
}
operation ensure id "presentation-states:ensure" : record {presentationMount:interface-ref<PresentationMount>;occurrence:string;parent:optional<interface-ref<PresentationState>>;} -> ref<Navigation> {
call id "presentation-states:ensure:call" execution transaction targets [browser,server];
call id "presentation-states:ensure:call" execution transaction;
}
}
+2 -2
View File
@@ -1,7 +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 targets [browser,server];
set id "reader-navigation:mode:set" execution transaction targets [browser,server];
get id "reader-navigation:mode:get" execution snapshot;
set id "reader-navigation:mode:set" execution transaction;
}
}
+1 -1
View File
@@ -1,6 +1,6 @@
import interface PresentationState;
interface StateFactory<object Navigation implements PresentationState> id "state-factory" revision "state-factory@1" {
static operation create id "state-factory:create" : record {occurrence:string;parent:optional<interface-ref<PresentationState>>;} -> ref<Navigation> {
call id "state-factory:create:call" execution transaction targets [browser,server];
call id "state-factory:create:call" execution transaction;
}
}
+5 -5
View File
@@ -1,15 +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 targets [browser, server];
get id "recurring:occurrence:get" execution snapshot;
}
queryable value due id "recurring:due" : int64 {
get id "recurring:due:get" execution snapshot targets [browser, server];
get id "recurring:due:get" execution snapshot;
}
queryable value title id "recurring:title" : string {
get id "recurring:title:get" execution snapshot targets [browser, server];
set id "recurring:title:set" execution transaction targets [browser, server];
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 targets [browser, server];
call id "recurring:complete:call" execution transaction;
}
}
+2 -1
View File
@@ -1,7 +1,8 @@
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 [browser, server] receiver atom TaskObject requires {
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];
};
+3 -3
View File
@@ -1,11 +1,11 @@
interface TreeData id "tree:data" revision "tree:data@1" {
operation reparent id "tree:reparent" : optional<interface-ref<TreeData>> -> unit {
call id "tree:reparent:call" execution transaction targets [browser, server];
call id "tree:reparent:call" execution transaction;
}
queryable relation parent id "tree:parent" : optional-one interface TreeData {
resolve id "tree:parent:resolve" execution snapshot targets [browser, server];
resolve id "tree:parent:resolve" execution snapshot;
}
queryable relation children id "tree:children" : many interface TreeData {
resolve id "tree:children:resolve" execution snapshot targets [browser, server];
resolve id "tree:children:resolve" execution snapshot;
}
}
+2 -1
View File
@@ -1,8 +1,9 @@
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<interface-ref<TreeData>> -> unit
mode call execution transaction targets [browser, server] receiver atom Node requires {
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<interface-ref<TreeData>> -> unit
+1 -1
View File
@@ -609,7 +609,7 @@ test("edge read targets honor declared interface prerequisites without inferring
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 targets [browser,server];}}',
'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));
+7 -7
View File
@@ -58,12 +58,12 @@ const contract = `external atom Document id "document";
type Event = record { document: atom-ref<Document>; title: string; };
interface Stream id "stream" revision "stream@1" {
operation publish id "publish" : Event -> record { publication: string; } {
call id "publish:call" execution transaction targets [server];
call id "publish:call" execution transaction;
}
queryable value head id "head" : optional<uint64> { get id "head:get" execution snapshot targets [server]; }
queryable value head id "head" : optional<uint64> { get id "head:get" execution snapshot; }
operation readPage id "page" : record { after: optional<string>; limit: uint32; }
-> record { entries: list<record { publication: string; position: uint64; payload: Event; }>; next: optional<string>; asOfHead: optional<uint64>; } {
call id "page:call" execution snapshot targets [server];
call id "page:call" execution snapshot;
}
}`;
const assembly = `workspace Journals id "journals" revision "journals@1" commit "${source.commit}" {
@@ -127,7 +127,7 @@ test("subscription storage infers its typed handler and retains configuration re
`external atom Document id "document";
interface Receiver id "receiver" revision "receiver@1" {
operation receive id "receive" : record { event: record { document: atom-ref<Document>; title: string; }; parameters: string; } -> unit {
call id "receive:call" execution transaction targets [server];
call id "receive:call" execution transaction;
}
}`,
);
@@ -139,12 +139,12 @@ 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<Stream>; handler: interface-ref<Receiver>; parameters: string; start: Start; } -> unit {
call id "attach:call" execution transaction targets [server];
call id "attach:call" execution transaction;
}
queryable value status id "status" : record { status: string; generation: uint64; cursor: uint64; failure: optional<record { publication: string; code: string; message: string; }>; } {
get id "status:get" execution snapshot targets [server];
get id "status:get" execution snapshot;
}
operation retire id "retire" : unit -> unit { call id "retire:call" execution transaction targets [server]; }
operation retire id "retire" : unit -> unit { call id "retire:call" execution transaction; }
}`,
);
const ws = assembly
+7 -6
View File
@@ -12,19 +12,20 @@ const source = { repository: "https://example.test/keyed.git", commit: "a".repea
const iface = compileCapabilityResourceSource(
`interface Index<object Item> id "index" revision "index@1" {
queryable relation entries id "entries" : many object Item keyed "string" {
resolve id "resolve" execution snapshot targets [browser,server];
connect id "connect" execution transaction targets [browser,server];
resolve id "resolve" execution snapshot;
connect id "connect" execution transaction;
}
operation add id "add" : record {mapKey:string;target:ref<Item>;} -> unit {
call id "add-call" execution transaction targets [browser,server];
call id "add-call" execution transaction;
}
}`,
{ source },
);
assert.ok(iface.ok && iface.resource.kind === "interface", JSON.stringify(iface.diagnostics));
const pkg = compileCapabilityResourceSource(
`package Runtime id "runtime" revision "runtime@1" {
operation add<object Item> id "add" : record {mapKey:string;target:ref<Item>;} -> unit mode call execution transaction targets [browser,server] receiver any requires {
`symbol JavaScript id "target:org.quixos.execution.javascript@1";
package Runtime id "runtime" revision "runtime@1" {
operation add<object Item> id "add" : record {mapKey:string;target:ref<Item>;} -> unit mode call execution transaction targets [JavaScript,server] receiver any requires {
edge entries id "entries" : many object Item keyed "string" [resolve,connect];
};
}`,
@@ -61,7 +62,7 @@ test("generic keyed relationships and portable ports have matching checked shape
const plan = compileExecutionPlan(result.workspace);
assert.doesNotThrow(() => compileKernelProgram(plan));
const bindings = generatePortableTypeScriptBindings(plan.definition.bindingSchema, "runtime@1", {
target: "browser",
target: "target:org.quixos.execution.javascript@1",
});
assert.match(bindings, /readonly "mapKey": string/);
assert.match(bindings, /readonly "connect": \(input: \{ readonly "mapKey": string;/);
+4 -5
View File
@@ -5,7 +5,7 @@ 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 targets [server];
call id "remove:call" execution transaction;
}
}`;
const workspace = `workspace Lifecycle id "workspace:lifecycle" revision "workspace:lifecycle@1" commit "${"a".repeat(40)}" {
@@ -45,10 +45,9 @@ test("lifecycle binding and endpoint policies survive formatting and kernel comp
});
test("deletion rejects browser, snapshot, remote, class, payload and unknown primitive promises", () => {
for (const [iface, ws] of [
[contract.replace("targets [server]", "targets [browser, server]"), workspace],
[contract.replace("transaction targets", "snapshot targets"), workspace],
[contract.replace("execution transaction targets [server]", "execution remote"), workspace],
[contract.replace("execution transaction targets [server]", ""), workspace],
[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],
+14 -9
View File
@@ -2,6 +2,7 @@ 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();
@@ -23,15 +24,19 @@ test("complete reader contracts compose factories, portable selectors, CRDT edit
);
});
test("reader composition reports the dependency that weakens a portable promise", async () => {
await assert.rejects(
load((name, text) =>
name === "ReaderRuntime.qx"
? text.replace(/(operation excerpt[\s\S]*?targets )\[browser, server\]/, "$1[server]")
: text,
),
/does not provide execution target browser/,
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"
@@ -56,6 +61,6 @@ test("a field can remain server-queryable while failing local query placement",
await compileQuery(admission, workspace.interfaceImports, read);
await assert.rejects(
compileQuery(local, workspace.interfaceImports, read),
/browser snapshot getter for HighlightData.color/,
/graph snapshot getter for HighlightData.color/,
);
});
+29 -12
View File
@@ -22,7 +22,9 @@ test("service bindings depend on their consumed contract, not workspace or sourc
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: "browser" });
const source = generatePortableTypeScriptBindings(schema, "package:reader-runtime@1", {
target: "target:org.quixos.execution.javascript@1",
});
assert.match(source, /readonly "sourceRevision.get": \(\) => Promise<string>/);
assert.match(
source,
@@ -32,22 +34,30 @@ test("reader portable bindings have selected facets, creation inputs and stable
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: "browser" }));
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 = "browser"', 'target = "server"'), 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: "browser" });
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: ["browser"] };
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: ["browser"] };
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 browser/);
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/);
@@ -59,7 +69,9 @@ test("closed generic portable handlers have distinct concrete codecs and no univ
const schema = plan.definition.bindingSchema;
const pkg = schema.packages[0]!;
assert.equal(pkg.exports.length, 2);
const generated = generatePortableTypeScriptBindings(schema, pkg.revisionId, { target: "browser" });
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<bigint>/);
for (const entry of pkg.exports) {
@@ -69,7 +81,7 @@ test("closed generic portable handlers have distinct concrete codecs and no univ
}
assert.notEqual(pkg.exports[0]!.displayName, pkg.exports[1]!.displayName);
assert.equal(
generated.replace('target = "browser"', 'target = "server"'),
generated.replace('target = "target:org.quixos.execution.javascript@1"', 'target = "server"'),
generatePortableTypeScriptBindings(schema, pkg.revisionId, { target: "server" }),
);
});
@@ -90,7 +102,10 @@ test("a portable subset of a mixed interface excludes remote dependencies", asyn
displayName: "remote",
operations: [operation],
});
const generate = () => generatePortableTypeScriptBindings(schema, "package:reader-runtime@1", { target: "browser" });
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/);
@@ -99,7 +114,7 @@ test("a portable subset of a mixed interface excludes remote dependencies", asyn
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: "browser" });
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"),
@@ -127,7 +142,9 @@ test("static interface ports keep unit arguments explicit for generic factory co
op.inputType = { kind: "builtin", name: "unit" };
port.requirement.operations = [op.id];
delete port.requirement.receiver;
const generated = generatePortableTypeScriptBindings(schema, "package:reader-runtime@1", { target: "browser" });
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/);
});
+4 -3
View File
@@ -9,17 +9,18 @@ export function portableGenericPlan() {
`
interface Echo<value T> id "echo-interface" revision "echo-interface@2" {
operation echo id "echo-member" : T -> T {
call id "echo-call" execution snapshot targets [browser, server];
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<value T> id "echo" : T -> T mode call
execution snapshot targets [browser, server] receiver any;
execution snapshot targets [JavaScript, server] receiver any;
}`,
{ source: { ...source, commit: "b".repeat(40) } },
);
+2 -2
View File
@@ -24,8 +24,8 @@ async function fixture(replacement = "First", change?: (artifact: ReactPreparati
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 targets [browser, server];} queryable value detail id "detail" : string {get id "detail:get" execution snapshot targets [browser, server];} }',
'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 targets [browser, server];} }',
'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; }',
]) {
+5 -5
View File
@@ -437,17 +437,17 @@ test("React tooling generates checked owner queries from imported closed navigat
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 targets [browser,server];}
queryable relation presentationMount id "mount" : optional-one interface PresentationMount {resolve id "mount:resolve" execution snapshot targets [browser,server];}
queryable relation parent id "parent" : optional-one interface PresentationState {resolve id "parent:resolve" execution snapshot targets [browser,server];}
queryable relation source id "source" : exactly-one interface Subject {resolve id "source:resolve" execution snapshot targets [browser,server];}
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<value T> id "navigation" revision "navigation@1" requires PresentationState {
queryable value custom id "custom" : T {get id "custom:get" execution snapshot targets [browser,server];}
queryable value custom id "custom" : T {get id "custom:get" execution snapshot;}
}`,
[mount, subject, state],
);
+1 -1
View File
@@ -6,7 +6,7 @@ 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 targets [browser,server];}
queryable value title id "title" : string {get id "title:get" execution snapshot;}
queryable rpc value remote id "remote" : string {get id "remote:get";}
}`,
{ source },
+2 -2
View File
@@ -7,8 +7,8 @@ import { defaultQueryBudgets, type QueryDeclaration } from "../src/query/types.j
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 targets [browser, server]; }
queryable value content id "content" : record { text:string; tags:list<string>; metadata:optional<record { score:int32; }>; } { get id "content:get" execution snapshot targets [browser, server]; }
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<string>; metadata:optional<record { score:int32; }>; } { get id "content:get" execution snapshot; }
}`,
{ source },
);
+9 -8
View File
@@ -6,10 +6,11 @@ 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 targets [server]; }
operation peek id "peek" : interface-ref<Data> -> string { call id "peek:call" execution snapshot targets [server]; }
value content id "content" : string { get id "content:get" execution snapshot; }
operation peek id "peek" : interface-ref<Data> -> string { call id "peek:call" execution snapshot; }
}`;
const pkg = `external atom Item id "item";
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<Data> -> string mode call execution snapshot targets [server]
@@ -83,15 +84,15 @@ test("closed generic specialization preserves reference-selected interface ports
});
test("portable input-selected reads use installed support beyond the public minimum", () => {
const portable = pkg.replaceAll("targets [server]", "targets [browser, server]");
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, [
"browser",
"server",
"target:org.quixos.execution.javascript@1",
]);
const result = compile(portable, workspace, iface.replaceAll("targets [server]", "targets [browser, server]"));
const result = compile(portable, workspace, iface);
assert.ok(result.ok, JSON.stringify(result.diagnostics));
});
@@ -102,8 +103,8 @@ test("input-selected transactions compose only within a compatible transaction c
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 targets [server]; }',
'operation update id "content" : unit -> string { call id "content:get" execution transaction targets [server]; }',
'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(
+3 -3
View File
@@ -35,7 +35,7 @@ test("execution scaffolds route typed handlers by declared effect and target wit
id: "export:" + name,
declaration: `function ${name} id "export:${name}" : unit -> unit execution ${execution};`,
});
await add("both", "transaction targets [browser, server]");
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");
@@ -48,8 +48,8 @@ test("execution scaffolds route typed handlers by declared effect and target wit
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 [browser, server]"), /unique/);
await assert.rejects(add("invalid", "snapshot targets [remote]"), /browser\/server/);
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/);
});