instar 1.3.520 → 1.3.522
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/dist/commands/server.d.ts.map +1 -1
- package/dist/commands/server.js +119 -2
- package/dist/commands/server.js.map +1 -1
- package/dist/config/ConfigDefaults.d.ts.map +1 -1
- package/dist/config/ConfigDefaults.js +32 -0
- package/dist/config/ConfigDefaults.js.map +1 -1
- package/dist/core/ReplicatedRecordEnvelope.d.ts +380 -0
- package/dist/core/ReplicatedRecordEnvelope.d.ts.map +1 -0
- package/dist/core/ReplicatedRecordEnvelope.js +464 -0
- package/dist/core/ReplicatedRecordEnvelope.js.map +1 -0
- package/dist/core/devGatedFeatures.d.ts.map +1 -1
- package/dist/core/devGatedFeatures.js +6 -0
- package/dist/core/devGatedFeatures.js.map +1 -1
- package/dist/core/stateSyncConfig.d.ts +94 -0
- package/dist/core/stateSyncConfig.d.ts.map +1 -0
- package/dist/core/stateSyncConfig.js +126 -0
- package/dist/core/stateSyncConfig.js.map +1 -0
- package/dist/core/types.d.ts +76 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/monitoring/OrphanedWorkSentinel.d.ts +148 -0
- package/dist/monitoring/OrphanedWorkSentinel.d.ts.map +1 -0
- package/dist/monitoring/OrphanedWorkSentinel.js +177 -0
- package/dist/monitoring/OrphanedWorkSentinel.js.map +1 -0
- package/dist/monitoring/guardManifest.d.ts.map +1 -1
- package/dist/monitoring/guardManifest.js +14 -0
- package/dist/monitoring/guardManifest.js.map +1 -1
- package/dist/monitoring/orphanedWorkGit.d.ts +30 -0
- package/dist/monitoring/orphanedWorkGit.d.ts.map +1 -0
- package/dist/monitoring/orphanedWorkGit.js +124 -0
- package/dist/monitoring/orphanedWorkGit.js.map +1 -0
- package/dist/server/AgentServer.d.ts +3 -0
- package/dist/server/AgentServer.d.ts.map +1 -1
- package/dist/server/AgentServer.js +1 -0
- package/dist/server/AgentServer.js.map +1 -1
- package/dist/server/CapabilityIndex.d.ts.map +1 -1
- package/dist/server/CapabilityIndex.js +1 -0
- package/dist/server/CapabilityIndex.js.map +1 -1
- package/dist/server/routes.d.ts +3 -0
- package/dist/server/routes.d.ts.map +1 -1
- package/dist/server/routes.js +11 -0
- package/dist/server/routes.js.map +1 -1
- package/package.json +1 -1
- package/src/data/builtin-manifest.json +47 -47
- package/upgrades/1.3.521.md +69 -0
- package/upgrades/1.3.522.md +25 -0
- package/upgrades/side-effects/multi-machine-replicated-store-foundation-step2-journal-kind.md +104 -0
- package/upgrades/side-effects/orphaned-work-sentinel.md +40 -0
- package/upgrades/1.3.520.md +0 -50
|
@@ -0,0 +1,464 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ReplicatedRecordEnvelope — the GENERIC substrate for replicated-store journal
|
|
3
|
+
* kinds (WS2 replicated-store foundation, Component 2 / Build-order step 2).
|
|
4
|
+
*
|
|
5
|
+
* Spec: docs/specs/multi-machine-replicated-store-foundation.md §4 (the
|
|
6
|
+
* replicated-record envelope, flag-gated + flag-coherence-gated emission), §10
|
|
7
|
+
* (config / advert / awareness), §13 build-order item 2, §14 (safety posture).
|
|
8
|
+
*
|
|
9
|
+
* This module is the REUSABLE machinery the concrete stores (WS2.1 preferences,
|
|
10
|
+
* relationships, learnings, …) layer a kind onto. It adds NO concrete store
|
|
11
|
+
* kind itself — the registry ships EMPTY. It provides exactly four pieces:
|
|
12
|
+
*
|
|
13
|
+
* A. The replicated-record envelope type + a parameterizable STRICT validator
|
|
14
|
+
* (`validateReplicatedEnvelope`) that mirrors the CoherenceJournal typed-
|
|
15
|
+
* schema discipline: reject free text, drop unknown fields (counted), jail
|
|
16
|
+
* any path-shaped field, validate `hlc` + (when present) `observed` as
|
|
17
|
+
* well-formed HlcTimestamps. ABSENT `observed` is LEGAL ⇒ "no prior
|
|
18
|
+
* witness" ⇒ flag-on-conflict (the safe direction, §4 / §7.2). The validated
|
|
19
|
+
* envelope fields are AUTHORITATIVE: a store schema can never override a
|
|
20
|
+
* reserved envelope field on the reconstructed `data` (reserved keys the
|
|
21
|
+
* store returns are stripped + counted), and the path-jail is REUSABLE
|
|
22
|
+
* machinery for store fields too — a store declares `pathSensitiveFields`
|
|
23
|
+
* for auto-jailing, or calls the exported `jailStoreStringField` helper.
|
|
24
|
+
* Reserved-key collisions in a store's `knownFields` are rejected at
|
|
25
|
+
* registration time (a wiring-time programmer error), not trusted to a
|
|
26
|
+
* store's self-check.
|
|
27
|
+
*
|
|
28
|
+
* B. A registry of replicated kinds (`ReplicatedKindRegistry`) — the generic
|
|
29
|
+
* registration mechanism a concrete store calls to register its kind +
|
|
30
|
+
* store-specific field schema. Initially empty (no concrete kind).
|
|
31
|
+
*
|
|
32
|
+
* C. Flag-gated emission (`isStoreEmissionEnabled`) — a replicated kind is
|
|
33
|
+
* emitted ONLY when `multiMachine.stateSync.<store>.enabled` is true
|
|
34
|
+
* (default FALSE). When off, strict no-op.
|
|
35
|
+
*
|
|
36
|
+
* D. Flag-coherence-gated emission (`shouldEmitToPeer`,
|
|
37
|
+
* `checkPoolFlagCoherence`) — a replicated kind is exchanged with a peer ONLY
|
|
38
|
+
* when that peer's `seamlessnessFlags` advertises the matching
|
|
39
|
+
* `stateSync.<store>` capability. The applier "silently drops unknown
|
|
40
|
+
* kinds" (forward-compat). The per-peer decision is correct for N peers
|
|
41
|
+
* (never assumes exactly 2); the boot-time pool-flag-coherence check
|
|
42
|
+
* coalesces ALL mixed-flag peers into ONE surfaced result.
|
|
43
|
+
*
|
|
44
|
+
* TRANSPORT IS PULL — `shouldEmitToPeer` is intentionally UNWIRED in Step 2.
|
|
45
|
+
* The real journal-sync transport is RECEIVER-DRIVEN PULL: `PeerPresencePuller`
|
|
46
|
+
* iterates the SENDER's advert (`CoherenceJournal.getOwnAdvert()`, over the static
|
|
47
|
+
* `JOURNAL_KINDS` const) and pulls each kind it is behind on; serve + apply both
|
|
48
|
+
* gate on `JOURNAL_KINDS`. There is NO push-forward step. The named "emit a new
|
|
49
|
+
* kind to an OLD peer → silently dropped" mode therefore manifests as "an old
|
|
50
|
+
* peer NEVER PULLS a kind absent from its own JOURNAL_KINDS" (nothing requested ⇒
|
|
51
|
+
* nothing dropped). On this PULL transport the flag-coherence gate's role is the
|
|
52
|
+
* boot-time pool surface (`checkPoolFlagCoherence`, wired in server.ts) PLUS the
|
|
53
|
+
* serve/pull-decision gate the WS2.1 consumer PR will consult once a concrete
|
|
54
|
+
* kind exists — that is where `shouldEmitToPeer` will be called. In Step 2 it is
|
|
55
|
+
* a pure decision with no concrete kind to serve, hence intentionally unwired
|
|
56
|
+
* (NOT an unintegrated push gate).
|
|
57
|
+
*
|
|
58
|
+
* DUAL-REGISTRY COUPLING — a replicated kind MUST be added to BOTH registries.
|
|
59
|
+
* The new `ReplicatedKindRegistry` (read by the gate + the stateSyncReceive
|
|
60
|
+
* advert) and the static `JOURNAL_KINDS` (in CoherenceJournal.ts, gating serve +
|
|
61
|
+
* apply + enumerated by getOwnAdvert) are TWO parallel kind registries. A WS2.1
|
|
62
|
+
* PR that registers into `ReplicatedKindRegistry` but forgets `JOURNAL_KINDS`
|
|
63
|
+
* yields a store that advertises receive=true yet serves/applies/pulls nothing —
|
|
64
|
+
* a SILENT no-replication. The wiring-integrity test asserts every replicated
|
|
65
|
+
* kind is present in BOTH (a CI ratchet, not a memory item).
|
|
66
|
+
*
|
|
67
|
+
* SAFETY POSTURE (§14): this is MECHANISM, dark by default. NO gate here blocks
|
|
68
|
+
* a user-initiated action. The only refusals are at the RECEIVE door (the
|
|
69
|
+
* validator rejects malformed data) and the EMISSION door (don't forward a kind
|
|
70
|
+
* to a non-advertising peer). Both protect data; neither blocks the user. A
|
|
71
|
+
* single-machine install (no advertising peers) is a strict no-op — emission is
|
|
72
|
+
* gated on having a peer advertising the matching flag.
|
|
73
|
+
*
|
|
74
|
+
* Purity: this module imports ONLY the HLC primitive + node:path (for the
|
|
75
|
+
* path-shape jail's separator detection). No fs, no network, no Date. Every
|
|
76
|
+
* function is a pure function of its inputs.
|
|
77
|
+
*/
|
|
78
|
+
import path from 'node:path';
|
|
79
|
+
import { coerceHlc } from './HybridLogicalClock.js';
|
|
80
|
+
/** The envelope's own field names — the union of all reserved keys a store may
|
|
81
|
+
* not reuse for a store-specific field. Exported AND ENFORCED:
|
|
82
|
+
* ReplicatedKindRegistry.register() throws if a schema's `knownFields` claims any
|
|
83
|
+
* of these, and validateReplicatedEnvelope strips any reserved key a store's
|
|
84
|
+
* validate() returns (counting it as a dropped field) so the validated envelope
|
|
85
|
+
* fields are the sole authority for the reconstructed `data`. */
|
|
86
|
+
export const RESERVED_ENVELOPE_FIELDS = Object.freeze([
|
|
87
|
+
'recordKey',
|
|
88
|
+
'hlc',
|
|
89
|
+
'op',
|
|
90
|
+
'origin',
|
|
91
|
+
'observed',
|
|
92
|
+
]);
|
|
93
|
+
/** Caps (mirror the CoherenceJournal length-cap discipline — a value over the
|
|
94
|
+
* cap is rejected, never truncated-and-accepted, so a record is whole or not). */
|
|
95
|
+
export const MAX_RECORD_KEY_LENGTH = 512;
|
|
96
|
+
export const MAX_ORIGIN_LENGTH = 256;
|
|
97
|
+
/**
|
|
98
|
+
* Reusable store-field path-jail (the §4 "jail any path-shaped field" rule, made
|
|
99
|
+
* reusable machinery instead of per-store re-implementation). Returns the value
|
|
100
|
+
* unchanged when it is a NON-path-shaped string; returns null (and bumps the
|
|
101
|
+
* shared jail counter) when it is path-shaped, so a store's validate() can reject
|
|
102
|
+
* the whole record. A non-string is returned unchanged (the store does its own
|
|
103
|
+
* type check) — this helper enforces the PATH discipline only.
|
|
104
|
+
*
|
|
105
|
+
* Usage inside a store's validate():
|
|
106
|
+
* const fp = jailStoreStringField(raw.filePath, ctx);
|
|
107
|
+
* if (fp === null) return null; // path-shaped → reject whole record
|
|
108
|
+
*/
|
|
109
|
+
export function jailStoreStringField(value, ctx) {
|
|
110
|
+
if (typeof value === 'string' && isPathShaped(value)) {
|
|
111
|
+
ctx.countJailReject();
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
return value;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Is a string PATH-SHAPED? (The §4 "jail any path-shaped field" rule, applied
|
|
118
|
+
* to the envelope's string fields — recordKey/origin — so a path can never
|
|
119
|
+
* smuggle in as a primary key.) A value is path-shaped if it contains a path
|
|
120
|
+
* separator, a `..` traversal segment, or is an absolute path. This is a
|
|
121
|
+
* STRUCTURAL reject (the field has no business holding a path), not a
|
|
122
|
+
* filesystem jail — the envelope carries identifiers, never artifact paths.
|
|
123
|
+
*/
|
|
124
|
+
export function isPathShaped(value) {
|
|
125
|
+
if (path.isAbsolute(value))
|
|
126
|
+
return true;
|
|
127
|
+
if (value.includes('/') || value.includes('\\'))
|
|
128
|
+
return true;
|
|
129
|
+
// A bare `..` or any `..`-as-segment.
|
|
130
|
+
const segments = value.split(/[/\\]/);
|
|
131
|
+
if (segments.some((s) => s === '..' || s === '.'))
|
|
132
|
+
return true;
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
/** Validate one HLC field, returning the narrowed value or null (never throws). */
|
|
136
|
+
function tryCoerceHlc(raw) {
|
|
137
|
+
try {
|
|
138
|
+
return coerceHlc(raw);
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
// @silent-fallback-ok: coerceHlc throws on malformed input by contract; here
|
|
142
|
+
// we are deliberately converting that into a typed rejection (null) so the
|
|
143
|
+
// validator can bump a counter and reject the WHOLE record — a malformed hlc
|
|
144
|
+
// is a schema reject, never a silent default. The reject is counted + surfaced
|
|
145
|
+
// by the caller via EnvelopeValidationCounters; nothing is swallowed.
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* The strict envelope validator (§4). Mirrors the CoherenceJournal typed-schema
|
|
151
|
+
* discipline:
|
|
152
|
+
* - rejects a non-object,
|
|
153
|
+
* - validates recordKey (non-empty, length-capped, NOT path-shaped → jail),
|
|
154
|
+
* - validates hlc as a well-formed HlcTimestamp,
|
|
155
|
+
* - validates op ∈ {'put','delete'},
|
|
156
|
+
* - validates origin (non-empty, length-capped, NOT path-shaped),
|
|
157
|
+
* - validates observed when present (well-formed HlcTimestamp); ABSENT is LEGAL,
|
|
158
|
+
* - delegates store-specific fields to the supplied StoreFieldSchema,
|
|
159
|
+
* - DROPS every field not in (RESERVED_ENVELOPE_FIELDS ∪ schema.knownFields),
|
|
160
|
+
* counting each drop.
|
|
161
|
+
*
|
|
162
|
+
* Returns a typed result. NEVER throws on bad DATA — it bumps a counter and
|
|
163
|
+
* returns `{ ok: false }`. The reconstructed `data` object on success is the
|
|
164
|
+
* validated store fields + the validated envelope fields, in a deterministic
|
|
165
|
+
* shape (only known fields, observed omitted when absent), ready to serialize as
|
|
166
|
+
* the journal entry's `data`. The validated envelope fields are AUTHORITATIVE:
|
|
167
|
+
* any RESERVED_ENVELOPE_FIELDS key the store's validate() returns is stripped
|
|
168
|
+
* (and counted as a dropped field) before merge, so a store can NEVER override a
|
|
169
|
+
* load-bearing envelope field (op/recordKey/hlc/origin/observed) on `data` — the
|
|
170
|
+
* on-disk `data.op/recordKey/hlc/origin/observed` always equal the validated
|
|
171
|
+
* `envelope.*`. `storeFields` on the result is likewise reserved-key-stripped.
|
|
172
|
+
*/
|
|
173
|
+
export function validateReplicatedEnvelope(raw, schema, counters) {
|
|
174
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
|
|
175
|
+
counters.bumpSchemaReject();
|
|
176
|
+
return { ok: false, reason: 'not-an-object' };
|
|
177
|
+
}
|
|
178
|
+
const obj = raw;
|
|
179
|
+
// recordKey — non-empty, length-capped, not path-shaped (jailed).
|
|
180
|
+
const recordKey = obj.recordKey;
|
|
181
|
+
if (typeof recordKey !== 'string' || recordKey.length === 0 || recordKey.length > MAX_RECORD_KEY_LENGTH) {
|
|
182
|
+
counters.bumpSchemaReject();
|
|
183
|
+
return { ok: false, reason: 'bad-record-key' };
|
|
184
|
+
}
|
|
185
|
+
if (isPathShaped(recordKey)) {
|
|
186
|
+
counters.bumpJailReject();
|
|
187
|
+
counters.bumpSchemaReject();
|
|
188
|
+
return { ok: false, reason: 'record-key-path-shaped' };
|
|
189
|
+
}
|
|
190
|
+
// hlc — well-formed HlcTimestamp (required).
|
|
191
|
+
const hlc = tryCoerceHlc(obj.hlc);
|
|
192
|
+
if (hlc === null) {
|
|
193
|
+
counters.bumpSchemaReject();
|
|
194
|
+
return { ok: false, reason: 'bad-hlc' };
|
|
195
|
+
}
|
|
196
|
+
// op — enum.
|
|
197
|
+
const op = obj.op;
|
|
198
|
+
if (op !== 'put' && op !== 'delete') {
|
|
199
|
+
counters.bumpSchemaReject();
|
|
200
|
+
return { ok: false, reason: 'bad-op' };
|
|
201
|
+
}
|
|
202
|
+
// origin — non-empty, length-capped, not path-shaped.
|
|
203
|
+
const origin = obj.origin;
|
|
204
|
+
if (typeof origin !== 'string' || origin.length === 0 || origin.length > MAX_ORIGIN_LENGTH) {
|
|
205
|
+
counters.bumpSchemaReject();
|
|
206
|
+
return { ok: false, reason: 'bad-origin' };
|
|
207
|
+
}
|
|
208
|
+
if (isPathShaped(origin)) {
|
|
209
|
+
counters.bumpJailReject();
|
|
210
|
+
counters.bumpSchemaReject();
|
|
211
|
+
return { ok: false, reason: 'bad-origin' };
|
|
212
|
+
}
|
|
213
|
+
// observed — well-formed HlcTimestamp WHEN PRESENT; absent is legal.
|
|
214
|
+
let observed;
|
|
215
|
+
if (obj.observed !== undefined && obj.observed !== null) {
|
|
216
|
+
const witness = tryCoerceHlc(obj.observed);
|
|
217
|
+
if (witness === null) {
|
|
218
|
+
counters.bumpSchemaReject();
|
|
219
|
+
return { ok: false, reason: 'bad-observed' };
|
|
220
|
+
}
|
|
221
|
+
observed = witness;
|
|
222
|
+
}
|
|
223
|
+
// Auto-jail every DECLARED path-sensitive store field BEFORE the store's own
|
|
224
|
+
// validate() — the §4 path-jail discipline applied to store fields as reusable
|
|
225
|
+
// machinery (Structure > Willpower), not left to each store to re-implement.
|
|
226
|
+
// A path-shaped declared field rejects the whole record (jail counter bumped),
|
|
227
|
+
// exactly as recordKey/origin do.
|
|
228
|
+
if (schema.pathSensitiveFields) {
|
|
229
|
+
for (const field of schema.pathSensitiveFields) {
|
|
230
|
+
const v = obj[field];
|
|
231
|
+
if (typeof v === 'string' && isPathShaped(v)) {
|
|
232
|
+
counters.bumpJailReject();
|
|
233
|
+
counters.bumpSchemaReject();
|
|
234
|
+
return { ok: false, reason: 'store-field-path-shaped' };
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
// Store-specific fields. The store validator may reject the whole record. The
|
|
239
|
+
// context carries BOTH counters so a store-side drop (countDroppedField) and a
|
|
240
|
+
// store-side imperative jail (countJailReject, e.g. via jailStoreStringField)
|
|
241
|
+
// feed the SAME counters CoherenceJournal surfaces — uniform accounting.
|
|
242
|
+
let storeRejected = false;
|
|
243
|
+
const storeCtx = {
|
|
244
|
+
countDroppedField: () => counters.bumpDroppedField(),
|
|
245
|
+
countJailReject: () => counters.bumpJailReject(),
|
|
246
|
+
};
|
|
247
|
+
const storeFields = schema.validate(obj, storeCtx);
|
|
248
|
+
if (storeFields === null) {
|
|
249
|
+
storeRejected = true;
|
|
250
|
+
}
|
|
251
|
+
if (storeRejected || storeFields === null) {
|
|
252
|
+
counters.bumpSchemaReject();
|
|
253
|
+
return { ok: false, reason: 'store-schema-rejected' };
|
|
254
|
+
}
|
|
255
|
+
// Count dropped unknown fields across the WHOLE object: any key not in the
|
|
256
|
+
// reserved envelope fields and not owned by the store schema is dropped.
|
|
257
|
+
const known = new Set([...RESERVED_ENVELOPE_FIELDS, ...schema.knownFields]);
|
|
258
|
+
for (const k of Object.keys(obj)) {
|
|
259
|
+
if (!known.has(k))
|
|
260
|
+
counters.bumpDroppedField();
|
|
261
|
+
}
|
|
262
|
+
const envelope = {
|
|
263
|
+
recordKey,
|
|
264
|
+
hlc,
|
|
265
|
+
op,
|
|
266
|
+
origin,
|
|
267
|
+
...(observed !== undefined ? { observed } : {}),
|
|
268
|
+
};
|
|
269
|
+
// Defense-in-depth: a store schema's validate() returns the store-specific
|
|
270
|
+
// portion, but a buggy/hostile store COULD echo a RESERVED envelope key (op,
|
|
271
|
+
// recordKey, hlc, origin, observed) in its returned object. Those keys are
|
|
272
|
+
// load-bearing — op decides put/delete tombstone semantics, hlc is the merge
|
|
273
|
+
// total-order, recordKey is the primary key — and the store's copy is the
|
|
274
|
+
// UN-validated, UN-jailed value. We therefore STRIP every reserved key from
|
|
275
|
+
// the store's returned fields (counting each as a dropped field, the same as
|
|
276
|
+
// an unknown field) BEFORE merging, so the VALIDATED envelope fields are the
|
|
277
|
+
// sole authority for the reconstructed `data`. This makes the reserved keys
|
|
278
|
+
// un-overridable by construction — not "spread the envelope last and hope".
|
|
279
|
+
const reserved = new Set(RESERVED_ENVELOPE_FIELDS);
|
|
280
|
+
const storeFieldsSafe = {};
|
|
281
|
+
for (const [k, v] of Object.entries(storeFields)) {
|
|
282
|
+
if (reserved.has(k)) {
|
|
283
|
+
counters.bumpDroppedField();
|
|
284
|
+
continue;
|
|
285
|
+
}
|
|
286
|
+
storeFieldsSafe[k] = v;
|
|
287
|
+
}
|
|
288
|
+
// The reconstructed data shape: validated store fields (reserved keys stripped)
|
|
289
|
+
// FIRST, then the validated envelope fields LAST so a store can never clobber a
|
|
290
|
+
// load-bearing envelope field on a key collision. observed omitted when absent
|
|
291
|
+
// so the on-disk shape is deterministic.
|
|
292
|
+
const data = {
|
|
293
|
+
...storeFieldsSafe,
|
|
294
|
+
recordKey,
|
|
295
|
+
hlc,
|
|
296
|
+
op,
|
|
297
|
+
origin,
|
|
298
|
+
...(observed !== undefined ? { observed } : {}),
|
|
299
|
+
};
|
|
300
|
+
return { ok: true, envelope, storeFields: storeFieldsSafe, data };
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* A registry of replicated kinds. Ships EMPTY — Step 2 adds the GENERIC
|
|
304
|
+
* registration mechanism; the first concrete kind is registered by WS2.1.
|
|
305
|
+
*
|
|
306
|
+
* Why a registry (not a hardcoded union extension): the spec mandates "add the
|
|
307
|
+
* GENERIC registration mechanism" and "do NOT hardcode the future concrete
|
|
308
|
+
* kinds into the union in a way that forces them to exist now". A registry lets
|
|
309
|
+
* each store register independently at wiring time; an unregistered/never-
|
|
310
|
+
* emitted kind is simply absent on read (the reader filters by kind), and the
|
|
311
|
+
* applier already drops unknown kinds forward-compat — so an old peer that does
|
|
312
|
+
* not know a kind poisons nothing.
|
|
313
|
+
*/
|
|
314
|
+
export class ReplicatedKindRegistry {
|
|
315
|
+
byKind = new Map();
|
|
316
|
+
byStore = new Map();
|
|
317
|
+
/**
|
|
318
|
+
* Register a replicated kind. Idempotent for an IDENTICAL re-registration of
|
|
319
|
+
* the same (kind, store); THROWS on a CONFLICTING registration (a different
|
|
320
|
+
* store claiming an already-claimed kind, or a store re-registered with a
|
|
321
|
+
* different kind) — a registration conflict is a programmer error at wiring
|
|
322
|
+
* time, surfaced loudly, never a silent overwrite.
|
|
323
|
+
*/
|
|
324
|
+
register(reg) {
|
|
325
|
+
if (typeof reg.kind !== 'string' || reg.kind.length === 0) {
|
|
326
|
+
throw new Error('ReplicatedKindRegistry.register: kind must be a non-empty string');
|
|
327
|
+
}
|
|
328
|
+
if (typeof reg.store !== 'string' || reg.store.length === 0) {
|
|
329
|
+
throw new Error('ReplicatedKindRegistry.register: store must be a non-empty string');
|
|
330
|
+
}
|
|
331
|
+
if (!reg.schema || typeof reg.schema.validate !== 'function' || !Array.isArray(reg.schema.knownFields)) {
|
|
332
|
+
throw new Error(`ReplicatedKindRegistry.register: kind "${reg.kind}" needs a StoreFieldSchema with knownFields[] + validate()`);
|
|
333
|
+
}
|
|
334
|
+
// ENFORCE non-collision with the reserved envelope fields (not merely
|
|
335
|
+
// documented for the store author to self-check). A schema that claims a
|
|
336
|
+
// reserved key in knownFields would defeat the unknown-field counter for that
|
|
337
|
+
// key (it'd be treated as store-owned), letting a store quietly opt a
|
|
338
|
+
// load-bearing envelope field into its own control surface. This is a
|
|
339
|
+
// wiring-time programmer error, surfaced loudly — same class as the conflict
|
|
340
|
+
// throws below, never a silent acceptance.
|
|
341
|
+
const reservedSet = new Set(RESERVED_ENVELOPE_FIELDS);
|
|
342
|
+
const claimedReserved = reg.schema.knownFields.filter((f) => reservedSet.has(f));
|
|
343
|
+
if (claimedReserved.length > 0) {
|
|
344
|
+
throw new Error(`ReplicatedKindRegistry.register: kind "${reg.kind}" schema.knownFields claims reserved envelope field(s) [${claimedReserved.join(',')}] — reserved fields are owned by the envelope and may not be store-owned`);
|
|
345
|
+
}
|
|
346
|
+
const existingByKind = this.byKind.get(reg.kind);
|
|
347
|
+
if (existingByKind) {
|
|
348
|
+
if (existingByKind.store !== reg.store) {
|
|
349
|
+
throw new Error(`ReplicatedKindRegistry: kind "${reg.kind}" already registered for store "${existingByKind.store}", cannot reassign to "${reg.store}"`);
|
|
350
|
+
}
|
|
351
|
+
// Same (kind, store) — idempotent; keep the first registration's schema.
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
const existingByStore = this.byStore.get(reg.store);
|
|
355
|
+
if (existingByStore && existingByStore.kind !== reg.kind) {
|
|
356
|
+
throw new Error(`ReplicatedKindRegistry: store "${reg.store}" already owns kind "${existingByStore.kind}", cannot also claim "${reg.kind}"`);
|
|
357
|
+
}
|
|
358
|
+
this.byKind.set(reg.kind, reg);
|
|
359
|
+
this.byStore.set(reg.store, reg);
|
|
360
|
+
}
|
|
361
|
+
/** Is this kind a registered replicated kind? */
|
|
362
|
+
isReplicatedKind(kind) {
|
|
363
|
+
return this.byKind.has(kind);
|
|
364
|
+
}
|
|
365
|
+
/** The registration for a kind, or undefined if unregistered. */
|
|
366
|
+
getByKind(kind) {
|
|
367
|
+
return this.byKind.get(kind);
|
|
368
|
+
}
|
|
369
|
+
/** The registration for a store, or undefined if unregistered. */
|
|
370
|
+
getByStore(store) {
|
|
371
|
+
return this.byStore.get(store);
|
|
372
|
+
}
|
|
373
|
+
/** All registered kinds (the JournalKind strings). Empty until a store registers. */
|
|
374
|
+
kinds() {
|
|
375
|
+
return [...this.byKind.keys()];
|
|
376
|
+
}
|
|
377
|
+
/** All registered store keys. Empty until a store registers. */
|
|
378
|
+
stores() {
|
|
379
|
+
return [...this.byStore.keys()];
|
|
380
|
+
}
|
|
381
|
+
/** Count of registered kinds (0 in the Step-2 substrate-only state). */
|
|
382
|
+
get size() {
|
|
383
|
+
return this.byKind.size;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
/**
|
|
387
|
+
* Is emission ENABLED for a store? (§4 flag-gated emission.) True ONLY when
|
|
388
|
+
* `stateSync[store].enabled === true` (default false). When off, the store
|
|
389
|
+
* NEVER emits its kind — a strict no-op. Nullish/absent ⇒ false.
|
|
390
|
+
*/
|
|
391
|
+
export function isStoreEmissionEnabled(stores, store) {
|
|
392
|
+
return stores?.[store]?.enabled === true;
|
|
393
|
+
}
|
|
394
|
+
/** Does THIS peer advertise the matching stateSync.<store> receive capability? */
|
|
395
|
+
export function peerAdvertisesStore(peer, store) {
|
|
396
|
+
return peer.stateSyncReceive?.[store] === true;
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* The per-peer emission decision (§4 flag-coherence gating). Emit a replicated
|
|
400
|
+
* kind to a peer ONLY when:
|
|
401
|
+
* - emission is enabled for the store locally (isStoreEmissionEnabled), AND
|
|
402
|
+
* - the peer is online, AND
|
|
403
|
+
* - the peer advertises the matching stateSync.<store> capability.
|
|
404
|
+
* Withhold otherwise — emitting to a non-advertising peer is the NAMED data-loss
|
|
405
|
+
* skew mode (the applier silently drops the unknown kind). Returns a typed
|
|
406
|
+
* decision so the caller can surface a withhold.
|
|
407
|
+
*
|
|
408
|
+
* This is PER-PEER and correct for N peers — the caller iterates peers and calls
|
|
409
|
+
* this once each; there is no "exactly 2" assumption anywhere.
|
|
410
|
+
*/
|
|
411
|
+
export function shouldEmitToPeer(stores, store, peer) {
|
|
412
|
+
if (!isStoreEmissionEnabled(stores, store))
|
|
413
|
+
return { emit: false, reason: 'store-disabled' };
|
|
414
|
+
if (peer.online === false)
|
|
415
|
+
return { emit: false, reason: 'peer-offline' };
|
|
416
|
+
if (!peerAdvertisesStore(peer, store))
|
|
417
|
+
return { emit: false, reason: 'peer-not-advertising' };
|
|
418
|
+
return { emit: true, reason: 'emit' };
|
|
419
|
+
}
|
|
420
|
+
/**
|
|
421
|
+
* The boot-time pool-flag-coherence check (§4: "A boot-time pool-flag-coherence
|
|
422
|
+
* check surfaces (ONCE) any mixed state"). Iterates ALL registered replicated
|
|
423
|
+
* stores × ALL advertising peers and computes, per store, the advertising vs
|
|
424
|
+
* non-advertising online peers. A store is MIXED when it is locally enabled AND
|
|
425
|
+
* has at least one online peer that does NOT advertise it (that peer would
|
|
426
|
+
* silently drop our kind) alongside at least one online peer that DOES (or there
|
|
427
|
+
* is anyone at all to forward to). The result is ONE coalesced object — the
|
|
428
|
+
* caller surfaces it once (e.g. one Attention item / one log line), never one
|
|
429
|
+
* per peer per tick. Correct for N peers.
|
|
430
|
+
*
|
|
431
|
+
* PURE: no I/O, no surfacing — it computes the verdict; the caller decides how
|
|
432
|
+
* to surface (and dedupes across boots).
|
|
433
|
+
*/
|
|
434
|
+
export function checkPoolFlagCoherence(registry, stores, peers) {
|
|
435
|
+
const onlinePeers = peers.filter((p) => p.online !== false);
|
|
436
|
+
const out = [];
|
|
437
|
+
const mixedStores = [];
|
|
438
|
+
const summary = [];
|
|
439
|
+
for (const store of registry.stores()) {
|
|
440
|
+
// Only locally-enabled stores can emit, so only they can suffer the skew.
|
|
441
|
+
if (!isStoreEmissionEnabled(stores, store))
|
|
442
|
+
continue;
|
|
443
|
+
const advertising = [];
|
|
444
|
+
const notAdvertising = [];
|
|
445
|
+
for (const peer of onlinePeers) {
|
|
446
|
+
if (peerAdvertisesStore(peer, store))
|
|
447
|
+
advertising.push(peer.machineId);
|
|
448
|
+
else
|
|
449
|
+
notAdvertising.push(peer.machineId);
|
|
450
|
+
}
|
|
451
|
+
// MIXED = locally enabled (we will emit) AND ≥1 online peer cannot receive
|
|
452
|
+
// it. That non-advertiser is the silent-drop victim — the named skew mode.
|
|
453
|
+
const mixed = notAdvertising.length > 0;
|
|
454
|
+
out.push({ store, advertising, notAdvertising, mixed });
|
|
455
|
+
if (mixed) {
|
|
456
|
+
mixedStores.push(store);
|
|
457
|
+
// Content-free: store key + counts + the non-advertiser ids (machine ids
|
|
458
|
+
// are routing identifiers, not user content).
|
|
459
|
+
summary.push(`stateSync.${store}: ${advertising.length} peer(s) ready, ${notAdvertising.length} peer(s) cannot receive (would silently drop) [${notAdvertising.join(',')}]`);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
return { stores: out, mixedStores, summary };
|
|
463
|
+
}
|
|
464
|
+
//# sourceMappingURL=ReplicatedRecordEnvelope.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ReplicatedRecordEnvelope.js","sourceRoot":"","sources":["../../src/core/ReplicatedRecordEnvelope.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"devGatedFeatures.d.ts","sourceRoot":"","sources":["../../src/core/devGatedFeatures.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,oEAAoE;AACpE,MAAM,WAAW,eAAe;IAC9B,mEAAmE;IACnE,IAAI,EAAE,MAAM,CAAC;IACb,uEAAuE;IACvE,UAAU,EAAE,MAAM,CAAC;IACnB,6DAA6D;IAC7D,WAAW,EAAE,MAAM,CAAC;IACpB;;;;;;OAMG;IACH,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,eAAO,MAAM,kBAAkB,EAAE,eAAe,
|
|
1
|
+
{"version":3,"file":"devGatedFeatures.d.ts","sourceRoot":"","sources":["../../src/core/devGatedFeatures.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,oEAAoE;AACpE,MAAM,WAAW,eAAe;IAC9B,mEAAmE;IACnE,IAAI,EAAE,MAAM,CAAC;IACb,uEAAuE;IACvE,UAAU,EAAE,MAAM,CAAC;IACnB,6DAA6D;IAC7D,WAAW,EAAE,MAAM,CAAC;IACpB;;;;;;OAMG;IACH,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,eAAO,MAAM,kBAAkB,EAAE,eAAe,EAyG/C,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,MAAM,MAAM,gBAAgB,GACxB,aAAa,GACb,sBAAsB,GACtB,cAAc,GACd,iBAAiB,GACjB,0BAA0B,CAAC;AAE/B,MAAM,WAAW,iBAAiB;IAChC,uEAAuE;IACvE,UAAU,EAAE,MAAM,CAAC;IACnB,6CAA6C;IAC7C,QAAQ,EAAE,gBAAgB,CAAC;IAC3B,mFAAmF;IACnF,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,eAAO,MAAM,oBAAoB,EAAE,iBAAiB,EAyHnD,CAAC;AAEF;;;GAGG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAO5E"}
|
|
@@ -114,6 +114,12 @@ export const DEV_GATED_FEATURES = [
|
|
|
114
114
|
description: 'Threadline single-negotiator lease — one session owns each conversation\'s outbound voice (THREADLINE-SINGLE-NEGOTIATOR-SPEC, CMT-1362).',
|
|
115
115
|
justification: 'Lives live on a dev agent ONLY in dry-run (dryRun defaults true) — it engages the lease logic and logs every would-hold verdict for the FD-7 false-positive telemetry but withholds NOTHING (a real send is only blocked by an explicit dryRun:false). No egress, no spend, no destructive action; this is exactly the dogfooding posture FD-7 requires before the lease can ever enforce. (Was mis-classified deliberate-fleet-default at ship, which starved the telemetry — corrected here.)',
|
|
116
116
|
},
|
|
117
|
+
{
|
|
118
|
+
name: 'orphanedWorkSentinel',
|
|
119
|
+
configPath: 'monitoring.orphanedWorkSentinel.enabled',
|
|
120
|
+
description: 'Silent-uncommitted-death backstop — flags agent worktrees with uncommitted work whose owning session died (/orphaned-work).',
|
|
121
|
+
justification: 'Signal-only local recorder + ONE deduped attention item; reads git status/diff + lsof read-only; no egress, no spend, no destructive action. The optional preservation is a NON-destructive patch write (git diff → a state-dir file) behind an off-by-default preserveWork sub-flag — it never mutates the worktree, its index, or any ref.',
|
|
122
|
+
},
|
|
117
123
|
];
|
|
118
124
|
export const DARK_GATE_EXCLUSIONS = [
|
|
119
125
|
// ── destructive — kills/deletes on a heuristic; off + dry-run for EVERYONE ──
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"devGatedFeatures.js","sourceRoot":"","sources":["../../src/core/devGatedFeatures.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"devGatedFeatures.js","sourceRoot":"","sources":["../../src/core/devGatedFeatures.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* stateSync config resolution + invariant validation (WS2 replicated-store
|
|
3
|
+
* foundation, §10.2).
|
|
4
|
+
*
|
|
5
|
+
* Spec: docs/specs/multi-machine-replicated-store-foundation.md §10 (config
|
|
6
|
+
* schema, validateStateSyncInvariants, rollout posture), §3.4 (the maxDriftMs
|
|
7
|
+
* clamp), §8.2 (snapshot-cache ceilings).
|
|
8
|
+
*
|
|
9
|
+
* Mirrors `seamlessnessConfig.ts`: resolve the optional `multiMachine.stateSync`
|
|
10
|
+
* knobs to concrete values, then enforce the cross-knob invariants. A violating
|
|
11
|
+
* config is REJECTED with a clear message (StateSyncConfigError) rather than
|
|
12
|
+
* silently coerced — the §10.2 contract: "a bad config is REJECTED, not silently
|
|
13
|
+
* degraded". The per-store `enabled` flags are NOT validated here (they are the
|
|
14
|
+
* dark-by-default on-switches, store by store); this module governs the
|
|
15
|
+
* FOUNDATION-LEVEL knobs every store shares (the journal budget, the HLC drift
|
|
16
|
+
* ceiling, the snapshot cache bounds).
|
|
17
|
+
*/
|
|
18
|
+
import { DEFAULT_MAX_DRIFT_MS, MIN_MAX_DRIFT_MS, MAX_MAX_DRIFT_MS } from './HybridLogicalClock.js';
|
|
19
|
+
import type { MultiMachineConfig } from './types.js';
|
|
20
|
+
/** Foundation-level stateSync defaults (§10.2). */
|
|
21
|
+
export declare const DEFAULT_AGGREGATE_JOURNAL_BUDGET_BYTES: number;
|
|
22
|
+
export declare const DEFAULT_MAX_CACHED_SNAPSHOTS = 16;
|
|
23
|
+
export declare const DEFAULT_MAX_CACHE_BYTES: number;
|
|
24
|
+
/**
|
|
25
|
+
* The propagation allowance (§10.2): the invariant `maxDriftMs > flush interval +
|
|
26
|
+
* propagation allowance` must hold so the drift bound can never be tighter than
|
|
27
|
+
* the journal's own flush+replicate latency (which would quarantine our own
|
|
28
|
+
* in-flight writes). With the 60s floor this always holds; the allowance is the
|
|
29
|
+
* realistic worst-case mesh propagation lag we account for explicitly.
|
|
30
|
+
*/
|
|
31
|
+
export declare const DEFAULT_PROPAGATION_ALLOWANCE_MS: number;
|
|
32
|
+
/** Fully-resolved foundation-level stateSync knobs (every field concrete). */
|
|
33
|
+
export interface ResolvedStateSyncConfig {
|
|
34
|
+
/** Aggregate journal byte budget across all replicated kinds (§10.2). */
|
|
35
|
+
aggregateJournalBudgetBytes: number;
|
|
36
|
+
/** The CLAMPED HLC bounded-drift ceiling the clock enforces (§3.4 / §10.2). */
|
|
37
|
+
maxDriftMs: number;
|
|
38
|
+
/** Snapshot-cache count ceiling (§8.2). */
|
|
39
|
+
maxCachedSnapshots: number;
|
|
40
|
+
/** Snapshot-cache byte ceiling (§8.2). */
|
|
41
|
+
maxCacheBytes: number;
|
|
42
|
+
}
|
|
43
|
+
export declare class StateSyncConfigError extends Error {
|
|
44
|
+
constructor(message: string);
|
|
45
|
+
}
|
|
46
|
+
/** The minimal shape this module reads from config — the per-store flags are a
|
|
47
|
+
* free-form map (a store's key → { enabled, dryRun }); the foundation knobs are
|
|
48
|
+
* optional siblings. Mirrors the spec §10 schema. */
|
|
49
|
+
export interface StateSyncRawConfig {
|
|
50
|
+
aggregateJournalBudgetBytes?: number;
|
|
51
|
+
maxDriftMs?: number;
|
|
52
|
+
maxCachedSnapshots?: number;
|
|
53
|
+
maxCacheBytes?: number;
|
|
54
|
+
/** Per-store on-switches live as sibling keys; this module ignores their
|
|
55
|
+
* shape beyond the foundation knobs. Stores are typed loosely so a new
|
|
56
|
+
* store added later needs no change here. */
|
|
57
|
+
[store: string]: unknown;
|
|
58
|
+
}
|
|
59
|
+
/** Read the stateSync block off a multiMachine config (may be absent). */
|
|
60
|
+
export declare function readStateSyncRaw(mm?: MultiMachineConfig): StateSyncRawConfig | undefined;
|
|
61
|
+
/**
|
|
62
|
+
* Resolve the foundation-level stateSync knobs to concrete values. Does NOT
|
|
63
|
+
* validate — call validateStateSyncInvariants() after (or use
|
|
64
|
+
* assertStateSyncInvariants() for resolve+validate+throw in one step). The
|
|
65
|
+
* maxDriftMs is CLAMPED here via the clock's clampMaxDriftMs so a resolved value
|
|
66
|
+
* is always in-range; the §10.2 invariant additionally REJECTS an out-of-range
|
|
67
|
+
* RAW value (so an operator sees an error, not a silent clamp) — see
|
|
68
|
+
* validateStateSyncInvariants.
|
|
69
|
+
*/
|
|
70
|
+
export declare function resolveStateSyncConfig(mm?: MultiMachineConfig): ResolvedStateSyncConfig;
|
|
71
|
+
/**
|
|
72
|
+
* Validate the foundation-level stateSync invariants (§10.2). Returns the list
|
|
73
|
+
* of violations (empty = valid). Operates on the RAW config (not the resolved
|
|
74
|
+
* one) for the maxDriftMs range check, so an out-of-range value is REJECTED
|
|
75
|
+
* rather than silently clamped — the spec's "rejected, not silently coerced".
|
|
76
|
+
*
|
|
77
|
+
* Invariants (§10.2):
|
|
78
|
+
* 1. aggregateJournalBudgetBytes > 0.
|
|
79
|
+
* 2. maxDriftMs within [60_000, 900_000] (the §3.4 clamp) — out-of-range REJECTED.
|
|
80
|
+
* 3. maxDriftMs > flush interval + propagation allowance (NOT the vacuous
|
|
81
|
+
* "> flush window") — so the bound can never be tighter than the journal's
|
|
82
|
+
* own flush+replicate latency.
|
|
83
|
+
* 4. maxCachedSnapshots > 0 and maxCacheBytes > 0 (§8.2).
|
|
84
|
+
*/
|
|
85
|
+
export declare function validateStateSyncInvariants(mm?: MultiMachineConfig): string[];
|
|
86
|
+
/**
|
|
87
|
+
* Resolve + validate in one step, throwing StateSyncConfigError on any invariant
|
|
88
|
+
* violation. Use at server startup so a bad config is rejected loudly rather
|
|
89
|
+
* than degrading silently (mirrors assertSeamlessnessInvariants).
|
|
90
|
+
*/
|
|
91
|
+
export declare function assertStateSyncInvariants(mm?: MultiMachineConfig): ResolvedStateSyncConfig;
|
|
92
|
+
/** Re-export the drift-clamp constants for callers/tests that assert the range. */
|
|
93
|
+
export { DEFAULT_MAX_DRIFT_MS, MIN_MAX_DRIFT_MS, MAX_MAX_DRIFT_MS };
|
|
94
|
+
//# sourceMappingURL=stateSyncConfig.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stateSyncConfig.d.ts","sourceRoot":"","sources":["../../src/core/stateSyncConfig.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAEL,oBAAoB,EACpB,gBAAgB,EAChB,gBAAgB,EACjB,MAAM,yBAAyB,CAAC;AAEjC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAErD,mDAAmD;AACnD,eAAO,MAAM,sCAAsC,QAAmB,CAAC;AACvE,eAAO,MAAM,4BAA4B,KAAK,CAAC;AAC/C,eAAO,MAAM,uBAAuB,QAAmB,CAAC;AACxD;;;;;;GAMG;AACH,eAAO,MAAM,gCAAgC,QAAY,CAAC;AAE1D,8EAA8E;AAC9E,MAAM,WAAW,uBAAuB;IACtC,yEAAyE;IACzE,2BAA2B,EAAE,MAAM,CAAC;IACpC,+EAA+E;IAC/E,UAAU,EAAE,MAAM,CAAC;IACnB,2CAA2C;IAC3C,kBAAkB,EAAE,MAAM,CAAC;IAC3B,0CAA0C;IAC1C,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,qBAAa,oBAAqB,SAAQ,KAAK;gBACjC,OAAO,EAAE,MAAM;CAI5B;AAED;;sDAEsD;AACtD,MAAM,WAAW,kBAAkB;IACjC,2BAA2B,CAAC,EAAE,MAAM,CAAC;IACrC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;kDAE8C;IAC9C,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC;CAC1B;AAED,0EAA0E;AAC1E,wBAAgB,gBAAgB,CAAC,EAAE,CAAC,EAAE,kBAAkB,GAAG,kBAAkB,GAAG,SAAS,CAGxF;AAED;;;;;;;;GAQG;AACH,wBAAgB,sBAAsB,CAAC,EAAE,CAAC,EAAE,kBAAkB,GAAG,uBAAuB,CAQvF;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,2BAA2B,CAAC,EAAE,CAAC,EAAE,kBAAkB,GAAG,MAAM,EAAE,CA2C7E;AAED;;;;GAIG;AACH,wBAAgB,yBAAyB,CAAC,EAAE,CAAC,EAAE,kBAAkB,GAAG,uBAAuB,CAS1F;AAED,mFAAmF;AACnF,OAAO,EAAE,oBAAoB,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,CAAC"}
|