instar 1.3.18 → 1.3.19

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.
Files changed (95) hide show
  1. package/dist/commands/server.d.ts.map +1 -1
  2. package/dist/commands/server.js +104 -6
  3. package/dist/commands/server.js.map +1 -1
  4. package/dist/core/FencedLease.d.ts +126 -0
  5. package/dist/core/FencedLease.d.ts.map +1 -0
  6. package/dist/core/FencedLease.js +189 -0
  7. package/dist/core/FencedLease.js.map +1 -0
  8. package/dist/core/GitLeaseStore.d.ts +46 -0
  9. package/dist/core/GitLeaseStore.d.ts.map +1 -0
  10. package/dist/core/GitLeaseStore.js +79 -0
  11. package/dist/core/GitLeaseStore.js.map +1 -0
  12. package/dist/core/GitSync.d.ts +19 -0
  13. package/dist/core/GitSync.d.ts.map +1 -1
  14. package/dist/core/GitSync.js +77 -0
  15. package/dist/core/GitSync.js.map +1 -1
  16. package/dist/core/HandoffSentinel.d.ts +80 -0
  17. package/dist/core/HandoffSentinel.d.ts.map +1 -0
  18. package/dist/core/HandoffSentinel.js +141 -0
  19. package/dist/core/HandoffSentinel.js.map +1 -0
  20. package/dist/core/LeaseCoordinator.d.ts +141 -0
  21. package/dist/core/LeaseCoordinator.d.ts.map +1 -0
  22. package/dist/core/LeaseCoordinator.js +226 -0
  23. package/dist/core/LeaseCoordinator.js.map +1 -0
  24. package/dist/core/LiveTailBuffer.d.ts +64 -0
  25. package/dist/core/LiveTailBuffer.d.ts.map +1 -0
  26. package/dist/core/LiveTailBuffer.js +122 -0
  27. package/dist/core/LiveTailBuffer.js.map +1 -0
  28. package/dist/core/MultiMachineCoordinator.d.ts +49 -0
  29. package/dist/core/MultiMachineCoordinator.d.ts.map +1 -1
  30. package/dist/core/MultiMachineCoordinator.js +136 -0
  31. package/dist/core/MultiMachineCoordinator.js.map +1 -1
  32. package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
  33. package/dist/core/PostUpdateMigrator.js +24 -0
  34. package/dist/core/PostUpdateMigrator.js.map +1 -1
  35. package/dist/core/RegistrySyncDebouncer.d.ts +77 -0
  36. package/dist/core/RegistrySyncDebouncer.d.ts.map +1 -0
  37. package/dist/core/RegistrySyncDebouncer.js +131 -0
  38. package/dist/core/RegistrySyncDebouncer.js.map +1 -0
  39. package/dist/core/liveTailRedaction.d.ts +36 -0
  40. package/dist/core/liveTailRedaction.d.ts.map +1 -0
  41. package/dist/core/liveTailRedaction.js +57 -0
  42. package/dist/core/liveTailRedaction.js.map +1 -0
  43. package/dist/core/registryReplayGuard.d.ts +59 -0
  44. package/dist/core/registryReplayGuard.d.ts.map +1 -0
  45. package/dist/core/registryReplayGuard.js +86 -0
  46. package/dist/core/registryReplayGuard.js.map +1 -0
  47. package/dist/core/seamlessnessConfig.d.ts +79 -0
  48. package/dist/core/seamlessnessConfig.d.ts.map +1 -0
  49. package/dist/core/seamlessnessConfig.js +131 -0
  50. package/dist/core/seamlessnessConfig.js.map +1 -0
  51. package/dist/core/types.d.ts +117 -0
  52. package/dist/core/types.d.ts.map +1 -1
  53. package/dist/core/types.js.map +1 -1
  54. package/dist/core/wireRegistrySync.d.ts +32 -0
  55. package/dist/core/wireRegistrySync.d.ts.map +1 -0
  56. package/dist/core/wireRegistrySync.js +31 -0
  57. package/dist/core/wireRegistrySync.js.map +1 -0
  58. package/dist/feedback-factory/dispatch/dispatch.d.ts +47 -0
  59. package/dist/feedback-factory/dispatch/dispatch.d.ts.map +1 -0
  60. package/dist/feedback-factory/dispatch/dispatch.js +72 -0
  61. package/dist/feedback-factory/dispatch/dispatch.js.map +1 -0
  62. package/dist/messaging/FencedOutbox.d.ts +42 -0
  63. package/dist/messaging/FencedOutbox.d.ts.map +1 -0
  64. package/dist/messaging/FencedOutbox.js +60 -0
  65. package/dist/messaging/FencedOutbox.js.map +1 -0
  66. package/dist/messaging/MessageProcessingLedger.d.ts +105 -0
  67. package/dist/messaging/MessageProcessingLedger.d.ts.map +1 -0
  68. package/dist/messaging/MessageProcessingLedger.js +206 -0
  69. package/dist/messaging/MessageProcessingLedger.js.map +1 -0
  70. package/dist/messaging/TelegramAdapter.d.ts +18 -1
  71. package/dist/messaging/TelegramAdapter.d.ts.map +1 -1
  72. package/dist/messaging/TelegramAdapter.js +50 -0
  73. package/dist/messaging/TelegramAdapter.js.map +1 -1
  74. package/dist/scaffold/templates.d.ts.map +1 -1
  75. package/dist/scaffold/templates.js +7 -0
  76. package/dist/scaffold/templates.js.map +1 -1
  77. package/dist/server/AgentServer.d.ts.map +1 -1
  78. package/dist/server/AgentServer.js +1 -0
  79. package/dist/server/AgentServer.js.map +1 -1
  80. package/dist/server/routes.d.ts +2 -0
  81. package/dist/server/routes.d.ts.map +1 -1
  82. package/dist/server/routes.js +6 -0
  83. package/dist/server/routes.js.map +1 -1
  84. package/package.json +1 -1
  85. package/src/data/builtin-manifest.json +64 -64
  86. package/src/scaffold/templates.ts +7 -0
  87. package/upgrades/1.3.19.md +23 -0
  88. package/upgrades/side-effects/PR-BODY-cross-machine-seamlessness.md +63 -0
  89. package/upgrades/side-effects/cross-machine-seamlessness-g1-lease.md +71 -0
  90. package/upgrades/side-effects/cross-machine-seamlessness-g2-foundation.md +75 -0
  91. package/upgrades/side-effects/cross-machine-seamlessness-g3-telegram-contract.md +36 -0
  92. package/upgrades/side-effects/cross-machine-seamlessness-g3a-ledger-handoff.md +56 -0
  93. package/upgrades/side-effects/cross-machine-seamlessness-g3bc-livetail-outbox.md +51 -0
  94. package/upgrades/side-effects/cross-machine-seamlessness-p4-awareness-observability.md +54 -0
  95. package/upgrades/side-effects/feedback-factory-dispatch.md +31 -0
