@fabricorg/platform 1.0.0 → 1.2.0
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 +75 -0
- package/README.md +20 -7
- package/dist/adapters/index.cjs +18 -6
- package/dist/adapters/index.d.cts +76 -1
- package/dist/adapters/index.d.ts +76 -1
- package/dist/adapters/index.js +1 -1
- package/dist/{chunk-YZKUEC2J.cjs → chunk-3BGFHSSK.cjs} +4 -4
- package/dist/{chunk-YZKUEC2J.cjs.map → chunk-3BGFHSSK.cjs.map} +1 -1
- package/dist/{chunk-35LADVKK.cjs → chunk-3UBX7KQM.cjs} +65 -13
- package/dist/chunk-3UBX7KQM.cjs.map +1 -0
- package/dist/{chunk-T5ESIJ4K.js → chunk-63MYMAEX.js} +57 -5
- package/dist/chunk-63MYMAEX.js.map +1 -0
- package/dist/{chunk-OWSSGFNY.js → chunk-7GAWSUOG.js} +22 -3
- package/dist/chunk-7GAWSUOG.js.map +1 -0
- package/dist/{chunk-IFMZPLM4.js → chunk-AWP6JDD5.js} +3 -3
- package/dist/{chunk-IFMZPLM4.js.map → chunk-AWP6JDD5.js.map} +1 -1
- package/dist/{chunk-M5FGWXT3.cjs → chunk-CXR6KEKQ.cjs} +11 -10
- package/dist/chunk-CXR6KEKQ.cjs.map +1 -0
- package/dist/{chunk-3ZW5USXF.js → chunk-DTY3CHQZ.js} +24 -5
- package/dist/chunk-DTY3CHQZ.js.map +1 -0
- package/dist/chunk-IFW43ELT.cjs +331 -0
- package/dist/chunk-IFW43ELT.cjs.map +1 -0
- package/dist/{chunk-J5LS5BSI.js → chunk-JG35DT2G.js} +11 -10
- package/dist/chunk-JG35DT2G.js.map +1 -0
- package/dist/{chunk-53VEUYCS.cjs → chunk-LDPBQ72C.cjs} +2 -2
- package/dist/chunk-LDPBQ72C.cjs.map +1 -0
- package/dist/{chunk-CRAGTJ3B.js → chunk-LZNXUTJQ.js} +4 -3
- package/dist/chunk-LZNXUTJQ.js.map +1 -0
- package/dist/{chunk-NPVIFZQV.cjs → chunk-N2BBUKCX.cjs} +26 -4
- package/dist/chunk-N2BBUKCX.cjs.map +1 -0
- package/dist/{chunk-CQSA3BII.js → chunk-NHGBTPPE.js} +2 -2
- package/dist/chunk-NHGBTPPE.js.map +1 -0
- package/dist/{chunk-ANPJYXBK.cjs → chunk-TBS2S5AX.cjs} +8 -2
- package/dist/chunk-TBS2S5AX.cjs.map +1 -0
- package/dist/{chunk-JFMNLJLY.cjs → chunk-UITRHBZH.cjs} +7 -6
- package/dist/chunk-UITRHBZH.cjs.map +1 -0
- package/dist/{chunk-WAI4UNWQ.cjs → chunk-WI7CWNYI.cjs} +22 -2
- package/dist/chunk-WI7CWNYI.cjs.map +1 -0
- package/dist/{chunk-3OWMPDBD.js → chunk-Y2PJLRIY.js} +8 -2
- package/dist/chunk-Y2PJLRIY.js.map +1 -0
- package/dist/chunk-ZAEVQSB5.js +326 -0
- package/dist/chunk-ZAEVQSB5.js.map +1 -0
- package/dist/events/index.cjs +2 -2
- package/dist/events/index.d.cts +7 -0
- package/dist/events/index.d.ts +7 -0
- package/dist/events/index.js +1 -1
- package/dist/governance/index.cjs +4 -4
- package/dist/governance/index.d.cts +4 -0
- package/dist/governance/index.d.ts +4 -0
- package/dist/governance/index.js +1 -1
- package/dist/index.cjs +64 -44
- package/dist/index.d.cts +4 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.js +9 -9
- package/dist/manifest/index.cjs +11 -11
- package/dist/manifest/index.d.cts +1 -0
- package/dist/manifest/index.d.ts +1 -0
- package/dist/manifest/index.js +5 -5
- package/dist/modules/index.cjs +12 -12
- package/dist/modules/index.d.cts +33 -1
- package/dist/modules/index.d.ts +33 -1
- package/dist/modules/index.js +4 -4
- package/dist/objects/index.cjs +8 -4
- package/dist/objects/index.d.cts +11 -1
- package/dist/objects/index.d.ts +11 -1
- package/dist/objects/index.js +1 -1
- package/dist/projections/index.cjs +5 -5
- package/dist/projections/index.d.cts +9 -1
- package/dist/projections/index.d.ts +9 -1
- package/dist/projections/index.js +1 -1
- package/dist/state-machines/index.cjs +7 -3
- package/dist/state-machines/index.d.cts +10 -1
- package/dist/state-machines/index.d.ts +10 -1
- package/dist/state-machines/index.js +1 -1
- package/dist/testing/index.cjs +5 -5
- package/dist/testing/index.js +2 -2
- package/package.json +1 -1
- package/dist/chunk-35LADVKK.cjs.map +0 -1
- package/dist/chunk-3OWMPDBD.js.map +0 -1
- package/dist/chunk-3ZW5USXF.js.map +0 -1
- package/dist/chunk-53VEUYCS.cjs.map +0 -1
- package/dist/chunk-ANPJYXBK.cjs.map +0 -1
- package/dist/chunk-CBY6GRJP.cjs +0 -140
- package/dist/chunk-CBY6GRJP.cjs.map +0 -1
- package/dist/chunk-CQSA3BII.js.map +0 -1
- package/dist/chunk-CRAGTJ3B.js.map +0 -1
- package/dist/chunk-J5LS5BSI.js.map +0 -1
- package/dist/chunk-JFMNLJLY.cjs.map +0 -1
- package/dist/chunk-M5FGWXT3.cjs.map +0 -1
- package/dist/chunk-NETLRKDS.js +0 -136
- package/dist/chunk-NETLRKDS.js.map +0 -1
- package/dist/chunk-NPVIFZQV.cjs.map +0 -1
- package/dist/chunk-OWSSGFNY.js.map +0 -1
- package/dist/chunk-T5ESIJ4K.js.map +0 -1
- package/dist/chunk-WAI4UNWQ.cjs.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,80 @@
|
|
|
1
1
|
# @fabricorg/platform
|
|
2
2
|
|
|
3
|
+
## 1.2.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- ef4e3eb: Classify adapter outcomes: add additive `AdapterOutcomeKind` (`success`, `transient_failure`,
|
|
8
|
+
`permanent_failure`, `timeout`, `ambiguous`) and `AdapterOutcomeEvidence` to the portable adapter
|
|
9
|
+
contract. The retry layer now retries only transient idempotent outcomes; permanent failures,
|
|
10
|
+
timeouts, and ambiguous results are never retried. Ambiguous outcomes are routed to
|
|
11
|
+
`reconciliation_required` with durable `ExternalReconciliation` evidence and an
|
|
12
|
+
`AdapterInvocationAmbiguous` lifecycle event. Add deadline and cancellation context to
|
|
13
|
+
`AdapterExecutionContext`. Backward compatible: adapters that omit `outcome` behave as before.
|
|
14
|
+
- 293561d: Close adapter and saga correctness gaps: deliver actual deadline and AbortSignal context to adapters, require the governed Host to classify thrown adapter failures before retrying, reject contradictory successful ambiguous outcomes, reserve parent lifecycle identity prefixes, and provide a durable Platform Host parent lifecycle adapter tested through public store seams. Direct `executeWithAdapterRetry` callers retain the prior idempotent retry default when they omit a classifier.
|
|
15
|
+
- 9a145a3: Add assembly and initiating SDUI release digests to durable governance runtime
|
|
16
|
+
evidence. Hosts can attach both through trusted configuration, including a
|
|
17
|
+
per-submission release resolver, and validate digest shape before creating the
|
|
18
|
+
invocation. Composition-bound hosts verify their assembly lockfile against the
|
|
19
|
+
exact loaded capability artifacts before accepting submissions.
|
|
20
|
+
- b932767: Add portable parameter schemas and validation for projection views. Projection snapshot keys now
|
|
21
|
+
include a canonical parameter identity, generated capability and experience contracts preserve view
|
|
22
|
+
parameter schemas, and compatible snapshots continue through the explicit `upgradeSnapshot` seam.
|
|
23
|
+
- fb14d67: Execute state guards with trusted Host context, correct filtered-ledger replay semantics, make
|
|
24
|
+
dispatcher failure retryable, fail closed for unsafe saga and non-idempotent recovery paths, preserve
|
|
25
|
+
outbox consistency, and bound and redact OPA HTTP failures.
|
|
26
|
+
|
|
27
|
+
### Additional release notes
|
|
28
|
+
|
|
29
|
+
### Minor Changes
|
|
30
|
+
|
|
31
|
+
- Add additive adapter outcome classification (`AdapterOutcomeKind`: `success`, `transient_failure`,
|
|
32
|
+
`permanent_failure`, `timeout`, `ambiguous`) and `AdapterOutcomeEvidence`. The retry layer retries
|
|
33
|
+
only transient idempotent outcomes. Add deadline and cancellation context to
|
|
34
|
+
`AdapterExecutionContext`. Backward compatible: adapters that omit `outcome` behave as before.
|
|
35
|
+
- Add `signal?: AbortSignal` to `AdapterExecutionContext` so the host can deliver a real
|
|
36
|
+
cancellation signal to adapters. Add `classifyThrownError` to `ExecuteWithRetryOptions`:
|
|
37
|
+
thrown errors are never retried without explicit classification (fail safe). Add
|
|
38
|
+
`assertAdapterOutcomeConsistency` to reject contradictory results where `success` is true
|
|
39
|
+
but the explicit `outcome` is a non-success kind.
|
|
40
|
+
- Add guard-aware state transition evaluation that fails closed on guard denial or exceptions.
|
|
41
|
+
- Clarify that projection `sequence` is a global ledger cursor and add optional
|
|
42
|
+
`subjectSequence` for sources that can prove subject-local continuity.
|
|
43
|
+
- Add portable JSON Schema validation for projection parameters and expose
|
|
44
|
+
`ViewContract.parameterSchema` for parameterized view variants.
|
|
45
|
+
- Add optional module `version` and compiler `manifestDigest` identity for
|
|
46
|
+
assembly-verified runtime registration, plus durable assembly and initiating
|
|
47
|
+
release digests in `GovernanceRuntimeEvidence`.
|
|
48
|
+
|
|
49
|
+
## 1.1.0
|
|
50
|
+
|
|
51
|
+
### Minor Changes
|
|
52
|
+
|
|
53
|
+
- c504d0a: Make durability a declared, diffable contract.
|
|
54
|
+
|
|
55
|
+
`CapabilityMetadata` gains `workflows`: each declares its task queue, the event that starts it, the
|
|
56
|
+
durable deadlines it carries (resolved from configuration keys, never literals), and which governed
|
|
57
|
+
action reverses each compensatable step. The field is additive, so `MODULE_MANIFEST_VERSION` stays
|
|
58
|
+
at 2.
|
|
59
|
+
|
|
60
|
+
`diffModuleManifests` classifies this plane instead of reporting one blanket capability change:
|
|
61
|
+
adding a workflow, timer or reversal is compatible; moving a task queue, changing a trigger, dropping
|
|
62
|
+
a deadline or repointing a reversal is potentially breaking; removing a workflow or reversal is
|
|
63
|
+
breaking.
|
|
64
|
+
|
|
65
|
+
The compiler validates serialized workflow declarations, which never pass through a registry, and
|
|
66
|
+
rejects an action declaring `completion: "immediate"` while `kind: "saga"` — a manifest contradiction
|
|
67
|
+
that now fails the build rather than a running host.
|
|
68
|
+
|
|
69
|
+
`platform-temporal` executes declared reversals. `runCompensatedSaga` unwinds newest-first, routes
|
|
70
|
+
every reversal through the governed Host under a distinct deterministic
|
|
71
|
+
`compensationInvocationIdentity`, and surfaces a reversal that itself failed on
|
|
72
|
+
`SagaCompensationError` rather than swallowing it. A step that _resolves_ with a non-successful
|
|
73
|
+
status — blocked, failed, parked for approval, sent to reconciliation — now triggers the unwind
|
|
74
|
+
instead of being recorded as completed. Step identities must be unique and may not use the reserved
|
|
75
|
+
`compensate:` prefix, and when a `workflow` declaration is supplied every reversal is checked against
|
|
76
|
+
it.
|
|
77
|
+
|
|
3
78
|
## 1.0.0
|
|
4
79
|
|
|
5
80
|
### Major Changes
|
package/README.md
CHANGED
|
@@ -44,7 +44,7 @@ That's what this package gives you. Nothing more.
|
|
|
44
44
|
| `@fabricorg/platform` | Re-exports everything below |
|
|
45
45
|
| `@fabricorg/platform/actions` | `ActionDefinition`, `ActionContext`, `SagaImplementation` |
|
|
46
46
|
| `@fabricorg/platform/policies` | `PolicyEvaluator`, `PolicyContext`, code/data/hybrid evaluation |
|
|
47
|
-
| `@fabricorg/platform/state-machines` | `StateMachineDefinition`,
|
|
47
|
+
| `@fabricorg/platform/state-machines` | `StateMachineDefinition`, structural and guard-aware transition evaluation |
|
|
48
48
|
| `@fabricorg/platform/events` | `AssetEventEnvelope`, `EventTypeRegistry` |
|
|
49
49
|
| `@fabricorg/platform/projections` | Replay/snapshot framework |
|
|
50
50
|
| `@fabricorg/platform/adapters` | `AdapterRegistry`, retry + circuit-breaker |
|
|
@@ -57,10 +57,12 @@ That's what this package gives you. Nothing more.
|
|
|
57
57
|
| `@fabricorg/platform/objects` | Shared object-type contracts |
|
|
58
58
|
| `@fabricorg/platform/testing` | In-memory adapter registry, fake `ActionContext`, deterministic IDs |
|
|
59
59
|
|
|
60
|
-
Modules may publish versioned `ViewContract`s.
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
60
|
+
Modules may publish versioned `ViewContract`s. A view may also declare a portable `parameterSchema`;
|
|
61
|
+
ProjectionHost validates those inputs and keeps each canonical parameter identity in a separate
|
|
62
|
+
snapshot. `replayView` filters declared event schema versions and requires an explicit
|
|
63
|
+
`upgradeSnapshot` function before resuming an older compatible snapshot; manifests serialize the
|
|
64
|
+
logical shape, parameter schema, and metadata but never executable functions or initial state.
|
|
65
|
+
Physical stores remain adapter concerns.
|
|
64
66
|
|
|
65
67
|
## Composing a vertical
|
|
66
68
|
|
|
@@ -105,9 +107,9 @@ An entity schema does not validate arbitrary writes through `TDb`. Persisted-rec
|
|
|
105
107
|
Every `invokeAction(id, params)` call goes through:
|
|
106
108
|
|
|
107
109
|
1. **`evaluatePolicies`** — every policy registered for the action runs (code/data/hybrid). A single `block` halts the invocation. Policies may return `guidance` (`factsUsed` + typed `correctiveActions`) so hosts can show exactly why a block happened and what the operator can do next. Corrective actions declare `kind: "invoke_action" | "navigate" | "manual"` so UIs do not guess whether a remediation is executable.
|
|
108
|
-
2. **State-machine validation** — if the action's target entity is bound to a state machine, the requested transition
|
|
110
|
+
2. **State-machine validation** — if the action's target entity is bound to a state machine, the Host reads authoritative state at the mutation boundary, validates the requested transition, and executes its guard with trusted entity, actor, parameter, invocation, and database context. A false or throwing guard fails closed.
|
|
109
111
|
3. **Handler** — `actionDef.handler(ctx, params)` runs inside a single `TDb` transaction. Handler parses its own input via its `schema` (single source of truth).
|
|
110
|
-
4. **Adapters** — for each `actionDef.adapterSteps`, the registered implementation runs via `executeWithAdapterRetry` (idempotent retry, exponential backoff, circuit breaker).
|
|
112
|
+
4. **Adapters** — for each `actionDef.adapterSteps`, the registered implementation runs via `executeWithAdapterRetry` (idempotent retry, exponential backoff, circuit breaker). Adapters may classify outcomes as `success`, `transient_failure`, `permanent_failure`, `timeout`, or `ambiguous`; only transient idempotent outcomes are retried. Ambiguous outcomes are routed to `reconciliation_required` with durable evidence.
|
|
111
113
|
5. **Events** — domain events are appended; projections derive read models on next replay.
|
|
112
114
|
6. **Completion** — invocation status is marked, telemetry emitted.
|
|
113
115
|
|
|
@@ -136,6 +138,17 @@ export const ingestSaga: SagaImplementation = async (ctx, runChild) => {
|
|
|
136
138
|
|
|
137
139
|
Your worker maps action IDs to implementations. The platform dispatches generically — no platform code references vertical action IDs.
|
|
138
140
|
|
|
141
|
+
`PlatformHost` does not execute a `kind: "saga"` definition as an atomic handler. Configure a
|
|
142
|
+
durable dispatcher and map the action to its saga implementation in the workflow worker. Direct
|
|
143
|
+
execution fails closed so a handlerless saga cannot be recorded as completed.
|
|
144
|
+
|
|
145
|
+
## Projection sequence semantics
|
|
146
|
+
|
|
147
|
+
`ReplayableAssetEvent.sequence` is the ordering cursor of the complete tenant/space ledger. A view
|
|
148
|
+
usually filters that ledger by event type and subject, so gaps in this global cursor do not prove
|
|
149
|
+
that subject events are missing. Event sources that can guarantee contiguous per-subject ordering
|
|
150
|
+
may also provide `subjectSequence`; only that explicit sequence is used for missing-event warnings.
|
|
151
|
+
|
|
139
152
|
## Portability boundary
|
|
140
153
|
|
|
141
154
|
The repository Boundary Guard scans source and packed artifacts and fails CI if any of these leak in:
|
package/dist/adapters/index.cjs
CHANGED
|
@@ -1,28 +1,40 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var chunkN2BBUKCX_cjs = require('../chunk-N2BBUKCX.cjs');
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
Object.defineProperty(exports, "AdapterRegistry", {
|
|
8
8
|
enumerable: true,
|
|
9
|
-
get: function () { return
|
|
9
|
+
get: function () { return chunkN2BBUKCX_cjs.AdapterRegistry; }
|
|
10
10
|
});
|
|
11
11
|
Object.defineProperty(exports, "MissingAdapterRegistrationError", {
|
|
12
12
|
enumerable: true,
|
|
13
|
-
get: function () { return
|
|
13
|
+
get: function () { return chunkN2BBUKCX_cjs.MissingAdapterRegistrationError; }
|
|
14
|
+
});
|
|
15
|
+
Object.defineProperty(exports, "assertAdapterOutcomeConsistency", {
|
|
16
|
+
enumerable: true,
|
|
17
|
+
get: function () { return chunkN2BBUKCX_cjs.assertAdapterOutcomeConsistency; }
|
|
18
|
+
});
|
|
19
|
+
Object.defineProperty(exports, "classifyAdapterOutcome", {
|
|
20
|
+
enumerable: true,
|
|
21
|
+
get: function () { return chunkN2BBUKCX_cjs.classifyAdapterOutcome; }
|
|
14
22
|
});
|
|
15
23
|
Object.defineProperty(exports, "executeWithAdapterRetry", {
|
|
16
24
|
enumerable: true,
|
|
17
|
-
get: function () { return
|
|
25
|
+
get: function () { return chunkN2BBUKCX_cjs.executeWithAdapterRetry; }
|
|
18
26
|
});
|
|
19
27
|
Object.defineProperty(exports, "getRetryDelayMs", {
|
|
20
28
|
enumerable: true,
|
|
21
|
-
get: function () { return
|
|
29
|
+
get: function () { return chunkN2BBUKCX_cjs.getRetryDelayMs; }
|
|
22
30
|
});
|
|
23
31
|
Object.defineProperty(exports, "resolveAdapterRetryPolicy", {
|
|
24
32
|
enumerable: true,
|
|
25
|
-
get: function () { return
|
|
33
|
+
get: function () { return chunkN2BBUKCX_cjs.resolveAdapterRetryPolicy; }
|
|
34
|
+
});
|
|
35
|
+
Object.defineProperty(exports, "shouldRetryAdapterOutcome", {
|
|
36
|
+
enumerable: true,
|
|
37
|
+
get: function () { return chunkN2BBUKCX_cjs.shouldRetryAdapterOutcome; }
|
|
26
38
|
});
|
|
27
39
|
//# sourceMappingURL=index.cjs.map
|
|
28
40
|
//# sourceMappingURL=index.cjs.map
|
|
@@ -7,11 +7,51 @@ interface AdapterExecutionContext {
|
|
|
7
7
|
causationId?: string;
|
|
8
8
|
attempt: number;
|
|
9
9
|
maxAttempts: number;
|
|
10
|
+
/** Epoch-millis deadline after which the adapter should abandon the call. */
|
|
11
|
+
deadlineMs?: number;
|
|
12
|
+
/** Set by the host when the invocation has been cancelled; adapters should stop work. */
|
|
13
|
+
cancelled?: boolean;
|
|
14
|
+
/** Non-secret reason the invocation was cancelled. */
|
|
15
|
+
cancellationReason?: string;
|
|
16
|
+
/**
|
|
17
|
+
* AbortSignal the host aborts when the deadline expires or the invocation is
|
|
18
|
+
* cancelled. Adapters should propagate this to underlying HTTP/database
|
|
19
|
+
* clients so in-flight work is abandoned rather than left dangling.
|
|
20
|
+
*/
|
|
21
|
+
signal?: AbortSignal;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Additive classification of an adapter outcome so the retry layer can
|
|
25
|
+
* distinguish retryable transient failures from permanent rejections,
|
|
26
|
+
* timeouts, and ambiguous results that require manual reconciliation.
|
|
27
|
+
*
|
|
28
|
+
* When `outcome` is absent on an {@link AdapterExecutionResult}, the retry
|
|
29
|
+
* layer treats a non-success result as `transient_failure` for backward
|
|
30
|
+
* compatibility.
|
|
31
|
+
*/
|
|
32
|
+
type AdapterOutcomeKind = "success" | "transient_failure" | "permanent_failure" | "timeout" | "ambiguous";
|
|
33
|
+
/**
|
|
34
|
+
* Durable, non-secret evidence attached to a classified adapter outcome.
|
|
35
|
+
* Used by the host to record reconciliation evidence when an adapter returns
|
|
36
|
+
* an ambiguous result whose external effect may or may not have been applied.
|
|
37
|
+
*/
|
|
38
|
+
interface AdapterOutcomeEvidence {
|
|
39
|
+
kind: AdapterOutcomeKind;
|
|
40
|
+
reason?: string;
|
|
41
|
+
/** Opaque, non-secret reference to external operation state for reconciliation. */
|
|
42
|
+
externalReference?: string;
|
|
43
|
+
/** Whether the external effect may have been applied (true for ambiguous/timeout). */
|
|
44
|
+
effectMayHaveApplied?: boolean;
|
|
45
|
+
observedAt: Date | string;
|
|
10
46
|
}
|
|
11
47
|
interface AdapterExecutionResult {
|
|
12
48
|
success: boolean;
|
|
13
49
|
correlationId?: string;
|
|
14
50
|
error?: string;
|
|
51
|
+
/** Additive outcome classification. Omitted = infer from `success`. */
|
|
52
|
+
outcome?: AdapterOutcomeKind;
|
|
53
|
+
/** Durable evidence for ambiguous or timeout outcomes requiring reconciliation. */
|
|
54
|
+
outcomeEvidence?: AdapterOutcomeEvidence;
|
|
15
55
|
}
|
|
16
56
|
type AdapterExecutor = (input: Record<string, unknown>, context: AdapterExecutionContext) => Promise<AdapterExecutionResult>;
|
|
17
57
|
interface AdapterRetryPolicy {
|
|
@@ -64,6 +104,20 @@ interface ExecuteWithRetryOptions<T> {
|
|
|
64
104
|
execute: (attempt: number, maxAttempts: number) => Promise<T>;
|
|
65
105
|
isSuccessful: (result: T) => boolean;
|
|
66
106
|
getError: (result: T) => string | undefined;
|
|
107
|
+
/**
|
|
108
|
+
* Optional outcome classifier. When provided, the retry layer retries only
|
|
109
|
+
* `transient_failure` outcomes (and only when the policy is idempotent).
|
|
110
|
+
* `permanent_failure`, `timeout`, and `ambiguous` outcomes are never retried.
|
|
111
|
+
* When omitted, all non-success results are treated as transient (backward compatible).
|
|
112
|
+
*/
|
|
113
|
+
classifyOutcome?: (result: T) => AdapterOutcomeKind;
|
|
114
|
+
/**
|
|
115
|
+
* Optional classifier for errors thrown by the execute callback (not returned
|
|
116
|
+
* as results). When provided, the retry layer retries only `transient_failure`
|
|
117
|
+
* classifications. When omitted, thrown errors are never retried — the caller
|
|
118
|
+
* must explicitly opt into retrying a thrown failure by classifying it.
|
|
119
|
+
*/
|
|
120
|
+
classifyThrownError?: (error: unknown) => AdapterOutcomeKind;
|
|
67
121
|
sleep?: (delayMs: number) => Promise<void>;
|
|
68
122
|
onAttempt?: (attempt: AdapterAttempt<T>) => Promise<void> | void;
|
|
69
123
|
}
|
|
@@ -78,6 +132,27 @@ declare class AdapterRegistry {
|
|
|
78
132
|
}
|
|
79
133
|
declare function resolveAdapterRetryPolicy(policy: AdapterRetryPolicy | undefined, defaultIdempotent: boolean): ResolvedAdapterRetryPolicy;
|
|
80
134
|
declare function getRetryDelayMs(attempt: number, policy: ResolvedAdapterRetryPolicy): number;
|
|
135
|
+
/**
|
|
136
|
+
* Classify an {@link AdapterExecutionResult} into an {@link AdapterOutcomeKind}.
|
|
137
|
+
* When `outcome` is explicitly set, it is returned directly, after rejecting a
|
|
138
|
+
* contradictory result where `success` is true but the outcome is non-success.
|
|
139
|
+
* Otherwise a successful result maps to `success` and a failed result maps to
|
|
140
|
+
* `transient_failure` (the historical default).
|
|
141
|
+
*/
|
|
142
|
+
declare function classifyAdapterOutcome(result: AdapterExecutionResult): AdapterOutcomeKind;
|
|
143
|
+
/**
|
|
144
|
+
* Determine whether an outcome kind is retryable. Only `transient_failure`
|
|
145
|
+
* is retryable, and only when the adapter policy is idempotent. Permanent
|
|
146
|
+
* failures, timeouts, and ambiguous outcomes are never retried.
|
|
147
|
+
*/
|
|
148
|
+
declare function shouldRetryAdapterOutcome(kind: AdapterOutcomeKind, idempotent: boolean): boolean;
|
|
149
|
+
/**
|
|
150
|
+
* Reject a contradictory adapter result where `success` is true but the
|
|
151
|
+
* explicit `outcome` is a non-success kind. A successful result claiming an
|
|
152
|
+
* ambiguous, permanent, timeout, or transient outcome is a programming
|
|
153
|
+
* error — the adapter cannot have both succeeded and require reconciliation.
|
|
154
|
+
*/
|
|
155
|
+
declare function assertAdapterOutcomeConsistency(result: AdapterExecutionResult): void;
|
|
81
156
|
declare function executeWithAdapterRetry<T>(options: ExecuteWithRetryOptions<T>): Promise<T>;
|
|
82
157
|
|
|
83
|
-
export { type AdapterAttempt, type AdapterCircuitBreakerConfig, type AdapterExecutionContext, type AdapterExecutionResult, type AdapterExecutor, type AdapterImplementation, AdapterRegistry, type AdapterRetryPolicy, type ExecuteWithRetryOptions, MissingAdapterRegistrationError, type ResolvedAdapterRetryPolicy, executeWithAdapterRetry, getRetryDelayMs, resolveAdapterRetryPolicy };
|
|
158
|
+
export { type AdapterAttempt, type AdapterCircuitBreakerConfig, type AdapterExecutionContext, type AdapterExecutionResult, type AdapterExecutor, type AdapterImplementation, type AdapterOutcomeEvidence, type AdapterOutcomeKind, AdapterRegistry, type AdapterRetryPolicy, type ExecuteWithRetryOptions, MissingAdapterRegistrationError, type ResolvedAdapterRetryPolicy, assertAdapterOutcomeConsistency, classifyAdapterOutcome, executeWithAdapterRetry, getRetryDelayMs, resolveAdapterRetryPolicy, shouldRetryAdapterOutcome };
|
package/dist/adapters/index.d.ts
CHANGED
|
@@ -7,11 +7,51 @@ interface AdapterExecutionContext {
|
|
|
7
7
|
causationId?: string;
|
|
8
8
|
attempt: number;
|
|
9
9
|
maxAttempts: number;
|
|
10
|
+
/** Epoch-millis deadline after which the adapter should abandon the call. */
|
|
11
|
+
deadlineMs?: number;
|
|
12
|
+
/** Set by the host when the invocation has been cancelled; adapters should stop work. */
|
|
13
|
+
cancelled?: boolean;
|
|
14
|
+
/** Non-secret reason the invocation was cancelled. */
|
|
15
|
+
cancellationReason?: string;
|
|
16
|
+
/**
|
|
17
|
+
* AbortSignal the host aborts when the deadline expires or the invocation is
|
|
18
|
+
* cancelled. Adapters should propagate this to underlying HTTP/database
|
|
19
|
+
* clients so in-flight work is abandoned rather than left dangling.
|
|
20
|
+
*/
|
|
21
|
+
signal?: AbortSignal;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Additive classification of an adapter outcome so the retry layer can
|
|
25
|
+
* distinguish retryable transient failures from permanent rejections,
|
|
26
|
+
* timeouts, and ambiguous results that require manual reconciliation.
|
|
27
|
+
*
|
|
28
|
+
* When `outcome` is absent on an {@link AdapterExecutionResult}, the retry
|
|
29
|
+
* layer treats a non-success result as `transient_failure` for backward
|
|
30
|
+
* compatibility.
|
|
31
|
+
*/
|
|
32
|
+
type AdapterOutcomeKind = "success" | "transient_failure" | "permanent_failure" | "timeout" | "ambiguous";
|
|
33
|
+
/**
|
|
34
|
+
* Durable, non-secret evidence attached to a classified adapter outcome.
|
|
35
|
+
* Used by the host to record reconciliation evidence when an adapter returns
|
|
36
|
+
* an ambiguous result whose external effect may or may not have been applied.
|
|
37
|
+
*/
|
|
38
|
+
interface AdapterOutcomeEvidence {
|
|
39
|
+
kind: AdapterOutcomeKind;
|
|
40
|
+
reason?: string;
|
|
41
|
+
/** Opaque, non-secret reference to external operation state for reconciliation. */
|
|
42
|
+
externalReference?: string;
|
|
43
|
+
/** Whether the external effect may have been applied (true for ambiguous/timeout). */
|
|
44
|
+
effectMayHaveApplied?: boolean;
|
|
45
|
+
observedAt: Date | string;
|
|
10
46
|
}
|
|
11
47
|
interface AdapterExecutionResult {
|
|
12
48
|
success: boolean;
|
|
13
49
|
correlationId?: string;
|
|
14
50
|
error?: string;
|
|
51
|
+
/** Additive outcome classification. Omitted = infer from `success`. */
|
|
52
|
+
outcome?: AdapterOutcomeKind;
|
|
53
|
+
/** Durable evidence for ambiguous or timeout outcomes requiring reconciliation. */
|
|
54
|
+
outcomeEvidence?: AdapterOutcomeEvidence;
|
|
15
55
|
}
|
|
16
56
|
type AdapterExecutor = (input: Record<string, unknown>, context: AdapterExecutionContext) => Promise<AdapterExecutionResult>;
|
|
17
57
|
interface AdapterRetryPolicy {
|
|
@@ -64,6 +104,20 @@ interface ExecuteWithRetryOptions<T> {
|
|
|
64
104
|
execute: (attempt: number, maxAttempts: number) => Promise<T>;
|
|
65
105
|
isSuccessful: (result: T) => boolean;
|
|
66
106
|
getError: (result: T) => string | undefined;
|
|
107
|
+
/**
|
|
108
|
+
* Optional outcome classifier. When provided, the retry layer retries only
|
|
109
|
+
* `transient_failure` outcomes (and only when the policy is idempotent).
|
|
110
|
+
* `permanent_failure`, `timeout`, and `ambiguous` outcomes are never retried.
|
|
111
|
+
* When omitted, all non-success results are treated as transient (backward compatible).
|
|
112
|
+
*/
|
|
113
|
+
classifyOutcome?: (result: T) => AdapterOutcomeKind;
|
|
114
|
+
/**
|
|
115
|
+
* Optional classifier for errors thrown by the execute callback (not returned
|
|
116
|
+
* as results). When provided, the retry layer retries only `transient_failure`
|
|
117
|
+
* classifications. When omitted, thrown errors are never retried — the caller
|
|
118
|
+
* must explicitly opt into retrying a thrown failure by classifying it.
|
|
119
|
+
*/
|
|
120
|
+
classifyThrownError?: (error: unknown) => AdapterOutcomeKind;
|
|
67
121
|
sleep?: (delayMs: number) => Promise<void>;
|
|
68
122
|
onAttempt?: (attempt: AdapterAttempt<T>) => Promise<void> | void;
|
|
69
123
|
}
|
|
@@ -78,6 +132,27 @@ declare class AdapterRegistry {
|
|
|
78
132
|
}
|
|
79
133
|
declare function resolveAdapterRetryPolicy(policy: AdapterRetryPolicy | undefined, defaultIdempotent: boolean): ResolvedAdapterRetryPolicy;
|
|
80
134
|
declare function getRetryDelayMs(attempt: number, policy: ResolvedAdapterRetryPolicy): number;
|
|
135
|
+
/**
|
|
136
|
+
* Classify an {@link AdapterExecutionResult} into an {@link AdapterOutcomeKind}.
|
|
137
|
+
* When `outcome` is explicitly set, it is returned directly, after rejecting a
|
|
138
|
+
* contradictory result where `success` is true but the outcome is non-success.
|
|
139
|
+
* Otherwise a successful result maps to `success` and a failed result maps to
|
|
140
|
+
* `transient_failure` (the historical default).
|
|
141
|
+
*/
|
|
142
|
+
declare function classifyAdapterOutcome(result: AdapterExecutionResult): AdapterOutcomeKind;
|
|
143
|
+
/**
|
|
144
|
+
* Determine whether an outcome kind is retryable. Only `transient_failure`
|
|
145
|
+
* is retryable, and only when the adapter policy is idempotent. Permanent
|
|
146
|
+
* failures, timeouts, and ambiguous outcomes are never retried.
|
|
147
|
+
*/
|
|
148
|
+
declare function shouldRetryAdapterOutcome(kind: AdapterOutcomeKind, idempotent: boolean): boolean;
|
|
149
|
+
/**
|
|
150
|
+
* Reject a contradictory adapter result where `success` is true but the
|
|
151
|
+
* explicit `outcome` is a non-success kind. A successful result claiming an
|
|
152
|
+
* ambiguous, permanent, timeout, or transient outcome is a programming
|
|
153
|
+
* error — the adapter cannot have both succeeded and require reconciliation.
|
|
154
|
+
*/
|
|
155
|
+
declare function assertAdapterOutcomeConsistency(result: AdapterExecutionResult): void;
|
|
81
156
|
declare function executeWithAdapterRetry<T>(options: ExecuteWithRetryOptions<T>): Promise<T>;
|
|
82
157
|
|
|
83
|
-
export { type AdapterAttempt, type AdapterCircuitBreakerConfig, type AdapterExecutionContext, type AdapterExecutionResult, type AdapterExecutor, type AdapterImplementation, AdapterRegistry, type AdapterRetryPolicy, type ExecuteWithRetryOptions, MissingAdapterRegistrationError, type ResolvedAdapterRetryPolicy, executeWithAdapterRetry, getRetryDelayMs, resolveAdapterRetryPolicy };
|
|
158
|
+
export { type AdapterAttempt, type AdapterCircuitBreakerConfig, type AdapterExecutionContext, type AdapterExecutionResult, type AdapterExecutor, type AdapterImplementation, type AdapterOutcomeEvidence, type AdapterOutcomeKind, AdapterRegistry, type AdapterRetryPolicy, type ExecuteWithRetryOptions, MissingAdapterRegistrationError, type ResolvedAdapterRetryPolicy, assertAdapterOutcomeConsistency, classifyAdapterOutcome, executeWithAdapterRetry, getRetryDelayMs, resolveAdapterRetryPolicy, shouldRetryAdapterOutcome };
|
package/dist/adapters/index.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { AdapterRegistry, MissingAdapterRegistrationError, executeWithAdapterRetry, getRetryDelayMs, resolveAdapterRetryPolicy } from '../chunk-
|
|
1
|
+
export { AdapterRegistry, MissingAdapterRegistrationError, assertAdapterOutcomeConsistency, classifyAdapterOutcome, executeWithAdapterRetry, getRetryDelayMs, resolveAdapterRetryPolicy, shouldRetryAdapterOutcome } from '../chunk-DTY3CHQZ.js';
|
|
2
2
|
//# sourceMappingURL=index.js.map
|
|
3
3
|
//# sourceMappingURL=index.js.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var chunkN2BBUKCX_cjs = require('./chunk-N2BBUKCX.cjs');
|
|
4
4
|
|
|
5
5
|
// testing/index.ts
|
|
6
6
|
function createFakeActionContext(options = {}) {
|
|
@@ -17,7 +17,7 @@ function createFakeActionContext(options = {}) {
|
|
|
17
17
|
};
|
|
18
18
|
}
|
|
19
19
|
function createInMemoryAdapterRegistry() {
|
|
20
|
-
return new
|
|
20
|
+
return new chunkN2BBUKCX_cjs.AdapterRegistry();
|
|
21
21
|
}
|
|
22
22
|
function createDeterministicIdFactory(prefix = "test") {
|
|
23
23
|
let sequence = 0;
|
|
@@ -27,5 +27,5 @@ function createDeterministicIdFactory(prefix = "test") {
|
|
|
27
27
|
exports.createDeterministicIdFactory = createDeterministicIdFactory;
|
|
28
28
|
exports.createFakeActionContext = createFakeActionContext;
|
|
29
29
|
exports.createInMemoryAdapterRegistry = createInMemoryAdapterRegistry;
|
|
30
|
-
//# sourceMappingURL=chunk-
|
|
31
|
-
//# sourceMappingURL=chunk-
|
|
30
|
+
//# sourceMappingURL=chunk-3BGFHSSK.cjs.map
|
|
31
|
+
//# sourceMappingURL=chunk-3BGFHSSK.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../testing/index.ts"],"names":["AdapterRegistry"],"mappings":";;;;;AAeO,SAAS,uBAAA,CACf,OAAA,GAAyC,EAAC,EACrB;AACrB,EAAA,OAAO;AAAA,IACN,kBAAA,EAAoB,QAAQ,kBAAA,IAAsB,UAAA;AAAA,IAClD,QAAA,EAAU,QAAQ,QAAA,IAAY,aAAA;AAAA,IAC9B,OAAA,EAAS,QAAQ,OAAA,IAAW,YAAA;AAAA,IAC5B,OAAA,EAAS,QAAQ,OAAA,IAAW,YAAA;AAAA,IAC5B,SAAA,EAAW,QAAQ,SAAA,IAAa,QAAA;AAAA,IAChC,aAAA,EAAe,QAAQ,aAAA,IAAiB,WAAA;AAAA,IACxC,aAAa,OAAA,CAAQ,WAAA;AAAA,IACrB,EAAA,EAAI,OAAA,CAAQ,EAAA,IAAO,EAAC;AAAA,IACpB,QAAA,EAAU,OAAA,CAAQ,QAAA,IAAY;AAAC,GAChC;AACD;AAEO,SAAS,6BAAA,GAAiD;AAChE,EAAA,OAAO,IAAIA,iCAAA,EAAgB;AAC5B;AAEO,SAAS,4BAAA,CAA6B,SAAS,MAAA,EAAsB;AAC3E,EAAA,IAAI,QAAA,GAAW,CAAA;AACf,EAAA,OAAO,MAAM,CAAA,EAAG,MAAM,CAAA,CAAA,EAAI,MAAA,CAAO,EAAE,QAAQ,CAAA,CAAE,QAAA,CAAS,CAAA,EAAG,GAAG,CAAC,CAAA,CAAA;AAC9D","file":"chunk-
|
|
1
|
+
{"version":3,"sources":["../testing/index.ts"],"names":["AdapterRegistry"],"mappings":";;;;;AAeO,SAAS,uBAAA,CACf,OAAA,GAAyC,EAAC,EACrB;AACrB,EAAA,OAAO;AAAA,IACN,kBAAA,EAAoB,QAAQ,kBAAA,IAAsB,UAAA;AAAA,IAClD,QAAA,EAAU,QAAQ,QAAA,IAAY,aAAA;AAAA,IAC9B,OAAA,EAAS,QAAQ,OAAA,IAAW,YAAA;AAAA,IAC5B,OAAA,EAAS,QAAQ,OAAA,IAAW,YAAA;AAAA,IAC5B,SAAA,EAAW,QAAQ,SAAA,IAAa,QAAA;AAAA,IAChC,aAAA,EAAe,QAAQ,aAAA,IAAiB,WAAA;AAAA,IACxC,aAAa,OAAA,CAAQ,WAAA;AAAA,IACrB,EAAA,EAAI,OAAA,CAAQ,EAAA,IAAO,EAAC;AAAA,IACpB,QAAA,EAAU,OAAA,CAAQ,QAAA,IAAY;AAAC,GAChC;AACD;AAEO,SAAS,6BAAA,GAAiD;AAChE,EAAA,OAAO,IAAIA,iCAAA,EAAgB;AAC5B;AAEO,SAAS,4BAAA,CAA6B,SAAS,MAAA,EAAsB;AAC3E,EAAA,IAAI,QAAA,GAAW,CAAA;AACf,EAAA,OAAO,MAAM,CAAA,EAAG,MAAM,CAAA,CAAA,EAAI,MAAA,CAAO,EAAE,QAAQ,CAAA,CAAE,QAAA,CAAS,CAAA,EAAG,GAAG,CAAC,CAAA,CAAA;AAC9D","file":"chunk-3BGFHSSK.cjs","sourcesContent":["import { AdapterRegistry } from \"../adapters\";\nimport type { ActionContext, ActorType } from \"../actions\";\n\nexport interface FakeActionContextOptions<TDb = Record<string, never>> {\n\tactionInvocationId?: string;\n\ttenantId?: string;\n\tspaceId?: string;\n\tactorId?: string;\n\tactorType?: ActorType;\n\tcorrelationId?: string;\n\tcausationId?: string;\n\tdb?: TDb;\n\tservices?: Record<string, unknown>;\n}\n\nexport function createFakeActionContext<TDb = Record<string, never>>(\n\toptions: FakeActionContextOptions<TDb> = {},\n): ActionContext<TDb> {\n\treturn {\n\t\tactionInvocationId: options.actionInvocationId ?? \"act_test\",\n\t\ttenantId: options.tenantId ?? \"tenant-test\",\n\t\tspaceId: options.spaceId ?? \"space-test\",\n\t\tactorId: options.actorId ?? \"actor-test\",\n\t\tactorType: options.actorType ?? \"system\",\n\t\tcorrelationId: options.correlationId ?? \"corr-test\",\n\t\tcausationId: options.causationId,\n\t\tdb: options.db ?? ({} as TDb),\n\t\tservices: options.services ?? {},\n\t};\n}\n\nexport function createInMemoryAdapterRegistry(): AdapterRegistry {\n\treturn new AdapterRegistry();\n}\n\nexport function createDeterministicIdFactory(prefix = \"test\"): () => string {\n\tlet sequence = 0;\n\treturn () => `${prefix}_${String(++sequence).padStart(6, \"0\")}`;\n}\n"]}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
4
|
-
var
|
|
5
|
-
var
|
|
3
|
+
var chunkIFW43ELT_cjs = require('./chunk-IFW43ELT.cjs');
|
|
4
|
+
var chunkCXR6KEKQ_cjs = require('./chunk-CXR6KEKQ.cjs');
|
|
5
|
+
var chunkLDPBQ72C_cjs = require('./chunk-LDPBQ72C.cjs');
|
|
6
6
|
|
|
7
7
|
// modules/index.ts
|
|
8
8
|
function getModuleDependencyNamespace(dependency) {
|
|
@@ -36,7 +36,7 @@ function createModuleRegistry(modules = []) {
|
|
|
36
36
|
const definition = machine;
|
|
37
37
|
return [definition.entityType, definition];
|
|
38
38
|
})));
|
|
39
|
-
const views = new Map(orderedModules.flatMap((module) => (module.views ?? []).map((view) => [
|
|
39
|
+
const views = new Map(orderedModules.flatMap((module) => (module.views ?? []).map((view) => [chunkCXR6KEKQ_cjs.viewContractKey(view), view])));
|
|
40
40
|
return {
|
|
41
41
|
modules: registered,
|
|
42
42
|
orderedModules,
|
|
@@ -74,7 +74,7 @@ function qualifiedObjectType(namespace, type) {
|
|
|
74
74
|
function validateObjectDefinition(objectType, namespace, context) {
|
|
75
75
|
if (typeof objectType === "string") return;
|
|
76
76
|
if (objectType.schema !== void 0) {
|
|
77
|
-
|
|
77
|
+
chunkIFW43ELT_cjs.assertPortableJsonSchema(objectType.schema, `Module "${namespace}" object "${objectType.type}" schema`);
|
|
78
78
|
}
|
|
79
79
|
if (objectType.semantics !== void 0) {
|
|
80
80
|
assertJsonValue(objectType.semantics, `Module "${namespace}" object "${objectType.type}" semantics`);
|
|
@@ -112,6 +112,56 @@ function validateObjectDefinition(objectType, namespace, context) {
|
|
|
112
112
|
}
|
|
113
113
|
}
|
|
114
114
|
}
|
|
115
|
+
function validateCapabilityWorkflows(module, context) {
|
|
116
|
+
const workflows = module.capability?.workflows ?? [];
|
|
117
|
+
const actionsById = new Map((module.actions ?? []).map((action) => [action.actionId, action]));
|
|
118
|
+
const names = /* @__PURE__ */ new Set();
|
|
119
|
+
for (const workflow of workflows) {
|
|
120
|
+
if (!/^[A-Za-z][A-Za-z0-9_-]*$/.test(workflow.name)) {
|
|
121
|
+
throw new Error(`Module "${module.namespace}" declares invalid workflow name "${workflow.name}".`);
|
|
122
|
+
}
|
|
123
|
+
if (names.has(workflow.name)) {
|
|
124
|
+
throw new Error(`Module "${module.namespace}" declares duplicate workflow "${workflow.name}".`);
|
|
125
|
+
}
|
|
126
|
+
names.add(workflow.name);
|
|
127
|
+
if (!/^[a-z][a-z0-9-]*(\.[a-z][a-z0-9-]*)*$/.test(workflow.taskQueue)) {
|
|
128
|
+
throw new Error(`Module "${module.namespace}" workflow "${workflow.name}" declares invalid task queue "${workflow.taskQueue}".`);
|
|
129
|
+
}
|
|
130
|
+
if (workflow.startedBy !== void 0) {
|
|
131
|
+
const known = context.plannedEventTypes?.has(workflow.startedBy) ?? (module.eventTypes ?? []).some((eventType) => getEventTypeName(eventType) === workflow.startedBy);
|
|
132
|
+
if (!known) {
|
|
133
|
+
throw new Error(`Module "${module.namespace}" workflow "${workflow.name}" is started by unknown event type "${workflow.startedBy}".`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
const timerNames = /* @__PURE__ */ new Set();
|
|
137
|
+
for (const timer of workflow.timers ?? []) {
|
|
138
|
+
if (!timer.name.trim() || !timer.configKey.trim()) {
|
|
139
|
+
throw new Error(`Module "${module.namespace}" workflow "${workflow.name}" has a timer without a name or configKey.`);
|
|
140
|
+
}
|
|
141
|
+
if (timerNames.has(timer.name)) {
|
|
142
|
+
throw new Error(`Module "${module.namespace}" workflow "${workflow.name}" declares duplicate timer "${timer.name}".`);
|
|
143
|
+
}
|
|
144
|
+
timerNames.add(timer.name);
|
|
145
|
+
}
|
|
146
|
+
const steps = /* @__PURE__ */ new Set();
|
|
147
|
+
for (const compensation of workflow.compensations ?? []) {
|
|
148
|
+
if (!compensation.step.trim()) {
|
|
149
|
+
throw new Error(`Module "${module.namespace}" workflow "${workflow.name}" has a compensation without a step.`);
|
|
150
|
+
}
|
|
151
|
+
if (steps.has(compensation.step)) {
|
|
152
|
+
throw new Error(`Module "${module.namespace}" workflow "${workflow.name}" declares duplicate compensation for step "${compensation.step}".`);
|
|
153
|
+
}
|
|
154
|
+
steps.add(compensation.step);
|
|
155
|
+
const compensatingAction = actionsById.get(compensation.actionId);
|
|
156
|
+
if (!compensatingAction) {
|
|
157
|
+
throw new Error(`Module "${module.namespace}" workflow "${workflow.name}" compensates step "${compensation.step}" with unknown action "${compensation.actionId}".`);
|
|
158
|
+
}
|
|
159
|
+
if (!compensatingAction.idempotent || compensatingAction.execution?.idempotency === "prohibited") {
|
|
160
|
+
throw new Error(`Module "${module.namespace}" workflow "${workflow.name}" compensates step "${compensation.step}" with non-idempotent action "${compensation.actionId}"; a reversal must be safely repeatable.`);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
115
165
|
function validateManifest(module, context = {}) {
|
|
116
166
|
validateModuleNamespace(module.namespace);
|
|
117
167
|
const dependencies = (module.dependencies ?? []).map(getModuleDependencyNamespace);
|
|
@@ -129,13 +179,14 @@ function validateManifest(module, context = {}) {
|
|
|
129
179
|
for (const port of module.capability.requirements?.ports ?? []) {
|
|
130
180
|
if (!/^[a-z][a-z0-9-]*$/.test(port.name)) throw new Error(`Module "${module.namespace}" capability has invalid port requirement "${port.name}".`);
|
|
131
181
|
}
|
|
182
|
+
validateCapabilityWorkflows(module, context);
|
|
132
183
|
}
|
|
133
|
-
for (const eventType of module.eventTypes ?? []) if (typeof eventType !== "string" && eventType.schema)
|
|
184
|
+
for (const eventType of module.eventTypes ?? []) if (typeof eventType !== "string" && eventType.schema) chunkIFW43ELT_cjs.assertPortableJsonSchema(eventType.schema, `Event "${eventType.eventType}" schema`);
|
|
134
185
|
for (const action of module.actions ?? []) {
|
|
135
186
|
if (action.execution?.idempotency === "required" && !action.idempotent) throw new Error(`Action "${action.actionId}" requires idempotency but is not declared idempotent.`);
|
|
136
187
|
if (action.execution?.connectivity === "offline-capturable" && action.execution.idempotency === "prohibited") throw new Error(`Offline action "${action.actionId}" cannot prohibit idempotency.`);
|
|
137
188
|
}
|
|
138
|
-
for (const view of module.views ?? []) if (view.usage?.collection?.filterSchema)
|
|
189
|
+
for (const view of module.views ?? []) if (view.usage?.collection?.filterSchema) chunkIFW43ELT_cjs.assertPortableJsonSchema(view.usage.collection.filterSchema, `View "${view.name}" filter schema`);
|
|
139
190
|
for (const dependency of dependencies) {
|
|
140
191
|
validateModuleNamespace(dependency);
|
|
141
192
|
if (dependency === module.namespace) {
|
|
@@ -173,14 +224,14 @@ function validateManifest(module, context = {}) {
|
|
|
173
224
|
} catch {
|
|
174
225
|
parameterSchema = void 0;
|
|
175
226
|
}
|
|
176
|
-
if (parameterSchema !== void 0)
|
|
227
|
+
if (parameterSchema !== void 0) chunkIFW43ELT_cjs.assertPortableJsonSchema(parameterSchema, `Action "${action.actionId}" parameters`);
|
|
177
228
|
let resultSchema;
|
|
178
229
|
try {
|
|
179
230
|
resultSchema = action.resultSchema?.toJSONSchema?.();
|
|
180
231
|
} catch {
|
|
181
232
|
resultSchema = void 0;
|
|
182
233
|
}
|
|
183
|
-
if (resultSchema !== void 0)
|
|
234
|
+
if (resultSchema !== void 0) chunkIFW43ELT_cjs.assertPortableJsonSchema(resultSchema, `Action "${action.actionId}" result`);
|
|
184
235
|
if (!/^[a-z][a-z0-9-]*\.[a-z][a-z0-9_]*$/.test(action.actionId)) {
|
|
185
236
|
throw new Error(`Invalid action ID: ${action.actionId}`);
|
|
186
237
|
}
|
|
@@ -209,7 +260,8 @@ function validateManifest(module, context = {}) {
|
|
|
209
260
|
const key = `${view.name}@${view.version}`;
|
|
210
261
|
if (viewKeys.has(key)) throw new Error(`Module "${module.namespace}" has duplicate view contract "${key}".`);
|
|
211
262
|
viewKeys.add(key);
|
|
212
|
-
|
|
263
|
+
chunkIFW43ELT_cjs.assertPortableJsonSchema(view.shape, `View "${key}" shape`);
|
|
264
|
+
if (view.parameterSchema !== void 0) chunkIFW43ELT_cjs.assertPortableJsonSchema(view.parameterSchema, `View "${key}" parameters`);
|
|
213
265
|
if (!Number.isInteger(view.snapshotVersion) || view.snapshotVersion < 1) {
|
|
214
266
|
throw new Error(`View "${key}" snapshotVersion must be a positive integer.`);
|
|
215
267
|
}
|
|
@@ -304,7 +356,7 @@ function validateAndOrderFabricModules(modules) {
|
|
|
304
356
|
const plannedEventTypes = new Set(
|
|
305
357
|
modules.flatMap((module) => (module.eventTypes ?? []).map(getEventTypeName))
|
|
306
358
|
);
|
|
307
|
-
for (const eventType of
|
|
359
|
+
for (const eventType of chunkLDPBQ72C_cjs.createEventContractRegistry().eventTypes()) plannedEventTypes.add(eventType);
|
|
308
360
|
const availableObjectTypes = /* @__PURE__ */ new Set([
|
|
309
361
|
...modules.flatMap(
|
|
310
362
|
(module) => (module.objectTypes ?? []).map((objectType) => qualifiedObjectType(module.namespace, getObjectTypeName(objectType)))
|
|
@@ -331,5 +383,5 @@ exports.registerFabricModule = registerFabricModule;
|
|
|
331
383
|
exports.registerFabricModules = registerFabricModules;
|
|
332
384
|
exports.validateAndOrderFabricModules = validateAndOrderFabricModules;
|
|
333
385
|
exports.validateModuleNamespace = validateModuleNamespace;
|
|
334
|
-
//# sourceMappingURL=chunk-
|
|
335
|
-
//# sourceMappingURL=chunk-
|
|
386
|
+
//# sourceMappingURL=chunk-3UBX7KQM.cjs.map
|
|
387
|
+
//# sourceMappingURL=chunk-3UBX7KQM.cjs.map
|