instar 1.3.781 → 1.3.783
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/core/IntelligenceRouter.d.ts +9 -14
- package/dist/core/IntelligenceRouter.d.ts.map +1 -1
- package/dist/core/IntelligenceRouter.js +117 -56
- package/dist/core/IntelligenceRouter.js.map +1 -1
- package/dist/core/MeteredSpendGate.d.ts +119 -0
- package/dist/core/MeteredSpendGate.d.ts.map +1 -0
- package/dist/core/MeteredSpendGate.js +228 -0
- package/dist/core/MeteredSpendGate.js.map +1 -0
- package/dist/core/MeteredSpendLedger.d.ts +154 -0
- package/dist/core/MeteredSpendLedger.d.ts.map +1 -0
- package/dist/core/MeteredSpendLedger.js +434 -0
- package/dist/core/MeteredSpendLedger.js.map +1 -0
- package/dist/core/PinAttemptStore.d.ts +35 -0
- package/dist/core/PinAttemptStore.d.ts.map +1 -0
- package/dist/core/PinAttemptStore.js +85 -0
- package/dist/core/PinAttemptStore.js.map +1 -0
- package/dist/core/PostUpdateMigrator.d.ts +7 -0
- package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
- package/dist/core/PostUpdateMigrator.js +24 -0
- package/dist/core/PostUpdateMigrator.js.map +1 -1
- package/dist/core/RenderedPlanStore.d.ts +61 -0
- package/dist/core/RenderedPlanStore.d.ts.map +1 -0
- package/dist/core/RenderedPlanStore.js +92 -0
- package/dist/core/RenderedPlanStore.js.map +1 -0
- package/dist/core/RoutingSpendCapsStore.d.ts +113 -0
- package/dist/core/RoutingSpendCapsStore.d.ts.map +1 -0
- package/dist/core/RoutingSpendCapsStore.js +241 -0
- package/dist/core/RoutingSpendCapsStore.js.map +1 -0
- package/dist/core/SpendAlertResolver.d.ts +77 -0
- package/dist/core/SpendAlertResolver.d.ts.map +1 -0
- package/dist/core/SpendAlertResolver.js +141 -0
- package/dist/core/SpendAlertResolver.js.map +1 -0
- package/dist/core/WriteDomainRegistry.d.ts.map +1 -1
- package/dist/core/WriteDomainRegistry.js +17 -0
- package/dist/core/WriteDomainRegistry.js.map +1 -1
- package/dist/core/devGatedFeatures.d.ts.map +1 -1
- package/dist/core/devGatedFeatures.js +9 -0
- package/dist/core/devGatedFeatures.js.map +1 -1
- package/dist/core/routingSpendView.d.ts +10 -2
- package/dist/core/routingSpendView.d.ts.map +1 -1
- package/dist/core/routingSpendView.js +18 -10
- package/dist/core/routingSpendView.js.map +1 -1
- package/dist/core/types.d.ts +24 -2
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/server/AgentServer.d.ts +9 -0
- package/dist/server/AgentServer.d.ts.map +1 -1
- package/dist/server/AgentServer.js +177 -0
- package/dist/server/AgentServer.js.map +1 -1
- package/dist/server/routes.d.ts +10 -0
- package/dist/server/routes.d.ts.map +1 -1
- package/dist/server/routes.js +196 -4
- package/dist/server/routes.js.map +1 -1
- package/dist/testing/selfActionRegistry.d.ts.map +1 -1
- package/dist/testing/selfActionRegistry.js +78 -0
- package/dist/testing/selfActionRegistry.js.map +1 -1
- package/package.json +1 -1
- package/src/data/builtin-manifest.json +64 -64
- package/upgrades/1.3.782.md +60 -0
- package/upgrades/1.3.783.md +66 -0
- package/upgrades/nature-routing-a2.2-enforcing.eli16.md +50 -0
- package/upgrades/side-effects/nature-routing-a2.2-enforcing.md +142 -0
- package/upgrades/side-effects/routing-spend-increment-b.md +44 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RenderedPlanStore — the S2-3/C3-4/C5-3 canonical rendered-plan machinery of
|
|
3
|
+
* the Routing Control Room PIN surfaces (docs/specs/routing-control-room-
|
|
4
|
+
* spend-alerts.md, Increment B, Surface 2).
|
|
5
|
+
*
|
|
6
|
+
* The PIN authorizes a CANONICAL SERVER-RENDERED PLAN — nothing else applies:
|
|
7
|
+
* - render(): takes the structured request, enumerates EVERY field the commit
|
|
8
|
+
* will change into an immutable plan snapshot (plain language + typed
|
|
9
|
+
* fields), pins the version(s) of every governed store the plan touches,
|
|
10
|
+
* mints a single-use nonce, stamps a short TTL.
|
|
11
|
+
* - commit(): validates plan exists / not consumed / not expired / nonce
|
|
12
|
+
* matches / pinned versions still current — then returns EXACTLY the
|
|
13
|
+
* snapshot's fields. A request field absent from the render can never be
|
|
14
|
+
* committed (the smuggled-field gap is closed by construction: the commit
|
|
15
|
+
* path never sees the original request).
|
|
16
|
+
* - The nonce is consumed on commit (no replay); an expired plan must be
|
|
17
|
+
* re-rendered; a version drift refuses deterministically ("re-rendered —
|
|
18
|
+
* here's the fresh plan" is the caller's UX).
|
|
19
|
+
*
|
|
20
|
+
* No separable "approved-plan token" outlives the single commit — a captured
|
|
21
|
+
* approval is worthless (C3-4).
|
|
22
|
+
*/
|
|
23
|
+
export declare const PLAN_TTL_MS: number;
|
|
24
|
+
export type PlanAction = 'caps-adjust' | 'go-live' | 'unfreeze' | 'price-promote';
|
|
25
|
+
export interface RenderedPlan {
|
|
26
|
+
planId: string;
|
|
27
|
+
nonce: string;
|
|
28
|
+
action: PlanAction;
|
|
29
|
+
/** The plain-language enumeration the operator PIN-approves. */
|
|
30
|
+
renderedText: string;
|
|
31
|
+
/** The EXACT typed fields the commit will apply — the ONLY commit input. */
|
|
32
|
+
fields: Record<string, unknown>;
|
|
33
|
+
/** Store-version pins (e.g. { capsStore: 3 }) checked at commit (C5-3). */
|
|
34
|
+
versionsPinned: Record<string, number>;
|
|
35
|
+
expiresAt: number;
|
|
36
|
+
consumed: boolean;
|
|
37
|
+
renderedAt: number;
|
|
38
|
+
}
|
|
39
|
+
export declare class PlanCommitError extends Error {
|
|
40
|
+
code: 'unknown-plan' | 'consumed' | 'expired' | 'bad-nonce' | 'version-drift';
|
|
41
|
+
constructor(code: 'unknown-plan' | 'consumed' | 'expired' | 'bad-nonce' | 'version-drift', msg: string);
|
|
42
|
+
}
|
|
43
|
+
export declare class RenderedPlanStore {
|
|
44
|
+
private plans;
|
|
45
|
+
private readonly now;
|
|
46
|
+
private readonly ttlMs;
|
|
47
|
+
constructor(opts?: {
|
|
48
|
+
now?: () => number;
|
|
49
|
+
ttlMs?: number;
|
|
50
|
+
});
|
|
51
|
+
/** Render an immutable plan snapshot. The caller provides the plain-language text and the exact typed fields. */
|
|
52
|
+
render(action: PlanAction, renderedText: string, fields: Record<string, unknown>, versionsPinned: Record<string, number>): RenderedPlan;
|
|
53
|
+
/**
|
|
54
|
+
* Validate + consume. `currentVersions` supplies the LIVE version of every
|
|
55
|
+
* governed store; commit refuses on a mismatch of any store the plan pinned.
|
|
56
|
+
* Returns the immutable snapshot fields — the SOLE commit input.
|
|
57
|
+
*/
|
|
58
|
+
commit(planId: string, nonce: string, currentVersions: Record<string, number>): RenderedPlan;
|
|
59
|
+
private prune;
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=RenderedPlanStore.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RenderedPlanStore.d.ts","sourceRoot":"","sources":["../../src/core/RenderedPlanStore.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAIH,eAAO,MAAM,WAAW,QAAiB,CAAC;AAE1C,MAAM,MAAM,UAAU,GAAG,aAAa,GAAG,SAAS,GAAG,UAAU,GAAG,eAAe,CAAC;AAElF,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,UAAU,CAAC;IACnB,gEAAgE;IAChE,YAAY,EAAE,MAAM,CAAC;IACrB,4EAA4E;IAC5E,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,2EAA2E;IAC3E,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACvC,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,OAAO,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,qBAAa,eAAgB,SAAQ,KAAK;IACrB,IAAI,EAAE,cAAc,GAAG,UAAU,GAAG,SAAS,GAAG,WAAW,GAAG,eAAe;gBAA7E,IAAI,EAAE,cAAc,GAAG,UAAU,GAAG,SAAS,GAAG,WAAW,GAAG,eAAe,EAAE,GAAG,EAAE,MAAM;CAI9G;AAED,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,KAAK,CAAmC;IAChD,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAe;IACnC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;gBAEnB,IAAI,CAAC,EAAE;QAAE,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE;IAKzD,iHAAiH;IACjH,MAAM,CAAC,MAAM,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,YAAY;IAiBvI;;;;OAIG;IACH,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,YAAY;IAmB5F,OAAO,CAAC,KAAK;CAMd"}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RenderedPlanStore — the S2-3/C3-4/C5-3 canonical rendered-plan machinery of
|
|
3
|
+
* the Routing Control Room PIN surfaces (docs/specs/routing-control-room-
|
|
4
|
+
* spend-alerts.md, Increment B, Surface 2).
|
|
5
|
+
*
|
|
6
|
+
* The PIN authorizes a CANONICAL SERVER-RENDERED PLAN — nothing else applies:
|
|
7
|
+
* - render(): takes the structured request, enumerates EVERY field the commit
|
|
8
|
+
* will change into an immutable plan snapshot (plain language + typed
|
|
9
|
+
* fields), pins the version(s) of every governed store the plan touches,
|
|
10
|
+
* mints a single-use nonce, stamps a short TTL.
|
|
11
|
+
* - commit(): validates plan exists / not consumed / not expired / nonce
|
|
12
|
+
* matches / pinned versions still current — then returns EXACTLY the
|
|
13
|
+
* snapshot's fields. A request field absent from the render can never be
|
|
14
|
+
* committed (the smuggled-field gap is closed by construction: the commit
|
|
15
|
+
* path never sees the original request).
|
|
16
|
+
* - The nonce is consumed on commit (no replay); an expired plan must be
|
|
17
|
+
* re-rendered; a version drift refuses deterministically ("re-rendered —
|
|
18
|
+
* here's the fresh plan" is the caller's UX).
|
|
19
|
+
*
|
|
20
|
+
* No separable "approved-plan token" outlives the single commit — a captured
|
|
21
|
+
* approval is worthless (C3-4).
|
|
22
|
+
*/
|
|
23
|
+
import crypto from 'node:crypto';
|
|
24
|
+
export const PLAN_TTL_MS = 10 * 60 * 1000;
|
|
25
|
+
export class PlanCommitError extends Error {
|
|
26
|
+
code;
|
|
27
|
+
constructor(code, msg) {
|
|
28
|
+
super(msg);
|
|
29
|
+
this.code = code;
|
|
30
|
+
this.name = 'PlanCommitError';
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
export class RenderedPlanStore {
|
|
34
|
+
plans = new Map();
|
|
35
|
+
now;
|
|
36
|
+
ttlMs;
|
|
37
|
+
constructor(opts) {
|
|
38
|
+
this.now = opts?.now ?? (() => Date.now());
|
|
39
|
+
this.ttlMs = opts?.ttlMs ?? PLAN_TTL_MS;
|
|
40
|
+
}
|
|
41
|
+
/** Render an immutable plan snapshot. The caller provides the plain-language text and the exact typed fields. */
|
|
42
|
+
render(action, renderedText, fields, versionsPinned) {
|
|
43
|
+
this.prune();
|
|
44
|
+
const plan = {
|
|
45
|
+
planId: crypto.randomBytes(8).toString('hex'),
|
|
46
|
+
nonce: crypto.randomBytes(16).toString('hex'),
|
|
47
|
+
action,
|
|
48
|
+
renderedText,
|
|
49
|
+
fields: structuredClone(fields),
|
|
50
|
+
versionsPinned: { ...versionsPinned },
|
|
51
|
+
expiresAt: this.now() + this.ttlMs,
|
|
52
|
+
consumed: false,
|
|
53
|
+
renderedAt: this.now(),
|
|
54
|
+
};
|
|
55
|
+
this.plans.set(plan.planId, plan);
|
|
56
|
+
return plan;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Validate + consume. `currentVersions` supplies the LIVE version of every
|
|
60
|
+
* governed store; commit refuses on a mismatch of any store the plan pinned.
|
|
61
|
+
* Returns the immutable snapshot fields — the SOLE commit input.
|
|
62
|
+
*/
|
|
63
|
+
commit(planId, nonce, currentVersions) {
|
|
64
|
+
const plan = this.plans.get(planId);
|
|
65
|
+
if (!plan)
|
|
66
|
+
throw new PlanCommitError('unknown-plan', 'no such plan — render one first');
|
|
67
|
+
if (plan.consumed)
|
|
68
|
+
throw new PlanCommitError('consumed', 'plan already committed (single-use nonce)');
|
|
69
|
+
if (this.now() > plan.expiresAt)
|
|
70
|
+
throw new PlanCommitError('expired', 'plan expired — re-render');
|
|
71
|
+
const a = crypto.createHash('sha256').update(String(nonce)).digest();
|
|
72
|
+
const b = crypto.createHash('sha256').update(plan.nonce).digest();
|
|
73
|
+
if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) {
|
|
74
|
+
throw new PlanCommitError('bad-nonce', 'nonce mismatch');
|
|
75
|
+
}
|
|
76
|
+
for (const [store, pinned] of Object.entries(plan.versionsPinned)) {
|
|
77
|
+
if (currentVersions[store] !== pinned) {
|
|
78
|
+
throw new PlanCommitError('version-drift', `store '${store}' changed since the plan was rendered (pinned ${pinned}, is ${currentVersions[store]}) — re-rendered plan required`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
plan.consumed = true; // consume BEFORE returning — no replay even on a downstream failure
|
|
82
|
+
return plan;
|
|
83
|
+
}
|
|
84
|
+
prune() {
|
|
85
|
+
const now = this.now();
|
|
86
|
+
for (const [id, p] of this.plans) {
|
|
87
|
+
if (p.consumed || now > p.expiresAt + this.ttlMs)
|
|
88
|
+
this.plans.delete(id);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=RenderedPlanStore.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RenderedPlanStore.js","sourceRoot":"","sources":["../../src/core/RenderedPlanStore.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RoutingSpendCapsStore — the DEDICATED PIN-only money-authority store of the
|
|
3
|
+
* Routing Control Room (docs/specs/routing-control-room-spend-alerts.md,
|
|
4
|
+
* Increment B, Surface 2 / S-F2).
|
|
5
|
+
*
|
|
6
|
+
* Caps + go-live + metered-lease designation live in
|
|
7
|
+
* `<stateDir>/state/routing-spend-caps.json`, written ONLY through this store's
|
|
8
|
+
* typed mutators — which the PIN routes (and the Bearer set-true-only freeze)
|
|
9
|
+
* call. The file is NEVER under any `PATCHABLE_CONFIG_KEYS` key, so a Bearer
|
|
10
|
+
* `PATCH /config` can structurally never arm a door, unfreeze a key, or raise a
|
|
11
|
+
* cap (regression-tested).
|
|
12
|
+
*
|
|
13
|
+
* Concurrency/authority discipline:
|
|
14
|
+
* - Every write is atomic (tmp+rename) and bumps the store VERSION — the
|
|
15
|
+
* optimistic-concurrency anchor the rendered-plan commit checks (C5-3):
|
|
16
|
+
* a plan pins the version it read; commit refuses on mismatch.
|
|
17
|
+
* - Every committed mutation ADDITIONALLY passes a schema-level validator
|
|
18
|
+
* independent of the plan machinery (C4-4) — a plan-renderer bug can never
|
|
19
|
+
* become the sole authority boundary.
|
|
20
|
+
* - Every change appends a BEFORE+AFTER audit row (never just the delta).
|
|
21
|
+
* - STOP is cheap: `freeze()` is set-TRUE-only (the Bearer surface); unfreeze
|
|
22
|
+
* goes through the PIN plan path like every other money-releasing action.
|
|
23
|
+
* - Deny-by-default: with no go-live record every metered door stays skipped.
|
|
24
|
+
*/
|
|
25
|
+
export interface KeyCapsRecord {
|
|
26
|
+
provider: string;
|
|
27
|
+
lifetimeCapUsd: number;
|
|
28
|
+
dailyCapUsd: number;
|
|
29
|
+
frozen: boolean;
|
|
30
|
+
frozenBy?: string;
|
|
31
|
+
frozenAt?: string;
|
|
32
|
+
}
|
|
33
|
+
export interface GoLiveRecord {
|
|
34
|
+
enabled: boolean;
|
|
35
|
+
keyRef: string;
|
|
36
|
+
/** The PIN-designated metered-lease machine (FD-13). */
|
|
37
|
+
designatedMachineId: string;
|
|
38
|
+
designatedAt: string;
|
|
39
|
+
/** Bumped on every designation/cap-lowering (A-M9/A-B4) — the gate re-validates against it. */
|
|
40
|
+
epoch: number;
|
|
41
|
+
}
|
|
42
|
+
export interface CapsStoreFile {
|
|
43
|
+
/** Optimistic-concurrency version — bumped on EVERY write (C5-3). */
|
|
44
|
+
version: number;
|
|
45
|
+
caps: Record<string, KeyCapsRecord>;
|
|
46
|
+
goLive: Record<string, GoLiveRecord>;
|
|
47
|
+
/** Metered-lease epoch — bumped on designation change and cap-lowering. */
|
|
48
|
+
leaseEpoch: number;
|
|
49
|
+
}
|
|
50
|
+
export interface CapsAuditRow {
|
|
51
|
+
ts: string;
|
|
52
|
+
action: string;
|
|
53
|
+
actor: string;
|
|
54
|
+
before: CapsStoreFile;
|
|
55
|
+
after: CapsStoreFile;
|
|
56
|
+
}
|
|
57
|
+
/** Schema-level validator, independent of the rendered-plan machinery (C4-4). */
|
|
58
|
+
export declare function validateCapsFile(f: unknown, knownKeyRefs?: ReadonlySet<string>, knownDoors?: ReadonlySet<string>): string | null;
|
|
59
|
+
export interface RoutingSpendCapsStoreOptions {
|
|
60
|
+
/** The agent's `.instar/` dir. */
|
|
61
|
+
stateDir: string;
|
|
62
|
+
/** Where audit rows append (default `<stateDir>/state/routing-spend-caps-audit.jsonl`). */
|
|
63
|
+
auditPath?: string;
|
|
64
|
+
knownKeyRefs?: ReadonlySet<string>;
|
|
65
|
+
knownDoors?: ReadonlySet<string>;
|
|
66
|
+
now?: () => number;
|
|
67
|
+
}
|
|
68
|
+
export declare class RoutingSpendCapsStore {
|
|
69
|
+
private readonly filePath;
|
|
70
|
+
private readonly auditPath;
|
|
71
|
+
private readonly knownKeyRefs?;
|
|
72
|
+
private readonly knownDoors?;
|
|
73
|
+
private readonly now;
|
|
74
|
+
constructor(opts: RoutingSpendCapsStoreOptions);
|
|
75
|
+
/** Read the store. A missing file is the deny-by-default EMPTY store; a corrupt file throws (fail closed — money state must never be silently defaulted). */
|
|
76
|
+
read(): CapsStoreFile;
|
|
77
|
+
/** Current optimistic-concurrency version (0 when absent). */
|
|
78
|
+
version(): number;
|
|
79
|
+
/**
|
|
80
|
+
* Apply a typed mutation. `expectedVersion` is the optimistic-concurrency check
|
|
81
|
+
* (pass null to skip ONLY for the Bearer freeze path — halting money must never
|
|
82
|
+
* be blocked by a concurrent PIN plan).
|
|
83
|
+
*/
|
|
84
|
+
private mutate;
|
|
85
|
+
/** PIN path: adjust caps for a key (raise or lower; lowering bumps the lease epoch — A-M9). */
|
|
86
|
+
adjustCaps(actor: string, expectedVersion: number, keyRef: string, provider: string, caps: {
|
|
87
|
+
lifetimeCapUsd: number;
|
|
88
|
+
dailyCapUsd: number;
|
|
89
|
+
}): CapsStoreFile;
|
|
90
|
+
/** PIN path: arm/disarm a door + designate the metered-lease machine (FD-13). */
|
|
91
|
+
setGoLive(actor: string, expectedVersion: number, door: string, rec: {
|
|
92
|
+
enabled: boolean;
|
|
93
|
+
keyRef: string;
|
|
94
|
+
designatedMachineId: string;
|
|
95
|
+
}): CapsStoreFile;
|
|
96
|
+
/** PIN path: unfreeze (releasing money is always the operator's). */
|
|
97
|
+
unfreeze(actor: string, expectedVersion: number, keyRef: string): CapsStoreFile;
|
|
98
|
+
/**
|
|
99
|
+
* Bearer path: FREEZE a key — set-TRUE-only (S-F5/X-C5); records the actor; never
|
|
100
|
+
* takes an expectedVersion (halting money is never blocked by a concurrent plan).
|
|
101
|
+
* Creates the caps record from defaults if absent so an un-adjusted key can still
|
|
102
|
+
* be frozen instantly.
|
|
103
|
+
*/
|
|
104
|
+
freeze(actor: string, keyRef: string, defaults?: {
|
|
105
|
+
provider: string;
|
|
106
|
+
lifetimeCapUsd: number;
|
|
107
|
+
dailyCapUsd: number;
|
|
108
|
+
}): CapsStoreFile;
|
|
109
|
+
/** Bearer-read audit trail (most recent last; bounded by the caller's limit). */
|
|
110
|
+
auditLog(limit?: number): CapsAuditRow[];
|
|
111
|
+
private appendAudit;
|
|
112
|
+
}
|
|
113
|
+
//# sourceMappingURL=RoutingSpendCapsStore.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RoutingSpendCapsStore.d.ts","sourceRoot":"","sources":["../../src/core/RoutingSpendCapsStore.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RoutingSpendCapsStore — the DEDICATED PIN-only money-authority store of the
|
|
3
|
+
* Routing Control Room (docs/specs/routing-control-room-spend-alerts.md,
|
|
4
|
+
* Increment B, Surface 2 / S-F2).
|
|
5
|
+
*
|
|
6
|
+
* Caps + go-live + metered-lease designation live in
|
|
7
|
+
* `<stateDir>/state/routing-spend-caps.json`, written ONLY through this store's
|
|
8
|
+
* typed mutators — which the PIN routes (and the Bearer set-true-only freeze)
|
|
9
|
+
* call. The file is NEVER under any `PATCHABLE_CONFIG_KEYS` key, so a Bearer
|
|
10
|
+
* `PATCH /config` can structurally never arm a door, unfreeze a key, or raise a
|
|
11
|
+
* cap (regression-tested).
|
|
12
|
+
*
|
|
13
|
+
* Concurrency/authority discipline:
|
|
14
|
+
* - Every write is atomic (tmp+rename) and bumps the store VERSION — the
|
|
15
|
+
* optimistic-concurrency anchor the rendered-plan commit checks (C5-3):
|
|
16
|
+
* a plan pins the version it read; commit refuses on mismatch.
|
|
17
|
+
* - Every committed mutation ADDITIONALLY passes a schema-level validator
|
|
18
|
+
* independent of the plan machinery (C4-4) — a plan-renderer bug can never
|
|
19
|
+
* become the sole authority boundary.
|
|
20
|
+
* - Every change appends a BEFORE+AFTER audit row (never just the delta).
|
|
21
|
+
* - STOP is cheap: `freeze()` is set-TRUE-only (the Bearer surface); unfreeze
|
|
22
|
+
* goes through the PIN plan path like every other money-releasing action.
|
|
23
|
+
* - Deny-by-default: with no go-live record every metered door stays skipped.
|
|
24
|
+
*/
|
|
25
|
+
import fs from 'node:fs';
|
|
26
|
+
import path from 'node:path';
|
|
27
|
+
const EMPTY = { version: 0, caps: {}, goLive: {}, leaseEpoch: 0 };
|
|
28
|
+
/** Schema-level validator, independent of the rendered-plan machinery (C4-4). */
|
|
29
|
+
export function validateCapsFile(f, knownKeyRefs, knownDoors) {
|
|
30
|
+
if (!f || typeof f !== 'object')
|
|
31
|
+
return 'not an object';
|
|
32
|
+
const file = f;
|
|
33
|
+
if (!Number.isInteger(file.version) || file.version < 0)
|
|
34
|
+
return 'version must be a non-negative integer';
|
|
35
|
+
if (!Number.isInteger(file.leaseEpoch) || file.leaseEpoch < 0)
|
|
36
|
+
return 'leaseEpoch must be a non-negative integer';
|
|
37
|
+
if (!file.caps || typeof file.caps !== 'object')
|
|
38
|
+
return 'caps must be an object';
|
|
39
|
+
if (!file.goLive || typeof file.goLive !== 'object')
|
|
40
|
+
return 'goLive must be an object';
|
|
41
|
+
for (const [keyRef, c] of Object.entries(file.caps)) {
|
|
42
|
+
if (knownKeyRefs && !knownKeyRefs.has(keyRef))
|
|
43
|
+
return `unknown keyRef '${keyRef}'`;
|
|
44
|
+
if (!c || typeof c !== 'object')
|
|
45
|
+
return `caps['${keyRef}'] not an object`;
|
|
46
|
+
if (typeof c.provider !== 'string' || !c.provider.trim())
|
|
47
|
+
return `caps['${keyRef}'].provider missing`;
|
|
48
|
+
for (const capField of ['lifetimeCapUsd', 'dailyCapUsd']) {
|
|
49
|
+
const v = c[capField];
|
|
50
|
+
if (typeof v !== 'number' || !Number.isFinite(v) || v < 0)
|
|
51
|
+
return `caps['${keyRef}'].${capField} must be ≥ 0`;
|
|
52
|
+
}
|
|
53
|
+
if (typeof c.frozen !== 'boolean')
|
|
54
|
+
return `caps['${keyRef}'].frozen must be boolean`;
|
|
55
|
+
}
|
|
56
|
+
for (const [door, g] of Object.entries(file.goLive)) {
|
|
57
|
+
if (knownDoors && !knownDoors.has(door))
|
|
58
|
+
return `unknown door '${door}'`;
|
|
59
|
+
if (!g || typeof g !== 'object')
|
|
60
|
+
return `goLive['${door}'] not an object`;
|
|
61
|
+
if (typeof g.enabled !== 'boolean')
|
|
62
|
+
return `goLive['${door}'].enabled must be boolean`;
|
|
63
|
+
if (typeof g.keyRef !== 'string' || !g.keyRef.trim())
|
|
64
|
+
return `goLive['${door}'].keyRef missing`;
|
|
65
|
+
if (typeof g.designatedMachineId !== 'string' || !g.designatedMachineId.trim())
|
|
66
|
+
return `goLive['${door}'].designatedMachineId missing`;
|
|
67
|
+
if (!Number.isInteger(g.epoch) || g.epoch < 0)
|
|
68
|
+
return `goLive['${door}'].epoch must be a non-negative integer`;
|
|
69
|
+
}
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
export class RoutingSpendCapsStore {
|
|
73
|
+
filePath;
|
|
74
|
+
auditPath;
|
|
75
|
+
knownKeyRefs;
|
|
76
|
+
knownDoors;
|
|
77
|
+
now;
|
|
78
|
+
constructor(opts) {
|
|
79
|
+
const stateSub = path.join(opts.stateDir, 'state');
|
|
80
|
+
fs.mkdirSync(stateSub, { recursive: true });
|
|
81
|
+
this.filePath = path.join(stateSub, 'routing-spend-caps.json');
|
|
82
|
+
this.auditPath = opts.auditPath ?? path.join(stateSub, 'routing-spend-caps-audit.jsonl');
|
|
83
|
+
this.knownKeyRefs = opts.knownKeyRefs;
|
|
84
|
+
this.knownDoors = opts.knownDoors;
|
|
85
|
+
this.now = opts.now ?? (() => Date.now());
|
|
86
|
+
}
|
|
87
|
+
/** Read the store. A missing file is the deny-by-default EMPTY store; a corrupt file throws (fail closed — money state must never be silently defaulted). */
|
|
88
|
+
read() {
|
|
89
|
+
let raw;
|
|
90
|
+
try {
|
|
91
|
+
raw = fs.readFileSync(this.filePath, 'utf-8');
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
// @silent-fallback-ok: an ABSENT store is the legitimate pre-go-live state —
|
|
95
|
+
// deny-by-default (no caps overrides, no doors armed). Only absence is safe
|
|
96
|
+
// to default; a PRESENT-but-corrupt file throws below.
|
|
97
|
+
return structuredClone(EMPTY);
|
|
98
|
+
}
|
|
99
|
+
const parsed = JSON.parse(raw); // throws on corrupt → caller fails closed
|
|
100
|
+
const err = validateCapsFile(parsed, this.knownKeyRefs, this.knownDoors);
|
|
101
|
+
if (err)
|
|
102
|
+
throw new Error(`routing-spend caps store invalid (fail-closed): ${err}`);
|
|
103
|
+
return parsed;
|
|
104
|
+
}
|
|
105
|
+
/** Current optimistic-concurrency version (0 when absent). */
|
|
106
|
+
version() {
|
|
107
|
+
try {
|
|
108
|
+
return this.read().version;
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
// A corrupt store has no usable version; surface a sentinel the plan path
|
|
112
|
+
// will fail to match (commit then refuses — the safe direction).
|
|
113
|
+
return -1;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Apply a typed mutation. `expectedVersion` is the optimistic-concurrency check
|
|
118
|
+
* (pass null to skip ONLY for the Bearer freeze path — halting money must never
|
|
119
|
+
* be blocked by a concurrent PIN plan).
|
|
120
|
+
*/
|
|
121
|
+
mutate(action, actor, expectedVersion, fn) {
|
|
122
|
+
const before = this.read();
|
|
123
|
+
if (expectedVersion !== null && before.version !== expectedVersion) {
|
|
124
|
+
throw new Error(`caps store version drift (expected ${expectedVersion}, is ${before.version}) — re-render the plan`);
|
|
125
|
+
}
|
|
126
|
+
const after = structuredClone(before);
|
|
127
|
+
fn(after);
|
|
128
|
+
after.version = before.version + 1;
|
|
129
|
+
const err = validateCapsFile(after, this.knownKeyRefs, this.knownDoors);
|
|
130
|
+
if (err)
|
|
131
|
+
throw new Error(`caps mutation rejected by schema validator (C4-4): ${err}`);
|
|
132
|
+
const tmp = this.filePath + '.tmp';
|
|
133
|
+
fs.writeFileSync(tmp, JSON.stringify(after, null, 2), { mode: 0o600 });
|
|
134
|
+
fs.renameSync(tmp, this.filePath);
|
|
135
|
+
this.appendAudit({ ts: new Date(this.now()).toISOString(), action, actor, before, after });
|
|
136
|
+
return after;
|
|
137
|
+
}
|
|
138
|
+
/** PIN path: adjust caps for a key (raise or lower; lowering bumps the lease epoch — A-M9). */
|
|
139
|
+
adjustCaps(actor, expectedVersion, keyRef, provider, caps) {
|
|
140
|
+
return this.mutate('caps-adjust', actor, expectedVersion, (f) => {
|
|
141
|
+
const prev = f.caps[keyRef];
|
|
142
|
+
const lowered = prev !== undefined &&
|
|
143
|
+
(caps.lifetimeCapUsd < prev.lifetimeCapUsd || caps.dailyCapUsd < prev.dailyCapUsd);
|
|
144
|
+
f.caps[keyRef] = {
|
|
145
|
+
provider,
|
|
146
|
+
lifetimeCapUsd: caps.lifetimeCapUsd,
|
|
147
|
+
dailyCapUsd: caps.dailyCapUsd,
|
|
148
|
+
frozen: prev?.frozen ?? false,
|
|
149
|
+
...(prev?.frozenBy ? { frozenBy: prev.frozenBy } : {}),
|
|
150
|
+
...(prev?.frozenAt ? { frozenAt: prev.frozenAt } : {}),
|
|
151
|
+
};
|
|
152
|
+
if (lowered)
|
|
153
|
+
f.leaseEpoch += 1; // fenced cap-lowering — the gate re-reads and clamps
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
/** PIN path: arm/disarm a door + designate the metered-lease machine (FD-13). */
|
|
157
|
+
setGoLive(actor, expectedVersion, door, rec) {
|
|
158
|
+
return this.mutate('go-live', actor, expectedVersion, (f) => {
|
|
159
|
+
const prevEpoch = f.goLive[door]?.epoch ?? 0;
|
|
160
|
+
f.goLive[door] = {
|
|
161
|
+
enabled: rec.enabled,
|
|
162
|
+
keyRef: rec.keyRef,
|
|
163
|
+
designatedMachineId: rec.designatedMachineId,
|
|
164
|
+
designatedAt: new Date(this.now()).toISOString(),
|
|
165
|
+
epoch: prevEpoch + 1,
|
|
166
|
+
};
|
|
167
|
+
f.leaseEpoch += 1;
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
/** PIN path: unfreeze (releasing money is always the operator's). */
|
|
171
|
+
unfreeze(actor, expectedVersion, keyRef) {
|
|
172
|
+
return this.mutate('unfreeze', actor, expectedVersion, (f) => {
|
|
173
|
+
const c = f.caps[keyRef];
|
|
174
|
+
if (!c)
|
|
175
|
+
throw new Error(`unfreeze: no caps record for '${keyRef}'`);
|
|
176
|
+
c.frozen = false;
|
|
177
|
+
delete c.frozenBy;
|
|
178
|
+
delete c.frozenAt;
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Bearer path: FREEZE a key — set-TRUE-only (S-F5/X-C5); records the actor; never
|
|
183
|
+
* takes an expectedVersion (halting money is never blocked by a concurrent plan).
|
|
184
|
+
* Creates the caps record from defaults if absent so an un-adjusted key can still
|
|
185
|
+
* be frozen instantly.
|
|
186
|
+
*/
|
|
187
|
+
freeze(actor, keyRef, defaults) {
|
|
188
|
+
return this.mutate('freeze', actor, null, (f) => {
|
|
189
|
+
const c = f.caps[keyRef];
|
|
190
|
+
if (c) {
|
|
191
|
+
c.frozen = true;
|
|
192
|
+
c.frozenBy = actor;
|
|
193
|
+
c.frozenAt = new Date(this.now()).toISOString();
|
|
194
|
+
}
|
|
195
|
+
else {
|
|
196
|
+
f.caps[keyRef] = {
|
|
197
|
+
provider: defaults?.provider ?? 'unknown',
|
|
198
|
+
lifetimeCapUsd: defaults?.lifetimeCapUsd ?? 0,
|
|
199
|
+
dailyCapUsd: defaults?.dailyCapUsd ?? 0,
|
|
200
|
+
frozen: true,
|
|
201
|
+
frozenBy: actor,
|
|
202
|
+
frozenAt: new Date(this.now()).toISOString(),
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
/** Bearer-read audit trail (most recent last; bounded by the caller's limit). */
|
|
208
|
+
auditLog(limit = 100) {
|
|
209
|
+
let raw;
|
|
210
|
+
try {
|
|
211
|
+
raw = fs.readFileSync(this.auditPath, 'utf-8');
|
|
212
|
+
}
|
|
213
|
+
catch {
|
|
214
|
+
// @silent-fallback-ok: no audit yet — an empty log is the fresh-store state.
|
|
215
|
+
return [];
|
|
216
|
+
}
|
|
217
|
+
const rows = [];
|
|
218
|
+
for (const line of raw.split('\n')) {
|
|
219
|
+
if (!line.trim())
|
|
220
|
+
continue;
|
|
221
|
+
try {
|
|
222
|
+
rows.push(JSON.parse(line));
|
|
223
|
+
}
|
|
224
|
+
catch {
|
|
225
|
+
// Malformed-row-skip (torn trailing append).
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
return rows.slice(-Math.max(1, limit));
|
|
230
|
+
}
|
|
231
|
+
appendAudit(row) {
|
|
232
|
+
try {
|
|
233
|
+
fs.appendFileSync(this.auditPath, JSON.stringify(row) + '\n', { mode: 0o600 });
|
|
234
|
+
}
|
|
235
|
+
catch {
|
|
236
|
+
// @silent-fallback-ok: the audit append is observability riding a mutation that
|
|
237
|
+
// already landed atomically; a failed append never rolls back money state.
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
//# sourceMappingURL=RoutingSpendCapsStore.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RoutingSpendCapsStore.js","sourceRoot":"","sources":["../../src/core/RoutingSpendCapsStore.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SpendAlertResolver — the MINIMAL topic-resolver foundation + the two
|
|
3
|
+
* Increment-B alert kinds (stale-price / observed-drift) of the Routing Control
|
|
4
|
+
* Room (docs/specs/routing-control-room-spend-alerts.md, §Surface 2 Alerts,
|
|
5
|
+
* C5-5 sequencing: stale pricing changes money ADMISSION behavior, so its alarm
|
|
6
|
+
* ships WITH the money increment; the full channel abstraction + the remaining
|
|
7
|
+
* emitter set are Increment C).
|
|
8
|
+
*
|
|
9
|
+
* Resolution LADDER (first hit wins; only the last rung creates):
|
|
10
|
+
* 1. Operator-configured `routingSpend.alerts.telegramTopicId` — that id IS the
|
|
11
|
+
* topic; nothing is ever created.
|
|
12
|
+
* 2. The machine-local persisted record (the `persistLifelineTopicId` pattern).
|
|
13
|
+
* (The pool-published half of rung 2 rides the machine-registry heartbeat
|
|
14
|
+
* surface and lands with Increment C — tracked: CMT-1929.)
|
|
15
|
+
* 3. Create ONCE — ONLY when this machine is the CONFIRMED serving-lease holder
|
|
16
|
+
* (fenced; lease re-confirmed within the same bounded staleness window the
|
|
17
|
+
* money gate uses) — as a bounded create-once SYSTEM topic. In-process
|
|
18
|
+
* single-flight guards a burst of first-alerts.
|
|
19
|
+
* Anyone who cannot resolve/create falls back to the LIFELINE — fail toward
|
|
20
|
+
* the lifeline, never toward a possible duplicate.
|
|
21
|
+
*
|
|
22
|
+
* Emission discipline: edge-triggered per dedupe key (an alert latches only on
|
|
23
|
+
* a CONFIRMED send, so a transient failure stays eligible for re-send);
|
|
24
|
+
* metadata-only text (door / age-days / threshold — NEVER a provider body or a
|
|
25
|
+
* key-shaped substring).
|
|
26
|
+
*/
|
|
27
|
+
export interface SpendAlert {
|
|
28
|
+
/** Informational lane in B (stale-price / observed-drift). Money-critical lanes land in C. */
|
|
29
|
+
kind: 'stale-price' | 'observed-drift';
|
|
30
|
+
dedupeKey: string;
|
|
31
|
+
text: string;
|
|
32
|
+
}
|
|
33
|
+
export interface SpendAlertResolverDeps {
|
|
34
|
+
/** Rung 1: the operator-configured topic id (config-read, live). */
|
|
35
|
+
configuredTopicId: () => number | undefined;
|
|
36
|
+
/** Rung 2 (machine-local half): read/persist the auto-created id. */
|
|
37
|
+
readPersistedTopicId: () => number | undefined;
|
|
38
|
+
persistTopicId: (topicId: number) => void;
|
|
39
|
+
/** Rung 3 gate: ms since the serving lease was POSITIVELY re-confirmed (null = not holder / unconfirmed). */
|
|
40
|
+
servingLeaseConfirmedAgoMs: () => number | null;
|
|
41
|
+
/** Create the bounded create-once system topic; returns its id. */
|
|
42
|
+
createTopic: () => Promise<number>;
|
|
43
|
+
/** Deliver one message into a topic; resolves true on CONFIRMED send. */
|
|
44
|
+
sendToTopic: (topicId: number, text: string) => Promise<boolean>;
|
|
45
|
+
/** The lifeline fallback topic id (always-existing system topic), if resolvable. */
|
|
46
|
+
lifelineTopicId: () => number | undefined;
|
|
47
|
+
/** Scrubbed observability sink (jsonl line per emission/decision). */
|
|
48
|
+
audit?: (entry: Record<string, unknown>) => void;
|
|
49
|
+
now?: () => number;
|
|
50
|
+
}
|
|
51
|
+
/** The same bounded staleness window the money gate uses (N-2). */
|
|
52
|
+
export declare const SERVING_LEASE_WINDOW_MS = 60000;
|
|
53
|
+
export declare class SpendAlertResolver {
|
|
54
|
+
private readonly d;
|
|
55
|
+
private readonly now;
|
|
56
|
+
/** Edge-trigger latch: dedupeKey → last CONFIRMED emission ms. */
|
|
57
|
+
private latched;
|
|
58
|
+
/** In-process single-flight for topic creation. */
|
|
59
|
+
private creating;
|
|
60
|
+
constructor(deps: SpendAlertResolverDeps);
|
|
61
|
+
/** Resolve the dedicated topic id via the ladder — or undefined (caller falls back to lifeline). */
|
|
62
|
+
resolveTopicId(): Promise<number | undefined>;
|
|
63
|
+
/**
|
|
64
|
+
* Emit one alert: dedicated topic first, lifeline on ANY failure (unset,
|
|
65
|
+
* create-refused, send-failed). Edge-triggered — a dedupe key that already
|
|
66
|
+
* emitted (confirmed) within `reArmMs` is suppressed; the latch only sets on
|
|
67
|
+
* CONFIRMED delivery, so a transient failure stays eligible.
|
|
68
|
+
*/
|
|
69
|
+
emit(alert: SpendAlert, reArmMs?: number): Promise<'sent' | 'sent-lifeline' | 'suppressed' | 'failed'>;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Build the two Increment-B alert kinds from the price authority's staleness /
|
|
73
|
+
* drift view for the metered doors. Metadata-only wording (S-F7).
|
|
74
|
+
*/
|
|
75
|
+
export declare function buildStalePriceAlert(door: string, ageDays: number, slaDays: number): SpendAlert;
|
|
76
|
+
export declare function buildObservedDriftAlert(door: string, modelId: string, driftPct: number): SpendAlert;
|
|
77
|
+
//# sourceMappingURL=SpendAlertResolver.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SpendAlertResolver.d.ts","sourceRoot":"","sources":["../../src/core/SpendAlertResolver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,MAAM,WAAW,UAAU;IACzB,8FAA8F;IAC9F,IAAI,EAAE,aAAa,GAAG,gBAAgB,CAAC;IACvC,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,sBAAsB;IACrC,oEAAoE;IACpE,iBAAiB,EAAE,MAAM,MAAM,GAAG,SAAS,CAAC;IAC5C,qEAAqE;IACrE,oBAAoB,EAAE,MAAM,MAAM,GAAG,SAAS,CAAC;IAC/C,cAAc,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1C,6GAA6G;IAC7G,0BAA0B,EAAE,MAAM,MAAM,GAAG,IAAI,CAAC;IAChD,mEAAmE;IACnE,WAAW,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;IACnC,yEAAyE;IACzE,WAAW,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACjE,oFAAoF;IACpF,eAAe,EAAE,MAAM,MAAM,GAAG,SAAS,CAAC;IAC1C,sEAAsE;IACtE,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;IACjD,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB;AAED,mEAAmE;AACnE,eAAO,MAAM,uBAAuB,QAAS,CAAC;AAE9C,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAyB;IAC3C,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAe;IACnC,kEAAkE;IAClE,OAAO,CAAC,OAAO,CAA6B;IAC5C,mDAAmD;IACnD,OAAO,CAAC,QAAQ,CAA4C;gBAEhD,IAAI,EAAE,sBAAsB;IAKxC,oGAAoG;IAC9F,cAAc,IAAI,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;IA+BnD;;;;;OAKG;IACG,IAAI,CAAC,KAAK,EAAE,UAAU,EAAE,OAAO,SAAsB,GAAG,OAAO,CAAC,MAAM,GAAG,eAAe,GAAG,YAAY,GAAG,QAAQ,CAAC;CAmC1H;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,UAAU,CAS/F;AAED,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,UAAU,CASnG"}
|