@ai-dossier/sched 0.8.0 → 0.9.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/README.md +88 -8
- package/dist/batch-dispatch.d.ts +121 -0
- package/dist/batch-dispatch.d.ts.map +1 -0
- package/dist/batch-dispatch.js +1114 -0
- package/dist/batch-dispatch.js.map +1 -0
- package/dist/dispatch.d.ts +43 -10
- package/dist/dispatch.d.ts.map +1 -1
- package/dist/dispatch.js +93 -4
- package/dist/dispatch.js.map +1 -1
- package/dist/engine.d.ts +36 -2
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +76 -21
- package/dist/engine.js.map +1 -1
- package/dist/enqueue.d.ts.map +1 -1
- package/dist/enqueue.js +17 -2
- package/dist/enqueue.js.map +1 -1
- package/dist/groundtruth.d.ts +23 -0
- package/dist/groundtruth.d.ts.map +1 -1
- package/dist/groundtruth.js +41 -0
- package/dist/groundtruth.js.map +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +16 -3
- package/dist/index.js.map +1 -1
- package/dist/journal.d.ts +2 -0
- package/dist/journal.d.ts.map +1 -1
- package/dist/journal.js +8 -0
- package/dist/journal.js.map +1 -1
- package/dist/persist.js +18 -0
- package/dist/persist.js.map +1 -1
- package/dist/state.d.ts +3 -1
- package/dist/state.d.ts.map +1 -1
- package/dist/state.js +37 -2
- package/dist/state.js.map +1 -1
- package/dist/types.d.ts +49 -6
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +4 -3
- package/dist/types.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -133,8 +133,9 @@ Two engine-safety policies were explicit product decisions on #464:
|
|
|
133
133
|
an outage holds in `verifying` until truth returns. Each pause is journaled as
|
|
134
134
|
`ground-truth-unreachable`.
|
|
135
135
|
|
|
136
|
-
|
|
137
|
-
|
|
136
|
+
This applies to `issue:<n>` unit dispatch (`dispatchAssignments`). `batch:<id>` units run
|
|
137
|
+
through a separate pass with its own claim/reconcile logic — see
|
|
138
|
+
[Batch dispatch (#523)](#batch-dispatch-523) below.
|
|
138
139
|
|
|
139
140
|
### Zombie-run fencing (#504)
|
|
140
141
|
|
|
@@ -182,9 +183,13 @@ but the unprotected redispatch is never silent.
|
|
|
182
183
|
What happens when a batch's aggregate suite goes red, or its PR will not merge
|
|
183
184
|
(RFC-0001 §F.2/F.8/F.9).
|
|
184
185
|
|
|
185
|
-
**
|
|
186
|
-
|
|
187
|
-
|
|
186
|
+
**Wired into `sched start` since #523** — the `validating → attributing → fixing/evicting`
|
|
187
|
+
rail below is called directly from `batch-dispatch.ts`'s `runValidate`/`evictOffender`
|
|
188
|
+
(a red AGGREGATE suite, after every member individually went green). A member that never
|
|
189
|
+
went green in the first place (its own gate failed) evicts through a separate, simpler
|
|
190
|
+
rail that never touches this module — see
|
|
191
|
+
[Batch dispatch (#523)](#batch-dispatch-523). These modules remain independently tested
|
|
192
|
+
against real scratch repos.
|
|
188
193
|
|
|
189
194
|
```
|
|
190
195
|
validating → attributing → fixing (ONE bounded attempt) → validating
|
|
@@ -270,6 +275,64 @@ suspect-dispatch streak (#505 above). The two fields are a single fact and must
|
|
|
270
275
|
on load: no suspect dispatches were ever tracked under them, so `0`/`null` is the exact
|
|
271
276
|
backfill, not a guess.
|
|
272
277
|
|
|
278
|
+
## Batch dispatch (#523)
|
|
279
|
+
|
|
280
|
+
`batch-dispatch.ts`'s `runBatchTick` — called from `tick()` after the issue-level pass,
|
|
281
|
+
only when `batchExec`/`runBatchSuite` are both configured on `EngineDeps` — drives every
|
|
282
|
+
`batch:<id>` unit through:
|
|
283
|
+
|
|
284
|
+
```
|
|
285
|
+
ready → executing(member i/N) ⟲ → validating → reviewing → shipping
|
|
286
|
+
→ awaiting-merge → merged → deployed → reported → done
|
|
287
|
+
failure rails: executing → dissolving (a member self-reports blocked)
|
|
288
|
+
validating → attributing → (fixing | evicting) → validating → dissolving
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
- **One shared worktree/branch per batch**, claimed once by a deterministic (no LLM)
|
|
292
|
+
`batch-setup` step: `git branch`/`push`/`worktree add` off `base_branch`, named
|
|
293
|
+
`batch/<id>-<date>`, plus a fresh `ai-dossier runstate mint` against the anchor issue.
|
|
294
|
+
- **Members run serially, one fresh `slot-cycle` agent at a time**, in the shared
|
|
295
|
+
worktree. A member's completion signal is `phase=review status=done mode=slot` on its
|
|
296
|
+
OWN issue (`slot-cycle` posts no phase of its own past `review` — ship is batch-owned);
|
|
297
|
+
its commit range on the batch branch is recomputed (`git log`) after every member and
|
|
298
|
+
kept on `BatchEntry.ranges` for eviction. An incremental gate (`ai-dossier cap run
|
|
299
|
+
typecheck.run` / `test.focused`, when the repo has a manifest) runs after each member
|
|
300
|
+
before advancing — a second, independent check that the member's self-reported "done"
|
|
301
|
+
is real.
|
|
302
|
+
- **The batch's single slot is claimed FRESH for each live step** (a member, the tail
|
|
303
|
+
agent, the report agent, a bounded fix agent) — never held across a wait. The aggregate
|
|
304
|
+
suite itself runs with NO slot claimed at all (deterministic engine work, not an LLM
|
|
305
|
+
step).
|
|
306
|
+
- **Two failure rails.** A member that never went green evicts directly (nothing to
|
|
307
|
+
attribute — see the #472 section above for what "directly" skips). A red AGGREGATE
|
|
308
|
+
suite (every member individually green, but integration-level conflict) routes through
|
|
309
|
+
the #472 attribution/fix/evict library.
|
|
310
|
+
- **The tail**, after the last member: the aggregate suite runs deterministically; green
|
|
311
|
+
spawns ONE bounded strong-tier agent that runs `review-issue` aggregate mode then
|
|
312
|
+
`ship-issue` batch mode (rebase-merge, a `Closes` list) and parks the PR exactly like a
|
|
313
|
+
detached full-cycle run; the engine's own PR watcher (a batch-granularity mirror of the
|
|
314
|
+
per-issue one) accepts the merge and dispatches a cheap mechanical-tier agent for
|
|
315
|
+
`report-issue`'s batch variant.
|
|
316
|
+
- **Scope cuts, recorded rather than discovered later:** no `git bisect` stage for an
|
|
317
|
+
ambiguous aggregate failure (an unattributable red suite dissolves instead); no
|
|
318
|
+
worktree-pool integration for batch-setup (cold `git worktree add` only); no per-phase
|
|
319
|
+
stall/escalation ladder for batch sub-agents (a dead-without-verification agent is
|
|
320
|
+
treated as blocked, not redispatched stronger).
|
|
321
|
+
|
|
322
|
+
Schema 1.7.0: `BatchEntry` gains `worktree` (absolute path of the shared batch worktree,
|
|
323
|
+
null until batch-setup lands), `ranges` (`MemberRange[]` — each member's commit range,
|
|
324
|
+
recomputed after every member completes) and `pr` (the batch PR parked on auto-merge,
|
|
325
|
+
persisted so a restart mid-watch still knows what to poll). 1.6.0 states migrate on load:
|
|
326
|
+
no batch was ever dispatched under them, so `null`/`[]`/`null` is the exact backfill, not
|
|
327
|
+
a guess. Config schema moves to 1.3.0: `dispatch` gains `member_prompt`,
|
|
328
|
+
`batch_tail_prompt` and `batch_report_prompt` (the three new agent prompt templates).
|
|
329
|
+
|
|
330
|
+
New journal events: `batch-setup-done`, `batch-setup-failed`, `member-advanced`. Member/
|
|
331
|
+
tail/report/fix-agent spawn, progress, completion and park events reuse the existing
|
|
332
|
+
unit-generic names (`assigned`/`spawned`/`unit-failed`/`external-advance`/`pr-parked`/
|
|
333
|
+
`merge-accepted`/`report-dispatched`/`teardown-done`/`teardown-failed`) with
|
|
334
|
+
`unit = batch:<id>`.
|
|
335
|
+
|
|
273
336
|
## API surface
|
|
274
337
|
|
|
275
338
|
```ts
|
|
@@ -284,9 +347,11 @@ import {
|
|
|
284
347
|
runLoop, // the sched start loop (tick, sleep, repeat)
|
|
285
348
|
type TickResult, // what one tick did (spawned/parked/merge-accepted/stale-reconciled/
|
|
286
349
|
// dependents-unblocked/report-dispatched/teardown/completed/
|
|
287
|
-
// redispatched/failed/blocked)
|
|
350
|
+
// redispatched/failed/blocked) — since #523 also carries
|
|
351
|
+
// `batch:<id>` unit ids (issue numbers for `blocked`)
|
|
288
352
|
type EngineDeps, // inject everything the engine touches (store/journal/spawn/ground
|
|
289
|
-
// truth/clock/repoDir/teardownExec/fencer
|
|
353
|
+
// truth/clock/repoDir/teardownExec/fencer/batchExec/runBatchSuite/
|
|
354
|
+
// runBatchCapability — #523)
|
|
290
355
|
createSpawnDeps, // real detached-spawn process I/O
|
|
291
356
|
createExecGroundTruth, // runstate/gh/git ground truth via subprocesses (injectable exec);
|
|
292
357
|
// since #468 also gh pr view PR state + setup info from comments
|
|
@@ -337,9 +402,24 @@ import {
|
|
|
337
402
|
transitionIssue, transitionBatch, transitionSlot, // typed §D transitions
|
|
338
403
|
TRANSITIONS, // the transition tables themselves (for previews)
|
|
339
404
|
buildStatusReport, // machine-readable status incl. blocked/failed sets
|
|
340
|
-
validateState, // strict persisted-state validation (1.0.0-1.
|
|
405
|
+
validateState, // strict persisted-state validation (1.0.0-1.6.0 files migrate)
|
|
341
406
|
IllegalTransitionError, EnqueueError, CorruptStateError, LockTimeoutError,
|
|
342
407
|
SchedNotFoundError,
|
|
408
|
+
runBatchTick, // #523: one batch reconcile+refill pass; called by tick() after
|
|
409
|
+
// the issue pass — loads/saves state itself, holds no lock
|
|
410
|
+
// across the call
|
|
411
|
+
type BatchDispatchDeps, // inject store/journal/groundTruth/spawnDeps/exec/runSuite/
|
|
412
|
+
// runCapability(optional)/fsExists(optional)
|
|
413
|
+
type BatchTickResult, // spawned/completed/parked/mergeAccepted/failed (batch:<id> ids)
|
|
414
|
+
// + blocked (issue numbers, dissolve-requeued)
|
|
415
|
+
type CapOutcome, // ok | task-failed | automation-broken | capability-unavailable
|
|
416
|
+
buildMemberPrompt, buildBatchTailPrompt, buildBatchReportPrompt, // #523 prompt builders
|
|
417
|
+
DEFAULT_MEMBER_PROMPT_TEMPLATE, DEFAULT_BATCH_TAIL_PROMPT_TEMPLATE,
|
|
418
|
+
DEFAULT_BATCH_REPORT_PROMPT_TEMPLATE,
|
|
419
|
+
isMemberComplete, isMemberBlocked, // member milestone predicates (mode=slot gated)
|
|
420
|
+
isBatchTailParked, // batch-ship awaiting-merge + pr= — the batch park signal
|
|
421
|
+
isBatchPhaseDone, // <phase> done on the anchor (batch-review/batch-report)
|
|
422
|
+
batchOfUnit, // batch:<id> → <id>; null for issue units or malformed ids
|
|
343
423
|
} from '@ai-dossier/sched';
|
|
344
424
|
```
|
|
345
425
|
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Batch dispatch (#523, RFC-0001 §C.4/D.2/D.3): the missing driver that
|
|
3
|
+
* executes `batch:<id>` units. #498 landed the batch failure-recovery library
|
|
4
|
+
* (attribution/bisect/eviction/dissolve, `recovery.ts`) and the batch state
|
|
5
|
+
* machine (`state.ts`); readiness/placement already treat a `ready` batch as
|
|
6
|
+
* a runnable unit (`readiness.ts`, `scheduler.ts`). Nothing dispatched one
|
|
7
|
+
* until now.
|
|
8
|
+
*
|
|
9
|
+
* Shape, mirroring `engine.ts`'s per-issue dispatch: claim a slot → spawn an
|
|
10
|
+
* agent → poll ground truth → verify → transition. Generalized to `BatchEntry`
|
|
11
|
+
* at batch-phase granularity instead of per-issue-phase granularity:
|
|
12
|
+
*
|
|
13
|
+
* ```
|
|
14
|
+
* ready → executing(member i/N) ⟲ → validating → reviewing → shipping
|
|
15
|
+
* → awaiting-merge → merged → deployed → reported → done
|
|
16
|
+
* failure rails (RFC F.2/F.8/F.9):
|
|
17
|
+
* executing → dissolving (a member self-reports blocked, RFC F.1)
|
|
18
|
+
* validating → attributing → (fixing | evicting) → validating
|
|
19
|
+
* → dissolving
|
|
20
|
+
* ```
|
|
21
|
+
*
|
|
22
|
+
* NO batch claim — not the first (`ready → executing`) nor any continuation
|
|
23
|
+
* (a later member, the tail agent, the report agent, the fix agent) — ever
|
|
24
|
+
* goes through `computeAssignments`/`runnableUnits`. Every one is a bespoke
|
|
25
|
+
* free-capacity-gated assignment, the same shape `engine.ts`'s
|
|
26
|
+
* `dispatchReportAgents` already uses (`runnableUnits` only ever offers a
|
|
27
|
+
* `status === 'ready'` batch, i.e. the moment BEFORE any claim). Between
|
|
28
|
+
* steps — a suite run, a PR merge wait — the slot is released to `idle` and
|
|
29
|
+
* holds no capacity (AC5): only a live member/tail/report/fix agent holds a
|
|
30
|
+
* slot.
|
|
31
|
+
*
|
|
32
|
+
* The aggregate suite itself is deterministic engine work, not an LLM step —
|
|
33
|
+
* it runs with no slot claimed at all, matching AC5's "member or batch-LLM-step"
|
|
34
|
+
* wording precisely.
|
|
35
|
+
*
|
|
36
|
+
* Two distinct failure rails, deliberately different:
|
|
37
|
+
* - A member's OWN agent reports itself blocked (its own gate never went
|
|
38
|
+
* green) — evicted directly, no attribution needed: the offender is already
|
|
39
|
+
* known, and either it has no commits yet (blocked before implementing) or
|
|
40
|
+
* its commits are exactly what gets reverted.
|
|
41
|
+
* - The AGGREGATE suite (run by the engine after every member individually
|
|
42
|
+
* went green) comes back red — an integration-level conflict no member's own
|
|
43
|
+
* gate caught. THIS is what `recovery.ts`'s attribution/fix/evict pipeline
|
|
44
|
+
* exists for (RFC F.2).
|
|
45
|
+
*
|
|
46
|
+
* Scope decisions recorded here, not silently cut: no `git bisect` stage for
|
|
47
|
+
* an ambiguous aggregate failure (bisect needs a per-project "run only these
|
|
48
|
+
* tests" command this module has no generic way to construct) — an
|
|
49
|
+
* unattributable red aggregate suite dissolves the batch rather than
|
|
50
|
+
* bisecting, which `attributing → dissolving` already models. No worktree-pool
|
|
51
|
+
* integration for batch-setup — cold `git worktree add` only, mirroring
|
|
52
|
+
* `teardown.ts`'s cold path. No per-phase stall/escalation ladder for batch
|
|
53
|
+
* sub-agents — a dead-without-verification agent is treated as blocked and
|
|
54
|
+
* evicted/reported rather than redispatched stronger. Both are documented
|
|
55
|
+
* follow-ups, not gaps discovered later.
|
|
56
|
+
*/
|
|
57
|
+
import { type ResolvedDispatch, type SpawnDeps } from './dispatch';
|
|
58
|
+
import { type GroundTruth } from './groundtruth';
|
|
59
|
+
import { type Journal } from './journal';
|
|
60
|
+
import type { SchedStore } from './persist';
|
|
61
|
+
import type { ExecFn } from './project';
|
|
62
|
+
import { type SuiteResult } from './recovery';
|
|
63
|
+
import { type FsExists } from './teardown';
|
|
64
|
+
import type { SchedConfig } from './types';
|
|
65
|
+
/**
|
|
66
|
+
* The four `ai-dossier cap run` outcomes (docs/reference/capabilities.md):
|
|
67
|
+
* `ok` = the capability ran and passed; `task-failed` = it ran and the TASK
|
|
68
|
+
* itself failed (trust the result); `automation-broken` = do not trust the
|
|
69
|
+
* machinery (missing tool, timeout, bad manifest); `capability-unavailable` =
|
|
70
|
+
* no manifest / no such id / `lifecycle: shadow` — no fast path here.
|
|
71
|
+
*/
|
|
72
|
+
export type CapOutcome = 'ok' | 'task-failed' | 'automation-broken' | 'capability-unavailable';
|
|
73
|
+
/** Everything batch dispatch needs from the outside world. */
|
|
74
|
+
export interface BatchDispatchDeps {
|
|
75
|
+
store: SchedStore;
|
|
76
|
+
journal: Journal;
|
|
77
|
+
groundTruth: GroundTruth;
|
|
78
|
+
spawnDeps: SpawnDeps;
|
|
79
|
+
now: () => Date;
|
|
80
|
+
/** Repo root — cwd for `git`/`ai-dossier` calls that are not batch-worktree-scoped. */
|
|
81
|
+
repoDir: string;
|
|
82
|
+
/** Exec for batch git/milestone-CLI operations (never throws — the `ExecFn` contract). */
|
|
83
|
+
exec: ExecFn;
|
|
84
|
+
/** Runs the aggregate suite inside a batch worktree; batches never leave `validating` without one. */
|
|
85
|
+
runSuite: (worktree: string) => SuiteResult;
|
|
86
|
+
/**
|
|
87
|
+
* Runs one `ai-dossier cap run <capabilityId>` in a batch worktree for the
|
|
88
|
+
* per-member incremental gate (#523 AC2 — "typecheck + focused tests via
|
|
89
|
+
* `cap run test.focused` when available"). Optional and degrade-not-crash,
|
|
90
|
+
* like `batchExec`/`runBatchSuite`: without it, or on `automation-broken`/
|
|
91
|
+
* `capability-unavailable`, the gate is skipped — the member's own
|
|
92
|
+
* `slot-cycle` run already attempted this fast path (with its own reasoning
|
|
93
|
+
* fallback) before ever posting `review done`, so a repo with no manifest
|
|
94
|
+
* loses nothing but the engine's independent re-check.
|
|
95
|
+
*/
|
|
96
|
+
runCapability?: (worktree: string, capabilityId: string) => CapOutcome;
|
|
97
|
+
fsExists?: FsExists;
|
|
98
|
+
}
|
|
99
|
+
/** What one `runBatchTick` call did, merged into `engine.ts`'s `TickResult` by the caller. */
|
|
100
|
+
export interface BatchTickResult {
|
|
101
|
+
spawned: string[];
|
|
102
|
+
completed: string[];
|
|
103
|
+
parked: string[];
|
|
104
|
+
mergeAccepted: string[];
|
|
105
|
+
failed: string[];
|
|
106
|
+
/** Issue numbers requeued full-cycle by a dissolve — matches `TickResult.blocked`'s shape. */
|
|
107
|
+
blocked: number[];
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* One batch reconcile+refill pass. Called from `engine.ts`'s `tick()` after
|
|
111
|
+
* the issue-level pass — batches never compete with issues for a slot within
|
|
112
|
+
* the same tick because this pass runs strictly after `dispatchAssignments`
|
|
113
|
+
* already filled every slot it could (see the module doc: batch claims never
|
|
114
|
+
* go through `computeAssignments`/`runnableUnits` at all). Loads and saves
|
|
115
|
+
* state itself via `deps.store.withLock` — the caller holds no lock across
|
|
116
|
+
* this call. `deps.exec` and `deps.runSuite` are mandatory; `deps.
|
|
117
|
+
* runCapability` is independently optional (AC2's incremental gate is itself
|
|
118
|
+
* a "when available" fast path).
|
|
119
|
+
*/
|
|
120
|
+
export declare function runBatchTick(deps: BatchDispatchDeps, config: SchedConfig, dispatch: ResolvedDispatch): BatchTickResult;
|
|
121
|
+
//# sourceMappingURL=batch-dispatch.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"batch-dispatch.d.ts","sourceRoot":"","sources":["../src/batch-dispatch.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuDG;AAWH,OAAO,EAKL,KAAK,gBAAgB,EACrB,KAAK,SAAS,EAEf,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,KAAK,WAAW,EAOjB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,KAAK,OAAO,EAAa,MAAM,WAAW,CAAC;AACpD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAC5C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AACxC,OAAO,EASL,KAAK,WAAW,EACjB,MAAM,YAAY,CAAC;AAWpB,OAAO,EAAE,KAAK,QAAQ,EAA+B,MAAM,YAAY,CAAC;AACxE,OAAO,KAAK,EAKV,WAAW,EAIZ,MAAM,SAAS,CAAC;AAEjB;;;;;;GAMG;AACH,MAAM,MAAM,UAAU,GAAG,IAAI,GAAG,aAAa,GAAG,mBAAmB,GAAG,wBAAwB,CAAC;AAE/F,8DAA8D;AAC9D,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,UAAU,CAAC;IAClB,OAAO,EAAE,OAAO,CAAC;IACjB,WAAW,EAAE,WAAW,CAAC;IACzB,SAAS,EAAE,SAAS,CAAC;IACrB,GAAG,EAAE,MAAM,IAAI,CAAC;IAChB,uFAAuF;IACvF,OAAO,EAAE,MAAM,CAAC;IAChB,0FAA0F;IAC1F,IAAI,EAAE,MAAM,CAAC;IACb,sGAAsG;IACtG,QAAQ,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,WAAW,CAAC;IAC5C;;;;;;;;;OASG;IACH,aAAa,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,KAAK,UAAU,CAAC;IACvE,QAAQ,CAAC,EAAE,QAAQ,CAAC;CACrB;AAED,8FAA8F;AAC9F,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,8FAA8F;IAC9F,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAyuCD;;;;;;;;;;GAUG;AACH,wBAAgB,YAAY,CAC1B,IAAI,EAAE,iBAAiB,EACvB,MAAM,EAAE,WAAW,EACnB,QAAQ,EAAE,gBAAgB,GACzB,eAAe,CAsEjB"}
|