@reventlessdev/reventless-spec 3.0.0-alpha.127 → 3.0.0-alpha.129
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +52 -0
- package/package.json +2 -2
- package/schema/platform-api.graphql +1 -0
- package/src/AnsiStyle.res +31 -7
- package/src/AnsiStyle.res.mjs +16 -2
- package/src/components/Plugin.res +66 -84
- package/src/components/Plugin.res.mjs +46 -81
- package/src/components/Sensitive.res +137 -0
- package/src/components/Sensitive.res.mjs +74 -0
- package/src/generator/GraftTrait.res +5 -4
- package/src/generator/GraftTrait.res.mjs +4 -4
- package/src/semantic/Bytes.res +21 -21
- package/src/semantic/Bytes.res.mjs +35 -0
- package/src/semantic/CaptionedImage.res +100 -0
- package/src/semantic/CaptionedImage.res.mjs +35 -0
- package/src/semantic/Duration.res +22 -24
- package/src/semantic/Duration.res.mjs +43 -0
- package/src/semantic/MemberRef.res +119 -0
- package/src/semantic/MemberRef.res.mjs +57 -0
- package/src/semantic/Offload.res.mjs +2 -2
- package/src/semantic/Percent.res +9 -20
- package/src/semantic/Percent.res.mjs +5 -0
- package/src/semantic/Semantic.res +63 -0
- package/src/semantic/Semantic.res.mjs +39 -0
- package/src/semantic/StorageRef.res.mjs +1 -1
- package/src/semantic/Template.res +412 -0
- package/src/semantic/Template.res.mjs +545 -0
- package/src/types/Message.res +14 -12
- package/src/types/Message.res.mjs +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,58 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
# 3.0.0-alpha.129 (2026-09-04)
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
* **logging:** let a runtime declare whether a person reads its logs ([df7a6cb](https://github.com/ReventlessDev/reventless-core/commit/df7a6cbdff62b856ea6b9d1f169b913563ba13a1))
|
|
11
|
+
* feat(spec)!: one optional encoding on the wire, with no annotation ([320f91d](https://github.com/ReventlessDev/reventless-core/commit/320f91daa8bd90812a6e82069e7a1cb473041930))
|
|
12
|
+
|
|
13
|
+
### BREAKING CHANGES
|
|
14
|
+
|
|
15
|
+
* the two encodings cannot read each other. Stored
|
|
16
|
+
pluginDefinition / pluginStructure payloads and already-deployed plugins must
|
|
17
|
+
go — wipe the platform scope (SEED_RESET_SCOPE=platform), quiesce, and
|
|
18
|
+
redeploy the fleet from one commit. Domain plugin data is untouched.
|
|
19
|
+
|
|
20
|
+
Two guards had to learn the new shape, both of which defined "optional" as
|
|
21
|
+
has.null and so mistook an omitted key for something to invent:
|
|
22
|
+
|
|
23
|
+
- Message.fillMissingDefaults reached `return undefined` only below two arms
|
|
24
|
+
that fire first — an enum's first const, and an object member filled with
|
|
25
|
+
zeros. Absent option<record> therefore healed to a zero-filled record, not
|
|
26
|
+
None: on the real schema, dcbEventLog became Some({name: "", eventTopicArn:
|
|
27
|
+
""}), which manageSubscriptions would have read as a peer to subscribe to.
|
|
28
|
+
One line, mirroring the has.null guard, above both arms.
|
|
29
|
+
- PluginDefinitionScalars' walker reported 16 optional fields as newly-added
|
|
30
|
+
bare required scalars. With both encodings understood, the golden list is
|
|
31
|
+
unchanged.
|
|
32
|
+
|
|
33
|
+
The frozen lifecycle corpus holds five real payloads in the old encoding.
|
|
34
|
+
Null-valued keys were stripped mechanically — the rewrite round-trips each
|
|
35
|
+
file unchanged before editing, and a key-by-key diff shows null removals and
|
|
36
|
+
nothing else. The README records it beside the account-id redaction and says
|
|
37
|
+
why it is not a regeneration: no fixture was rebuilt from ReScript types, so
|
|
38
|
+
every generation in its table is still pinned.
|
|
39
|
+
|
|
40
|
+
check:graphql is unchanged, as expected: SchemaType.fromSury collapses Null
|
|
41
|
+
and Undefined to the same Nullable, so the emitted schema never distinguished
|
|
42
|
+
them.
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
# 3.0.0-alpha.128 (2026-09-04)
|
|
47
|
+
|
|
48
|
+
### Features
|
|
49
|
+
|
|
50
|
+
* **notifications:** the wording is a table of values, not a switch ([bfcc939](https://github.com/ReventlessDev/reventless-core/commit/bfcc93940b81060d201fd231447fb14dcf48d80e))
|
|
51
|
+
* **plugin:** a slice publishes which topics it subscribes to ([c689695](https://github.com/ReventlessDev/reventless-core/commit/c6896957ecb636204678222ac5a26b30870439cb))
|
|
52
|
+
* **spec,traits:** an image carries the text that goes with it, and a set's first member is its primary ([e4e5845](https://github.com/ReventlessDev/reventless-core/commit/e4e58458aee7b3db5564727d358a3a9767362ca4))
|
|
53
|
+
* **spec:** a field can say it selects one of the values its row already holds ([2ae50c3](https://github.com/ReventlessDev/reventless-core/commit/2ae50c34deee48508a9f1f39e3eef6a5d2f5df00))
|
|
54
|
+
* **spec:** a field can say its value must not be rendered into a message ([3183f53](https://github.com/ReventlessDev/reventless-core/commit/3183f53a0ae667f794bbd1a3d77acf362dfa8e57))
|
|
55
|
+
* **spec:** a message template renders a payload through its own schema ([39e3f61](https://github.com/ReventlessDev/reventless-core/commit/39e3f6126bd831a316323b4663bc37c50cdfc704))
|
|
56
|
+
|
|
57
|
+
|
|
6
58
|
# 3.0.0-alpha.127 (2026-09-02)
|
|
7
59
|
|
|
8
60
|
* feat(aws)!: the messaging sender is configuration, and a stack can choose to only log ([23b8b4b](https://github.com/ReventlessDev/reventless-core/commit/23b8b4bfe9c70555de4d74266ca686cb427485ca))
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reventlessdev/reventless-spec",
|
|
3
|
-
"version": "3.0.0-alpha.
|
|
3
|
+
"version": "3.0.0-alpha.129",
|
|
4
4
|
"description": "Specifications for Reventless",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"bin": {
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"sury": "11.0.0-rc.2",
|
|
25
25
|
"sury-ppx": "11.0.0-rc.2",
|
|
26
26
|
"yaml": "^2.8.3",
|
|
27
|
-
"@reventlessdev/rescript-node": "2.0.0-alpha.
|
|
27
|
+
"@reventlessdev/rescript-node": "2.0.0-alpha.9"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
30
|
"rescript": "12.3.0",
|
package/src/AnsiStyle.res
CHANGED
|
@@ -11,14 +11,23 @@
|
|
|
11
11
|
@val external _isTty: option<bool> = "process.stdout.isTTY"
|
|
12
12
|
@val external _logFormat: option<string> = "process.env.REVENTLESS_LOG_FORMAT"
|
|
13
13
|
|
|
14
|
-
//
|
|
15
|
-
// a
|
|
16
|
-
//
|
|
14
|
+
// What this runtime is, for when stdout cannot say. A local dev platform's logs
|
|
15
|
+
// are read by a person even when its stdout is a pipe — `concurrently`, `tsx
|
|
16
|
+
// watch` and an IDE terminal all pipe — while a Lambda's are read by a collector
|
|
17
|
+
// through a pipe that looks identical. TTY-ness is only a proxy for "a person is
|
|
18
|
+
// reading this", and a pipe is exactly where the proxy fails, so a runtime that
|
|
19
|
+
// knows which it is says so instead of being guessed at.
|
|
20
|
+
let _default: ref<option<string>> = ref(None)
|
|
21
|
+
|
|
22
|
+
// "json" | "text", in precedence order: an explicit REVENTLESS_LOG_FORMAT, then
|
|
23
|
+
// what the runtime declared itself to be, then the TTY probe — a TTY stdout ⇒
|
|
24
|
+
// text, everything else (Lambda, Fargate, ECS, Docker, CI) ⇒ structured JSON.
|
|
17
25
|
let _resolveFormat = (): string =>
|
|
18
|
-
switch (_logFormat, _isTty) {
|
|
19
|
-
| (Some("json"), _) => "json"
|
|
20
|
-
| (Some("text"), _) => "text"
|
|
21
|
-
| (_, Some(
|
|
26
|
+
switch (_logFormat, _default.contents, _isTty) {
|
|
27
|
+
| (Some("json"), _, _) => "json"
|
|
28
|
+
| (Some("text"), _, _) => "text"
|
|
29
|
+
| (_, Some(declared), _) => declared
|
|
30
|
+
| (_, None, Some(true)) => "text"
|
|
22
31
|
| _ => "json"
|
|
23
32
|
}
|
|
24
33
|
|
|
@@ -27,6 +36,21 @@ let _resolveFormat = (): string =>
|
|
|
27
36
|
// keeps every log call off `process.env`.
|
|
28
37
|
let _format = ref(_resolveFormat())
|
|
29
38
|
|
|
39
|
+
/** Declares what this runtime is, for the pipe case the TTY probe gets wrong.
|
|
40
|
+
Call it at module init, before anything logs. An explicit
|
|
41
|
+
REVENTLESS_LOG_FORMAT still wins, so a developer can always ask for the other
|
|
42
|
+
one. A runtime that says nothing keeps the TTY behaviour. */
|
|
43
|
+
let setDefaultFormat = (format: [#text | #json]) => {
|
|
44
|
+
_default :=
|
|
45
|
+
Some(
|
|
46
|
+
switch format {
|
|
47
|
+
| #text => "text"
|
|
48
|
+
| #json => "json"
|
|
49
|
+
},
|
|
50
|
+
)
|
|
51
|
+
_format := _resolveFormat()
|
|
52
|
+
}
|
|
53
|
+
|
|
30
54
|
/** Test-only: re-evaluate REVENTLESS_LOG_FORMAT / process.stdout.isTTY. */
|
|
31
55
|
let reload = () => _format := _resolveFormat()
|
|
32
56
|
|
package/src/AnsiStyle.res.mjs
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
2
|
|
|
3
3
|
|
|
4
|
+
let _default = {
|
|
5
|
+
contents: undefined
|
|
6
|
+
};
|
|
7
|
+
|
|
4
8
|
function _resolveFormat() {
|
|
5
9
|
let match = process.env.REVENTLESS_LOG_FORMAT;
|
|
6
|
-
let match$1 =
|
|
10
|
+
let match$1 = _default.contents;
|
|
11
|
+
let match$2 = process.stdout.isTTY;
|
|
7
12
|
if (match !== undefined) {
|
|
8
13
|
switch (match) {
|
|
9
14
|
case "json" :
|
|
@@ -12,7 +17,9 @@ function _resolveFormat() {
|
|
|
12
17
|
return "text";
|
|
13
18
|
}
|
|
14
19
|
}
|
|
15
|
-
if (match$1 !== undefined
|
|
20
|
+
if (match$1 !== undefined) {
|
|
21
|
+
return match$1;
|
|
22
|
+
} else if (match$2 !== undefined && match$2) {
|
|
16
23
|
return "text";
|
|
17
24
|
} else {
|
|
18
25
|
return "json";
|
|
@@ -23,6 +30,11 @@ let _format = {
|
|
|
23
30
|
contents: _resolveFormat()
|
|
24
31
|
};
|
|
25
32
|
|
|
33
|
+
function setDefaultFormat(format) {
|
|
34
|
+
_default.contents = format === "text" ? "text" : "json";
|
|
35
|
+
_format.contents = _resolveFormat();
|
|
36
|
+
}
|
|
37
|
+
|
|
26
38
|
function reload() {
|
|
27
39
|
_format.contents = _resolveFormat();
|
|
28
40
|
}
|
|
@@ -44,8 +56,10 @@ function bold(s) {
|
|
|
44
56
|
}
|
|
45
57
|
|
|
46
58
|
export {
|
|
59
|
+
_default,
|
|
47
60
|
_resolveFormat,
|
|
48
61
|
_format,
|
|
62
|
+
setDefaultFormat,
|
|
49
63
|
reload,
|
|
50
64
|
isJsonSink,
|
|
51
65
|
useAnsi,
|
|
@@ -73,13 +73,13 @@ type apiSchemaFragment = {encoded: string, protocol: string}
|
|
|
73
73
|
@schema
|
|
74
74
|
type apiTarget = Domain | Platform
|
|
75
75
|
|
|
76
|
-
//
|
|
77
|
-
//
|
|
76
|
+
// Every optional below carries sury's default `option` encoding: the key is omitted,
|
|
77
|
+
// never written as `null`. That is the shape the rest of the repo already uses
|
|
78
|
+
// (`Message.meta`, `storedEvent.tags`), so there is one optional form on the wire and
|
|
79
|
+
// nothing to annotate. The `T | null` these fields used to carry worked around a sury
|
|
80
|
+
// bug — undefined failing jsonableValidation inside a union variant payload — fixed in
|
|
81
|
+
// 11.0.0-alpha.11. `Offload` below is not an optional wrapper but an either-or codec.
|
|
78
82
|
let apiSchemaFragmentOffloadSchema = Offload.optionSchema(~store="pluginApiFragments", apiSchemaFragmentSchema)
|
|
79
|
-
let dcbEventLogOptionSchema = dcbEventLogDefinitionSchema->S.nullAsOption
|
|
80
|
-
let stringOptionSchema = S.string->S.nullAsOption
|
|
81
|
-
let stringArrayOptionSchema = S.array(S.string)->S.nullAsOption
|
|
82
|
-
let boolOptionSchema = S.bool->S.nullAsOption
|
|
83
83
|
|
|
84
84
|
// ── UI fragment manifest types ────────────────────────────────────────────────
|
|
85
85
|
|
|
@@ -89,14 +89,14 @@ type panelManifestEntry = {
|
|
|
89
89
|
title: string,
|
|
90
90
|
description: string,
|
|
91
91
|
positions: array<string>,
|
|
92
|
-
requiredAccess:
|
|
92
|
+
requiredAccess: option<string>,
|
|
93
93
|
}
|
|
94
94
|
|
|
95
95
|
@schema
|
|
96
96
|
type menuEntry = {
|
|
97
97
|
label: string,
|
|
98
|
-
icon:
|
|
99
|
-
group:
|
|
98
|
+
icon: option<string>,
|
|
99
|
+
group: option<string>,
|
|
100
100
|
sortOrder: int,
|
|
101
101
|
}
|
|
102
102
|
|
|
@@ -105,7 +105,7 @@ type pageManifestEntry = {
|
|
|
105
105
|
fragmentId: string,
|
|
106
106
|
title: string,
|
|
107
107
|
menuEntry: menuEntry,
|
|
108
|
-
requiredAccess:
|
|
108
|
+
requiredAccess: option<string>,
|
|
109
109
|
}
|
|
110
110
|
|
|
111
111
|
@schema
|
|
@@ -115,7 +115,7 @@ type uiFragmentManifest = {
|
|
|
115
115
|
pages: array<pageManifestEntry>,
|
|
116
116
|
}
|
|
117
117
|
|
|
118
|
-
let uiFragmentManifestOptionSchema =
|
|
118
|
+
let uiFragmentManifestOptionSchema = S.option(uiFragmentManifestSchema)
|
|
119
119
|
|
|
120
120
|
// ── Plugin structure types (component metadata for Auto UI and event graph) ──
|
|
121
121
|
|
|
@@ -126,7 +126,7 @@ type commandLevel = Collection | Instance
|
|
|
126
126
|
type fieldReference = {
|
|
127
127
|
fieldName: string,
|
|
128
128
|
entity: string,
|
|
129
|
-
plugin:
|
|
129
|
+
plugin: option<string>,
|
|
130
130
|
}
|
|
131
131
|
|
|
132
132
|
@schema
|
|
@@ -134,23 +134,23 @@ type commandDef = {
|
|
|
134
134
|
name: string,
|
|
135
135
|
schema: string,
|
|
136
136
|
level: commandLevel,
|
|
137
|
-
aggregateIdField:
|
|
137
|
+
aggregateIdField: option<string>,
|
|
138
138
|
mutationField: string,
|
|
139
139
|
references: array<fieldReference>,
|
|
140
140
|
/** The declared *from* set — lifecycle states this command is meaningful in.
|
|
141
141
|
`None` means always available; `Some([])` means never show. */
|
|
142
|
-
allowedStates:
|
|
142
|
+
allowedStates: option<array<string>>,
|
|
143
143
|
/** The declared *to* state this command's handler writes. `None` with a
|
|
144
144
|
from-set present means the command does not move the row. */
|
|
145
|
-
targetState:
|
|
145
|
+
targetState: option<string>,
|
|
146
146
|
/** Whether the variant is exposed in the generated API (non-`@noApi`). */
|
|
147
|
-
apiExposed:
|
|
147
|
+
apiExposed: option<bool>,
|
|
148
148
|
/** Access keys — any one of them — a caller needs to be *offered* this command.
|
|
149
149
|
A hint derived from the server's rule, never the refusal itself. */
|
|
150
|
-
requiredAccess:
|
|
150
|
+
requiredAccess: option<array<string>>,
|
|
151
151
|
/** The `@owner` command field the server stamps with the caller's identity; a
|
|
152
152
|
client omits it from a form, since whatever it collects is discarded. */
|
|
153
|
-
ownerField:
|
|
153
|
+
ownerField: option<string>,
|
|
154
154
|
}
|
|
155
155
|
|
|
156
156
|
@schema
|
|
@@ -168,41 +168,41 @@ type queryableDef = {
|
|
|
168
168
|
searchableFields: array<string>,
|
|
169
169
|
/** Which rung produced `labelField`, so a consumer can rank it against its own
|
|
170
170
|
rule: `"annotation"` | `"convention"` | `"position"` | `"fallback"`. */
|
|
171
|
-
labelFieldSource:
|
|
171
|
+
labelFieldSource: option<string>,
|
|
172
172
|
/** The state field holding the row's lifecycle, paired with
|
|
173
173
|
`commandDef.allowedStates`. From `@lifecycle`, else an enum named `lifecycle`. */
|
|
174
|
-
lifecycleField:
|
|
174
|
+
lifecycleField: option<string>,
|
|
175
175
|
/** The `@owner` state field. Reads of this view are narrowed server-side to a
|
|
176
176
|
non-elevated caller's own rows. */
|
|
177
|
-
ownerField:
|
|
177
|
+
ownerField: option<string>,
|
|
178
178
|
/** The `@retired` state field withdrawing a row from ordinary reads. From the
|
|
179
179
|
annotation only — no fallback by name, since guessing hides data. */
|
|
180
|
-
retiredField:
|
|
180
|
+
retiredField: option<string>,
|
|
181
181
|
/** The states a row is retired *in* (state form of `@retired`); `None` is the
|
|
182
182
|
boolean form, where the excluded value is always `true`. */
|
|
183
|
-
retiredValues:
|
|
183
|
+
retiredValues: option<array<string>>,
|
|
184
184
|
/** Whether the view publishes the by-ids reference door that names a retired row
|
|
185
185
|
to any caller holding a pointer (`@namedWhenRetired`). Never true without
|
|
186
186
|
`retiredField`. */
|
|
187
|
-
namedWhenRetired:
|
|
187
|
+
namedWhenRetired: option<bool>,
|
|
188
188
|
/** `@@reventless.visibility`. `Some("Internal")` hides the component from AutoUI;
|
|
189
189
|
it is still carried here for developer tooling. `None` means Public. */
|
|
190
|
-
visibility:
|
|
190
|
+
visibility: option<string>,
|
|
191
191
|
/** Intra-plugin grouping band, the first non-kind path segment under `src/`.
|
|
192
192
|
`None` renders flat. */
|
|
193
|
-
chapter:
|
|
193
|
+
chapter: option<string>,
|
|
194
194
|
/** The singular counterpart of `queryField` (`Plugin_Order`), also the prefix of
|
|
195
195
|
the generated input types. Not derivable without `Api_Naming.singularize`. */
|
|
196
|
-
singleQueryField:
|
|
196
|
+
singleQueryField: option<string>,
|
|
197
197
|
/** The state field identifying a row, as opposed to a reference to another entity.
|
|
198
198
|
`None` means unresolved — no key-derived filter or sort until `@id` is declared. */
|
|
199
|
-
idField:
|
|
199
|
+
idField: option<string>,
|
|
200
200
|
/** Which rung produced `idField`, as `labelFieldSource` does: `"annotation"` |
|
|
201
201
|
`"convention"` | `"sole"`. */
|
|
202
|
-
idFieldSource:
|
|
202
|
+
idFieldSource: option<string>,
|
|
203
203
|
/** Access keys — any one of them — a caller needs to be *offered* this view. A
|
|
204
204
|
denied read comes back empty rather than erroring, hence the hint. */
|
|
205
|
-
requiredAccess:
|
|
205
|
+
requiredAccess: option<array<string>>,
|
|
206
206
|
}
|
|
207
207
|
|
|
208
208
|
/** One emitted event of a write side. `name` is the variant name, `schema` its
|
|
@@ -230,7 +230,7 @@ type writableDef = {
|
|
|
230
230
|
producedEventTypes: array<string>,
|
|
231
231
|
consumedEventTypes: array<string>,
|
|
232
232
|
linkedViews: array<string>,
|
|
233
|
-
consistencyRead:
|
|
233
|
+
consistencyRead: option<string>,
|
|
234
234
|
/** Emitted-event field schemas; `[]` when there are none. */
|
|
235
235
|
events: array<eventDef>,
|
|
236
236
|
/** Declared-error field schemas. Required, but a persisted structure predating a
|
|
@@ -238,7 +238,7 @@ type writableDef = {
|
|
|
238
238
|
`[]` on read, and a new one must be added there too. */
|
|
239
239
|
errors: array<errorDef>,
|
|
240
240
|
/** Chapter grouping band — see `queryableDef.chapter`. */
|
|
241
|
-
chapter:
|
|
241
|
+
chapter: option<string>,
|
|
242
242
|
}
|
|
243
243
|
|
|
244
244
|
@schema
|
|
@@ -248,7 +248,7 @@ type automationSliceDef = {
|
|
|
248
248
|
producedCommandTypes: array<string>,
|
|
249
249
|
targetName: string,
|
|
250
250
|
/** Chapter grouping band — see `queryableDef.chapter`. */
|
|
251
|
-
chapter:
|
|
251
|
+
chapter: option<string>,
|
|
252
252
|
}
|
|
253
253
|
|
|
254
254
|
@schema
|
|
@@ -256,11 +256,19 @@ type outboundTranslationSliceDef = {
|
|
|
256
256
|
name: string,
|
|
257
257
|
consumedEventTypes: array<string>,
|
|
258
258
|
inboundCommandTypes: array<string>,
|
|
259
|
-
targetName:
|
|
259
|
+
targetName: option<string>,
|
|
260
260
|
// Foreign system this slice publishes to — drives the external box (Event Graph).
|
|
261
|
-
externalSystem:
|
|
261
|
+
externalSystem: option<string>,
|
|
262
262
|
/** Chapter grouping band — see `queryableDef.chapter`. */
|
|
263
|
-
chapter:
|
|
263
|
+
chapter: option<string>,
|
|
264
|
+
/** The topics this slice subscribes to, as `Spec.sourceNames` declares them —
|
|
265
|
+
an Aggregate's `Spec.name` or a DCB source name. `Some([])` is the declared
|
|
266
|
+
default and means this plugin's own DCB log; `None` is an older structure
|
|
267
|
+
that did not publish the field. A different fact from `consumedEventTypes`,
|
|
268
|
+
which names event types and not where they came from — two topics carrying
|
|
269
|
+
an event of the same name are indistinguishable there. Optional so an older
|
|
270
|
+
reader ignores it. */
|
|
271
|
+
consumedSources: option<array<string>>,
|
|
264
272
|
}
|
|
265
273
|
|
|
266
274
|
@schema
|
|
@@ -269,9 +277,9 @@ type inboundTranslationSliceDef = {
|
|
|
269
277
|
commandTypes: array<string>,
|
|
270
278
|
targetName: string,
|
|
271
279
|
// Foreign system this slice receives from — drives the external box (Event Graph).
|
|
272
|
-
externalSystem:
|
|
280
|
+
externalSystem: option<string>,
|
|
273
281
|
/** Chapter grouping band — see `queryableDef.chapter`. */
|
|
274
|
-
chapter:
|
|
282
|
+
chapter: option<string>,
|
|
275
283
|
}
|
|
276
284
|
|
|
277
285
|
/** A published event of an extension point and the internal events producing it.
|
|
@@ -283,8 +291,6 @@ type publishedEventDef = {
|
|
|
283
291
|
fromEventTypes: array<string>,
|
|
284
292
|
}
|
|
285
293
|
|
|
286
|
-
let publishedEventDefArrayOptionSchema = S.array(publishedEventDefSchema)->S.nullAsOption
|
|
287
|
-
|
|
288
294
|
/** The command direction's producer half: a command an extension point takes and
|
|
289
295
|
the delegate commands it routes to. `name` is EP-qualified, `toCommandTypes`
|
|
290
296
|
plugin-qualified. */
|
|
@@ -294,8 +300,6 @@ type acceptedCommandDef = {
|
|
|
294
300
|
toCommandTypes: array<string>,
|
|
295
301
|
}
|
|
296
302
|
|
|
297
|
-
let acceptedCommandDefArrayOptionSchema = S.array(acceptedCommandDefSchema)->S.nullAsOption
|
|
298
|
-
|
|
299
303
|
/** The subscriber's half: a published event and the commands it routes to. A
|
|
300
304
|
delegate command is plugin-qualified, one sent back to the EP is EP-qualified. */
|
|
301
305
|
@schema
|
|
@@ -304,8 +308,6 @@ type handledEventDef = {
|
|
|
304
308
|
toCommandTypes: array<string>,
|
|
305
309
|
}
|
|
306
310
|
|
|
307
|
-
let handledEventDefArrayOptionSchema = S.array(handledEventDefSchema)->S.nullAsOption
|
|
308
|
-
|
|
309
311
|
/** The command direction's subscriber half: a command sent back to the port and
|
|
310
312
|
the internal events producing it. `name` is EP-qualified, `fromEventTypes`
|
|
311
313
|
plugin-qualified. */
|
|
@@ -315,21 +317,19 @@ type issuedCommandDef = {
|
|
|
315
317
|
fromEventTypes: array<string>,
|
|
316
318
|
}
|
|
317
319
|
|
|
318
|
-
let issuedCommandDefArrayOptionSchema = S.array(issuedCommandDefSchema)->S.nullAsOption
|
|
319
|
-
|
|
320
320
|
@schema
|
|
321
321
|
type extensionDef = {
|
|
322
322
|
name: string,
|
|
323
323
|
delegateNames: array<string>,
|
|
324
324
|
eventTypes: array<string>,
|
|
325
325
|
commandTypes: array<string>,
|
|
326
|
-
/** Which published event routes to which commands.
|
|
326
|
+
/** Which published event routes to which commands. Optional like
|
|
327
327
|
`extensionPointDef.commandTypes`; re-emit definitions persisted before it. */
|
|
328
|
-
handledEvents:
|
|
328
|
+
handledEvents: option<array<handledEventDef>>,
|
|
329
329
|
/** Which internal event sends which command back to the port. `None` means a
|
|
330
330
|
definition persisted before the field, NOT an extension that issues nothing —
|
|
331
331
|
a reader joining the two halves must keep them apart. */
|
|
332
|
-
issuedCommands:
|
|
332
|
+
issuedCommands: option<array<issuedCommandDef>>,
|
|
333
333
|
}
|
|
334
334
|
|
|
335
335
|
/**
|
|
@@ -338,28 +338,21 @@ An extension point owned by a plugin, from the producer side.
|
|
|
338
338
|
`sourceEventTypes` are the `Delegate`'s events feeding the published protocol,
|
|
339
339
|
plugin-qualified to match `writableDef.producedEventTypes`. `commandTypes` is the
|
|
340
340
|
EP's inbound protocol — None (read as []) for a `command = unit` EP, which routes
|
|
341
|
-
nothing.
|
|
342
|
-
lifecycle Message union); definitions persisted before a field must be re-emitted.
|
|
341
|
+
nothing. Definitions persisted before a field was added must be re-emitted.
|
|
343
342
|
*/
|
|
344
343
|
@schema
|
|
345
344
|
type extensionPointDef = {
|
|
346
345
|
name: string,
|
|
347
346
|
delegateNames: array<string>,
|
|
348
347
|
sourceEventTypes: array<string>,
|
|
349
|
-
commandTypes:
|
|
348
|
+
commandTypes: option<array<string>>,
|
|
350
349
|
/** Which internal event becomes which published event. */
|
|
351
|
-
publishedEvents:
|
|
352
|
-
option<array<publishedEventDef>>,
|
|
350
|
+
publishedEvents: option<array<publishedEventDef>>,
|
|
353
351
|
/** Which arriving command becomes which delegate command. `None` means a
|
|
354
352
|
definition persisted before the field, NOT a port that accepts nothing. */
|
|
355
|
-
acceptedCommands:
|
|
356
|
-
option<array<acceptedCommandDef>>,
|
|
353
|
+
acceptedCommands: option<array<acceptedCommandDef>>,
|
|
357
354
|
}
|
|
358
355
|
|
|
359
|
-
// js_nullable creates `array | null` (not `| undefined`), which passes sury's
|
|
360
|
-
// jsonableValidation inside the pluginStructure union variant payload.
|
|
361
|
-
let extensionPointDefArrayOptionSchema = S.array(extensionPointDefSchema)->S.nullAsOption
|
|
362
|
-
|
|
363
356
|
/**
|
|
364
357
|
One field's store requirement, with its provenance.
|
|
365
358
|
|
|
@@ -374,12 +367,9 @@ type requiredStoreDeclaration = {
|
|
|
374
367
|
store: string,
|
|
375
368
|
component: string,
|
|
376
369
|
field: string,
|
|
377
|
-
annotation:
|
|
370
|
+
annotation: option<string>,
|
|
378
371
|
}
|
|
379
372
|
|
|
380
|
-
let requiredStoreDeclarationArrayOptionSchema =
|
|
381
|
-
S.array(requiredStoreDeclarationSchema)->S.nullAsOption
|
|
382
|
-
|
|
383
373
|
/**
|
|
384
374
|
One component's capability requirement, with its provenance.
|
|
385
375
|
|
|
@@ -392,9 +382,6 @@ expressible as an annotation on one, which is why the need is declared.
|
|
|
392
382
|
@schema
|
|
393
383
|
type requiredCapabilityDeclaration = {capability: string, component: string}
|
|
394
384
|
|
|
395
|
-
let requiredCapabilityDeclarationArrayOptionSchema =
|
|
396
|
-
S.array(requiredCapabilityDeclarationSchema)->S.nullAsOption
|
|
397
|
-
|
|
398
385
|
/**
|
|
399
386
|
One graft's provenance: which trait, at which version, on which component.
|
|
400
387
|
|
|
@@ -419,14 +406,13 @@ type traitDeclaration = {
|
|
|
419
406
|
component: string,
|
|
420
407
|
}
|
|
421
408
|
|
|
422
|
-
let traitDeclarationArrayOptionSchema = S.array(traitDeclarationSchema)->S.nullAsOption
|
|
423
|
-
|
|
424
409
|
/**
|
|
425
410
|
Adding a field here? It must be a shape a stale event can be healed into — the
|
|
426
411
|
lifecycle aggregate replays its own log before every decision, so one event that
|
|
427
|
-
fails to decode freezes that plugin's registration. `
|
|
428
|
-
|
|
429
|
-
and
|
|
412
|
+
fails to decode freezes that plugin's registration. Make it `option`, which
|
|
413
|
+
`Message.parseJsonTolerant` heals to `None` whatever the inner type is. A required
|
|
414
|
+
array heals to `[]` and a required enum to its first variant; a required *scalar*
|
|
415
|
+
is fabricated and warned about, so it is the one shape to avoid. Regression suite:
|
|
430
416
|
`PluginLifecycleCorpusTest` — if it goes red, re-shape the field, not the fixtures.
|
|
431
417
|
*/
|
|
432
418
|
@schema
|
|
@@ -441,27 +427,23 @@ type pluginStructure = {
|
|
|
441
427
|
extensions: array<extensionDef>,
|
|
442
428
|
// Extension points owned by this plugin (producer side). Optional so older
|
|
443
429
|
// definitions still decode (absent → None, read as []).
|
|
444
|
-
extensionPoints:
|
|
445
|
-
option<array<extensionPointDef>>,
|
|
430
|
+
extensionPoints: option<array<extensionPointDef>>,
|
|
446
431
|
/** The object stores this plugin's fields declare they need, deduplicated and
|
|
447
432
|
qualified as `{plugin}.{store}` even for the same-plugin case. */
|
|
448
|
-
requiredStores:
|
|
433
|
+
requiredStores: option<array<string>>,
|
|
449
434
|
/** Provenance for `requiredStores`: one entry per declaring `(component, field)`.
|
|
450
435
|
`requiredStores` is derived from it, so the two cannot disagree. */
|
|
451
|
-
requiredStoreDeclarations:
|
|
452
|
-
option<array<requiredStoreDeclaration>>,
|
|
436
|
+
requiredStoreDeclarations: option<array<requiredStoreDeclaration>>,
|
|
453
437
|
/** The platform capabilities this plugin's components declare they need, one
|
|
454
438
|
entry per declaring component. Object stores are not here — a store need is
|
|
455
439
|
a field's, and travels as `requiredStores`. Absent → None, read as []. */
|
|
456
|
-
requiredCapabilities:
|
|
457
|
-
option<array<requiredCapabilityDeclaration>>,
|
|
440
|
+
requiredCapabilities: option<array<requiredCapabilityDeclaration>>,
|
|
458
441
|
/** The domain traits grafted into this plugin, one entry per declaring
|
|
459
442
|
component. Absent → None, read as []. The only signal a graft leaves that
|
|
460
443
|
survives into a deployed plugin — every other one (the dependency, the
|
|
461
444
|
variant spread, the rules alias, the conformance binding) is source-side.
|
|
462
445
|
A claim about origin, never about behaviour: see `Trait`. */
|
|
463
|
-
traitDeclarations:
|
|
464
|
-
option<array<traitDeclaration>>,
|
|
446
|
+
traitDeclarations: option<array<traitDeclaration>>,
|
|
465
447
|
}
|
|
466
448
|
|
|
467
449
|
let pluginStructureOffloadSchema = Offload.optionSchema(~store="pluginStructures", pluginStructureSchema)
|
|
@@ -488,15 +470,15 @@ type pluginDefinition = {
|
|
|
488
470
|
apiSchemaFragment: @s.matches(apiSchemaFragmentOffloadSchema) option<Offload.payload<apiSchemaFragment>>,
|
|
489
471
|
// Schema routing in split-API mode: None/"Domain" → DomainApi, Some("Platform") →
|
|
490
472
|
// PlatformApi (and excluded from the DomainApi runtime schema).
|
|
491
|
-
apiTarget:
|
|
473
|
+
apiTarget: option<string>,
|
|
492
474
|
// Component graph metadata, offloadable like apiSchemaFragment. Absent for older
|
|
493
475
|
// protocol versions.
|
|
494
476
|
structure: @s.matches(pluginStructureOffloadSchema) option<Offload.payload<pluginStructure>>,
|
|
495
477
|
// EventTopic ARN of a bundled DcbEventLog, so the admin can subscribe peer
|
|
496
478
|
// EventCollectors to it. None for plugins without one.
|
|
497
|
-
dcbEventLog:
|
|
479
|
+
dcbEventLog: option<dcbEventLogDefinition>,
|
|
498
480
|
// Mandatory; `Domain` is resolved as the default in Plugin_Builder. Payload-less
|
|
499
|
-
// variant → a bare JSON string
|
|
481
|
+
// variant → a bare JSON string.
|
|
500
482
|
kind: pluginKind,
|
|
501
483
|
}
|
|
502
484
|
|