@fabricorg/platform 1.7.0 → 1.8.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 +56 -0
- package/README.md +214 -3
- package/dist/actions/index.cjs +6 -6
- package/dist/actions/index.d.cts +9 -2
- package/dist/actions/index.d.ts +9 -2
- package/dist/actions/index.js +1 -1
- package/dist/adapters/index.cjs +33 -9
- package/dist/adapters/index.d.cts +388 -1
- package/dist/adapters/index.d.ts +388 -1
- package/dist/adapters/index.js +1 -1
- package/dist/chunk-5FOCPRTR.js +511 -0
- package/dist/chunk-5FOCPRTR.js.map +1 -0
- package/dist/{chunk-USDLP4E7.cjs → chunk-5IVFPWK6.cjs} +4 -4
- package/dist/{chunk-USDLP4E7.cjs.map → chunk-5IVFPWK6.cjs.map} +1 -1
- package/dist/chunk-6HY35TKB.cjs +491 -0
- package/dist/chunk-6HY35TKB.cjs.map +1 -0
- package/dist/chunk-6UGEIDLC.cjs +26 -0
- package/dist/chunk-6UGEIDLC.cjs.map +1 -0
- package/dist/chunk-BJGZE3GE.js +20 -0
- package/dist/chunk-BJGZE3GE.js.map +1 -0
- package/dist/{chunk-X3GHYMAJ.cjs → chunk-CRZXTZ36.cjs} +2 -22
- package/dist/chunk-CRZXTZ36.cjs.map +1 -0
- package/dist/{chunk-DCC3UIUJ.js → chunk-GFU7BGEJ.js} +3 -3
- package/dist/{chunk-DCC3UIUJ.js.map → chunk-GFU7BGEJ.js.map} +1 -1
- package/dist/chunk-HEYMMVDP.js +480 -0
- package/dist/chunk-HEYMMVDP.js.map +1 -0
- package/dist/chunk-IF6NJORI.cjs +526 -0
- package/dist/chunk-IF6NJORI.cjs.map +1 -0
- package/dist/{chunk-6QX4I45I.js → chunk-PIDZQAEL.js} +2 -2
- package/dist/{chunk-6QX4I45I.js.map → chunk-PIDZQAEL.js.map} +1 -1
- package/dist/{chunk-ATWWZTRC.js → chunk-TYNIFC7J.js} +3 -18
- package/dist/chunk-TYNIFC7J.js.map +1 -0
- package/dist/{chunk-XCAOO6KG.cjs → chunk-UGWGHTNC.cjs} +2 -2
- package/dist/{chunk-XCAOO6KG.cjs.map → chunk-UGWGHTNC.cjs.map} +1 -1
- package/dist/evidence/index.cjs +45 -1
- package/dist/evidence/index.d.cts +187 -1
- package/dist/evidence/index.d.ts +187 -1
- package/dist/evidence/index.js +3 -1
- package/dist/governance/index.d.cts +1 -0
- package/dist/governance/index.d.ts +1 -0
- package/dist/index.cjs +124 -59
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +7 -6
- package/dist/privacy/index.cjs +19 -18
- package/dist/privacy/index.js +2 -1
- package/dist/testing/index.cjs +5 -5
- package/dist/testing/index.js +2 -2
- package/package.json +1 -1
- package/dist/chunk-ATWWZTRC.js.map +0 -1
- package/dist/chunk-FAG2RTPM.js +0 -120
- package/dist/chunk-FAG2RTPM.js.map +0 -1
- package/dist/chunk-OZLZHISS.js +0 -3
- package/dist/chunk-OZLZHISS.js.map +0 -1
- package/dist/chunk-PG5LYTUX.cjs +0 -4
- package/dist/chunk-PG5LYTUX.cjs.map +0 -1
- package/dist/chunk-X3GHYMAJ.cjs.map +0 -1
- package/dist/chunk-XZQZQ5SW.cjs +0 -129
- package/dist/chunk-XZQZQ5SW.cjs.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,61 @@
|
|
|
1
1
|
# @fabricorg/platform
|
|
2
2
|
|
|
3
|
+
## 1.8.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- eac74a9: Adapter circuit breakers now work. `AdapterImplementation.circuitBreaker` was declared and documented, but nothing read it. Platform now implements the breaker, and Host enforces it (issue #22).
|
|
8
|
+
|
|
9
|
+
**Release note: new failure semantics.** An adapter that declares `circuitBreaker: { mode: "enforce", ... }` can now have calls refused. Before this release that config had no effect. Review every adapter that already declares `mode: "enforce"` before upgrading, or switch it to `"monitor"` to observe first.
|
|
10
|
+
|
|
11
|
+
- **`@fabricorg/platform` (adapters subpath and root).**
|
|
12
|
+
- New exports: `InMemoryAdapterCircuitBreaker`, `AdapterCircuitBreakerRegistry`, `AdapterCircuitOpenError`, `isAdapterCircuitOpenError`, `classifyCircuitBreakerOutcome`, `assertAdapterCircuitBreakerConfig`, and the types `AdapterCircuitBreaker`, `AdapterCircuitBreakerProvider`, `AdapterCircuitState`, `AdapterCircuitOutcome`, `AdapterCircuitAdmission`, `AdapterCircuitBreakerEvent` and `AdapterCircuitBreakerManualOverride`. `AdapterCircuitAdmission.generation` identifies the epoch an admission was granted in.
|
|
13
|
+
- `AdapterCircuitBreakerConfig` gains the optional `manualOverride` (`"force_open"` or `"force_closed"`) and `probeLeaseMs` fields. No existing field changed.
|
|
14
|
+
- The breaker is per process: state is in memory, is not shared between replicas and is lost on restart.
|
|
15
|
+
- It goes closed → open → half-open (one probe) → closed, over a rolling `windowMs` and a `cooldownMs`. At most one half-open probe is outstanding until it records or its lease expires. The lease is the new optional `probeLeaseMs` (default `max(cooldownMs, windowMs)`), independent of the cooldown. `check()` is a side-effect-free preview.
|
|
16
|
+
- Only outcomes from the current epoch change state. The breaker's epoch advances on every state change, manual-override change and probe grant, and each admission carries it as `generation`. An outcome admitted in an earlier epoch is dropped: it never closes or reopens the breaker, never resolves or frees the probe slot, and is not counted toward a later trip. For example, a replaced probe that fails after its replacement closed the breaker does not reopen it. A direct `record()` call without an admission, or with one that has no `generation`, is treated as current.
|
|
17
|
+
- `failureThreshold` is a failure count, matching the .NET port. Once `minimumSamples` (default `failureThreshold`) outcomes are in the window, the breaker opens on `failureThreshold` consecutive failures, or on a failure ratio of at least `failureThreshold / minimumSamples`.
|
|
18
|
+
- Outcomes `transient_failure`, `timeout`, `ambiguous` and unclassified thrown errors count as failures. `permanent_failure` is ignored.
|
|
19
|
+
- **`executeWithAdapterRetry`** takes an optional `circuitBreaker`. Without it, behaviour is unchanged.
|
|
20
|
+
- With it, admission is checked immediately before every attempt, with no await between admission and `execute`, and the outcome of every attempt that reached `execute` is recorded.
|
|
21
|
+
- New optional `prepareAttempt(attempt, maxAttempts)` hook, awaited before admission. If it throws, no admission is taken and no outcome is recorded; the error is classified and retried or rethrown like an error thrown by `execute`.
|
|
22
|
+
- In `enforce` mode, a refused attempt is not executed and `AdapterCircuitOpenError` is thrown (`code: "ADAPTER_CIRCUIT_OPEN"`, with `adapterKey`, `attempt` and `retryAfterMs` when known).
|
|
23
|
+
- When the breaker opens between attempts, retrying stops and the same error is thrown.
|
|
24
|
+
- `monitor` mode never blocks. It reports `would_reject` telemetry through `onEvent`, and the callback's errors are swallowed. Events are delivered after the breaker operation that produced them commits, never during a decision or a record, so a callback that calls back into the breaker (for example to force it open) sees committed state, and its change applies from the next decision on. A callback never revokes an admission that is already committed; the README lists the two edge cases this leaves (a breaker call made from inside a callback, and `cooldownMs: 0`).
|
|
25
|
+
- **`@fabricorg/platform-host`.**
|
|
26
|
+
- Each Host instance keeps one breaker per `adapterType:operation`. A breaker is created on the adapter's first call, and only for adapters that declare `circuitBreaker`.
|
|
27
|
+
- Host now writes each attempt number to the adapter invocation record in `prepareAttempt`, before breaker admission, instead of inside `execute`. A store failure there is classified and retried as before, and is not counted against the breaker.
|
|
28
|
+
- The new `adapterCircuitBreakers` option supplies a shared `AdapterCircuitBreakerRegistry` or a custom provider (shared breakers, telemetry, manual override).
|
|
29
|
+
- A circuit-open refusal means the adapter never ran. Host marks the adapter invocation `failed` and appends `AdapterInvocationFailed` with the additive payload fields `reason: "circuit_open"` and `retryAfterMs`. The invocation then fails with status `failed`, the same path as an unsuccessful adapter result.
|
|
30
|
+
- A refusal is never routed to `reconciliation_required`, and it is never classified as a timeout. Domain events already appended in the `before_adapters` phase are kept.
|
|
31
|
+
- `createGovernedActionHost()` now throws at construction if an adapter declares an invalid `circuitBreaker` config, for example a `failureThreshold` below 1. Such a config was previously ignored.
|
|
32
|
+
- **Peer range:** Host now requires `@fabricorg/platform` `^1.8.0` (was `^1.2.0`), because it imports the new breaker exports at runtime. The manifest declares it as `workspace:^`, like the other workspace packages that peer on Platform, so the published range is the Platform version released with Host. A literal `^1.8.0` would fail the packed-consumer check (`pnpm verify:packed`) until the version PR publishes 1.8.0. A side effect: each later Host release will require at least the Platform version that is current when that Host is released.
|
|
33
|
+
|
|
34
|
+
- e0f7ca7: Platform can now build evidence packets. `@fabricorg/platform/evidence` previously declared only the envelope types, so applications had to write their own generator (issue #24).
|
|
35
|
+
|
|
36
|
+
**Release note: new redaction and tenant-isolation semantics for applications that adopt the generator.** Nothing changes for code that does not call it.
|
|
37
|
+
|
|
38
|
+
- **`generateEvidencePacket(input, context, options)`** builds a `sealed` `EvidencePacketEnvelope` for one subject through an application-supplied read port. It persists nothing; storing the packet is the application's job.
|
|
39
|
+
- Subjects: the primary subject first, then the related subjects from `resolveRelatedSubjects`, de-duplicated and sorted by `subjectType` then `subjectId`. `includeRelated: false`, or a port without the resolver, uses only the primary subject, and `payload.includeRelated` records whether the lookup ran.
|
|
40
|
+
- It reads events, then the action invocations, policy evaluations and adapter invocations those events reference. The three dependent readers are optional; an absent one yields an empty section, and none is called when no event references an action invocation.
|
|
41
|
+
- **Fails closed on scope.** A record whose `tenantId` or `spaceId` differs from the generation context throws `EvidencePacketGenerationError` (`code: "EVIDENCE_SCOPE_MISMATCH"`) instead of being included or dropped. The check runs on the record as returned and again after normalization. A dependent record the generator did not ask for throws `EVIDENCE_UNREQUESTED_RECORD`. Errors thrown by the port propagate and no packet is returned.
|
|
42
|
+
- What the generator enforces on port data is tenant/space scope, requested invocation ids and record shape. It does not check an event's subject against the requested subjects; choosing the events (for example including events of the primary subject's own action invocations) is the port's responsibility.
|
|
43
|
+
- The payload is plain JSON: every `Date` becomes an ISO string, and values JSON cannot represent faithfully (NaN, Infinity, invalid `Date` objects, bigint, Maps, class instances without `toJSON`, an own `__proto__` key) throw `EVIDENCE_INVALID_RECORD` instead of being changed or dropped. A cyclic value is not detected and fails with a `RangeError` instead; no packet is produced. Fields inherited from a prototype are not read. Events are sorted by the `occurredAt` instant, the `recordedAt` instant, `sequence`, then `id`; the other sections by `id`.
|
|
44
|
+
- Event `occurredAt`/`recordedAt` strings must be ISO 8601 or PostgreSQL ISO date-times with an explicit zone (`Z`, `+HH`, `+HH:MM` or `+HHMM`, offset hours up to 14, optionally after a single space), for example `2026-05-01 10:20:30.123456+00`. They are parsed field by field rather than with `new Date(string)`: a zone-less string, any other format, or an out-of-range field (February 30, hour 24, offset `+15`) throws `EVIDENCE_INVALID_RECORD` instead of being guessed or rolled over, so the payload and checksum do not depend on the host's time zone or on the JavaScript engine's fallback parser. Timestamps are kept to millisecond precision (the fraction is truncated).
|
|
45
|
+
- **Redaction uses the existing privacy engine.** `options.anonymizationPolicy` (required; pass `{ fields: [] }` to redact nothing) runs through `AnonymizationEngine` with data class `"evidence_packet"`, before hashing. The resulting `AnonymizationMetadata` is stored as `payload.redaction`. Its `jobId` defaults to the packet id and its `reason` to `"evidence_packet_generation"`. Envelope subjects and reference metadata are read from the redacted payload.
|
|
46
|
+
- **`_privacy` values are validated.** The engine passes any `_privacy` key through without applying the policy, because it stores its own metadata there. The generator accepts a `_privacy` value from the port, at any depth, only when it has exactly the engine's `AnonymizationMetadata` shape (as on an event anonymized with `anonymizeAssetEventPayload`); any other `_privacy` value throws `EVIDENCE_INVALID_RECORD`. `redactedAt` must be an exact `toISOString()` value and `beforeChecksum` a SHA-256 hex digest. Residual risk: the free-text fields (`reason`, `jobId`, `policyVersion`, `fieldPath`, and the `preservedSemantics` entries) are type-checked as strings but not inspected, so sensitive text a port writes into them reaches the packet. The data-class and redaction-method lists the validator uses fail type-checking if they drift from `PlatformDataClass` or `RedactionMethod`.
|
|
47
|
+
- Record `id`s must pass through redaction unchanged; a policy that removes or changes one throws `EVIDENCE_IDENTIFIER_REDACTED`. Subject ids may be transformed (for example tokenized) but must remain non-empty strings. The engine scopes tokens to their field path and array position, so a tokenized value differs in each place it appears and cannot be used to match records or look packets up; tokenizing `actionInvocationId` likewise breaks the links to action invocations, undetected. Like `beforeChecksum`, `tokenize` and `hash` outputs let a low-entropy value be guessed.
|
|
48
|
+
- Not redacted: values used as object keys (the policy matches field names and paths), and envelope fields copied from the context or input (`generatedBy`, `correlationId`, `causationId`, `policyVersion`).
|
|
49
|
+
- References: one per action invocation, asset event, policy evaluation and adapter invocation, with identifier-only metadata (for example `eventType`, subject ids, `adapterType`, `operation`, `status`).
|
|
50
|
+
- **New read-port and record types:** `EvidencePacketReadPort`, `EvidenceEventQuery`, `EvidenceScope`, `EvidenceEventRecord` (satisfied by `AssetEventEnvelope` and `AssetEventLike`), `EvidenceRecord`, `EvidenceInvocationRecord`, `EvidenceJsonRecord`, `EvidenceTimestamp`, `EvidencePacketPayload`, `GenerateEvidencePacketInput` and `GenerateEvidencePacketOptions`, plus `EvidencePacketGenerationError` and its code type.
|
|
51
|
+
- **Checksums.** `checksum` is the hex SHA-256 of `canonicalJson(payload)`. The new constant `EVIDENCE_PACKET_CHECKSUM_ALGORITHM` (`"fabric-canonical-json-sha256-v1"`, the same identifier as Host's parameter digest because the algorithm is the same) is recorded in the new optional `EvidencePacketEnvelope.checksumAlgorithm` field. No existing field changed. `verifyEvidencePacketChecksum(envelope)` recomputes it and returns `false`, without throwing, on a mismatch, a missing checksum or an unknown algorithm. `computeEvidencePacketChecksum(payload)` is exported too. The checksum covers the payload only, not the other envelope fields.
|
|
52
|
+
- **`canonicalJson`** is now exported from `@fabricorg/platform/evidence` and the root. It moved from `@fabricorg/platform-host` unchanged, byte for byte, including its treatment of `Date` as `{}`.
|
|
53
|
+
- `createEvidencePacketRenderer(options)` wraps the generator as an `EvidenceRenderer`. Other new constants: `EVIDENCE_PACKET_SCHEMA_VERSION`, `DEFAULT_EVIDENCE_PACKET_TYPE` (`"compliance_evidence"`) and `DEFAULT_EVIDENCE_REDACTION_REASON`.
|
|
54
|
+
|
|
55
|
+
### Patch Changes
|
|
56
|
+
|
|
57
|
+
- d5fa7c2: Documentation only: the `SagaContext.awaitSignal` contract now says that a runtime drops a signal whose stable signal ID it has already received for the saga, and that FIFO buffering applies among distinct signals. Signals without an ID are not deduplicated. No runtime in this package implements `awaitSignal`; `@fabricorg/platform-temporal` ships `createSagaSignalInbox()` for Temporal workflows (issue #23).
|
|
58
|
+
|
|
3
59
|
## 1.7.0
|
|
4
60
|
|
|
5
61
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -48,12 +48,12 @@ That's what this package gives you. Nothing more.
|
|
|
48
48
|
| `@fabricorg/platform/state-machines` | `StateMachineDefinition`, structural and guard-aware transition evaluation |
|
|
49
49
|
| `@fabricorg/platform/events` | `AssetEventEnvelope`, `EventTypeRegistry` |
|
|
50
50
|
| `@fabricorg/platform/projections` | Replay/snapshot framework |
|
|
51
|
-
| `@fabricorg/platform/adapters` | `AdapterRegistry`,
|
|
51
|
+
| `@fabricorg/platform/adapters` | `AdapterRegistry`, `executeWithAdapterRetry`, in-process circuit breaker (`InMemoryAdapterCircuitBreaker`, `AdapterCircuitBreakerRegistry`, `AdapterCircuitOpenError`) |
|
|
52
52
|
| `@fabricorg/platform/modules` | `FabricModule<TDb>`, `createModuleRegistry` |
|
|
53
53
|
| `@fabricorg/platform/manifest` | `exportModuleManifest`, `exportModuleManifestBundle`, `ModuleManifest` |
|
|
54
54
|
| `@fabricorg/platform/ids` | `createFabricId`, `FABRIC_ID_PREFIXES` |
|
|
55
55
|
| `@fabricorg/platform/privacy` | Redaction + data classification |
|
|
56
|
-
| `@fabricorg/platform/evidence` | Compliance evidence packets |
|
|
56
|
+
| `@fabricorg/platform/evidence` | Compliance evidence packets: `generateEvidencePacket`, `verifyEvidencePacketChecksum`, `canonicalJson` (see [Evidence packets](#evidence-packets)) |
|
|
57
57
|
| `@fabricorg/platform/governance` | External footprints, delegated principals, obligations, attestations, and reconciliation |
|
|
58
58
|
| `@fabricorg/platform/objects` | Shared object-type contracts |
|
|
59
59
|
| `@fabricorg/platform/testing` | In-memory adapter registry, fake `ActionContext`, deterministic IDs |
|
|
@@ -112,12 +112,223 @@ Every `invokeAction(id, params)` call goes through:
|
|
|
112
112
|
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.
|
|
113
113
|
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.
|
|
114
114
|
3. **Handler** — `actionDef.handler(ctx, params)` runs inside a single `TDb` transaction. Handler parses its own input via its `schema` (single source of truth).
|
|
115
|
-
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.
|
|
115
|
+
4. **Adapters** — for each `actionDef.adapterSteps`, the registered implementation runs via `executeWithAdapterRetry` (idempotent retry, exponential backoff, and an optional per-adapter circuit breaker; see [Adapter circuit breakers](#adapter-circuit-breakers)). 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.
|
|
116
116
|
5. **Events** — domain events are appended; projections derive read models on next replay.
|
|
117
117
|
6. **Completion** — invocation status is marked, telemetry emitted.
|
|
118
118
|
|
|
119
119
|
`validateActionParameters` is also exposed for preview/dry-run paths but is **not** part of the execute pipeline.
|
|
120
120
|
|
|
121
|
+
## Adapter circuit breakers
|
|
122
|
+
|
|
123
|
+
`AdapterImplementation.circuitBreaker` configures a breaker for one adapter operation:
|
|
124
|
+
|
|
125
|
+
```ts
|
|
126
|
+
const sendAdapter: AdapterImplementation = {
|
|
127
|
+
adapterType: "email",
|
|
128
|
+
operation: "send",
|
|
129
|
+
vendor: "mailco",
|
|
130
|
+
idempotent: true,
|
|
131
|
+
circuitBreaker: { mode: "enforce", failureThreshold: 5, windowMs: 60_000, cooldownMs: 30_000 },
|
|
132
|
+
execute: async (input, context) => ({ success: true }),
|
|
133
|
+
};
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
`AdapterCircuitBreakerRegistry` creates one `InMemoryAdapterCircuitBreaker` per `adapterType:operation`
|
|
137
|
+
from each adapter's own config, on first use, and none for an adapter without a config.
|
|
138
|
+
`executeWithAdapterRetry({ ..., circuitBreaker })` asks it for admission immediately before every
|
|
139
|
+
attempt, with no await between admission and the `execute` call, and records the outcome of every
|
|
140
|
+
attempt that reached `execute`. Put any awaited work an attempt needs first (for example, writing
|
|
141
|
+
the attempt number) in the optional `prepareAttempt(attempt, maxAttempts)` hook, which runs before
|
|
142
|
+
admission. If `prepareAttempt` throws, no admission is taken and no outcome is recorded; the error is
|
|
143
|
+
classified and retried or rethrown like an error thrown by `execute`. Without `circuitBreaker` and
|
|
144
|
+
`prepareAttempt` the retry loop is unchanged.
|
|
145
|
+
`@fabricorg/platform-host` 7.4.0 and later wires this up for every adapter step.
|
|
146
|
+
|
|
147
|
+
**Scope.** The breaker is in-process memory. It is not distributed: each process, replica or Host
|
|
148
|
+
instance with its own registry trips on the failures it observes itself, and state is lost on restart.
|
|
149
|
+
To share a breaker across processes, implement the `AdapterCircuitBreaker` interface over shared
|
|
150
|
+
storage and supply it through an `AdapterCircuitBreakerProvider`.
|
|
151
|
+
|
|
152
|
+
**States.** `closed` → `open` → (after `cooldownMs`) `half_open` → `closed` or back to `open`.
|
|
153
|
+
Half-open admits one probe call. A probe success closes the breaker and clears its window, and a
|
|
154
|
+
probe failure reopens it and restarts the cooldown. In `enforce` mode, other calls are refused while
|
|
155
|
+
the probe is in flight. The probe holds its slot until it records an outcome or its lease of
|
|
156
|
+
`probeLeaseMs` expires (default `max(cooldownMs, windowMs)`, independent of the cooldown, so
|
|
157
|
+
`cooldownMs: 0` still admits one probe at a time). After the lease expires the next call becomes the
|
|
158
|
+
probe. `check()` is a side-effect-free preview: it does not commit an elapsed cooldown, take the
|
|
159
|
+
probe, change the epoch or emit telemetry.
|
|
160
|
+
|
|
161
|
+
**Stale outcomes.** An outcome may change the breaker's state only if the call was admitted in the
|
|
162
|
+
breaker's current epoch. The epoch advances on every state change (including the one an elapsed
|
|
163
|
+
cooldown commits), every manual-override change, and every probe grant. Each admission returned by
|
|
164
|
+
`acquire()` carries its epoch as `generation`, and `executeWithAdapterRetry` passes it back to
|
|
165
|
+
`record()`. An outcome from an earlier epoch is dropped: it does not close or reopen the breaker,
|
|
166
|
+
does not resolve or free the probe slot, and is not added to the rolling window, so it cannot help
|
|
167
|
+
trip the breaker later. This covers a replaced probe that reports after its replacement closed the
|
|
168
|
+
breaker, and a call admitted while closed whose failure arrives after the breaker opened. Outcomes
|
|
169
|
+
from the current epoch count exactly as described under Thresholds. A direct caller that records
|
|
170
|
+
without an admission, or with one that has no `generation`, is treated as current: the outcome is
|
|
171
|
+
sampled, can trip a closed breaker and resolves an outstanding probe.
|
|
172
|
+
|
|
173
|
+
**Thresholds.** `failureThreshold` is a failure count, with the same rule as the .NET port. Let
|
|
174
|
+
`M = minimumSamples ?? failureThreshold`. Once at least `M` outcomes fall within the last `windowMs`,
|
|
175
|
+
a recorded failure opens the breaker when either:
|
|
176
|
+
|
|
177
|
+
- the most recent `failureThreshold` outcomes are all failures, or
|
|
178
|
+
- the failure ratio in the window is at least `failureThreshold / M`.
|
|
179
|
+
|
|
180
|
+
Without `minimumSamples` this means "`failureThreshold` consecutive failures". With
|
|
181
|
+
`failureThreshold: 10, minimumSamples: 20` the breaker also opens at a 50% failure rate over at least
|
|
182
|
+
20 calls. `assertAdapterCircuitBreakerConfig` rejects a non-positive or fractional `failureThreshold`
|
|
183
|
+
or `minimumSamples`, a non-positive `windowMs`, and a negative `cooldownMs`.
|
|
184
|
+
|
|
185
|
+
**Modes.** `monitor` runs the same state machine and telemetry but never blocks a call. Calls that
|
|
186
|
+
`enforce` mode would have refused are reported as `would_reject`. `enforce` refuses calls while
|
|
187
|
+
the breaker is open: `execute` is not called, and `executeWithAdapterRetry` throws
|
|
188
|
+
`AdapterCircuitOpenError` (`code: "ADAPTER_CIRCUIT_OPEN"`, `adapterKey`, `state`, `attempt`,
|
|
189
|
+
`retryAfterMs` when known). If the breaker opens between attempts, retrying stops and the same error
|
|
190
|
+
is thrown. Use `isAdapterCircuitOpenError(error)` to recognise it, which also works across duplicate
|
|
191
|
+
copies of this package.
|
|
192
|
+
|
|
193
|
+
**Outcome mapping** (`classifyCircuitBreakerOutcome`):
|
|
194
|
+
|
|
195
|
+
| Adapter outcome | Counts as |
|
|
196
|
+
|---|---|
|
|
197
|
+
| `success` | success |
|
|
198
|
+
| `transient_failure` | failure |
|
|
199
|
+
| `timeout` | failure |
|
|
200
|
+
| `ambiguous` | failure (no definite answer is a health signal, even though Host routes it to reconciliation) |
|
|
201
|
+
| `permanent_failure` | ignored (the dependency answered and rejected the request; not a dependency failure) |
|
|
202
|
+
| thrown error | the kind `classifyThrownError` returns, `transient_failure` by default, mapped as above |
|
|
203
|
+
|
|
204
|
+
**Manual override.** `manualOverride: "force_open" | "force_closed"` in the config, or
|
|
205
|
+
`breaker.setManualOverride(...)` at runtime (`undefined` clears it). An override pins the reported
|
|
206
|
+
state and suspends automatic transitions. Outcomes of calls admitted under the override keep being
|
|
207
|
+
recorded; setting or clearing the override starts a new epoch, so outcomes of calls admitted before
|
|
208
|
+
the change are dropped as stale. Like an automatic open, `force_open` blocks only in `enforce` mode.
|
|
209
|
+
Clearing returns the breaker to `closed` with its window intact.
|
|
210
|
+
|
|
211
|
+
**Telemetry.** Pass `onEvent` to the breaker or registry to receive `state_change` (with a
|
|
212
|
+
`reason`), `rejected` and `would_reject` events. Errors the callback throws, and promises it
|
|
213
|
+
rejects, are swallowed so telemetry cannot change a call's outcome. Events are delivered after the
|
|
214
|
+
operation that produced them commits, never part-way through a decision or a record. Committing an
|
|
215
|
+
elapsed cooldown is its own step, delivered before `acquire()` decides, so a callback that forces the
|
|
216
|
+
breaker open on `cooldown_elapsed` refuses that call. A callback may call back into the breaker: it
|
|
217
|
+
sees committed state, its changes apply from the next decision on, and the events they produce are
|
|
218
|
+
delivered after it returns, in order. A callback never revokes an admission that is already
|
|
219
|
+
committed, so there are two exceptions to the cooldown rule above: a breaker call made from inside a
|
|
220
|
+
callback decides before its own `cooldown_elapsed` is delivered, and with `cooldownMs: 0` a cooldown
|
|
221
|
+
that falls due again during a decision (because a callback reopened the breaker) is admitted before
|
|
222
|
+
its event is delivered. Use a non-zero `cooldownMs` and avoid calling `acquire()` from a callback if
|
|
223
|
+
an override set from telemetry must always stop the next call.
|
|
224
|
+
|
|
225
|
+
## Evidence packets
|
|
226
|
+
|
|
227
|
+
`generateEvidencePacket` builds a sealed, redacted, checksummed evidence packet for one subject. Your
|
|
228
|
+
application supplies a read port over its own store; the generator persists nothing, so storing the
|
|
229
|
+
returned envelope is up to you.
|
|
230
|
+
|
|
231
|
+
```ts
|
|
232
|
+
import {
|
|
233
|
+
generateEvidencePacket,
|
|
234
|
+
verifyEvidencePacketChecksum,
|
|
235
|
+
type EvidencePacketReadPort,
|
|
236
|
+
} from "@fabricorg/platform/evidence";
|
|
237
|
+
|
|
238
|
+
const port: EvidencePacketReadPort = {
|
|
239
|
+
listEvents: ({ tenantId, spaceId, subjects }) => store.eventsFor(tenantId, spaceId, subjects),
|
|
240
|
+
// Optional. Each absent reader leaves its section empty.
|
|
241
|
+
resolveRelatedSubjects: (subject, scope) => store.relatedSubjects(subject, scope),
|
|
242
|
+
listActionInvocations: (scope, ids) => store.actionInvocations(scope, ids),
|
|
243
|
+
listPolicyEvaluations: (scope, invocationIds) => store.policyEvaluations(scope, invocationIds),
|
|
244
|
+
listAdapterInvocations: (scope, invocationIds) => store.adapterInvocations(scope, invocationIds),
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
const packet = await generateEvidencePacket(
|
|
248
|
+
{ subject: { subjectType: "Case", subjectId: caseId }, policyVersion: "2026-06" },
|
|
249
|
+
{ tenantId, spaceId, actorId, correlationId, db, now: new Date() },
|
|
250
|
+
{
|
|
251
|
+
port,
|
|
252
|
+
anonymizationPolicy: { fields: [{ match: "key", value: "email", method: "tokenize" }] },
|
|
253
|
+
},
|
|
254
|
+
);
|
|
255
|
+
await store.savePacket(packet);
|
|
256
|
+
verifyEvidencePacketChecksum(packet); // true until the payload is altered
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
What the tests in `evidence/generator.test.ts` cover:
|
|
260
|
+
|
|
261
|
+
- **Scope fails closed.** A record from the port whose `tenantId` or `spaceId` differs from the context
|
|
262
|
+
throws `EvidencePacketGenerationError` with code `EVIDENCE_SCOPE_MISMATCH`, in every section. The
|
|
263
|
+
check runs on the record as returned and again after normalization. A dependent record for an action
|
|
264
|
+
invocation the generator did not request throws `EVIDENCE_UNREQUESTED_RECORD`. An error thrown by the
|
|
265
|
+
port propagates, and no packet is returned.
|
|
266
|
+
- **What the port decides.** The generator does not check an event's subject against the requested
|
|
267
|
+
subjects: a port may return events recorded against the primary subject's own action invocations
|
|
268
|
+
under another subject. Selecting the right events is the port's job.
|
|
269
|
+
- **Record shape.** Records must have a non-empty `id`, and events their documented fields.
|
|
270
|
+
`occurredAt` and `recordedAt` must be a `Date` or an ISO 8601 / PostgreSQL ISO date-time string
|
|
271
|
+
with an explicit zone: `YYYY-MM-DD` (or a signed 4–6 digit year), `T` or a space,
|
|
272
|
+
`HH:MM[:SS[.fraction]]`, an optional single space, then `Z`, `+HH`, `+HH:MM` or `+HHMM` with offset
|
|
273
|
+
hours up to 14 (for example `2026-05-01 10:20:30.123456+00`). The string is parsed field by
|
|
274
|
+
field without `Date`'s lenient parser, and an out-of-range field (February 30, hour 24, offset
|
|
275
|
+
`+15`) or any other format throws `EVIDENCE_INVALID_RECORD` instead of being rolled over or
|
|
276
|
+
guessed. A zone-less string such as `"2026-05-01 00:00:00"` throws too, because reading it would
|
|
277
|
+
depend on the host's time zone. Timestamps are kept to millisecond precision, truncated
|
|
278
|
+
(`.123999+00` becomes `.123Z`). Values JSON cannot represent faithfully (NaN, Infinity, invalid
|
|
279
|
+
`Date` objects, bigint, Maps, class instances without `toJSON`, an own `__proto__` key) throw
|
|
280
|
+
`EVIDENCE_INVALID_RECORD` instead of being changed or dropped. A cyclic value (including a `toJSON`
|
|
281
|
+
that returns itself) is not detected: it fails with a `RangeError` rather than an
|
|
282
|
+
`EvidencePacketGenerationError`, and no packet is produced. Fields inherited from a prototype are not read.
|
|
283
|
+
- **Subjects.** The primary subject comes first, then the related subjects, de-duplicated and sorted.
|
|
284
|
+
Related subjects are used only when the port has `resolveRelatedSubjects` and `includeRelated` is not
|
|
285
|
+
`false`; `payload.includeRelated` records whether that lookup ran.
|
|
286
|
+
- **Deterministic payload.** Events are sorted by the `occurredAt` instant, then the `recordedAt`
|
|
287
|
+
instant, `sequence` and `id`; the other sections by `id`. Every `Date` becomes an ISO string, and
|
|
288
|
+
event timestamps given as zoned strings are rewritten as the ISO string of the same instant; other
|
|
289
|
+
strings are kept as given. So with the same `now`, packet id and options, the same records give the
|
|
290
|
+
same payload and checksum when they come back in a different order, with keys in a different order,
|
|
291
|
+
or with an event timestamp as a `Date` or as a zoned string (the tests use `Z`, `+00`, `+00:00` and
|
|
292
|
+
`+0200`) for the same millisecond.
|
|
293
|
+
- **Redaction before hashing.** The payload runs through the privacy `AnonymizationEngine` with data
|
|
294
|
+
class `"evidence_packet"`, and its `AnonymizationMetadata` is stored as `payload.redaction` (`jobId`
|
|
295
|
+
defaults to the packet id, `reason` to `"evidence_packet_generation"`, `redactedAt` to the generation
|
|
296
|
+
time). In the tests, a redacted value does not appear anywhere in the serialized envelope, because the
|
|
297
|
+
envelope's subjects and reference metadata are read from the redacted payload.
|
|
298
|
+
- The engine passes any `_privacy` key through without applying the policy, because that is where it
|
|
299
|
+
keeps its own metadata. The generator therefore accepts a `_privacy` value from the port, at any
|
|
300
|
+
depth, only when it has exactly the `AnonymizationMetadata` shape the engine writes (for example on
|
|
301
|
+
an event already anonymized with `anonymizeAssetEventPayload`); any other `_privacy` value throws
|
|
302
|
+
`EVIDENCE_INVALID_RECORD`. Fixed-format fields are pinned: `redactedAt` (top level and per field)
|
|
303
|
+
must be an exact `toISOString()` value and `beforeChecksum` 64 lowercase hex characters. The
|
|
304
|
+
free-text fields (`reason`, `jobId`, `policyVersion`, each `fieldPath`, and the `preservedSemantics`
|
|
305
|
+
entries) are only type-checked as strings, not inspected, so a port that writes sensitive text into
|
|
306
|
+
them would carry it into the packet.
|
|
307
|
+
- Record `id`s must come through redaction unchanged; a policy that removes or changes one (for
|
|
308
|
+
example `mask` on `id`) throws `EVIDENCE_IDENTIFIER_REDACTED`. Subject ids may be transformed, for
|
|
309
|
+
example tokenized, but must remain non-empty strings.
|
|
310
|
+
- The engine scopes each token to its field path, including array positions. The same raw value
|
|
311
|
+
therefore gets a different token in each place it appears, within one packet and across packets
|
|
312
|
+
(a related subject's token changes when its position in `subjects` does). Tokenized values cannot
|
|
313
|
+
be used to match records or to look packets up; index the raw subject outside the packet if you
|
|
314
|
+
need that. For the same reason, tokenizing `actionInvocationId` breaks the link between records
|
|
315
|
+
and their action invocations, and the generator does not detect it.
|
|
316
|
+
- The policy matches field names and paths, not values used as object keys, so a sensitive value
|
|
317
|
+
stored as a key (`{ byEmail: { "jane@example.com": 1 } }`) is not redacted.
|
|
318
|
+
- Envelope fields copied from the context (`generatedBy`, `correlationId`, `causationId`) and from
|
|
319
|
+
the input (`policyVersion`) are not redacted.
|
|
320
|
+
- The engine's field metadata keeps a SHA-256 `beforeChecksum` of each redacted value, as it does
|
|
321
|
+
elsewhere. `hash` output is an unsalted SHA-256, and every input to a `tokenize` token (tenant,
|
|
322
|
+
space, field path, policy version) is in the packet. So a low-entropy value can be guessed from
|
|
323
|
+
any of the three; keep such values out of the store or use opaque references.
|
|
324
|
+
- **Checksum.** `checksum` is the hex SHA-256 of `canonicalJson(payload)`, recorded with
|
|
325
|
+
`checksumAlgorithm: EVIDENCE_PACKET_CHECKSUM_ALGORITHM`. `verifyEvidencePacketChecksum` returns `false`
|
|
326
|
+
for a changed payload, a missing checksum or an unknown algorithm. The checksum covers the payload,
|
|
327
|
+
not the other envelope fields.
|
|
328
|
+
|
|
329
|
+
`createEvidencePacketRenderer(options)` wraps the generator as an `EvidenceRenderer` for one packet
|
|
330
|
+
type.
|
|
331
|
+
|
|
121
332
|
## Saga actions
|
|
122
333
|
|
|
123
334
|
Multi-step orchestrations declare `kind: "saga"` on the `ActionDefinition` and ship a `SagaImplementation`:
|
package/dist/actions/index.cjs
CHANGED
|
@@ -1,28 +1,28 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var chunkUGWGHTNC_cjs = require('../chunk-UGWGHTNC.cjs');
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
Object.defineProperty(exports, "ACTOR_TYPES", {
|
|
8
8
|
enumerable: true,
|
|
9
|
-
get: function () { return
|
|
9
|
+
get: function () { return chunkUGWGHTNC_cjs.ACTOR_TYPES; }
|
|
10
10
|
});
|
|
11
11
|
Object.defineProperty(exports, "assertActionId", {
|
|
12
12
|
enumerable: true,
|
|
13
|
-
get: function () { return
|
|
13
|
+
get: function () { return chunkUGWGHTNC_cjs.assertActionId; }
|
|
14
14
|
});
|
|
15
15
|
Object.defineProperty(exports, "assertActorType", {
|
|
16
16
|
enumerable: true,
|
|
17
|
-
get: function () { return
|
|
17
|
+
get: function () { return chunkUGWGHTNC_cjs.assertActorType; }
|
|
18
18
|
});
|
|
19
19
|
Object.defineProperty(exports, "createActionRegistry", {
|
|
20
20
|
enumerable: true,
|
|
21
|
-
get: function () { return
|
|
21
|
+
get: function () { return chunkUGWGHTNC_cjs.createActionRegistry; }
|
|
22
22
|
});
|
|
23
23
|
Object.defineProperty(exports, "isActorType", {
|
|
24
24
|
enumerable: true,
|
|
25
|
-
get: function () { return
|
|
25
|
+
get: function () { return chunkUGWGHTNC_cjs.isActorType; }
|
|
26
26
|
});
|
|
27
27
|
//# sourceMappingURL=index.cjs.map
|
|
28
28
|
//# sourceMappingURL=index.cjs.map
|
package/dist/actions/index.d.cts
CHANGED
|
@@ -205,8 +205,15 @@ interface SagaContext {
|
|
|
205
205
|
* Wait for a named external signal carrying a payload of `T`, with a timeout.
|
|
206
206
|
*
|
|
207
207
|
* Resolves with the payload of the first signal received, or `null` if the timeout
|
|
208
|
-
* fires first. Subsequent signals with the same name received before
|
|
209
|
-
* `awaitSignal` call are buffered
|
|
208
|
+
* fires first. Subsequent distinct signals with the same name received before
|
|
209
|
+
* another `awaitSignal` call are buffered in FIFO order so none is lost.
|
|
210
|
+
*
|
|
211
|
+
* A signal may carry a stable signal ID that the sender keeps the same on each redelivery
|
|
212
|
+
* (for example a webhook delivery ID or an approval decision ID). A runtime drops
|
|
213
|
+
* a signal whose ID it has already received for this saga, so a sender retrying
|
|
214
|
+
* after a timeout does not wake the saga twice. Signals without an ID are not
|
|
215
|
+
* deduplicated. `@fabricorg/platform-temporal` provides `createSagaSignalInbox()`
|
|
216
|
+
* for Temporal runtimes.
|
|
210
217
|
*
|
|
211
218
|
* Intended for agent-driven and webhook-driven coordination: replace polling loops
|
|
212
219
|
* with push-based wakeups while preserving the saga's deterministic replay
|
package/dist/actions/index.d.ts
CHANGED
|
@@ -205,8 +205,15 @@ interface SagaContext {
|
|
|
205
205
|
* Wait for a named external signal carrying a payload of `T`, with a timeout.
|
|
206
206
|
*
|
|
207
207
|
* Resolves with the payload of the first signal received, or `null` if the timeout
|
|
208
|
-
* fires first. Subsequent signals with the same name received before
|
|
209
|
-
* `awaitSignal` call are buffered
|
|
208
|
+
* fires first. Subsequent distinct signals with the same name received before
|
|
209
|
+
* another `awaitSignal` call are buffered in FIFO order so none is lost.
|
|
210
|
+
*
|
|
211
|
+
* A signal may carry a stable signal ID that the sender keeps the same on each redelivery
|
|
212
|
+
* (for example a webhook delivery ID or an approval decision ID). A runtime drops
|
|
213
|
+
* a signal whose ID it has already received for this saga, so a sender retrying
|
|
214
|
+
* after a timeout does not wake the saga twice. Signals without an ID are not
|
|
215
|
+
* deduplicated. `@fabricorg/platform-temporal` provides `createSagaSignalInbox()`
|
|
216
|
+
* for Temporal runtimes.
|
|
210
217
|
*
|
|
211
218
|
* Intended for agent-driven and webhook-driven coordination: replace polling loops
|
|
212
219
|
* with push-based wakeups while preserving the saga's deterministic replay
|
package/dist/actions/index.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { ACTOR_TYPES, assertActionId, assertActorType, createActionRegistry, isActorType } from '../chunk-
|
|
1
|
+
export { ACTOR_TYPES, assertActionId, assertActorType, createActionRegistry, isActorType } from '../chunk-PIDZQAEL.js';
|
|
2
2
|
//# sourceMappingURL=index.js.map
|
|
3
3
|
//# sourceMappingURL=index.js.map
|
package/dist/adapters/index.cjs
CHANGED
|
@@ -1,40 +1,64 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var chunkIF6NJORI_cjs = require('../chunk-IF6NJORI.cjs');
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
|
|
7
|
+
Object.defineProperty(exports, "AdapterCircuitBreakerRegistry", {
|
|
8
|
+
enumerable: true,
|
|
9
|
+
get: function () { return chunkIF6NJORI_cjs.AdapterCircuitBreakerRegistry; }
|
|
10
|
+
});
|
|
11
|
+
Object.defineProperty(exports, "AdapterCircuitOpenError", {
|
|
12
|
+
enumerable: true,
|
|
13
|
+
get: function () { return chunkIF6NJORI_cjs.AdapterCircuitOpenError; }
|
|
14
|
+
});
|
|
7
15
|
Object.defineProperty(exports, "AdapterRegistry", {
|
|
8
16
|
enumerable: true,
|
|
9
|
-
get: function () { return
|
|
17
|
+
get: function () { return chunkIF6NJORI_cjs.AdapterRegistry; }
|
|
18
|
+
});
|
|
19
|
+
Object.defineProperty(exports, "InMemoryAdapterCircuitBreaker", {
|
|
20
|
+
enumerable: true,
|
|
21
|
+
get: function () { return chunkIF6NJORI_cjs.InMemoryAdapterCircuitBreaker; }
|
|
10
22
|
});
|
|
11
23
|
Object.defineProperty(exports, "MissingAdapterRegistrationError", {
|
|
12
24
|
enumerable: true,
|
|
13
|
-
get: function () { return
|
|
25
|
+
get: function () { return chunkIF6NJORI_cjs.MissingAdapterRegistrationError; }
|
|
26
|
+
});
|
|
27
|
+
Object.defineProperty(exports, "assertAdapterCircuitBreakerConfig", {
|
|
28
|
+
enumerable: true,
|
|
29
|
+
get: function () { return chunkIF6NJORI_cjs.assertAdapterCircuitBreakerConfig; }
|
|
14
30
|
});
|
|
15
31
|
Object.defineProperty(exports, "assertAdapterOutcomeConsistency", {
|
|
16
32
|
enumerable: true,
|
|
17
|
-
get: function () { return
|
|
33
|
+
get: function () { return chunkIF6NJORI_cjs.assertAdapterOutcomeConsistency; }
|
|
18
34
|
});
|
|
19
35
|
Object.defineProperty(exports, "classifyAdapterOutcome", {
|
|
20
36
|
enumerable: true,
|
|
21
|
-
get: function () { return
|
|
37
|
+
get: function () { return chunkIF6NJORI_cjs.classifyAdapterOutcome; }
|
|
38
|
+
});
|
|
39
|
+
Object.defineProperty(exports, "classifyCircuitBreakerOutcome", {
|
|
40
|
+
enumerable: true,
|
|
41
|
+
get: function () { return chunkIF6NJORI_cjs.classifyCircuitBreakerOutcome; }
|
|
22
42
|
});
|
|
23
43
|
Object.defineProperty(exports, "executeWithAdapterRetry", {
|
|
24
44
|
enumerable: true,
|
|
25
|
-
get: function () { return
|
|
45
|
+
get: function () { return chunkIF6NJORI_cjs.executeWithAdapterRetry; }
|
|
26
46
|
});
|
|
27
47
|
Object.defineProperty(exports, "getRetryDelayMs", {
|
|
28
48
|
enumerable: true,
|
|
29
|
-
get: function () { return
|
|
49
|
+
get: function () { return chunkIF6NJORI_cjs.getRetryDelayMs; }
|
|
50
|
+
});
|
|
51
|
+
Object.defineProperty(exports, "isAdapterCircuitOpenError", {
|
|
52
|
+
enumerable: true,
|
|
53
|
+
get: function () { return chunkIF6NJORI_cjs.isAdapterCircuitOpenError; }
|
|
30
54
|
});
|
|
31
55
|
Object.defineProperty(exports, "resolveAdapterRetryPolicy", {
|
|
32
56
|
enumerable: true,
|
|
33
|
-
get: function () { return
|
|
57
|
+
get: function () { return chunkIF6NJORI_cjs.resolveAdapterRetryPolicy; }
|
|
34
58
|
});
|
|
35
59
|
Object.defineProperty(exports, "shouldRetryAdapterOutcome", {
|
|
36
60
|
enumerable: true,
|
|
37
|
-
get: function () { return
|
|
61
|
+
get: function () { return chunkIF6NJORI_cjs.shouldRetryAdapterOutcome; }
|
|
38
62
|
});
|
|
39
63
|
//# sourceMappingURL=index.cjs.map
|
|
40
64
|
//# sourceMappingURL=index.cjs.map
|