@@ -0,0 +1,77 @@
1
+ /**
2
+ * RegistrySyncDebouncer — G2 automated state sync (durable half).
3
+ *
4
+ * Spec: docs/specs/CROSS-MACHINE-SEAMLESSNESS-SPEC.md §8 G2.
5
+ *
6
+ * The Phase-0 root cause: a running server changed its registry (role/lease)
7
+ * but NOTHING pushed that change to git, so cross-machine state only ever
8
+ * propagated by hand. This component closes that: a marked-dirty registry is
9
+ * debounced and committed+pushed to git — but ONLY the authoritative machine
10
+ * (lease holder / awake) writes durable authority-bearing state (single-writer,
11
+ * removing the O(N) thundering-herd of every machine pushing a corrected
12
+ * registry).
13
+ *
14
+ * This is the DURABLE path only. High-frequency liveness/heartbeat (ephemeral)
15
+ * never comes through here — it travels over the tunnel and stays out of git
16
+ * history, so steady-state healthy operation produces ~0 commits (the
17
+ * ephemeral-vs-durable split, spec §Design Principles).
18
+ *
19
+ * Push contention: commitAndPush performs `git push` which fails (returns
20
+ * false) on a non-fast-forward — we never force-push. Repeated failures past a
21
+ * threshold emit a sync-health signal so the lease layer can self-suspend
22
+ * ingress (a machine that cannot prove it is visible must not serve). The
23
+ * pull/rebase reconciliation itself lives in the periodic GitSync.sync cycle.
24
+ */
25
+ export interface SyncHealthState {
26
+ healthy: boolean;
27
+ consecutiveFailures: number;
28
+ lastError?: string;
29
+ lastPushAt?: string;
30
+ lastAttemptAt?: string;
31
+ }
32
+ export interface RegistrySyncDeps {
33
+ /** Commit + push the given paths. Returns true if a commit was pushed. */
34
+ commitAndPush: (message: string, paths: string[]) => boolean;
35
+ /** Absolute path of the registry file to stage. */
36
+ registryAbsPath: string;
37
+ /**
38
+ * Single-writer gate: only flush when THIS machine has authority to write
39
+ * durable registry state (holds the lease / is awake). A standby marking
40
+ * dirty is a no-op push (it still updates its own liveness elsewhere).
41
+ */
42
+ isAuthoritative: () => boolean;
43
+ /** Debounce window (registrySyncDebounceMs). */
44
+ debounceMs: number;
45
+ /** Failures before the sync-health signal flips unhealthy. Default 3. */
46
+ maxConsecutiveFailures?: number;
47
+ /** Sync-health signal consumer (lease layer self-suspends ingress on unhealthy). */
48
+ onSyncHealth?: (s: SyncHealthState) => void;
49
+ logger?: (msg: string) => void;
50
+ now?: () => number;
51
+ }
52
+ export declare class RegistrySyncDebouncer {
53
+ private readonly deps;
54
+ private readonly maxFailures;
55
+ private timer;
56
+ private pendingReasons;
57
+ private flushing;
58
+ private health;
59
+ private stopped;
60
+ constructor(deps: RegistrySyncDeps);
61
+ private now;
62
+ private log;
63
+ /**
64
+ * Mark the registry as needing a durable push. Debounced — many rapid marks
65
+ * within the window coalesce into a single commit (coarse, not per-tick).
66
+ */
67
+ markRegistryDirty(reason: string): void;
68
+ /**
69
+ * Flush immediately (also used by stop()). Returns true if a commit was
70
+ * pushed. A non-authoritative machine returns false without pushing
71
+ * (single-writer). Re-entrancy guarded.
72
+ */
73
+ flush(): Promise<boolean>;
74
+ getHealth(): SyncHealthState;
75
+ stop(): void;
76
+ }
77
+ //# sourceMappingURL=RegistrySyncDebouncer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RegistrySyncDebouncer.d.ts","sourceRoot":"","sources":["../../src/core/RegistrySyncDebouncer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,OAAO,CAAC;IACjB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,gBAAgB;IAC/B,0EAA0E;IAC1E,aAAa,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC;IAC7D,mDAAmD;IACnD,eAAe,EAAE,MAAM,CAAC;IACxB;;;;OAIG;IACH,eAAe,EAAE,MAAM,OAAO,CAAC;IAC/B,gDAAgD;IAChD,UAAU,EAAE,MAAM,CAAC;IACnB,yEAAyE;IACzE,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,oFAAoF;IACpF,YAAY,CAAC,EAAE,CAAC,CAAC,EAAE,eAAe,KAAK,IAAI,CAAC;IAC5C,MAAM,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB;AAED,qBAAa,qBAAqB;IAChC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAmB;IACxC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,KAAK,CAA8C;IAC3D,OAAO,CAAC,cAAc,CAAgB;IACtC,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,MAAM,CAA8D;IAC5E,OAAO,CAAC,OAAO,CAAS;gBAEZ,IAAI,EAAE,gBAAgB;IAKlC,OAAO,CAAC,GAAG;IAIX,OAAO,CAAC,GAAG;IAIX;;;OAGG;IACH,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAWvC;;;;OAIG;IACG,KAAK,IAAI,OAAO,CAAC,OAAO,CAAC;IAqD/B,SAAS,IAAI,eAAe;IAI5B,IAAI,IAAI,IAAI;CAOb"}
@@ -0,0 +1,131 @@
1
+ /**
2
+ * RegistrySyncDebouncer — G2 automated state sync (durable half).
3
+ *
4
+ * Spec: docs/specs/CROSS-MACHINE-SEAMLESSNESS-SPEC.md §8 G2.
5
+ *
6
+ * The Phase-0 root cause: a running server changed its registry (role/lease)
7
+ * but NOTHING pushed that change to git, so cross-machine state only ever
8
+ * propagated by hand. This component closes that: a marked-dirty registry is
9
+ * debounced and committed+pushed to git — but ONLY the authoritative machine
10
+ * (lease holder / awake) writes durable authority-bearing state (single-writer,
11
+ * removing the O(N) thundering-herd of every machine pushing a corrected
12
+ * registry).
13
+ *
14
+ * This is the DURABLE path only. High-frequency liveness/heartbeat (ephemeral)
15
+ * never comes through here — it travels over the tunnel and stays out of git
16
+ * history, so steady-state healthy operation produces ~0 commits (the
17
+ * ephemeral-vs-durable split, spec §Design Principles).
18
+ *
19
+ * Push contention: commitAndPush performs `git push` which fails (returns
20
+ * false) on a non-fast-forward — we never force-push. Repeated failures past a
21
+ * threshold emit a sync-health signal so the lease layer can self-suspend
22
+ * ingress (a machine that cannot prove it is visible must not serve). The
23
+ * pull/rebase reconciliation itself lives in the periodic GitSync.sync cycle.
24
+ */
25
+ export class RegistrySyncDebouncer {
26
+ deps;
27
+ maxFailures;
28
+ timer = null;
29
+ pendingReasons = [];
30
+ flushing = false;
31
+ health = { healthy: true, consecutiveFailures: 0 };
32
+ stopped = false;
33
+ constructor(deps) {
34
+ this.deps = deps;
35
+ this.maxFailures = deps.maxConsecutiveFailures ?? 3;
36
+ }
37
+ now() {
38
+ return (this.deps.now ?? Date.now)();
39
+ }
40
+ log(msg) {
41
+ this.deps.logger?.(`[registry-sync] ${msg}`);
42
+ }
43
+ /**
44
+ * Mark the registry as needing a durable push. Debounced — many rapid marks
45
+ * within the window coalesce into a single commit (coarse, not per-tick).
46
+ */
47
+ markRegistryDirty(reason) {
48
+ if (this.stopped)
49
+ return;
50
+ this.pendingReasons.push(reason);
51
+ if (this.timer)
52
+ return; // already scheduled
53
+ this.timer = setTimeout(() => {
54
+ this.timer = null;
55
+ void this.flush();
56
+ }, this.deps.debounceMs);
57
+ if (this.timer.unref)
58
+ this.timer.unref();
59
+ }
60
+ /**
61
+ * Flush immediately (also used by stop()). Returns true if a commit was
62
+ * pushed. A non-authoritative machine returns false without pushing
63
+ * (single-writer). Re-entrancy guarded.
64
+ */
65
+ async flush() {
66
+ if (this.flushing)
67
+ return false;
68
+ if (this.pendingReasons.length === 0)
69
+ return false;
70
+ this.flushing = true;
71
+ const reasons = this.pendingReasons.slice(0, 8);
72
+ this.pendingReasons = [];
73
+ this.health.lastAttemptAt = new Date(this.now()).toISOString();
74
+ try {
75
+ if (!this.deps.isAuthoritative()) {
76
+ // Single-writer: a standby never writes durable authority-bearing state.
77
+ this.log(`skip flush — not authoritative (${reasons.length} pending reason(s) dropped)`);
78
+ return false;
79
+ }
80
+ const message = `chore(mesh): registry sync — ${reasons.join('; ').slice(0, 200)}`;
81
+ const pushed = this.deps.commitAndPush(message, [this.deps.registryAbsPath]);
82
+ if (pushed) {
83
+ this.health = {
84
+ healthy: true,
85
+ consecutiveFailures: 0,
86
+ lastPushAt: new Date(this.now()).toISOString(),
87
+ lastAttemptAt: this.health.lastAttemptAt,
88
+ };
89
+ this.deps.onSyncHealth?.(this.health);
90
+ this.log(`pushed: ${message}`);
91
+ return true;
92
+ }
93
+ // commitAndPush returns false either when nothing changed (benign) OR on
94
+ // a push failure. We can't distinguish here, so a "nothing to push" is
95
+ // treated as success (no failure increment) only if a prior push exists.
96
+ // To stay conservative we treat false as a non-failure unless we can see
97
+ // the working tree is dirty — handled by the caller's periodic sync.
98
+ this.log(`commitAndPush returned false (no-op or push declined): ${message}`);
99
+ return false;
100
+ }
101
+ catch (err) {
102
+ const lastError = err instanceof Error ? err.message : String(err);
103
+ this.health = {
104
+ healthy: this.health.consecutiveFailures + 1 < this.maxFailures,
105
+ consecutiveFailures: this.health.consecutiveFailures + 1,
106
+ lastError,
107
+ lastPushAt: this.health.lastPushAt,
108
+ lastAttemptAt: this.health.lastAttemptAt,
109
+ };
110
+ this.deps.onSyncHealth?.(this.health);
111
+ this.log(`push failed (${this.health.consecutiveFailures}/${this.maxFailures}): ${lastError}`);
112
+ // Re-queue the reasons so the next mark/flush retries them.
113
+ this.pendingReasons.unshift(...reasons);
114
+ return false;
115
+ }
116
+ finally {
117
+ this.flushing = false;
118
+ }
119
+ }
120
+ getHealth() {
121
+ return { ...this.health };
122
+ }
123
+ stop() {
124
+ this.stopped = true;
125
+ if (this.timer) {
126
+ clearTimeout(this.timer);
127
+ this.timer = null;
128
+ }
129
+ }
130
+ }
131
+ //# sourceMappingURL=RegistrySyncDebouncer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RegistrySyncDebouncer.js","sourceRoot":"","sources":["../../src/core/RegistrySyncDebouncer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * liveTailRedaction — versioned secret-redaction for the live tail (spec §8 G3c).
3
+ *
4
+ * The live tail carries the most sensitive data in the system (user messages,
5
+ * tool outputs, incidentally-present secrets). Before any tail content leaves
6
+ * this machine it is scrubbed of credential-shaped material. The category set
7
+ * is a NAMED, VERSIONED enum (not an ad-hoc inline regex) so it can be extended
8
+ * as new token/credential shapes appear — combined with carry-by-reference for
9
+ * large tool output (§8 G3b), the tail minimizes raw sensitive bytes
10
+ * structurally rather than relying on pattern-matching alone.
11
+ *
12
+ * Bump REDACTION_CATEGORY_VERSION when the category set changes, so a receiver
13
+ * can record which version scrubbed a flush.
14
+ */
15
+ export declare const REDACTION_CATEGORY_VERSION = 1;
16
+ export declare enum RedactionCategory {
17
+ BearerToken = "bearer-token",
18
+ ApiKey = "api-key",
19
+ PrivateKeyBlock = "private-key-block",
20
+ AwsAccessKey = "aws-access-key",
21
+ SecretAssignment = "secret-assignment",
22
+ JwtLike = "jwt-like"
23
+ }
24
+ export interface RedactionResult {
25
+ text: string;
26
+ redactedCount: number;
27
+ categories: RedactionCategory[];
28
+ version: number;
29
+ }
30
+ /**
31
+ * Redact credential-shaped material from a string. Returns the scrubbed text,
32
+ * how many redactions occurred, and which categories fired (for the flush's
33
+ * redaction metadata).
34
+ */
35
+ export declare function redactForLiveTail(input: string): RedactionResult;
36
+ //# sourceMappingURL=liveTailRedaction.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"liveTailRedaction.d.ts","sourceRoot":"","sources":["../../src/core/liveTailRedaction.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,eAAO,MAAM,0BAA0B,IAAI,CAAC;AAE5C,oBAAY,iBAAiB;IAC3B,WAAW,iBAAiB;IAC5B,MAAM,YAAY;IAClB,eAAe,sBAAsB;IACrC,YAAY,mBAAmB;IAC/B,gBAAgB,sBAAsB;IACtC,OAAO,aAAa;CACrB;AAuBD,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,iBAAiB,EAAE,CAAC;IAChC,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,eAAe,CAYhE"}
@@ -0,0 +1,57 @@
1
+ /**
2
+ * liveTailRedaction — versioned secret-redaction for the live tail (spec §8 G3c).
3
+ *
4
+ * The live tail carries the most sensitive data in the system (user messages,
5
+ * tool outputs, incidentally-present secrets). Before any tail content leaves
6
+ * this machine it is scrubbed of credential-shaped material. The category set
7
+ * is a NAMED, VERSIONED enum (not an ad-hoc inline regex) so it can be extended
8
+ * as new token/credential shapes appear — combined with carry-by-reference for
9
+ * large tool output (§8 G3b), the tail minimizes raw sensitive bytes
10
+ * structurally rather than relying on pattern-matching alone.
11
+ *
12
+ * Bump REDACTION_CATEGORY_VERSION when the category set changes, so a receiver
13
+ * can record which version scrubbed a flush.
14
+ */
15
+ export const REDACTION_CATEGORY_VERSION = 1;
16
+ export var RedactionCategory;
17
+ (function (RedactionCategory) {
18
+ RedactionCategory["BearerToken"] = "bearer-token";
19
+ RedactionCategory["ApiKey"] = "api-key";
20
+ RedactionCategory["PrivateKeyBlock"] = "private-key-block";
21
+ RedactionCategory["AwsAccessKey"] = "aws-access-key";
22
+ RedactionCategory["SecretAssignment"] = "secret-assignment";
23
+ RedactionCategory["JwtLike"] = "jwt-like";
24
+ })(RedactionCategory || (RedactionCategory = {}));
25
+ // Conservative, high-precision patterns — false positives (over-redaction) are
26
+ // cheaper than leaking a secret over the wire. Each is global + case-insensitive
27
+ // where appropriate.
28
+ const RULES = [
29
+ { category: RedactionCategory.PrivateKeyBlock, pattern: /-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z ]*PRIVATE KEY-----/g },
30
+ { category: RedactionCategory.BearerToken, pattern: /\bBearer\s+[A-Za-z0-9._\-]{12,}/gi },
31
+ { category: RedactionCategory.AwsAccessKey, pattern: /\bAKIA[0-9A-Z]{16}\b/g },
32
+ { category: RedactionCategory.JwtLike, pattern: /\beyJ[A-Za-z0-9_\-]{8,}\.[A-Za-z0-9_\-]{8,}\.[A-Za-z0-9_\-]{8,}\b/g },
33
+ // key/secret/token/password = "value" or : value
34
+ { category: RedactionCategory.SecretAssignment, pattern: /\b(?:api[_-]?key|secret|token|password|passwd|client[_-]?secret)\b\s*[:=]\s*["']?[A-Za-z0-9._\-]{8,}["']?/gi },
35
+ // Generic long opaque api-key-ish tokens (sk-, ghp_, xoxb-, etc.)
36
+ { category: RedactionCategory.ApiKey, pattern: /\b(?:sk|pk|rk|ghp|gho|ghs|xox[bpoa])[_-][A-Za-z0-9]{16,}\b/g },
37
+ ];
38
+ const PLACEHOLDER = (c) => `[redacted:${c}]`;
39
+ /**
40
+ * Redact credential-shaped material from a string. Returns the scrubbed text,
41
+ * how many redactions occurred, and which categories fired (for the flush's
42
+ * redaction metadata).
43
+ */
44
+ export function redactForLiveTail(input) {
45
+ let text = input;
46
+ let redactedCount = 0;
47
+ const categories = new Set();
48
+ for (const { category, pattern } of RULES) {
49
+ text = text.replace(pattern, () => {
50
+ redactedCount++;
51
+ categories.add(category);
52
+ return PLACEHOLDER(category);
53
+ });
54
+ }
55
+ return { text, redactedCount, categories: [...categories], version: REDACTION_CATEGORY_VERSION };
56
+ }
57
+ //# sourceMappingURL=liveTailRedaction.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"liveTailRedaction.js","sourceRoot":"","sources":["../../src/core/liveTailRedaction.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,MAAM,CAAC,MAAM,0BAA0B,GAAG,CAAC,CAAC;AAE5C,MAAM,CAAN,IAAY,iBAOX;AAPD,WAAY,iBAAiB;IAC3B,iDAA4B,CAAA;IAC5B,uCAAkB,CAAA;IAClB,0DAAqC,CAAA;IACrC,oDAA+B,CAAA;IAC/B,2DAAsC,CAAA;IACtC,yCAAoB,CAAA;AACtB,CAAC,EAPW,iBAAiB,KAAjB,iBAAiB,QAO5B;AAOD,+EAA+E;AAC/E,iFAAiF;AACjF,qBAAqB;AACrB,MAAM,KAAK,GAAmB;IAC5B,EAAE,QAAQ,EAAE,iBAAiB,CAAC,eAAe,EAAE,OAAO,EAAE,6EAA6E,EAAE;IACvI,EAAE,QAAQ,EAAE,iBAAiB,CAAC,WAAW,EAAE,OAAO,EAAE,mCAAmC,EAAE;IACzF,EAAE,QAAQ,EAAE,iBAAiB,CAAC,YAAY,EAAE,OAAO,EAAE,uBAAuB,EAAE;IAC9E,EAAE,QAAQ,EAAE,iBAAiB,CAAC,OAAO,EAAE,OAAO,EAAE,oEAAoE,EAAE;IACtH,iDAAiD;IACjD,EAAE,QAAQ,EAAE,iBAAiB,CAAC,gBAAgB,EAAE,OAAO,EAAE,6GAA6G,EAAE;IACxK,kEAAkE;IAClE,EAAE,QAAQ,EAAE,iBAAiB,CAAC,MAAM,EAAE,OAAO,EAAE,6DAA6D,EAAE;CAC/G,CAAC;AAEF,MAAM,WAAW,GAAG,CAAC,CAAoB,EAAE,EAAE,CAAC,aAAa,CAAC,GAAG,CAAC;AAShE;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAAC,KAAa;IAC7C,IAAI,IAAI,GAAG,KAAK,CAAC;IACjB,IAAI,aAAa,GAAG,CAAC,CAAC;IACtB,MAAM,UAAU,GAAG,IAAI,GAAG,EAAqB,CAAC;IAChD,KAAK,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,KAAK,EAAE,CAAC;QAC1C,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,GAAG,EAAE;YAChC,aAAa,EAAE,CAAC;YAChB,UAAU,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;YACzB,OAAO,WAAW,CAAC,QAAQ,CAAC,CAAC;QAC/B,CAAC,CAAC,CAAC;IACL,CAAC;IACD,OAAO,EAAE,IAAI,EAAE,aAAa,EAAE,UAAU,EAAE,CAAC,GAAG,UAAU,CAAC,EAAE,OAAO,EAAE,0BAA0B,EAAE,CAAC;AACnG,CAAC"}
@@ -0,0 +1,59 @@
1
+ /**
2
+ * registryReplayGuard — G2 replay/freshness + unknown-key constraint (spec §8 G2).
3
+ *
4
+ * Validates an incoming (pulled) registry entry before it is applied locally.
5
+ * Signed-commit verification (v3) already rejects unsigned/revoked commits;
6
+ * this guard adds the content-level checks a signature alone cannot give:
7
+ *
8
+ * 1. Replay/freshness — a pulled entry whose per-author `syncSequence` is not
9
+ * strictly greater than the last applied for its author is discarded.
10
+ * 2. Epoch floor — a pulled entry whose `authoredUnderEpoch` is below the
11
+ * current committed epoch is discarded. This catches the case a per-author
12
+ * sequence cannot: a machine that wiped/restored local state (sequence
13
+ * resets to 0) or re-keyed cannot smuggle in an authority-bearing write,
14
+ * because its stale leaseEpoch is rejected regardless of a locally-monotonic
15
+ * sequence.
16
+ * 3. Unknown-key first commit — the FIRST commit from a previously-unseen
17
+ * machineId is accepted ONLY if it is role:standby + rejoined:true (or a
18
+ * pairing-join record). Any unknown-key first commit asserting an awake
19
+ * role or a lease claim is rejected — a rejoining machine must pull-and-read
20
+ * before writing, never assert its stale prior role.
21
+ *
22
+ * Pure logic, unit-tested on both sides of every boundary.
23
+ */
24
+ import type { MachineRegistryEntry } from './types.js';
25
+ export interface ReplayGuardInput {
26
+ machineId: string;
27
+ /** The incoming (pulled) entry for this machine. */
28
+ incoming: MachineRegistryEntry;
29
+ /** The locally-known entry for this machine (undefined = previously unseen). */
30
+ local?: MachineRegistryEntry;
31
+ /** The current committed lease epoch (registry.lease?.epoch ?? 0). */
32
+ currentCommittedEpoch: number;
33
+ /** Whether this incoming entry carries a valid pairing-join record (allows an awake-less first write). */
34
+ hasValidJoinRecord?: boolean;
35
+ }
36
+ export interface ReplayDecision {
37
+ accept: boolean;
38
+ reason: string;
39
+ }
40
+ export declare function evaluateRegistryEntry(input: ReplayGuardInput): ReplayDecision;
41
+ /**
42
+ * Reconcile a full incoming registry against the local one, entry by entry.
43
+ * Returns the entries to apply (accepted) and the rejected ones with reasons
44
+ * (logged, never applied). The lease object is reconciled separately by the
45
+ * FencedLease layer (epoch CAS), not here.
46
+ */
47
+ export declare function reconcileRegistryEntries(opts: {
48
+ localEntries: Record<string, MachineRegistryEntry>;
49
+ incomingEntries: Record<string, MachineRegistryEntry>;
50
+ currentCommittedEpoch: number;
51
+ joinRecords?: ReadonlySet<string>;
52
+ }): {
53
+ accepted: Record<string, MachineRegistryEntry>;
54
+ rejected: Array<{
55
+ machineId: string;
56
+ reason: string;
57
+ }>;
58
+ };
59
+ //# sourceMappingURL=registryReplayGuard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registryReplayGuard.d.ts","sourceRoot":"","sources":["../../src/core/registryReplayGuard.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAEvD,MAAM,WAAW,gBAAgB;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,oDAAoD;IACpD,QAAQ,EAAE,oBAAoB,CAAC;IAC/B,gFAAgF;IAChF,KAAK,CAAC,EAAE,oBAAoB,CAAC;IAC7B,sEAAsE;IACtE,qBAAqB,EAAE,MAAM,CAAC;IAC9B,0GAA0G;IAC1G,kBAAkB,CAAC,EAAE,OAAO,CAAC;CAC9B;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,OAAO,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,gBAAgB,GAAG,cAAc,CAuC7E;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,EAAE;IAC7C,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,oBAAoB,CAAC,CAAC;IACnD,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,oBAAoB,CAAC,CAAC;IACtD,qBAAqB,EAAE,MAAM,CAAC;IAC9B,WAAW,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;CACnC,GAAG;IACF,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,oBAAoB,CAAC,CAAC;IAC/C,QAAQ,EAAE,KAAK,CAAC;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACxD,CAoBA"}
@@ -0,0 +1,86 @@
1
+ /**
2
+ * registryReplayGuard — G2 replay/freshness + unknown-key constraint (spec §8 G2).
3
+ *
4
+ * Validates an incoming (pulled) registry entry before it is applied locally.
5
+ * Signed-commit verification (v3) already rejects unsigned/revoked commits;
6
+ * this guard adds the content-level checks a signature alone cannot give:
7
+ *
8
+ * 1. Replay/freshness — a pulled entry whose per-author `syncSequence` is not
9
+ * strictly greater than the last applied for its author is discarded.
10
+ * 2. Epoch floor — a pulled entry whose `authoredUnderEpoch` is below the
11
+ * current committed epoch is discarded. This catches the case a per-author
12
+ * sequence cannot: a machine that wiped/restored local state (sequence
13
+ * resets to 0) or re-keyed cannot smuggle in an authority-bearing write,
14
+ * because its stale leaseEpoch is rejected regardless of a locally-monotonic
15
+ * sequence.
16
+ * 3. Unknown-key first commit — the FIRST commit from a previously-unseen
17
+ * machineId is accepted ONLY if it is role:standby + rejoined:true (or a
18
+ * pairing-join record). Any unknown-key first commit asserting an awake
19
+ * role or a lease claim is rejected — a rejoining machine must pull-and-read
20
+ * before writing, never assert its stale prior role.
21
+ *
22
+ * Pure logic, unit-tested on both sides of every boundary.
23
+ */
24
+ export function evaluateRegistryEntry(input) {
25
+ const { incoming, local, currentCommittedEpoch, hasValidJoinRecord } = input;
26
+ // ── Unknown-key first commit constraint ───────────────────────────
27
+ if (!local) {
28
+ if (hasValidJoinRecord) {
29
+ return { accept: true, reason: 'unknown-key-with-valid-join-record' };
30
+ }
31
+ const isStandby = incoming.role === 'standby';
32
+ const isRejoin = incoming.rejoined === true;
33
+ if (isStandby && isRejoin) {
34
+ return { accept: true, reason: 'unknown-key-standby-rejoin' };
35
+ }
36
+ return {
37
+ accept: false,
38
+ reason: `unknown-key-first-commit must be standby+rejoined (got role=${incoming.role}, rejoined=${incoming.rejoined})`,
39
+ };
40
+ }
41
+ // ── Replay/freshness (per-author monotonic sequence) ──────────────
42
+ const localSeq = local.syncSequence ?? -1;
43
+ const incomingSeq = incoming.syncSequence ?? -1;
44
+ if (incomingSeq <= localSeq) {
45
+ return {
46
+ accept: false,
47
+ reason: `stale-sync-sequence (incoming ${incomingSeq} <= local ${localSeq})`,
48
+ };
49
+ }
50
+ // ── Epoch floor (catches wiped/re-keyed sequence reset) ───────────
51
+ const authoredUnder = incoming.authoredUnderEpoch ?? 0;
52
+ if (authoredUnder < currentCommittedEpoch) {
53
+ return {
54
+ accept: false,
55
+ reason: `below-epoch-floor (authoredUnderEpoch ${authoredUnder} < committed ${currentCommittedEpoch})`,
56
+ };
57
+ }
58
+ return { accept: true, reason: 'fresh' };
59
+ }
60
+ /**
61
+ * Reconcile a full incoming registry against the local one, entry by entry.
62
+ * Returns the entries to apply (accepted) and the rejected ones with reasons
63
+ * (logged, never applied). The lease object is reconciled separately by the
64
+ * FencedLease layer (epoch CAS), not here.
65
+ */
66
+ export function reconcileRegistryEntries(opts) {
67
+ const accepted = {};
68
+ const rejected = [];
69
+ for (const [machineId, incoming] of Object.entries(opts.incomingEntries)) {
70
+ const decision = evaluateRegistryEntry({
71
+ machineId,
72
+ incoming,
73
+ local: opts.localEntries[machineId],
74
+ currentCommittedEpoch: opts.currentCommittedEpoch,
75
+ hasValidJoinRecord: opts.joinRecords?.has(machineId) ?? false,
76
+ });
77
+ if (decision.accept) {
78
+ accepted[machineId] = incoming;
79
+ }
80
+ else {
81
+ rejected.push({ machineId, reason: decision.reason });
82
+ }
83
+ }
84
+ return { accepted, rejected };
85
+ }
86
+ //# sourceMappingURL=registryReplayGuard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registryReplayGuard.js","sourceRoot":"","sources":["../../src/core/registryReplayGuard.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAqBH,MAAM,UAAU,qBAAqB,CAAC,KAAuB;IAC3D,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,qBAAqB,EAAE,kBAAkB,EAAE,GAAG,KAAK,CAAC;IAE7E,qEAAqE;IACrE,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,IAAI,kBAAkB,EAAE,CAAC;YACvB,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,oCAAoC,EAAE,CAAC;QACxE,CAAC;QACD,MAAM,SAAS,GAAG,QAAQ,CAAC,IAAI,KAAK,SAAS,CAAC;QAC9C,MAAM,QAAQ,GAAG,QAAQ,CAAC,QAAQ,KAAK,IAAI,CAAC;QAC5C,IAAI,SAAS,IAAI,QAAQ,EAAE,CAAC;YAC1B,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,4BAA4B,EAAE,CAAC;QAChE,CAAC;QACD,OAAO;YACL,MAAM,EAAE,KAAK;YACb,MAAM,EAAE,+DAA+D,QAAQ,CAAC,IAAI,cAAc,QAAQ,CAAC,QAAQ,GAAG;SACvH,CAAC;IACJ,CAAC;IAED,qEAAqE;IACrE,MAAM,QAAQ,GAAG,KAAK,CAAC,YAAY,IAAI,CAAC,CAAC,CAAC;IAC1C,MAAM,WAAW,GAAG,QAAQ,CAAC,YAAY,IAAI,CAAC,CAAC,CAAC;IAChD,IAAI,WAAW,IAAI,QAAQ,EAAE,CAAC;QAC5B,OAAO;YACL,MAAM,EAAE,KAAK;YACb,MAAM,EAAE,iCAAiC,WAAW,aAAa,QAAQ,GAAG;SAC7E,CAAC;IACJ,CAAC;IAED,qEAAqE;IACrE,MAAM,aAAa,GAAG,QAAQ,CAAC,kBAAkB,IAAI,CAAC,CAAC;IACvD,IAAI,aAAa,GAAG,qBAAqB,EAAE,CAAC;QAC1C,OAAO;YACL,MAAM,EAAE,KAAK;YACb,MAAM,EAAE,yCAAyC,aAAa,gBAAgB,qBAAqB,GAAG;SACvG,CAAC;IACJ,CAAC;IAED,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;AAC3C,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,wBAAwB,CAAC,IAKxC;IAIC,MAAM,QAAQ,GAAyC,EAAE,CAAC;IAC1D,MAAM,QAAQ,GAAiD,EAAE,CAAC;IAElE,KAAK,MAAM,CAAC,SAAS,EAAE,QAAQ,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,eAAe,CAAC,EAAE,CAAC;QACzE,MAAM,QAAQ,GAAG,qBAAqB,CAAC;YACrC,SAAS;YACT,QAAQ;YACR,KAAK,EAAE,IAAI,CAAC,YAAY,CAAC,SAAS,CAAC;YACnC,qBAAqB,EAAE,IAAI,CAAC,qBAAqB;YACjD,kBAAkB,EAAE,IAAI,CAAC,WAAW,EAAE,GAAG,CAAC,SAAS,CAAC,IAAI,KAAK;SAC9D,CAAC,CAAC;QACH,IAAI,QAAQ,CAAC,MAAM,EAAE,CAAC;YACpB,QAAQ,CAAC,SAAS,CAAC,GAAG,QAAQ,CAAC;QACjC,CAAC;aAAM,CAAC;YACN,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC;QACxD,CAAC;IACH,CAAC;IAED,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC;AAChC,CAAC"}
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Cross-Machine Seamlessness — config resolution + invariant validation.
3
+ *
4
+ * Spec: docs/specs/CROSS-MACHINE-SEAMLESSNESS-SPEC.md §9 (Tunability).
5
+ *
6
+ * Resolves the optional `multiMachine` seamlessness knobs to concrete values
7
+ * with sane 2-machine-personal-use defaults, auto-derives the standby pull
8
+ * cadence, and enforces the cross-knob invariants. A violating config is
9
+ * REJECTED with a clear message rather than degrading silently — so a user
10
+ * widening `ingressHeartbeatMs` (which widens `leaseTtlMs`) cannot quietly
11
+ * invalidate the RPO bound the spec promises (§3).
12
+ */
13
+ import type { MultiMachineConfig } from './types.js';
14
+ /**
15
+ * The mesh protocol version this build speaks. A machine below this version
16
+ * is ineligible for the awake lease during a seamless handoff (spec §11
17
+ * partial-migration safety). Bump only on a breaking coordination change.
18
+ */
19
+ export declare const SEAMLESSNESS_PROTOCOL_VERSION = 1;
20
+ /** Fully-resolved seamlessness knobs (every field concrete). */
21
+ export interface ResolvedSeamlessnessConfig {
22
+ ingressHeartbeatMs: number;
23
+ registrySyncDebounceMs: number;
24
+ standbyPullIntervalMs: number;
25
+ failoverThresholdMs: number;
26
+ leaseTtlMs: number;
27
+ liveTailTransport: 'tunnel' | 'git';
28
+ liveTailMaxStalenessMs: number;
29
+ liveTailPushRateMs: number;
30
+ liveTailOutOfOrderTimeoutMs: number;
31
+ liveTailMaxBytesPerTopic: number;
32
+ handoffAckTimeoutMs: number;
33
+ minHandoffIntervalMs: number;
34
+ splitBrainEscalationCooldownMs: number;
35
+ handoffBar: 'near-instant' | 'relaxed';
36
+ maxProcessingMs: number;
37
+ protocolVersion: number;
38
+ }
39
+ export declare class SeamlessnessConfigError extends Error {
40
+ constructor(message: string);
41
+ }
42
+ /**
43
+ * Resolve the failover threshold (ms) from the existing config field.
44
+ * `failoverTimeoutMinutes` is the canonical knob; default 15 min.
45
+ * Nullish-coalescing (not ||) so an explicit 0 isn't clobbered — but 0 is
46
+ * itself invalid and caught by validation.
47
+ */
48
+ export declare function resolveFailoverThresholdMs(mm?: MultiMachineConfig): number;
49
+ /**
50
+ * Resolve all seamlessness knobs to concrete values. Auto-derives
51
+ * `standbyPullIntervalMs` (failoverThresholdMs/4) and the tail/lease-linked
52
+ * defaults. Does NOT validate — call validateSeamlessnessInvariants() after.
53
+ */
54
+ export declare function resolveSeamlessnessConfig(mm?: MultiMachineConfig): ResolvedSeamlessnessConfig;
55
+ /**
56
+ * Validate the cross-knob invariants (spec §9). Returns the list of
57
+ * violations (empty = valid). Callers that want hard-rejection use
58
+ * assertSeamlessnessInvariants().
59
+ *
60
+ * Invariants:
61
+ * 1. standbyPullIntervalMs < failoverThresholdMs / 3
62
+ * — a standby must pull often enough to notice a dead holder well
63
+ * before the failover threshold, or failover races stale data.
64
+ * 2. standbyPullIntervalMs < leaseTtlMs
65
+ * — a standby must refresh its git-committed epoch view at least once
66
+ * per lease lifetime, or it decides max(tunnel,git) on a stale git floor.
67
+ * 3. liveTailPushRateMs ≤ liveTailMaxStalenessMs
68
+ * — the holder must push tail flushes at least as often as the RPO bound,
69
+ * or the promised RPO is unachievable.
70
+ * Plus basic positivity (a 0/negative cadence is never valid).
71
+ */
72
+ export declare function validateSeamlessnessInvariants(c: ResolvedSeamlessnessConfig): string[];
73
+ /**
74
+ * Resolve + validate in one step, throwing SeamlessnessConfigError on any
75
+ * invariant violation. Use at server startup so a bad config is rejected
76
+ * loudly rather than degrading silently.
77
+ */
78
+ export declare function assertSeamlessnessInvariants(mm?: MultiMachineConfig): ResolvedSeamlessnessConfig;
79
+ //# sourceMappingURL=seamlessnessConfig.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"seamlessnessConfig.d.ts","sourceRoot":"","sources":["../../src/core/seamlessnessConfig.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAErD;;;;GAIG;AACH,eAAO,MAAM,6BAA6B,IAAI,CAAC;AAE/C,gEAAgE;AAChE,MAAM,WAAW,0BAA0B;IACzC,kBAAkB,EAAE,MAAM,CAAC;IAC3B,sBAAsB,EAAE,MAAM,CAAC;IAC/B,qBAAqB,EAAE,MAAM,CAAC;IAC9B,mBAAmB,EAAE,MAAM,CAAC;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,iBAAiB,EAAE,QAAQ,GAAG,KAAK,CAAC;IACpC,sBAAsB,EAAE,MAAM,CAAC;IAC/B,kBAAkB,EAAE,MAAM,CAAC;IAC3B,2BAA2B,EAAE,MAAM,CAAC;IACpC,wBAAwB,EAAE,MAAM,CAAC;IACjC,mBAAmB,EAAE,MAAM,CAAC;IAC5B,oBAAoB,EAAE,MAAM,CAAC;IAC7B,8BAA8B,EAAE,MAAM,CAAC;IACvC,UAAU,EAAE,cAAc,GAAG,SAAS,CAAC;IACvC,eAAe,EAAE,MAAM,CAAC;IACxB,eAAe,EAAE,MAAM,CAAC;CACzB;AAED,qBAAa,uBAAwB,SAAQ,KAAK;gBACpC,OAAO,EAAE,MAAM;CAI5B;AAED;;;;;GAKG;AACH,wBAAgB,0BAA0B,CAAC,EAAE,CAAC,EAAE,kBAAkB,GAAG,MAAM,CAG1E;AAED;;;;GAIG;AACH,wBAAgB,yBAAyB,CAAC,EAAE,CAAC,EAAE,kBAAkB,GAAG,0BAA0B,CA+B7F;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,8BAA8B,CAAC,CAAC,EAAE,0BAA0B,GAAG,MAAM,EAAE,CAqCtF;AAED;;;;GAIG;AACH,wBAAgB,4BAA4B,CAAC,EAAE,CAAC,EAAE,kBAAkB,GAAG,0BAA0B,CAUhG"}