Add factory creation ports and owner initialization contracts

This commit is contained in:
Timothy J. Aveni
2026-09-19 22:20:55 -07:00
parent 03ac7f8557
commit 0555c929b1
25 changed files with 3899 additions and 3012 deletions
+2
View File
@@ -115,6 +115,8 @@ export const genericImplementationType = (
}
case "query":
return queryPort(requirement);
case "creation":
throw new Error("Creation ports require the portable transaction context generator (LF-09)");
case "constructor":
return object([
[
+2
View File
@@ -249,6 +249,8 @@ export const generateTypeScriptBindings = (
},
};
}
case "creation":
throw new Error("Creation ports require the portable transaction context generator (LF-09)");
case "constructor": {
const input = requirement.inputType;
if (!input)
File diff suppressed because one or more lines are too long
@@ -1,279 +1,283 @@
WORKSPACE=1
EXECUTION=2
SNAPSHOT=3
TRANSACTION=4
REMOTE=5
TARGETS=6
PORTABLE=7
REPLICA=8
LOCAL=9
FROM=10
RESULT_BYTES=11
RPC_CALLS=12
DEADLINE_MS=13
QUERY=14
REFLECTION=15
SPECIALIZE=16
ROOT=17
DOCUMENT=18
FRAGMENTS=19
VIEW=20
MAX=21
ALLOW=22
POLL=23
QUERYABLE=24
RPC=25
QUERY_REASON=26
TYPE=27
OBJECT=28
STORABLE=29
IMPLEMENTS=30
REF=31
FRAGMENT=32
IMPORT=33
EXTERNAL=34
ATOM=35
INTERFACE=36
INTERFACES=37
PACKAGE=38
VALUE=39
RELATION=40
OPERATION=41
FUNCTION=42
CONSTRUCTOR=43
CONSTRUCTS=44
INPUT=45
CONFORM=46
AS=47
BIND=48
STATIC=49
TO=50
PRIVATE=51
SHARED=52
STATE=53
EDGE=54
PROJECTION=55
WITH=56
USING=57
VIA=58
MATERIALIZE=59
IF=60
ABSENT=61
ON=62
POLICY=63
DEFAULT=64
SOURCE=65
REPOSITORY=66
COMMIT=67
REVISION=68
SEMANTIC_MAJOR=69
ON_DELETE=70
RETAIN_OTHER=71
KEYED=72
PUBLIC_TRAVERSAL=73
ID=74
DOC=75
MODE=76
EMITS=77
RECEIVER=78
REQUIRES=79
ANY=80
GET=81
SET=82
WATCH=83
START=84
STOP=85
READ=86
WRITE=87
RESOLVE=88
CONNECT=89
DISCONNECT=90
CALL=91
WATCH_START=92
WATCH_STOP=93
SUBSCRIBE=94
UNSUBSCRIBE=95
OPTIMISTIC_REGISTER=96
CRDT=97
OPTIONAL_ONE=98
EXACTLY_ONE=99
MANY_UNIQUE=100
MANY=101
ORDERED=102
UNIT=103
WATCH_HANDLE=104
MESSAGE=105
ATOM_REF=106
INTERFACE_REF=107
OPTIONAL=108
LIST=109
RECORD=110
BOOL=111
BYTES=112
DOUBLE=113
INT32=114
INT64=115
STRING=116
UINT32=117
UINT64=118
TRUE=119
FALSE=120
NULL=121
ARROW=122
COLON=123
SEMI=124
COMMA=125
DOT=126
LBRACE=127
RBRACE=128
LBRACK=129
RBRACK=130
LPAREN=131
RPAREN=132
LT=133
GT=134
AMP=135
EQUAL=136
INTEGER=137
JSON_NUMBER=138
IDENTIFIER=139
STRING_LITERAL=140
LINE_COMMENT=141
BLOCK_COMMENT=142
WS=143
'workspace'=1
'execution'=2
'snapshot'=3
'transaction'=4
'remote'=5
'targets'=6
'portable'=7
'replica'=8
'local'=9
'from'=10
'result-bytes'=11
'rpc-calls'=12
'deadline-ms'=13
'query'=14
'reflection'=15
'specialize'=16
'root'=17
'document'=18
'fragments'=19
'view'=20
'max'=21
'allow'=22
'poll'=23
'queryable'=24
'rpc'=25
'query-reason'=26
'type'=27
'object'=28
'storable'=29
'implements'=30
'ref'=31
'fragment'=32
'import'=33
'external'=34
'atom'=35
'interface'=36
'interfaces'=37
'package'=38
'value'=39
'relation'=40
'operation'=41
'function'=42
'constructor'=43
'constructs'=44
'input'=45
'conform'=46
'as'=47
'bind'=48
'static'=49
'to'=50
'private'=51
'shared'=52
'state'=53
'edge'=54
'projection'=55
'with'=56
'using'=57
'via'=58
'materialize'=59
'if'=60
'absent'=61
'on'=62
'policy'=63
'default'=64
'source'=65
'repository'=66
'commit'=67
'revision'=68
'semantic-major'=69
'on-delete'=70
'retain-other'=71
'keyed'=72
'public-traversal'=73
'id'=74
'doc'=75
'mode'=76
'emits'=77
'receiver'=78
'requires'=79
'any'=80
'get'=81
'set'=82
'watch'=83
'start'=84
'stop'=85
'read'=86
'write'=87
'resolve'=88
'connect'=89
'disconnect'=90
'call'=91
'watch-start'=92
'watch-stop'=93
'subscribe'=94
'unsubscribe'=95
'optimistic-register'=96
'crdt'=97
'optional-one'=98
'exactly-one'=99
'many-unique'=100
'many'=101
'ordered'=102
'unit'=103
'watch-handle'=104
'message'=105
'atom-ref'=106
'interface-ref'=107
'optional'=108
'list'=109
'record'=110
'bool'=111
'bytes'=112
'double'=113
'int32'=114
'int64'=115
'string'=116
'uint32'=117
'uint64'=118
'true'=119
'false'=120
'null'=121
'->'=122
':'=123
';'=124
','=125
'.'=126
'{'=127
'}'=128
'['=129
']'=130
'('=131
')'=132
'<'=133
'>'=134
'&'=135
'='=136
CREATION=1
INITIALIZE=2
WORKSPACE=3
EXECUTION=4
SNAPSHOT=5
TRANSACTION=6
REMOTE=7
TARGETS=8
PORTABLE=9
REPLICA=10
LOCAL=11
FROM=12
RESULT_BYTES=13
RPC_CALLS=14
DEADLINE_MS=15
QUERY=16
REFLECTION=17
SPECIALIZE=18
ROOT=19
DOCUMENT=20
FRAGMENTS=21
VIEW=22
MAX=23
ALLOW=24
POLL=25
QUERYABLE=26
RPC=27
QUERY_REASON=28
TYPE=29
OBJECT=30
STORABLE=31
IMPLEMENTS=32
REF=33
FRAGMENT=34
IMPORT=35
EXTERNAL=36
ATOM=37
INTERFACE=38
INTERFACES=39
PACKAGE=40
VALUE=41
RELATION=42
OPERATION=43
FUNCTION=44
CONSTRUCTOR=45
CONSTRUCTS=46
INPUT=47
CONFORM=48
AS=49
BIND=50
STATIC=51
TO=52
PRIVATE=53
SHARED=54
STATE=55
EDGE=56
PROJECTION=57
WITH=58
USING=59
VIA=60
MATERIALIZE=61
IF=62
ABSENT=63
ON=64
POLICY=65
DEFAULT=66
SOURCE=67
REPOSITORY=68
COMMIT=69
REVISION=70
SEMANTIC_MAJOR=71
ON_DELETE=72
RETAIN_OTHER=73
KEYED=74
PUBLIC_TRAVERSAL=75
ID=76
DOC=77
MODE=78
EMITS=79
RECEIVER=80
REQUIRES=81
ANY=82
GET=83
SET=84
WATCH=85
START=86
STOP=87
READ=88
WRITE=89
RESOLVE=90
CONNECT=91
DISCONNECT=92
CALL=93
WATCH_START=94
WATCH_STOP=95
SUBSCRIBE=96
UNSUBSCRIBE=97
REGISTER=98
CRDT=99
OPTIONAL_ONE=100
EXACTLY_ONE=101
MANY_UNIQUE=102
MANY=103
ORDERED=104
UNIT=105
WATCH_HANDLE=106
MESSAGE=107
ATOM_REF=108
INTERFACE_REF=109
OPTIONAL=110
LIST=111
RECORD=112
BOOL=113
BYTES=114
DOUBLE=115
INT32=116
INT64=117
STRING=118
UINT32=119
UINT64=120
TRUE=121
FALSE=122
NULL=123
ARROW=124
COLON=125
SEMI=126
COMMA=127
DOT=128
LBRACE=129
RBRACE=130
LBRACK=131
RBRACK=132
LPAREN=133
RPAREN=134
LT=135
GT=136
AMP=137
EQUAL=138
INTEGER=139
JSON_NUMBER=140
IDENTIFIER=141
STRING_LITERAL=142
LINE_COMMENT=143
BLOCK_COMMENT=144
WS=145
'creation'=1
'initialize'=2
'workspace'=3
'execution'=4
'snapshot'=5
'transaction'=6
'remote'=7
'targets'=8
'portable'=9
'replica'=10
'local'=11
'from'=12
'result-bytes'=13
'rpc-calls'=14
'deadline-ms'=15
'query'=16
'reflection'=17
'specialize'=18
'root'=19
'document'=20
'fragments'=21
'view'=22
'max'=23
'allow'=24
'poll'=25
'queryable'=26
'rpc'=27
'query-reason'=28
'type'=29
'object'=30
'storable'=31
'implements'=32
'ref'=33
'fragment'=34
'import'=35
'external'=36
'atom'=37
'interface'=38
'interfaces'=39
'package'=40
'value'=41
'relation'=42
'operation'=43
'function'=44
'constructor'=45
'constructs'=46
'input'=47
'conform'=48
'as'=49
'bind'=50
'static'=51
'to'=52
'private'=53
'shared'=54
'state'=55
'edge'=56
'projection'=57
'with'=58
'using'=59
'via'=60
'materialize'=61
'if'=62
'absent'=63
'on'=64
'policy'=65
'default'=66
'source'=67
'repository'=68
'commit'=69
'revision'=70
'semantic-major'=71
'on-delete'=72
'retain-other'=73
'keyed'=74
'public-traversal'=75
'id'=76
'doc'=77
'mode'=78
'emits'=79
'receiver'=80
'requires'=81
'any'=82
'get'=83
'set'=84
'watch'=85
'start'=86
'stop'=87
'read'=88
'write'=89
'resolve'=90
'connect'=91
'disconnect'=92
'call'=93
'watch-start'=94
'watch-stop'=95
'subscribe'=96
'unsubscribe'=97
'register'=98
'crdt'=99
'optional-one'=100
'exactly-one'=101
'many-unique'=102
'many'=103
'ordered'=104
'unit'=105
'watch-handle'=106
'message'=107
'atom-ref'=108
'interface-ref'=109
'optional'=110
'list'=111
'record'=112
'bool'=113
'bytes'=114
'double'=115
'int32'=116
'int64'=117
'string'=118
'uint32'=119
'uint64'=120
'true'=121
'false'=122
'null'=123
'->'=124
':'=125
';'=126
','=127
'.'=128
'{'=129
'}'=130
'['=131
']'=132
'('=133
')'=134
'<'=135
'>'=136
'&'=137
'='=138
File diff suppressed because one or more lines are too long
@@ -1,279 +1,283 @@
WORKSPACE=1
EXECUTION=2
SNAPSHOT=3
TRANSACTION=4
REMOTE=5
TARGETS=6
PORTABLE=7
REPLICA=8
LOCAL=9
FROM=10
RESULT_BYTES=11
RPC_CALLS=12
DEADLINE_MS=13
QUERY=14
REFLECTION=15
SPECIALIZE=16
ROOT=17
DOCUMENT=18
FRAGMENTS=19
VIEW=20
MAX=21
ALLOW=22
POLL=23
QUERYABLE=24
RPC=25
QUERY_REASON=26
TYPE=27
OBJECT=28
STORABLE=29
IMPLEMENTS=30
REF=31
FRAGMENT=32
IMPORT=33
EXTERNAL=34
ATOM=35
INTERFACE=36
INTERFACES=37
PACKAGE=38
VALUE=39
RELATION=40
OPERATION=41
FUNCTION=42
CONSTRUCTOR=43
CONSTRUCTS=44
INPUT=45
CONFORM=46
AS=47
BIND=48
STATIC=49
TO=50
PRIVATE=51
SHARED=52
STATE=53
EDGE=54
PROJECTION=55
WITH=56
USING=57
VIA=58
MATERIALIZE=59
IF=60
ABSENT=61
ON=62
POLICY=63
DEFAULT=64
SOURCE=65
REPOSITORY=66
COMMIT=67
REVISION=68
SEMANTIC_MAJOR=69
ON_DELETE=70
RETAIN_OTHER=71
KEYED=72
PUBLIC_TRAVERSAL=73
ID=74
DOC=75
MODE=76
EMITS=77
RECEIVER=78
REQUIRES=79
ANY=80
GET=81
SET=82
WATCH=83
START=84
STOP=85
READ=86
WRITE=87
RESOLVE=88
CONNECT=89
DISCONNECT=90
CALL=91
WATCH_START=92
WATCH_STOP=93
SUBSCRIBE=94
UNSUBSCRIBE=95
OPTIMISTIC_REGISTER=96
CRDT=97
OPTIONAL_ONE=98
EXACTLY_ONE=99
MANY_UNIQUE=100
MANY=101
ORDERED=102
UNIT=103
WATCH_HANDLE=104
MESSAGE=105
ATOM_REF=106
INTERFACE_REF=107
OPTIONAL=108
LIST=109
RECORD=110
BOOL=111
BYTES=112
DOUBLE=113
INT32=114
INT64=115
STRING=116
UINT32=117
UINT64=118
TRUE=119
FALSE=120
NULL=121
ARROW=122
COLON=123
SEMI=124
COMMA=125
DOT=126
LBRACE=127
RBRACE=128
LBRACK=129
RBRACK=130
LPAREN=131
RPAREN=132
LT=133
GT=134
AMP=135
EQUAL=136
INTEGER=137
JSON_NUMBER=138
IDENTIFIER=139
STRING_LITERAL=140
LINE_COMMENT=141
BLOCK_COMMENT=142
WS=143
'workspace'=1
'execution'=2
'snapshot'=3
'transaction'=4
'remote'=5
'targets'=6
'portable'=7
'replica'=8
'local'=9
'from'=10
'result-bytes'=11
'rpc-calls'=12
'deadline-ms'=13
'query'=14
'reflection'=15
'specialize'=16
'root'=17
'document'=18
'fragments'=19
'view'=20
'max'=21
'allow'=22
'poll'=23
'queryable'=24
'rpc'=25
'query-reason'=26
'type'=27
'object'=28
'storable'=29
'implements'=30
'ref'=31
'fragment'=32
'import'=33
'external'=34
'atom'=35
'interface'=36
'interfaces'=37
'package'=38
'value'=39
'relation'=40
'operation'=41
'function'=42
'constructor'=43
'constructs'=44
'input'=45
'conform'=46
'as'=47
'bind'=48
'static'=49
'to'=50
'private'=51
'shared'=52
'state'=53
'edge'=54
'projection'=55
'with'=56
'using'=57
'via'=58
'materialize'=59
'if'=60
'absent'=61
'on'=62
'policy'=63
'default'=64
'source'=65
'repository'=66
'commit'=67
'revision'=68
'semantic-major'=69
'on-delete'=70
'retain-other'=71
'keyed'=72
'public-traversal'=73
'id'=74
'doc'=75
'mode'=76
'emits'=77
'receiver'=78
'requires'=79
'any'=80
'get'=81
'set'=82
'watch'=83
'start'=84
'stop'=85
'read'=86
'write'=87
'resolve'=88
'connect'=89
'disconnect'=90
'call'=91
'watch-start'=92
'watch-stop'=93
'subscribe'=94
'unsubscribe'=95
'optimistic-register'=96
'crdt'=97
'optional-one'=98
'exactly-one'=99
'many-unique'=100
'many'=101
'ordered'=102
'unit'=103
'watch-handle'=104
'message'=105
'atom-ref'=106
'interface-ref'=107
'optional'=108
'list'=109
'record'=110
'bool'=111
'bytes'=112
'double'=113
'int32'=114
'int64'=115
'string'=116
'uint32'=117
'uint64'=118
'true'=119
'false'=120
'null'=121
'->'=122
':'=123
';'=124
','=125
'.'=126
'{'=127
'}'=128
'['=129
']'=130
'('=131
')'=132
'<'=133
'>'=134
'&'=135
'='=136
CREATION=1
INITIALIZE=2
WORKSPACE=3
EXECUTION=4
SNAPSHOT=5
TRANSACTION=6
REMOTE=7
TARGETS=8
PORTABLE=9
REPLICA=10
LOCAL=11
FROM=12
RESULT_BYTES=13
RPC_CALLS=14
DEADLINE_MS=15
QUERY=16
REFLECTION=17
SPECIALIZE=18
ROOT=19
DOCUMENT=20
FRAGMENTS=21
VIEW=22
MAX=23
ALLOW=24
POLL=25
QUERYABLE=26
RPC=27
QUERY_REASON=28
TYPE=29
OBJECT=30
STORABLE=31
IMPLEMENTS=32
REF=33
FRAGMENT=34
IMPORT=35
EXTERNAL=36
ATOM=37
INTERFACE=38
INTERFACES=39
PACKAGE=40
VALUE=41
RELATION=42
OPERATION=43
FUNCTION=44
CONSTRUCTOR=45
CONSTRUCTS=46
INPUT=47
CONFORM=48
AS=49
BIND=50
STATIC=51
TO=52
PRIVATE=53
SHARED=54
STATE=55
EDGE=56
PROJECTION=57
WITH=58
USING=59
VIA=60
MATERIALIZE=61
IF=62
ABSENT=63
ON=64
POLICY=65
DEFAULT=66
SOURCE=67
REPOSITORY=68
COMMIT=69
REVISION=70
SEMANTIC_MAJOR=71
ON_DELETE=72
RETAIN_OTHER=73
KEYED=74
PUBLIC_TRAVERSAL=75
ID=76
DOC=77
MODE=78
EMITS=79
RECEIVER=80
REQUIRES=81
ANY=82
GET=83
SET=84
WATCH=85
START=86
STOP=87
READ=88
WRITE=89
RESOLVE=90
CONNECT=91
DISCONNECT=92
CALL=93
WATCH_START=94
WATCH_STOP=95
SUBSCRIBE=96
UNSUBSCRIBE=97
REGISTER=98
CRDT=99
OPTIONAL_ONE=100
EXACTLY_ONE=101
MANY_UNIQUE=102
MANY=103
ORDERED=104
UNIT=105
WATCH_HANDLE=106
MESSAGE=107
ATOM_REF=108
INTERFACE_REF=109
OPTIONAL=110
LIST=111
RECORD=112
BOOL=113
BYTES=114
DOUBLE=115
INT32=116
INT64=117
STRING=118
UINT32=119
UINT64=120
TRUE=121
FALSE=122
NULL=123
ARROW=124
COLON=125
SEMI=126
COMMA=127
DOT=128
LBRACE=129
RBRACE=130
LBRACK=131
RBRACK=132
LPAREN=133
RPAREN=134
LT=135
GT=136
AMP=137
EQUAL=138
INTEGER=139
JSON_NUMBER=140
IDENTIFIER=141
STRING_LITERAL=142
LINE_COMMENT=143
BLOCK_COMMENT=144
WS=145
'creation'=1
'initialize'=2
'workspace'=3
'execution'=4
'snapshot'=5
'transaction'=6
'remote'=7
'targets'=8
'portable'=9
'replica'=10
'local'=11
'from'=12
'result-bytes'=13
'rpc-calls'=14
'deadline-ms'=15
'query'=16
'reflection'=17
'specialize'=18
'root'=19
'document'=20
'fragments'=21
'view'=22
'max'=23
'allow'=24
'poll'=25
'queryable'=26
'rpc'=27
'query-reason'=28
'type'=29
'object'=30
'storable'=31
'implements'=32
'ref'=33
'fragment'=34
'import'=35
'external'=36
'atom'=37
'interface'=38
'interfaces'=39
'package'=40
'value'=41
'relation'=42
'operation'=43
'function'=44
'constructor'=45
'constructs'=46
'input'=47
'conform'=48
'as'=49
'bind'=50
'static'=51
'to'=52
'private'=53
'shared'=54
'state'=55
'edge'=56
'projection'=57
'with'=58
'using'=59
'via'=60
'materialize'=61
'if'=62
'absent'=63
'on'=64
'policy'=65
'default'=66
'source'=67
'repository'=68
'commit'=69
'revision'=70
'semantic-major'=71
'on-delete'=72
'retain-other'=73
'keyed'=74
'public-traversal'=75
'id'=76
'doc'=77
'mode'=78
'emits'=79
'receiver'=80
'requires'=81
'any'=82
'get'=83
'set'=84
'watch'=85
'start'=86
'stop'=87
'read'=88
'write'=89
'resolve'=90
'connect'=91
'disconnect'=92
'call'=93
'watch-start'=94
'watch-stop'=95
'subscribe'=96
'unsubscribe'=97
'register'=98
'crdt'=99
'optional-one'=100
'exactly-one'=101
'many-unique'=102
'many'=103
'ordered'=104
'unit'=105
'watch-handle'=106
'message'=107
'atom-ref'=108
'interface-ref'=109
'optional'=110
'list'=111
'record'=112
'bool'=113
'bytes'=114
'double'=115
'int32'=116
'int64'=117
'string'=118
'uint32'=119
'uint64'=120
'true'=121
'false'=122
'null'=123
'->'=124
':'=125
';'=126
','=127
'.'=128
'{'=129
'}'=130
'['=131
']'=132
'('=133
')'=134
'<'=135
'>'=136
'&'=137
'='=138
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -53,6 +53,9 @@ import { EdgeDeclContext } from "./QuixosCapabilityParser.js";
import { EdgeEndpointContext } from "./QuixosCapabilityParser.js";
import { ConformanceDeclContext } from "./QuixosCapabilityParser.js";
import { ConformanceItemContext } from "./QuixosCapabilityParser.js";
import { InitializationDeclContext } from "./QuixosCapabilityParser.js";
import { InitializationFieldContext } from "./QuixosCapabilityParser.js";
import { CreationOwnerMappingContext } from "./QuixosCapabilityParser.js";
import { StateFieldBindingDeclContext } from "./QuixosCapabilityParser.js";
import { RelationshipMaterializationDeclContext } from "./QuixosCapabilityParser.js";
import { OperationBindingDeclContext } from "./QuixosCapabilityParser.js";
@@ -390,6 +393,24 @@ export class QuixosCapabilityVisitor<Result> extends AbstractParseTreeVisitor<Re
* @return the visitor result
*/
visitConformanceItem?: (ctx: ConformanceItemContext) => Result;
/**
* Visit a parse tree produced by `QuixosCapabilityParser.initializationDecl`.
* @param ctx the parse tree
* @return the visitor result
*/
visitInitializationDecl?: (ctx: InitializationDeclContext) => Result;
/**
* Visit a parse tree produced by `QuixosCapabilityParser.initializationField`.
* @param ctx the parse tree
* @return the visitor result
*/
visitInitializationField?: (ctx: InitializationFieldContext) => Result;
/**
* Visit a parse tree produced by `QuixosCapabilityParser.creationOwnerMapping`.
* @param ctx the parse tree
* @return the visitor result
*/
visitCreationOwnerMapping?: (ctx: CreationOwnerMappingContext) => Result;
/**
* Visit a parse tree produced by `QuixosCapabilityParser.stateFieldBindingDecl`.
* @param ctx the parse tree
+69 -4
View File
@@ -1009,6 +1009,14 @@ const lowerDependencyPort = (state: LoweringState, context: DependencyPortContex
: undefined;
}
const atomId = requireSymbol(state, state.atoms, targetName, context, "atom");
if (context.CREATION())
return atomId
? {
id,
displayName: name,
requirement: { kind: "creation", atomId, inputType: lowerValueType(state, context.valueType()) },
}
: undefined;
return atomId
? {
id,
@@ -1229,7 +1237,11 @@ const lowerGenericPackageExport = (
throw new GenericTypeError("missing-constructor-input", id, "Constructor ports require an explicit input type");
return {
...base,
requirement: { kind: "constructor", target, inputType: state.types.expression(port.valueType()!) },
requirement: {
kind: port.CREATION() ? "creation" : "constructor",
target,
inputType: state.types.expression(port.valueType()!),
},
};
});
const operation = "receiverRequirement" in context ? context : undefined;
@@ -1567,8 +1579,8 @@ const lowerState = (state: LoweringState, context: StateDeclContext): StateSlotD
attachedTo: atomId,
displayName: identifier(context.identifier(0)),
valueType: lowerValueType(state, context.valueType()),
storagePolicy: policy.OPTIMISTIC_REGISTER()
? { kind: "optimistic-register" }
storagePolicy: policy.REGISTER()
? { kind: "register" }
: {
kind: "crdt-document",
updateType: lowerValueType(state, policy.valueType()),
@@ -1774,6 +1786,24 @@ const lowerBoundDependencies = (
}
const atomName = identifier(entry.identifier(1));
const atomId = requireSymbol(state, state.atoms, atomName, entry, "atom");
if (entry.CREATION())
return atomId
? [
{
portId,
binding: {
kind: "creation",
atomId,
owners: entry.creationOwnerMapping().map((owner) => ({
interfaceRevisionId: new TypeSubstitution(state.types.environment()).application(
state.types.interface(owner.interfaceType()),
),
inputPath: owner.identifier().map(identifier),
})),
},
},
]
: [];
return atomId ? [{ portId, binding: { kind: "constructor", atomId } }] : [];
});
};
@@ -2025,8 +2055,43 @@ const lowerConformance = (
const lowered = lowerRelationshipMaterialization(state, interfaceName, materialization);
return lowered ? [lowered] : [];
});
const initializers = context
.conformanceItem()
.flatMap((item) => (item.initializationDecl() ? [item.initializationDecl()!] : []));
if (initializers.length > 1)
loweringIssue(state, context, "duplicate-initialization", "Declare one initialization mapping per owner");
const initialization = initializers.flatMap((initializer) =>
initializer.initializationField().flatMap((field): import("../capability-model/types.js").InitializationField[] => {
const input = identifier(field.identifier(0));
const attachment = requireSymbol(
state,
state.attachments,
identifier(field.identifier(1)),
field,
"initialization attachment",
);
if (!attachment) return [];
if (field.STATE() && attachment.attachment.kind === "state")
return [{ input, binding: { kind: "state", slotId: attachment.attachment.id } }];
if (field.EDGE() && attachment.attachment.kind === "edge") {
const projectionId = requireSymbol(
state,
attachment.projections,
identifier(field.identifier(2)),
field,
"initialization projection",
);
return projectionId
? [{ input, binding: { kind: "edge", edgeTypeId: attachment.attachment.id, projectionId } }]
: [];
}
loweringIssue(state, field, "invalid-initialization", "Initialization attachment kind does not match");
return [];
}),
);
state.interfaces.set(interfaceName, originalSymbol);
return {
...(initializers.length ? { initialization } : {}),
atomId,
interfaceRevisionId: interfaceSymbol.revisionId,
...(context.stringLiteral() ? { id: capabilityId.conformance(stringValue(context.stringLiteral()!)) } : {}),
@@ -2562,7 +2627,7 @@ const compileCapabilityResourceSourceInternal = (
types.push(requirement.valueType);
}
if (requirement.kind === "edge") types.push({ kind: "object-ref", expectation: requirement.target });
if (requirement.kind === "constructor")
if (requirement.kind === "constructor" || requirement.kind === "creation")
types.push(requirement.inputType, { kind: "object-ref", expectation: requirement.target });
if (requirement.kind === "interface") requires.push(requirement.application);
}
+7
View File
@@ -149,6 +149,10 @@ export const runtimeContracts = (workspace: WorkspaceRevision): RuntimeContract[
}
if (binding.kind === "state") parent.dependencies.add(`attachment:${binding.slotId}`);
if (binding.kind === "edge") parent.dependencies.add(`attachment:${binding.edgeTypeId}`);
if (binding.kind === "creation") {
for (const owner of binding.owners)
parent.dependencies.add(conformanceKey(binding.atomId, owner.interfaceRevisionId));
}
if (binding.kind === "constructor") {
parent.dependencies.add(`constructor:${binding.atomId}`);
const ctor = workspace.constructors.find((entry) => entry.atomId === binding.atomId);
@@ -210,6 +214,9 @@ export const runtimeContracts = (workspace: WorkspaceRevision): RuntimeContract[
atomId: conformance.atomId,
interfaceRevisionId: conformance.interfaceRevisionId,
operations: sorted(conformance.operationBindings, (entry) => entry.operationId),
...(conformance.initialization
? { initialization: sorted(conformance.initialization, (entry) => entry.input) }
: {}),
materializations: sorted(conformance.relationshipMaterializations, (entry) => entry.memberId),
});
parent.dependencies.add(`interface:${conformance.interfaceRevisionId}`);
+2
View File
@@ -89,6 +89,8 @@ export function validateExecutionContracts(workspace: WorkspaceRevision): Capabi
const query = packages.get(requirement.packageRevisionId)?.queries?.find((q) => q.id === requirement.queryId);
if (query?.placement?.kind !== "local")
fail(portPath, "Portable operations require a local query dependency");
} else if (requirement.kind === "creation") {
if (entry.execution.kind !== "transaction") fail(portPath, "Object creation requires transaction execution");
} else if (requirement.kind !== "interface")
fail(portPath, `Portable execution does not support ${requirement.kind} dependencies`);
}
+3 -1
View File
@@ -39,6 +39,7 @@ export type GenericDependencyPort = Omit<DependencyPort, "requirement"> & {
application: InterfaceApplicationExpression;
operations?: import("./types.js").OperationId[];
}
| { kind: "creation"; target: ObjectTypeExpression; inputType: ValueTypeExpression }
| { kind: "constructor"; target: ObjectTypeExpression; inputType: ValueTypeExpression };
};
@@ -101,6 +102,7 @@ export const specializePackageExport = (
...(requirement.operations ? { operations: requirement.operations } : {}),
},
};
case "creation":
case "constructor": {
const target = substitution.object(requirement.target);
if (target.kind !== "atom")
@@ -112,7 +114,7 @@ export const specializePackageExport = (
return {
...port,
requirement: {
kind: "constructor",
kind: requirement.kind,
atomId: target.atomId,
inputType: substitution.value(requirement.inputType),
},
+121
View File
@@ -0,0 +1,121 @@
import type {
BoundDependency,
Conformance,
DependencyPort,
InitializationField,
ValueType,
WorkspaceRevision,
} from "./types.js";
import type { CapabilityValidationIssue } from "./validation.js";
import { cardinalityValueType, valueTypesEqual } from "./validation.js";
/** Creation grants seed inputs on new objects, never general private-state authority. */
export function validateInitialization(workspace: WorkspaceRevision): CapabilityValidationIssue[] {
const issues: CapabilityValidationIssue[] = [];
const fail = (path: string, message: string) => issues.push({ code: "invalid-initialization", path, message });
const ownerKey = (owner: Conformance) => `${owner.atomId}/${owner.interfaceRevisionId}`;
const shapes = new Map<string, ValueType>();
const fieldType = (owner: Conformance, field: InitializationField): ValueType | undefined => {
const binding = field.binding;
const attachment = owner.privateAttachments.find(
(a) => a.kind === binding.kind && a.id === (binding.kind === "state" ? binding.slotId : binding.edgeTypeId),
);
if (!attachment) {
fail(ownerKey(owner), `Initialization input ${field.input} must name an attachment owned by this conformance`);
return;
}
if (attachment.kind === "state") return attachment.valueType;
if (binding.kind !== "edge") return;
const index = attachment.endpoints.findIndex((e) => e.projectionId === binding.projectionId);
const endpoint = attachment.endpoints[index];
const target = attachment.endpoints[1 - index];
if (!endpoint || !target || endpoint.constraint.kind !== "atom" || endpoint.constraint.atomId !== owner.atomId) {
fail(ownerKey(owner), `Initialization input ${field.input} must name an edge projection on the newborn atom`);
return;
}
if (endpoint.cardinality === "many" || endpoint.cardinality === "many-unique") {
const fields: Record<string, ValueType> = {
key: { kind: "scalar", name: "string" },
target: cardinalityValueType(target.constraint, "exactly-one"),
};
if (endpoint.keyType)
fields.mapKey = { kind: "scalar", name: endpoint.keyType === "boolean" ? "bool" : endpoint.keyType };
return { kind: "list", value: { kind: "record", fields } };
}
return cardinalityValueType(target.constraint, endpoint.cardinality);
};
for (const owner of workspace.conformances) {
if (!owner.initialization) continue;
const fields: Record<string, ValueType> = Object.create(null);
const destinations = new Set<string>();
for (const field of owner.initialization) {
const path = `${ownerKey(owner)}/${field.input}`;
if (!field.input || Object.hasOwn(fields, field.input))
fail(path, "Initialization input names must be nonempty and distinct");
const destination = JSON.stringify(field.binding);
if (destinations.has(destination)) fail(path, "An attachment may have only one initialization input");
destinations.add(destination);
const type = fieldType(owner, field);
if (type) fields[field.input] = type;
}
shapes.set(ownerKey(owner), { kind: "record", fields });
}
const checkBindings = (ports: DependencyPort[], dependencies: BoundDependency[], path: string) => {
for (const dependency of dependencies) {
if (dependency.binding.kind !== "creation") continue;
const binding = dependency.binding;
const requirement = ports.find((p) => p.id === dependency.portId)?.requirement;
if (requirement?.kind !== "creation") continue; // General binding validation reports the kind mismatch.
const portPath = `${path}/${dependency.portId}`;
if (binding.atomId !== requirement.atomId) fail(portPath, `Creation port requires atom ${requirement.atomId}`);
const initialized = new Set<string>();
const supplied = new Set<string>();
for (const mapping of binding.owners) {
const key = `${binding.atomId}/${mapping.interfaceRevisionId}`;
if (supplied.has(key)) fail(portPath, `Duplicate initialization owner ${key}`);
supplied.add(key);
const owner = workspace.conformances.find((o) => ownerKey(o) === key);
const shape = shapes.get(key);
if (!owner || !shape) {
fail(portPath, `Owner ${key} has no declared initialization mapping`);
continue;
}
let seed: ValueType | undefined = requirement.inputType;
for (const component of mapping.inputPath) seed = seed?.kind === "record" ? seed.fields[component] : undefined;
if (!seed || !valueTypesEqual(seed, shape))
fail(
portPath,
`Seed input${mapping.inputPath.map((p) => `.${p}`).join("")} does not match initialization inputs of ${key}`,
);
for (const field of owner.initialization!)
if (field.binding.kind === "state") initialized.add(field.binding.slotId);
}
const states = [
...workspace.sharedAttachments,
...workspace.conformances.flatMap((o) => o.privateAttachments),
].filter((a) => a.kind === "state" && a.attachedTo === binding.atomId);
for (const state of states)
if (state.kind === "state" && state.defaultValue === undefined && !initialized.has(state.id))
fail(portPath, `Required state ${state.id} has no default or declared seed mapping`);
}
};
const exports = new Map(
workspace.packageImports.flatMap((p) => p.exports.map((e) => [`${p.revisionId}/${e.id}`, e] as const)),
);
for (const owner of workspace.conformances)
for (const operation of owner.operationBindings) {
if (operation.binding.kind !== "package") continue;
const entry = exports.get(`${operation.binding.packageRevisionId}/${operation.binding.exportId}`);
if (entry)
checkBindings(
entry.dependencyPorts,
operation.binding.dependencies,
`${ownerKey(owner)}/${operation.operationId}`,
);
}
for (const constructor of workspace.constructors) {
const entry = exports.get(`${constructor.packageRevisionId}/${constructor.exportId}`);
if (entry) checkBindings(entry.dependencyPorts, constructor.dependencies, `constructor/${constructor.atomId}`);
}
return issues;
}
+14 -1
View File
@@ -181,7 +181,7 @@ export interface InterfaceRevision {
argumentRequirements?: { target: ObjectExpectation; required: InterfaceRevisionId }[];
}
export type StoragePolicy = { kind: "optimistic-register" } | { kind: "crdt-document"; updateType: ValueType };
export type StoragePolicy = { kind: "register" } | { kind: "crdt-document"; updateType: ValueType };
export interface StateSlotDefinition {
kind: "state";
@@ -246,6 +246,7 @@ export type DependencyPortRequirement =
/** Exact public operation identities, not implementation export names. */
operations?: OperationId[];
}
| { kind: "creation"; atomId: AtomId; inputType: ValueType }
| { kind: "constructor"; atomId: AtomId; inputType?: ValueType };
export interface DependencyPort {
@@ -320,6 +321,12 @@ export type DependencyBinding =
interfaceRevisionId: InterfaceRevisionId;
via?: EdgeTraversal;
}
| {
kind: "creation";
atomId: AtomId;
owners: { interfaceRevisionId: InterfaceRevisionId; inputPath: string[] }[];
via?: never;
}
| { kind: "constructor"; atomId: AtomId };
export interface BoundDependency {
@@ -376,7 +383,13 @@ export interface OperationBinding {
queryReason?: string;
}
export interface InitializationField {
input: string;
binding: { kind: "state"; slotId: SlotId } | { kind: "edge"; edgeTypeId: EdgeTypeId; projectionId: EdgeProjectionId };
}
export interface Conformance {
initialization?: InitializationField[];
/** Absent only for legacy assemblies awaiting explicit ownership enrollment. */
id?: ConformanceId;
semanticMajor?: number;
+12 -4
View File
@@ -1,3 +1,4 @@
import { validateInitialization } from "./initialization.js";
import { validateExecutionContracts } from "./execution.js";
import { resolveForwardedBinding, type ConcreteBinding } from "./forwarding.js";
import type {
@@ -42,6 +43,7 @@ import { specializePackageExport } from "./generic-packages.js";
import { isDeepStrictEqual } from "node:util";
export type CapabilityValidationIssueCode =
| "invalid-initialization"
| "invalid-execution-contract"
| "invalid-query-contract"
| "query-provider-reason-required"
@@ -1043,6 +1045,7 @@ const validatePackages = (
);
}
break;
case "creation":
case "constructor":
if (port.requirement.inputType)
validateValueType(issues, port.requirement.inputType, `${portPath}.requirement.inputType`, indexes);
@@ -1794,14 +1797,14 @@ const validateConformances = (
});
if (
operation.scope === "class" &&
(binding.dependencies.some((entry) => entry.binding.kind !== "constructor") ||
packageExport.dependencyPorts.some((entry) => entry.requirement.kind !== "constructor"))
(binding.dependencies.some((entry) => !["constructor", "creation"].includes(entry.binding.kind)) ||
packageExport.dependencyPorts.some((entry) => !["constructor", "creation"].includes(entry.requirement.kind)))
) {
issue(
issues,
"invalid-package-binding",
bindingPath,
"Class functions may inject constructors, not instance-dependent ports",
"Class functions require receiverless dependencies such as creation ports",
);
}
validateBoundDependencies(issues, {
@@ -2084,7 +2087,7 @@ export const validateWorkspaceRevision = (workspace: WorkspaceRevision): Capabil
validatePackages(workspace, issues, indexes);
validateConformances(workspace, issues, indexes);
validateConstructors(workspace, issues, indexes);
issues.push(...validateExecutionContracts(workspace));
issues.push(...validateExecutionContracts(workspace), ...validateInitialization(workspace));
return issues;
};
@@ -2327,6 +2330,11 @@ export const computeCapabilityClosure = (
}
break;
}
case "creation": {
for (const owner of dependency.binding.owners)
queued.push({ atomId: dependency.binding.atomId, interfaceRevisionId: owner.interfaceRevisionId });
break;
}
case "constructor": {
constructors.add(dependency.binding.atomId);
const constructor = plan.constructors.get(dependency.binding.atomId);