Add activated workspace reflection and Studio definition watches

Expose bounded snapshot-bound definition reads and latest-only watches from the serving checked bundle. Add checked reflection dependency ports, generated SDK bindings and selector-scoped invocation authority.

Move Studio factory, contract and query discovery to public reflection inventories. Share an active-definition watch across canvas and standalone routes and invalidate/reload on epoch changes instead of polling GetWorkspace.

Document the API and deferred scope; cover compiler/codegen, SDK injection, transport authorization, activation publication, pagination and reconnect behavior. Keep all work local; no deployment or publication.
This commit is contained in:
Timothy J. Aveni
2026-09-19 10:18:47 -07:00
parent 1aa55f7a35
commit 757531bb63
20 changed files with 2870 additions and 2105 deletions
+7 -2
View File
@@ -207,7 +207,8 @@ dependencyBlock
; ;
dependencyPort dependencyPort
: STATE identifier ID stringLiteral COLON valueType primitiveList SEMI : REFLECTION identifier ID stringLiteral LBRACK identifierList RBRACK SEMI
| STATE identifier ID stringLiteral COLON valueType primitiveList SEMI
| EDGE identifier ID stringLiteral COLON cardinality targetConstraint primitiveList SEMI | EDGE identifier ID stringLiteral COLON cardinality targetConstraint primitiveList SEMI
| INTERFACE identifier ID stringLiteral COLON identifier typeArguments? SEMI | INTERFACE identifier ID stringLiteral COLON identifier typeArguments? SEMI
| CONSTRUCTOR identifier ID stringLiteral COLON identifier (INPUT valueType)? SEMI | CONSTRUCTOR identifier ID stringLiteral COLON identifier (INPUT valueType)? SEMI
@@ -325,7 +326,8 @@ dependencyBindingBlock
; ;
dependencyBinding dependencyBinding
: identifier TO STATE identifier (VIA EDGE identifier DOT identifier)? SEMI : identifier TO WORKSPACE REFLECTION LBRACK identifierList RBRACK SEMI
| identifier TO STATE identifier (VIA EDGE identifier DOT identifier)? SEMI
| identifier TO EDGE identifier DOT identifier (VIA EDGE identifier DOT identifier)? SEMI | identifier TO EDGE identifier DOT identifier (VIA EDGE identifier DOT identifier)? SEMI
| identifier TO INTERFACE identifier typeArguments? (VIA EDGE identifier DOT identifier)? SEMI | identifier TO INTERFACE identifier typeArguments? (VIA EDGE identifier DOT identifier)? SEMI
| identifier TO CONSTRUCTOR identifier SEMI | identifier TO CONSTRUCTOR identifier SEMI
@@ -397,6 +399,8 @@ jsonArray
identifier identifier
: IDENTIFIER : IDENTIFIER
| INTERFACES
| REFLECTION
| SOURCE | SOURCE
| QUERY | QUERY
| SPECIALIZE | SPECIALIZE
@@ -423,6 +427,7 @@ RESULT_BYTES: 'result-bytes';
RPC_CALLS: 'rpc-calls'; RPC_CALLS: 'rpc-calls';
DEADLINE_MS: 'deadline-ms'; DEADLINE_MS: 'deadline-ms';
QUERY: 'query'; QUERY: 'query';
REFLECTION: 'reflection';
SPECIALIZE: 'specialize'; SPECIALIZE: 'specialize';
ROOT: 'root'; ROOT: 'root';
DOCUMENT: 'document'; DOCUMENT: 'document';
+79
View File
@@ -9,6 +9,8 @@ import "quixos/refs.proto";
import "quixos/runtime.proto"; import "quixos/runtime.proto";
service OrchestratorRuntime { service OrchestratorRuntime {
rpc ReadDefinitions(DefinitionRequest) returns (DefinitionPage);
rpc WatchDefinitions(DefinitionRequest) returns (stream DefinitionPage);
rpc ExecuteQuery(camino.QueryRequest) returns (camino.QueryResponse); rpc ExecuteQuery(camino.QueryRequest) returns (camino.QueryResponse);
rpc WatchQuery(camino.QueryRequest) returns (stream QueryEvent); rpc WatchQuery(camino.QueryRequest) returns (stream QueryEvent);
rpc TryConform(TryConformRequest) returns (TryConformResponse); rpc TryConform(TryConformRequest) returns (TryConformResponse);
@@ -26,6 +28,83 @@ service OrchestratorRuntime {
rpc CloseActivation(CloseActivationRequest) returns (CloseActivationResponse); rpc CloseActivation(CloseActivationRequest) returns (CloseActivationResponse);
} }
// Activated definitions, not runtime metrics or object data. Nested type JSON
// uses the compiler's canonical ValueType/InterfaceRevision public contracts.
message DefinitionSelector {
string kind = 1; // active | atoms | interfaces | conformances | queries | indexes
repeated string ids = 2;
string atom_id = 3;
string interface_revision_id = 4;
string package_revision_id = 5;
}
message DefinitionIdentity {
string workspace_id = 1;
string workspace_revision_id = 2;
string source_root_commit = 3;
string workspace_epoch = 4;
string definition_digest = 5;
}
message DefinitionRequest {
DefinitionSelector selector = 1;
uint32 first = 2; // 1..100; 0 uses 50
string after = 3;
string snapshot = 4; // issued definition_digest; current serving snapshot only
string workspace_id = 5; // required for injected package authority
string workspace_epoch = 6; // required for invocation-bound package calls
}
message ReflectedAtom {
string id = 1;
string display_name = 2;
string constructor_input_type_json = 3;
}
message ReflectedInterface {
string id = 1;
string interface_id = 2;
string display_name = 3;
string contract_json = 4; // canonical interface contract, excluding source location
}
message ReflectedConformance {
string id = 1;
string atom_id = 2;
string interface_revision_id = 3;
string definition_id = 4;
repeated string operation_ids = 5;
repeated ClassCapability class_capabilities = 6;
bool instance = 7;
}
message ReflectedQuery {
string id = 1;
string name = 2;
string package_revision_id = 3;
string definition_digest = 4;
string binding_digest = 5;
string root_kind = 6;
string interface_revision_id = 7;
string variables_type_json = 8;
string output_type_json = 9;
string effects_json = 10;
bool watch = 11;
WorkspaceQueryScope workspace_scope = 12;
}
message ReflectedIndex {
string id = 1;
string contract_json = 2;
}
message DefinitionRecord {
oneof definition {
ReflectedAtom atom = 1;
ReflectedInterface interface = 2;
ReflectedConformance conformance = 3;
ReflectedQuery query = 4;
ReflectedIndex index = 5;
}
}
message DefinitionPage {
DefinitionIdentity identity = 1;
repeated DefinitionRecord records = 2;
string next_cursor = 3;
}
message QueryEvent { message QueryEvent {
string run_id = 1; string run_id = 1;
uint64 sequence = 2; uint64 sequence = 2;
+7
View File
@@ -45,12 +45,19 @@ message InjectedDependency {
string constructor_atom_id = 5; string constructor_atom_id = 5;
string query_id = 7; string query_id = 7;
WorkspaceQueryDependency workspace_query = 8; WorkspaceQueryDependency workspace_query = 8;
ReflectionDependency reflection = 9;
} }
// Defaults to the invocation receiver. A checked dependency traversal can // Defaults to the invocation receiver. A checked dependency traversal can
// select a related object explicitly before the package runtime starts. // select a related object explicitly before the package runtime starts.
string object_id = 6; string object_id = 6;
} }
message ReflectionDependency {
string workspace_id = 1;
string workspace_epoch = 2;
repeated string selectors = 3;
}
// Scope evidence is not authority by itself. The caller must also hold the // Scope evidence is not authority by itself. The caller must also hold the
// matching workspace-query grant (or the authenticated workspace user grant). // matching workspace-query grant (or the authenticated workspace user grant).
message WorkspaceQueryScope { message WorkspaceQueryScope {
+2
View File
@@ -79,6 +79,8 @@ export const genericImplementationType = (
const requirement = entry.requirement, const requirement = entry.requirement,
scope = rootScope(); scope = rootScope();
switch (requirement.kind) { switch (requirement.kind) {
case "reflection":
return `QxReflectionPort<${requirement.selectors.map((value) => JSON.stringify(value)).join(" | ")}>`;
case "state": case "state":
return object([ return object([
...requirement.primitives.map((primitive): [string, string] => { ...requirement.primitives.map((primitive): [string, string] => {
+6
View File
@@ -144,6 +144,11 @@ export const generateTypeScriptBindings = (
const port = (entry: DependencyPort): { type: string; spec: unknown } => { const port = (entry: DependencyPort): { type: string; spec: unknown } => {
const requirement = entry.requirement; const requirement = entry.requirement;
switch (requirement.kind) { switch (requirement.kind) {
case "reflection":
return {
type: `QxReflectionPort<${requirement.selectors.map((value) => q(value)).join(" | ")}>`,
spec: { kind: "reflection", id: entry.id, selectors: requirement.selectors },
};
case "query": { case "query": {
const query = schema.packages const query = schema.packages
.find((candidate) => candidate.revisionId === requirement.packageRevisionId) .find((candidate) => candidate.revisionId === requirement.packageRevisionId)
@@ -378,6 +383,7 @@ export const generateTypeScriptBindings = (
"RelationshipCollection", "RelationshipCollection",
"RelationshipEntry", "RelationshipEntry",
"QxQuerySnapshot", "QxQuerySnapshot",
"QxReflectionPort",
"QxQueryDescriptor", "QxQueryDescriptor",
].filter((name) => new RegExp(`\\b${name}\\b`).test(signatures)); ].filter((name) => new RegExp(`\\b${name}\\b`).test(signatures));
return ( return (
File diff suppressed because one or more lines are too long
@@ -3,257 +3,259 @@ RESULT_BYTES=2
RPC_CALLS=3 RPC_CALLS=3
DEADLINE_MS=4 DEADLINE_MS=4
QUERY=5 QUERY=5
SPECIALIZE=6 REFLECTION=6
ROOT=7 SPECIALIZE=7
DOCUMENT=8 ROOT=8
FRAGMENTS=9 DOCUMENT=9
VIEW=10 FRAGMENTS=10
MAX=11 VIEW=11
ALLOW=12 MAX=12
POLL=13 ALLOW=13
QUERYABLE=14 POLL=14
RPC=15 QUERYABLE=15
QUERY_REASON=16 RPC=16
TYPE=17 QUERY_REASON=17
OBJECT=18 TYPE=18
STORABLE=19 OBJECT=19
IMPLEMENTS=20 STORABLE=20
REF=21 IMPLEMENTS=21
FRAGMENT=22 REF=22
IMPORT=23 FRAGMENT=23
EXTERNAL=24 IMPORT=24
ATOM=25 EXTERNAL=25
INTERFACE=26 ATOM=26
INTERFACES=27 INTERFACE=27
PACKAGE=28 INTERFACES=28
VALUE=29 PACKAGE=29
RELATION=30 VALUE=30
OPERATION=31 RELATION=31
FUNCTION=32 OPERATION=32
CONSTRUCTOR=33 FUNCTION=33
CONSTRUCTS=34 CONSTRUCTOR=34
INPUT=35 CONSTRUCTS=35
CONFORM=36 INPUT=36
AS=37 CONFORM=37
BIND=38 AS=38
STATIC=39 BIND=39
TO=40 STATIC=40
PRIVATE=41 TO=41
SHARED=42 PRIVATE=42
STATE=43 SHARED=43
EDGE=44 STATE=44
PROJECTION=45 EDGE=45
WITH=46 PROJECTION=46
USING=47 WITH=47
VIA=48 USING=48
MATERIALIZE=49 VIA=49
IF=50 MATERIALIZE=50
ABSENT=51 IF=51
ON=52 ABSENT=52
POLICY=53 ON=53
DEFAULT=54 POLICY=54
SOURCE=55 DEFAULT=55
REPOSITORY=56 SOURCE=56
COMMIT=57 REPOSITORY=57
REVISION=58 COMMIT=58
SEMANTIC_MAJOR=59 REVISION=59
ON_DELETE=60 SEMANTIC_MAJOR=60
RETAIN_OTHER=61 ON_DELETE=61
KEYED=62 RETAIN_OTHER=62
PUBLIC_TRAVERSAL=63 KEYED=63
ID=64 PUBLIC_TRAVERSAL=64
DOC=65 ID=65
MODE=66 DOC=66
EMITS=67 MODE=67
RECEIVER=68 EMITS=68
REQUIRES=69 RECEIVER=69
ANY=70 REQUIRES=70
GET=71 ANY=71
SET=72 GET=72
WATCH=73 SET=73
START=74 WATCH=74
STOP=75 START=75
READ=76 STOP=76
WRITE=77 READ=77
RESOLVE=78 WRITE=78
CONNECT=79 RESOLVE=79
DISCONNECT=80 CONNECT=80
CALL=81 DISCONNECT=81
WATCH_START=82 CALL=82
WATCH_STOP=83 WATCH_START=83
SUBSCRIBE=84 WATCH_STOP=84
UNSUBSCRIBE=85 SUBSCRIBE=85
OPTIMISTIC_REGISTER=86 UNSUBSCRIBE=86
CRDT=87 OPTIMISTIC_REGISTER=87
OPTIONAL_ONE=88 CRDT=88
EXACTLY_ONE=89 OPTIONAL_ONE=89
MANY_UNIQUE=90 EXACTLY_ONE=90
MANY=91 MANY_UNIQUE=91
ORDERED=92 MANY=92
UNIT=93 ORDERED=93
WATCH_HANDLE=94 UNIT=94
MESSAGE=95 WATCH_HANDLE=95
ATOM_REF=96 MESSAGE=96
INTERFACE_REF=97 ATOM_REF=97
OPTIONAL=98 INTERFACE_REF=98
LIST=99 OPTIONAL=99
RECORD=100 LIST=100
BOOL=101 RECORD=101
BYTES=102 BOOL=102
DOUBLE=103 BYTES=103
INT32=104 DOUBLE=104
INT64=105 INT32=105
STRING=106 INT64=106
UINT32=107 STRING=107
UINT64=108 UINT32=108
TRUE=109 UINT64=109
FALSE=110 TRUE=110
NULL=111 FALSE=111
ARROW=112 NULL=112
COLON=113 ARROW=113
SEMI=114 COLON=114
COMMA=115 SEMI=115
DOT=116 COMMA=116
LBRACE=117 DOT=117
RBRACE=118 LBRACE=118
LBRACK=119 RBRACE=119
RBRACK=120 LBRACK=120
LPAREN=121 RBRACK=121
RPAREN=122 LPAREN=122
LT=123 RPAREN=123
GT=124 LT=124
AMP=125 GT=125
EQUAL=126 AMP=126
INTEGER=127 EQUAL=127
JSON_NUMBER=128 INTEGER=128
IDENTIFIER=129 JSON_NUMBER=129
STRING_LITERAL=130 IDENTIFIER=130
LINE_COMMENT=131 STRING_LITERAL=131
BLOCK_COMMENT=132 LINE_COMMENT=132
WS=133 BLOCK_COMMENT=133
WS=134
'workspace'=1 'workspace'=1
'result-bytes'=2 'result-bytes'=2
'rpc-calls'=3 'rpc-calls'=3
'deadline-ms'=4 'deadline-ms'=4
'query'=5 'query'=5
'specialize'=6 'reflection'=6
'root'=7 'specialize'=7
'document'=8 'root'=8
'fragments'=9 'document'=9
'view'=10 'fragments'=10
'max'=11 'view'=11
'allow'=12 'max'=12
'poll'=13 'allow'=13
'queryable'=14 'poll'=14
'rpc'=15 'queryable'=15
'query-reason'=16 'rpc'=16
'type'=17 'query-reason'=17
'object'=18 'type'=18
'storable'=19 'object'=19
'implements'=20 'storable'=20
'ref'=21 'implements'=21
'fragment'=22 'ref'=22
'import'=23 'fragment'=23
'external'=24 'import'=24
'atom'=25 'external'=25
'interface'=26 'atom'=26
'interfaces'=27 'interface'=27
'package'=28 'interfaces'=28
'value'=29 'package'=29
'relation'=30 'value'=30
'operation'=31 'relation'=31
'function'=32 'operation'=32
'constructor'=33 'function'=33
'constructs'=34 'constructor'=34
'input'=35 'constructs'=35
'conform'=36 'input'=36
'as'=37 'conform'=37
'bind'=38 'as'=38
'static'=39 'bind'=39
'to'=40 'static'=40
'private'=41 'to'=41
'shared'=42 'private'=42
'state'=43 'shared'=43
'edge'=44 'state'=44
'projection'=45 'edge'=45
'with'=46 'projection'=46
'using'=47 'with'=47
'via'=48 'using'=48
'materialize'=49 'via'=49
'if'=50 'materialize'=50
'absent'=51 'if'=51
'on'=52 'absent'=52
'policy'=53 'on'=53
'default'=54 'policy'=54
'source'=55 'default'=55
'repository'=56 'source'=56
'commit'=57 'repository'=57
'revision'=58 'commit'=58
'semantic-major'=59 'revision'=59
'on-delete'=60 'semantic-major'=60
'retain-other'=61 'on-delete'=61
'keyed'=62 'retain-other'=62
'public-traversal'=63 'keyed'=63
'id'=64 'public-traversal'=64
'doc'=65 'id'=65
'mode'=66 'doc'=66
'emits'=67 'mode'=67
'receiver'=68 'emits'=68
'requires'=69 'receiver'=69
'any'=70 'requires'=70
'get'=71 'any'=71
'set'=72 'get'=72
'watch'=73 'set'=73
'start'=74 'watch'=74
'stop'=75 'start'=75
'read'=76 'stop'=76
'write'=77 'read'=77
'resolve'=78 'write'=78
'connect'=79 'resolve'=79
'disconnect'=80 'connect'=80
'call'=81 'disconnect'=81
'watch-start'=82 'call'=82
'watch-stop'=83 'watch-start'=83
'subscribe'=84 'watch-stop'=84
'unsubscribe'=85 'subscribe'=85
'optimistic-register'=86 'unsubscribe'=86
'crdt'=87 'optimistic-register'=87
'optional-one'=88 'crdt'=88
'exactly-one'=89 'optional-one'=89
'many-unique'=90 'exactly-one'=90
'many'=91 'many-unique'=91
'ordered'=92 'many'=92
'unit'=93 'ordered'=93
'watch-handle'=94 'unit'=94
'message'=95 'watch-handle'=95
'atom-ref'=96 'message'=96
'interface-ref'=97 'atom-ref'=97
'optional'=98 'interface-ref'=98
'list'=99 'optional'=99
'record'=100 'list'=100
'bool'=101 'record'=101
'bytes'=102 'bool'=102
'double'=103 'bytes'=103
'int32'=104 'double'=104
'int64'=105 'int32'=105
'string'=106 'int64'=106
'uint32'=107 'string'=107
'uint64'=108 'uint32'=108
'true'=109 'uint64'=109
'false'=110 'true'=110
'null'=111 'false'=111
'->'=112 'null'=112
':'=113 '->'=113
';'=114 ':'=114
','=115 ';'=115
'.'=116 ','=116
'{'=117 '.'=117
'}'=118 '{'=118
'['=119 '}'=119
']'=120 '['=120
'('=121 ']'=121
')'=122 '('=122
'<'=123 ')'=123
'>'=124 '<'=124
'&'=125 '>'=125
'='=126 '&'=126
'='=127
File diff suppressed because one or more lines are too long
@@ -3,257 +3,259 @@ RESULT_BYTES=2
RPC_CALLS=3 RPC_CALLS=3
DEADLINE_MS=4 DEADLINE_MS=4
QUERY=5 QUERY=5
SPECIALIZE=6 REFLECTION=6
ROOT=7 SPECIALIZE=7
DOCUMENT=8 ROOT=8
FRAGMENTS=9 DOCUMENT=9
VIEW=10 FRAGMENTS=10
MAX=11 VIEW=11
ALLOW=12 MAX=12
POLL=13 ALLOW=13
QUERYABLE=14 POLL=14
RPC=15 QUERYABLE=15
QUERY_REASON=16 RPC=16
TYPE=17 QUERY_REASON=17
OBJECT=18 TYPE=18
STORABLE=19 OBJECT=19
IMPLEMENTS=20 STORABLE=20
REF=21 IMPLEMENTS=21
FRAGMENT=22 REF=22
IMPORT=23 FRAGMENT=23
EXTERNAL=24 IMPORT=24
ATOM=25 EXTERNAL=25
INTERFACE=26 ATOM=26
INTERFACES=27 INTERFACE=27
PACKAGE=28 INTERFACES=28
VALUE=29 PACKAGE=29
RELATION=30 VALUE=30
OPERATION=31 RELATION=31
FUNCTION=32 OPERATION=32
CONSTRUCTOR=33 FUNCTION=33
CONSTRUCTS=34 CONSTRUCTOR=34
INPUT=35 CONSTRUCTS=35
CONFORM=36 INPUT=36
AS=37 CONFORM=37
BIND=38 AS=38
STATIC=39 BIND=39
TO=40 STATIC=40
PRIVATE=41 TO=41
SHARED=42 PRIVATE=42
STATE=43 SHARED=43
EDGE=44 STATE=44
PROJECTION=45 EDGE=45
WITH=46 PROJECTION=46
USING=47 WITH=47
VIA=48 USING=48
MATERIALIZE=49 VIA=49
IF=50 MATERIALIZE=50
ABSENT=51 IF=51
ON=52 ABSENT=52
POLICY=53 ON=53
DEFAULT=54 POLICY=54
SOURCE=55 DEFAULT=55
REPOSITORY=56 SOURCE=56
COMMIT=57 REPOSITORY=57
REVISION=58 COMMIT=58
SEMANTIC_MAJOR=59 REVISION=59
ON_DELETE=60 SEMANTIC_MAJOR=60
RETAIN_OTHER=61 ON_DELETE=61
KEYED=62 RETAIN_OTHER=62
PUBLIC_TRAVERSAL=63 KEYED=63
ID=64 PUBLIC_TRAVERSAL=64
DOC=65 ID=65
MODE=66 DOC=66
EMITS=67 MODE=67
RECEIVER=68 EMITS=68
REQUIRES=69 RECEIVER=69
ANY=70 REQUIRES=70
GET=71 ANY=71
SET=72 GET=72
WATCH=73 SET=73
START=74 WATCH=74
STOP=75 START=75
READ=76 STOP=76
WRITE=77 READ=77
RESOLVE=78 WRITE=78
CONNECT=79 RESOLVE=79
DISCONNECT=80 CONNECT=80
CALL=81 DISCONNECT=81
WATCH_START=82 CALL=82
WATCH_STOP=83 WATCH_START=83
SUBSCRIBE=84 WATCH_STOP=84
UNSUBSCRIBE=85 SUBSCRIBE=85
OPTIMISTIC_REGISTER=86 UNSUBSCRIBE=86
CRDT=87 OPTIMISTIC_REGISTER=87
OPTIONAL_ONE=88 CRDT=88
EXACTLY_ONE=89 OPTIONAL_ONE=89
MANY_UNIQUE=90 EXACTLY_ONE=90
MANY=91 MANY_UNIQUE=91
ORDERED=92 MANY=92
UNIT=93 ORDERED=93
WATCH_HANDLE=94 UNIT=94
MESSAGE=95 WATCH_HANDLE=95
ATOM_REF=96 MESSAGE=96
INTERFACE_REF=97 ATOM_REF=97
OPTIONAL=98 INTERFACE_REF=98
LIST=99 OPTIONAL=99
RECORD=100 LIST=100
BOOL=101 RECORD=101
BYTES=102 BOOL=102
DOUBLE=103 BYTES=103
INT32=104 DOUBLE=104
INT64=105 INT32=105
STRING=106 INT64=106
UINT32=107 STRING=107
UINT64=108 UINT32=108
TRUE=109 UINT64=109
FALSE=110 TRUE=110
NULL=111 FALSE=111
ARROW=112 NULL=112
COLON=113 ARROW=113
SEMI=114 COLON=114
COMMA=115 SEMI=115
DOT=116 COMMA=116
LBRACE=117 DOT=117
RBRACE=118 LBRACE=118
LBRACK=119 RBRACE=119
RBRACK=120 LBRACK=120
LPAREN=121 RBRACK=121
RPAREN=122 LPAREN=122
LT=123 RPAREN=123
GT=124 LT=124
AMP=125 GT=125
EQUAL=126 AMP=126
INTEGER=127 EQUAL=127
JSON_NUMBER=128 INTEGER=128
IDENTIFIER=129 JSON_NUMBER=129
STRING_LITERAL=130 IDENTIFIER=130
LINE_COMMENT=131 STRING_LITERAL=131
BLOCK_COMMENT=132 LINE_COMMENT=132
WS=133 BLOCK_COMMENT=133
WS=134
'workspace'=1 'workspace'=1
'result-bytes'=2 'result-bytes'=2
'rpc-calls'=3 'rpc-calls'=3
'deadline-ms'=4 'deadline-ms'=4
'query'=5 'query'=5
'specialize'=6 'reflection'=6
'root'=7 'specialize'=7
'document'=8 'root'=8
'fragments'=9 'document'=9
'view'=10 'fragments'=10
'max'=11 'view'=11
'allow'=12 'max'=12
'poll'=13 'allow'=13
'queryable'=14 'poll'=14
'rpc'=15 'queryable'=15
'query-reason'=16 'rpc'=16
'type'=17 'query-reason'=17
'object'=18 'type'=18
'storable'=19 'object'=19
'implements'=20 'storable'=20
'ref'=21 'implements'=21
'fragment'=22 'ref'=22
'import'=23 'fragment'=23
'external'=24 'import'=24
'atom'=25 'external'=25
'interface'=26 'atom'=26
'interfaces'=27 'interface'=27
'package'=28 'interfaces'=28
'value'=29 'package'=29
'relation'=30 'value'=30
'operation'=31 'relation'=31
'function'=32 'operation'=32
'constructor'=33 'function'=33
'constructs'=34 'constructor'=34
'input'=35 'constructs'=35
'conform'=36 'input'=36
'as'=37 'conform'=37
'bind'=38 'as'=38
'static'=39 'bind'=39
'to'=40 'static'=40
'private'=41 'to'=41
'shared'=42 'private'=42
'state'=43 'shared'=43
'edge'=44 'state'=44
'projection'=45 'edge'=45
'with'=46 'projection'=46
'using'=47 'with'=47
'via'=48 'using'=48
'materialize'=49 'via'=49
'if'=50 'materialize'=50
'absent'=51 'if'=51
'on'=52 'absent'=52
'policy'=53 'on'=53
'default'=54 'policy'=54
'source'=55 'default'=55
'repository'=56 'source'=56
'commit'=57 'repository'=57
'revision'=58 'commit'=58
'semantic-major'=59 'revision'=59
'on-delete'=60 'semantic-major'=60
'retain-other'=61 'on-delete'=61
'keyed'=62 'retain-other'=62
'public-traversal'=63 'keyed'=63
'id'=64 'public-traversal'=64
'doc'=65 'id'=65
'mode'=66 'doc'=66
'emits'=67 'mode'=67
'receiver'=68 'emits'=68
'requires'=69 'receiver'=69
'any'=70 'requires'=70
'get'=71 'any'=71
'set'=72 'get'=72
'watch'=73 'set'=73
'start'=74 'watch'=74
'stop'=75 'start'=75
'read'=76 'stop'=76
'write'=77 'read'=77
'resolve'=78 'write'=78
'connect'=79 'resolve'=79
'disconnect'=80 'connect'=80
'call'=81 'disconnect'=81
'watch-start'=82 'call'=82
'watch-stop'=83 'watch-start'=83
'subscribe'=84 'watch-stop'=84
'unsubscribe'=85 'subscribe'=85
'optimistic-register'=86 'unsubscribe'=86
'crdt'=87 'optimistic-register'=87
'optional-one'=88 'crdt'=88
'exactly-one'=89 'optional-one'=89
'many-unique'=90 'exactly-one'=90
'many'=91 'many-unique'=91
'ordered'=92 'many'=92
'unit'=93 'ordered'=93
'watch-handle'=94 'unit'=94
'message'=95 'watch-handle'=95
'atom-ref'=96 'message'=96
'interface-ref'=97 'atom-ref'=97
'optional'=98 'interface-ref'=98
'list'=99 'optional'=99
'record'=100 'list'=100
'bool'=101 'record'=101
'bytes'=102 'bool'=102
'double'=103 'bytes'=103
'int32'=104 'double'=104
'int64'=105 'int32'=105
'string'=106 'int64'=106
'uint32'=107 'string'=107
'uint64'=108 'uint32'=108
'true'=109 'uint64'=109
'false'=110 'true'=110
'null'=111 'false'=111
'->'=112 'null'=112
':'=113 '->'=113
';'=114 ':'=114
','=115 ';'=115
'.'=116 ','=116
'{'=117 '.'=117
'}'=118 '{'=118
'['=119 '}'=119
']'=120 '['=120
'('=121 ']'=121
')'=122 '('=122
'<'=123 ')'=123
'>'=124 '<'=124
'&'=125 '>'=125
'='=126 '&'=126
'='=127
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+15
View File
@@ -849,6 +849,12 @@ const lowerQueryReference = (
const lowerDependencyPort = (state: LoweringState, context: DependencyPortContext): DependencyPort | undefined => { const lowerDependencyPort = (state: LoweringState, context: DependencyPortContext): DependencyPort | undefined => {
const name = identifier(context.identifier(0)); const name = identifier(context.identifier(0));
const id = capabilityId.dependencyPort(stringValue(context.stringLiteral())); const id = capabilityId.dependencyPort(stringValue(context.stringLiteral()));
if (context.REFLECTION())
return {
id,
displayName: name,
requirement: { kind: "reflection", selectors: context.identifierList()!.identifier().map(identifier) },
};
if (context.QUERY()) { if (context.QUERY()) {
const query = lowerQueryReference( const query = lowerQueryReference(
state, state,
@@ -1097,6 +1103,11 @@ const lowerGenericPackageExport = (
.primitiveList() .primitiveList()
?.primitive() ?.primitive()
.map((item) => text(item)) ?? []; .map((item) => text(item)) ?? [];
if (port.REFLECTION())
return {
...base,
requirement: { kind: "reflection", selectors: port.identifierList()!.identifier().map(identifier) },
};
if (port.QUERY()) { if (port.QUERY()) {
const query = lowerQueryReference(state, identifier(port.identifier(1)), identifier(port.identifier(2)), port); const query = lowerQueryReference(state, identifier(port.identifier(1)), identifier(port.identifier(2)), port);
if (!query) if (!query)
@@ -1575,6 +1586,10 @@ const lowerBoundDependencies = (
); );
return projectionId ? { edgeTypeId: attachment.attachment.id, projectionId } : undefined; return projectionId ? { edgeTypeId: attachment.attachment.id, projectionId } : undefined;
}; };
if (entry.REFLECTION())
return [
{ portId, binding: { kind: "reflection", selectors: entry.identifierList()!.identifier().map(identifier) } },
];
if (entry.QUERY()) { if (entry.QUERY()) {
const query = lowerQueryReference(state, identifier(entry.identifier(1)), identifier(entry.identifier(2)), entry); const query = lowerQueryReference(state, identifier(entry.identifier(1)), identifier(entry.identifier(2)), entry);
const via = entry.VIA() ? traversal(identifier(entry.identifier(3)), identifier(entry.identifier(4))) : undefined; const via = entry.VIA() ? traversal(identifier(entry.identifier(3)), identifier(entry.identifier(4))) : undefined;
+2 -1
View File
@@ -22,7 +22,7 @@ import {
export type GenericDependencyPort = Omit<DependencyPort, "requirement"> & { export type GenericDependencyPort = Omit<DependencyPort, "requirement"> & {
requirement: requirement:
| Extract<DependencyPort["requirement"], { kind: "query" }> | Extract<DependencyPort["requirement"], { kind: "query" | "reflection" }>
| { | {
kind: "state"; kind: "state";
valueType: ValueTypeExpression; valueType: ValueTypeExpression;
@@ -80,6 +80,7 @@ export const specializePackageExport = (
const ports: DependencyPort[] = definition.dependencyPorts.map((port) => { const ports: DependencyPort[] = definition.dependencyPorts.map((port) => {
const requirement = port.requirement; const requirement = port.requirement;
switch (requirement.kind) { switch (requirement.kind) {
case "reflection":
case "query": case "query":
return { ...port, requirement }; return { ...port, requirement };
case "state": case "state":
+2
View File
@@ -223,6 +223,7 @@ export type PackageReceiverRequirement =
}; };
export type DependencyPortRequirement = export type DependencyPortRequirement =
| { kind: "reflection"; selectors: string[] }
| { kind: "query"; packageRevisionId: PackageRevisionId; queryId: string } | { kind: "query"; packageRevisionId: PackageRevisionId; queryId: string }
| { | {
kind: "state"; kind: "state";
@@ -292,6 +293,7 @@ export interface PackageRevision {
} }
export type DependencyBinding = export type DependencyBinding =
| { kind: "reflection"; selectors: string[]; via?: never }
| { | {
kind: "query"; kind: "query";
packageRevisionId: PackageRevisionId; packageRevisionId: PackageRevisionId;
+24
View File
@@ -998,6 +998,16 @@ const validatePackages = (
for (const [portIndex, port] of entry.dependencyPorts.entries()) { for (const [portIndex, port] of entry.dependencyPorts.entries()) {
const portPath = `${exportPath}.dependencyPorts[${portIndex}]`; const portPath = `${exportPath}.dependencyPorts[${portIndex}]`;
switch (port.requirement.kind) { switch (port.requirement.kind) {
case "reflection":
if (
!port.requirement.selectors.length ||
new Set(port.requirement.selectors).size !== port.requirement.selectors.length ||
port.requirement.selectors.some(
(kind) => !["active", "atoms", "interfaces", "conformances", "queries", "indexes"].includes(kind),
)
)
issue(issues, "invalid-dependency-binding", portPath, "Reflection requires distinct supported selectors");
break;
case "query": case "query":
if ( if (
!indexes.packages !indexes.packages
@@ -1162,6 +1172,20 @@ const validateBoundDependencies = (
continue; continue;
} }
switch (requirement.kind) { switch (requirement.kind) {
case "reflection": {
const selected = binding as Extract<DependencyBinding, { kind: "reflection" }>;
if (
selected.via ||
JSON.stringify([...selected.selectors].sort()) !== JSON.stringify([...requirement.selectors].sort())
)
issue(
issues,
"invalid-dependency-binding",
path,
"Reflection requires an explicit workspace binding with exactly the declared selectors",
);
break;
}
case "query": { case "query": {
const selected = binding as Extract<DependencyBinding, { kind: "query" }>; const selected = binding as Extract<DependencyBinding, { kind: "query" }>;
const query = params.indexes.packages const query = params.indexes.packages
+451 -30
View File
File diff suppressed because one or more lines are too long
+37 -4
View File
@@ -10,7 +10,7 @@ import type { Message } from "@bufbuild/protobuf";
* Describes the file quixos/refs.proto. * Describes the file quixos/refs.proto.
*/ */
export const file_quixos_refs: GenFile = /*@__PURE__*/ export const file_quixos_refs: GenFile = /*@__PURE__*/
fileDesc("ChFxdWl4b3MvcmVmcy5wcm90bxIGcXVpeG9zInUKDUNhcGFiaWxpdHlSZWYSHQoVaW50ZXJmYWNlX3JldmlzaW9uX2lkGAEgASgJEhQKDG9wZXJhdGlvbl9pZBgCIAEoCRIvCgtjb25mb3JtYW5jZRgDIAEoCzIaLnF1aXhvcy5Db25mb3JtYW5jZVdpdG5lc3Mi0QEKDEZpZWxkRWRpdGluZxIbChNnZXR0ZXJfb3BlcmF0aW9uX2lkGAEgASgJEhsKE3NldHRlcl9vcGVyYXRpb25faWQYAiABKAkSFQoNZG9jdW1lbnRfdHlwZRgDIAEoCRIWCg5iaW5kaW5nX2RpZ2VzdBgEIAEoCRInCgRtb2RlGAUgASgOMhkucXVpeG9zLkZpZWxkRWRpdGluZy5Nb2RlIi8KBE1vZGUSDwoLVU5TUEVDSUZJRUQQABIMCghSRUdJU1RFUhABEggKBENSRFQQAiKWAQoSQ29uZm9ybWFuY2VXaXRuZXNzEhEKCW9iamVjdF9pZBgBIAEoCRIdChVpbnRlcmZhY2VfcmV2aXNpb25faWQYAiABKAkSFgoOY29uZm9ybWFuY2VfaWQYAyABKAkSHQoVd29ya3NwYWNlX3JldmlzaW9uX2lkGAQgASgJEhcKD3dvcmtzcGFjZV9lcG9jaBgFIAEoCSJCChBQYWNrYWdlRXhwb3J0UmVmEhsKE3BhY2thZ2VfcmV2aXNpb25faWQYASABKAkSEQoJZXhwb3J0X2lkGAIgASgJIpUCChJJbmplY3RlZERlcGVuZGVuY3kSDwoHcG9ydF9pZBgBIAEoCRIXCg1zdGF0ZV9zbG90X2lkGAIgASgJSAASJgoEZWRnZRgDIAEoCzIWLnF1aXhvcy5FZGdlRGVwZW5kZW5jeUgAEh8KFWludGVyZmFjZV9yZXZpc2lvbl9pZBgEIAEoCUgAEh0KE2NvbnN0cnVjdG9yX2F0b21faWQYBSABKAlIABISCghxdWVyeV9pZBgHIAEoCUgAEjsKD3dvcmtzcGFjZV9xdWVyeRgIIAEoCzIgLnF1aXhvcy5Xb3Jrc3BhY2VRdWVyeURlcGVuZGVuY3lIABIRCglvYmplY3RfaWQYBiABKAlCCQoHYmluZGluZyKaAQoTV29ya3NwYWNlUXVlcnlTY29wZRIUCgx3b3Jrc3BhY2VfaWQYASABKAkSHQoVd29ya3NwYWNlX3JldmlzaW9uX2lkGAIgASgJEhcKD3dvcmtzcGFjZV9lcG9jaBgDIAEoCRIdChVpbnRlcmZhY2VfcmV2aXNpb25faWQYBCABKAkSFgoOYmluZGluZ19kaWdlc3QYBSABKAkiWAoYV29ya3NwYWNlUXVlcnlEZXBlbmRlbmN5EhAKCHF1ZXJ5X2lkGAEgASgJEioKBXNjb3BlGAIgASgLMhsucXVpeG9zLldvcmtzcGFjZVF1ZXJ5U2NvcGUiPQoORWRnZURlcGVuZGVuY3kSFAoMZWRnZV90eXBlX2lkGAEgASgJEhUKDXByb2plY3Rpb25faWQYAiABKAliBnByb3RvMw"); fileDesc("ChFxdWl4b3MvcmVmcy5wcm90bxIGcXVpeG9zInUKDUNhcGFiaWxpdHlSZWYSHQoVaW50ZXJmYWNlX3JldmlzaW9uX2lkGAEgASgJEhQKDG9wZXJhdGlvbl9pZBgCIAEoCRIvCgtjb25mb3JtYW5jZRgDIAEoCzIaLnF1aXhvcy5Db25mb3JtYW5jZVdpdG5lc3Mi0QEKDEZpZWxkRWRpdGluZxIbChNnZXR0ZXJfb3BlcmF0aW9uX2lkGAEgASgJEhsKE3NldHRlcl9vcGVyYXRpb25faWQYAiABKAkSFQoNZG9jdW1lbnRfdHlwZRgDIAEoCRIWCg5iaW5kaW5nX2RpZ2VzdBgEIAEoCRInCgRtb2RlGAUgASgOMhkucXVpeG9zLkZpZWxkRWRpdGluZy5Nb2RlIi8KBE1vZGUSDwoLVU5TUEVDSUZJRUQQABIMCghSRUdJU1RFUhABEggKBENSRFQQAiKWAQoSQ29uZm9ybWFuY2VXaXRuZXNzEhEKCW9iamVjdF9pZBgBIAEoCRIdChVpbnRlcmZhY2VfcmV2aXNpb25faWQYAiABKAkSFgoOY29uZm9ybWFuY2VfaWQYAyABKAkSHQoVd29ya3NwYWNlX3JldmlzaW9uX2lkGAQgASgJEhcKD3dvcmtzcGFjZV9lcG9jaBgFIAEoCSJCChBQYWNrYWdlRXhwb3J0UmVmEhsKE3BhY2thZ2VfcmV2aXNpb25faWQYASABKAkSEQoJZXhwb3J0X2lkGAIgASgJIskCChJJbmplY3RlZERlcGVuZGVuY3kSDwoHcG9ydF9pZBgBIAEoCRIXCg1zdGF0ZV9zbG90X2lkGAIgASgJSAASJgoEZWRnZRgDIAEoCzIWLnF1aXhvcy5FZGdlRGVwZW5kZW5jeUgAEh8KFWludGVyZmFjZV9yZXZpc2lvbl9pZBgEIAEoCUgAEh0KE2NvbnN0cnVjdG9yX2F0b21faWQYBSABKAlIABISCghxdWVyeV9pZBgHIAEoCUgAEjsKD3dvcmtzcGFjZV9xdWVyeRgIIAEoCzIgLnF1aXhvcy5Xb3Jrc3BhY2VRdWVyeURlcGVuZGVuY3lIABIyCgpyZWZsZWN0aW9uGAkgASgLMhwucXVpeG9zLlJlZmxlY3Rpb25EZXBlbmRlbmN5SAASEQoJb2JqZWN0X2lkGAYgASgJQgkKB2JpbmRpbmciWAoUUmVmbGVjdGlvbkRlcGVuZGVuY3kSFAoMd29ya3NwYWNlX2lkGAEgASgJEhcKD3dvcmtzcGFjZV9lcG9jaBgCIAEoCRIRCglzZWxlY3RvcnMYAyADKAkimgEKE1dvcmtzcGFjZVF1ZXJ5U2NvcGUSFAoMd29ya3NwYWNlX2lkGAEgASgJEh0KFXdvcmtzcGFjZV9yZXZpc2lvbl9pZBgCIAEoCRIXCg93b3Jrc3BhY2VfZXBvY2gYAyABKAkSHQoVaW50ZXJmYWNlX3JldmlzaW9uX2lkGAQgASgJEhYKDmJpbmRpbmdfZGlnZXN0GAUgASgJIlgKGFdvcmtzcGFjZVF1ZXJ5RGVwZW5kZW5jeRIQCghxdWVyeV9pZBgBIAEoCRIqCgVzY29wZRgCIAEoCzIbLnF1aXhvcy5Xb3Jrc3BhY2VRdWVyeVNjb3BlIj0KDkVkZ2VEZXBlbmRlbmN5EhQKDGVkZ2VfdHlwZV9pZBgBIAEoCRIVCg1wcm9qZWN0aW9uX2lkGAIgASgJYgZwcm90bzM");
/** /**
* @generated from message quixos.CapabilityRef * @generated from message quixos.CapabilityRef
@@ -213,6 +213,12 @@ export type InjectedDependency = Message<"quixos.InjectedDependency"> & {
*/ */
value: WorkspaceQueryDependency; value: WorkspaceQueryDependency;
case: "workspaceQuery"; case: "workspaceQuery";
} | {
/**
* @generated from field: quixos.ReflectionDependency reflection = 9;
*/
value: ReflectionDependency;
case: "reflection";
} | { case: undefined; value?: undefined }; } | { case: undefined; value?: undefined };
/** /**
@@ -231,6 +237,33 @@ export type InjectedDependency = Message<"quixos.InjectedDependency"> & {
export const InjectedDependencySchema: GenMessage<InjectedDependency> = /*@__PURE__*/ export const InjectedDependencySchema: GenMessage<InjectedDependency> = /*@__PURE__*/
messageDesc(file_quixos_refs, 4); messageDesc(file_quixos_refs, 4);
/**
* @generated from message quixos.ReflectionDependency
*/
export type ReflectionDependency = Message<"quixos.ReflectionDependency"> & {
/**
* @generated from field: string workspace_id = 1;
*/
workspaceId: string;
/**
* @generated from field: string workspace_epoch = 2;
*/
workspaceEpoch: string;
/**
* @generated from field: repeated string selectors = 3;
*/
selectors: string[];
};
/**
* Describes the message quixos.ReflectionDependency.
* Use `create(ReflectionDependencySchema)` to create a new message.
*/
export const ReflectionDependencySchema: GenMessage<ReflectionDependency> = /*@__PURE__*/
messageDesc(file_quixos_refs, 5);
/** /**
* Scope evidence is not authority by itself. The caller must also hold the * Scope evidence is not authority by itself. The caller must also hold the
* matching workspace-query grant (or the authenticated workspace user grant). * matching workspace-query grant (or the authenticated workspace user grant).
@@ -269,7 +302,7 @@ export type WorkspaceQueryScope = Message<"quixos.WorkspaceQueryScope"> & {
* Use `create(WorkspaceQueryScopeSchema)` to create a new message. * Use `create(WorkspaceQueryScopeSchema)` to create a new message.
*/ */
export const WorkspaceQueryScopeSchema: GenMessage<WorkspaceQueryScope> = /*@__PURE__*/ export const WorkspaceQueryScopeSchema: GenMessage<WorkspaceQueryScope> = /*@__PURE__*/
messageDesc(file_quixos_refs, 5); messageDesc(file_quixos_refs, 6);
/** /**
* @generated from message quixos.WorkspaceQueryDependency * @generated from message quixos.WorkspaceQueryDependency
@@ -291,7 +324,7 @@ export type WorkspaceQueryDependency = Message<"quixos.WorkspaceQueryDependency"
* Use `create(WorkspaceQueryDependencySchema)` to create a new message. * Use `create(WorkspaceQueryDependencySchema)` to create a new message.
*/ */
export const WorkspaceQueryDependencySchema: GenMessage<WorkspaceQueryDependency> = /*@__PURE__*/ export const WorkspaceQueryDependencySchema: GenMessage<WorkspaceQueryDependency> = /*@__PURE__*/
messageDesc(file_quixos_refs, 6); messageDesc(file_quixos_refs, 7);
/** /**
* @generated from message quixos.EdgeDependency * @generated from message quixos.EdgeDependency
@@ -313,5 +346,5 @@ export type EdgeDependency = Message<"quixos.EdgeDependency"> & {
* Use `create(EdgeDependencySchema)` to create a new message. * Use `create(EdgeDependencySchema)` to create a new message.
*/ */
export const EdgeDependencySchema: GenMessage<EdgeDependency> = /*@__PURE__*/ export const EdgeDependencySchema: GenMessage<EdgeDependency> = /*@__PURE__*/
messageDesc(file_quixos_refs, 7); messageDesc(file_quixos_refs, 8);
+11
View File
@@ -12,6 +12,17 @@ const schemaFor = (body: string): BindingSchema => {
if (!compiled.ok || compiled.resource.kind !== "package") throw new Error("expected package"); if (!compiled.ok || compiled.resource.kind !== "package") throw new Error("expected package");
return { format: "quixos-bindings", version: 1, interfaces: [], packages: [compiled.resource.revision] }; return { format: "quixos-bindings", version: 1, interfaces: [], packages: [compiled.resource.revision] };
}; };
test("reflection ports generate explicit selector-restricted package bindings", () => {
const schema = schemaFor(
'function inspect id "inspect" : unit -> unit requires { reflection meta id "meta" [active, atoms, interfaces, conformances, queries, indexes]; };',
);
const generated = generateTypeScriptBindings(schema, "demo@1");
assert.match(
generated,
/QxReflectionPort<"active" \| "atoms" \| "interfaces" \| "conformances" \| "queries" \| "indexes">/,
);
assert.match(generated, /"kind": "reflection"/);
});
test("generator uses exact IDs, restricted ports, nominal references, and lossless scalar types", () => { test("generator uses exact IDs, restricted ports, nominal references, and lossless scalar types", () => {
const schema = schemaFor( const schema = schemaFor(
'operation run id "run-id" : list<int64> -> optional<atom-ref<Thing>> mode call receiver atom Thing requires { state payload id "payload-id" : bytes [read]; };', 'operation run id "run-id" : list<int64> -> optional<atom-ref<Thing>> mode call receiver atom Thing requires { state payload id "payload-id" : bytes [read]; };',
+6
View File
@@ -43,6 +43,7 @@ test("runtime conformance descriptors preserve closed and universal collection i
}; };
operation inspect<object Item> id "inspect" : ref<Item> -> unit mode call receiver any requires { operation inspect<object Item> id "inspect" : ref<Item> -> unit mode call receiver any requires {
interface items id "items" : Collection<Item>; interface items id "items" : Collection<Item>;
reflection metadata id "metadata" [atoms, interfaces];
}; };
}`, }`,
{ source, environment: { interfaces: new Map([["Collection", collection.resource.revision]]) } }, { source, environment: { interfaces: new Map([["Collection", collection.resource.revision]]) } },
@@ -77,6 +78,11 @@ test("runtime conformance descriptors preserve closed and universal collection i
return null; return null;
}, },
inspect: async (context) => { inspect: async (context) => {
await context.ports.metadata.read({selector:{kind:"atoms",interfaceRevisionId:"named"}});
// @ts-expect-error Queries are outside this port's declared authority.
await context.ports.metadata.read({selector:{kind:"queries"}});
// @ts-expect-error Interface inventory does not support atom filters.
await context.ports.metadata.read({selector:{kind:"interfaces",atomId:"book"}});
const view = await context.conform.tryConform(context.objectId, context.ports.items.contract); const view = await context.conform.tryConform(context.objectId, context.ports.items.contract);
if (view) await view["items.connect"](context.input); if (view) await view["items.connect"](context.input);
return null; return null;
+48
View File
@@ -0,0 +1,48 @@
import assert from "node:assert/strict";
import test from "node:test";
import { compileCapabilityResourceSource, compileCapabilitySource } from "../src/capability-language/parser.js";
import { generateTypeScriptBindings } from "../src/bindings/index.js";
const source = { repository: "https://example.test/meta.git", commit: "a".repeat(40) };
test("reflection authority is an explicit checked workspace binding, including generic exports", () => {
const iface = compileCapabilityResourceSource(
'interface Inspect id "inspect" revision "inspect@1" {operation run id "run" : unit -> unit {call id "call";}}',
{ source },
);
assert.ok(iface.ok && iface.resource.kind === "interface");
if (!iface.ok || iface.resource.kind !== "interface") throw Error("interface");
const interfaceRevision = iface.resource.revision;
for (const generic of [false, true]) {
const pkg = compileCapabilityResourceSource(
`package Inspector id "inspector" revision "inspector@1" {operation run${generic ? "<value T>" : ""} id "run" : unit -> unit mode call receiver any requires {reflection meta id "meta" [atoms, interfaces];};}`,
{ source },
);
assert.ok(pkg.ok && pkg.resource.kind === "package", JSON.stringify(pkg.diagnostics));
if (!pkg.ok || pkg.resource.kind !== "package") throw Error("package");
const packageRevision = pkg.resource.revision;
const compile = (selectors: string) =>
compileCapabilitySource(
`workspace W id "ws" revision "ws@1" commit "${source.commit}" {
import interface Inspect;import package Inspector;atom Thing id "thing";
conform Thing as Inspect id "inspection" {bind run.call to package Inspector.run${generic ? "<string>" : ""} with {meta to workspace reflection [${selectors}];};}
}`,
"workspace.qx",
{ interfaces: new Map([["Inspect", interfaceRevision]]), packages: new Map([["Inspector", packageRevision]]) },
);
const result = compile("atoms, interfaces");
assert.ok(result.ok, JSON.stringify(result.diagnostics));
assert.equal(compile("atoms").ok, false, "binding cannot narrow or expand the declared contract silently");
assert.equal(compile("atoms, interfaces, queries").ok, false);
if (!result.ok) throw Error("workspace");
const generated = generateTypeScriptBindings(
{
format: "quixos-bindings",
version: 1,
interfaces: result.workspace.interfaceImports,
packages: result.workspace.packageImports,
},
"inspector@1",
);
assert.match(generated, /QxReflectionPort/);
}
});