@rubytech/create-maxy-code 0.1.475 → 0.1.477

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 (89) hide show
  1. package/package.json +1 -1
  2. package/payload/platform/lib/storage-broker/dist/cf-exec.d.ts.map +1 -1
  3. package/payload/platform/lib/storage-broker/dist/cf-exec.js +3 -2
  4. package/payload/platform/lib/storage-broker/dist/cf-exec.js.map +1 -1
  5. package/payload/platform/lib/storage-broker/src/cf-exec.ts +3 -2
  6. package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +3 -3
  7. package/payload/platform/plugins/cloudflare/skills/calendar-site/template/schema.sql +13 -3
  8. package/payload/platform/plugins/docs/references/calendar-booking.md +2 -2
  9. package/payload/platform/plugins/scheduling/PLUGIN.md +2 -2
  10. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/booking-block-sqlite.test.d.ts +2 -0
  11. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/booking-block-sqlite.test.d.ts.map +1 -0
  12. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/booking-block-sqlite.test.js +150 -0
  13. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/booking-block-sqlite.test.js.map +1 -0
  14. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/booking-block.test.d.ts +2 -0
  15. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/booking-block.test.d.ts.map +1 -0
  16. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/booking-block.test.js +248 -0
  17. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/booking-block.test.js.map +1 -0
  18. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/booking-reconcile.test.js +158 -23
  19. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/booking-reconcile.test.js.map +1 -1
  20. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/booking-schema-migration.test.d.ts +2 -0
  21. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/booking-schema-migration.test.d.ts.map +1 -0
  22. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/booking-schema-migration.test.js +200 -0
  23. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/booking-schema-migration.test.js.map +1 -0
  24. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/connector-sync-provision.test.d.ts +2 -0
  25. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/connector-sync-provision.test.d.ts.map +1 -0
  26. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/connector-sync-provision.test.js +309 -0
  27. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/connector-sync-provision.test.js.map +1 -0
  28. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/d1-command.test.js +219 -19
  29. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/d1-command.test.js.map +1 -1
  30. package/payload/platform/plugins/scheduling/mcp/dist/lib/booking-reconcile.d.ts +184 -11
  31. package/payload/platform/plugins/scheduling/mcp/dist/lib/booking-reconcile.d.ts.map +1 -1
  32. package/payload/platform/plugins/scheduling/mcp/dist/lib/booking-reconcile.js +211 -22
  33. package/payload/platform/plugins/scheduling/mcp/dist/lib/booking-reconcile.js.map +1 -1
  34. package/payload/platform/plugins/scheduling/mcp/dist/lib/connector-sync-provision.d.ts +109 -0
  35. package/payload/platform/plugins/scheduling/mcp/dist/lib/connector-sync-provision.d.ts.map +1 -0
  36. package/payload/platform/plugins/scheduling/mcp/dist/lib/connector-sync-provision.js +213 -0
  37. package/payload/platform/plugins/scheduling/mcp/dist/lib/connector-sync-provision.js.map +1 -0
  38. package/payload/platform/plugins/scheduling/mcp/dist/lib/d1-command.d.ts +80 -8
  39. package/payload/platform/plugins/scheduling/mcp/dist/lib/d1-command.d.ts.map +1 -1
  40. package/payload/platform/plugins/scheduling/mcp/dist/lib/d1-command.js +202 -8
  41. package/payload/platform/plugins/scheduling/mcp/dist/lib/d1-command.js.map +1 -1
  42. package/payload/platform/plugins/scheduling/mcp/dist/scripts/reconcile-bookings.d.ts +6 -4
  43. package/payload/platform/plugins/scheduling/mcp/dist/scripts/reconcile-bookings.d.ts.map +1 -1
  44. package/payload/platform/plugins/scheduling/mcp/dist/scripts/reconcile-bookings.js +411 -141
  45. package/payload/platform/plugins/scheduling/mcp/dist/scripts/reconcile-bookings.js.map +1 -1
  46. package/payload/platform/scripts/cpu-triage.sh +313 -0
  47. package/payload/server/{chunk-Y6V45R6M.js → chunk-JECAP3Z2.js} +296 -63
  48. package/payload/server/maxy-edge.js +1 -1
  49. package/payload/server/public/activity.html +6 -6
  50. package/payload/server/public/assets/{AdminLoginScreens-B44KbEuC.js → AdminLoginScreens-mEKP4pDi.js} +1 -1
  51. package/payload/server/public/assets/{AdminShell-BjxV2qkb.js → AdminShell-BwSaZ88d.js} +1 -1
  52. package/payload/server/public/assets/{Checkbox-DSeZNGNA.js → Checkbox-CN4uQ80w.js} +1 -1
  53. package/payload/server/public/assets/activity-CIQ8ozNZ.js +1 -0
  54. package/payload/server/public/assets/{admin-B4LpTCur.js → admin-BVhgfgs1.js} +1 -1
  55. package/payload/server/public/assets/{browser-CEFvaWwI.js → browser-D-HMmHcM.js} +1 -1
  56. package/payload/server/public/assets/{calendar-B3zo6V34.js → calendar-tGWvFyFn.js} +1 -1
  57. package/payload/server/public/assets/chat-DkttVxAz.js +1 -0
  58. package/payload/server/public/assets/chevron-left-CZ4ez9G5.js +1 -0
  59. package/payload/server/public/assets/data-P-mcmnNi.js +1 -0
  60. package/payload/server/public/assets/{graph-labels-GlTEIYJj.js → graph-labels-qnBleOE6.js} +1 -1
  61. package/payload/server/public/assets/{graph-RaOrnSf2.js → graph-zEw610xK.js} +1 -1
  62. package/payload/server/public/assets/{maximize-2-CHDa8p5h.js → maximize-2-BlTjXT_Y.js} +1 -1
  63. package/payload/server/public/assets/{operator-DtqHY4jV.js → operator-C7oIw2PG.js} +1 -1
  64. package/payload/server/public/assets/{page-DxnWgPtd.js → page-CTP7OFZa.js} +1 -1
  65. package/payload/server/public/assets/{page-CkhFj8GQ.js → page-DL6Zsdvk.js} +1 -1
  66. package/payload/server/public/assets/{public-D5TUfQ3I.js → public-r1A9dqR_.js} +1 -1
  67. package/payload/server/public/assets/{rotate-ccw-BJjVAQQY.js → rotate-ccw-CzkkKx4-.js} +1 -1
  68. package/payload/server/public/assets/{tasks-CM9pYYG1.js → tasks-fIJwYFWG.js} +1 -1
  69. package/payload/server/public/assets/{time-entry-format-C5RYEYjM.js → time-entry-format-j669DgXj.js} +1 -1
  70. package/payload/server/public/assets/{triangle-alert-DLfIsuo_.js → triangle-alert-BPgCgIef.js} +1 -1
  71. package/payload/server/public/assets/{useCopyFeedback-7yKeiAzV.js → useCopyFeedback-BmLa2aes.js} +1 -1
  72. package/payload/server/public/assets/{useSelectionMode-CWlh6AEq.js → useSelectionMode-DnnM7A8-.js} +1 -1
  73. package/payload/server/public/assets/{useSubAccountSwitcher-B2n9QPVI.css → useSubAccountSwitcher-C_E8h07P.css} +1 -1
  74. package/payload/server/public/assets/{useVoiceRecorder-j3ZArZ2H.js → useVoiceRecorder-u2dwZfen.js} +1 -1
  75. package/payload/server/public/browser.html +5 -5
  76. package/payload/server/public/calendar.html +6 -6
  77. package/payload/server/public/chat.html +12 -12
  78. package/payload/server/public/data.html +11 -11
  79. package/payload/server/public/graph.html +11 -11
  80. package/payload/server/public/index.html +14 -14
  81. package/payload/server/public/operator.html +14 -14
  82. package/payload/server/public/public.html +12 -12
  83. package/payload/server/public/tasks.html +5 -5
  84. package/payload/server/server.js +358 -189
  85. package/payload/server/public/assets/activity-Mz1BhgB1.js +0 -1
  86. package/payload/server/public/assets/chat-5tJdo9i_.js +0 -1
  87. package/payload/server/public/assets/chevron-left-BJllrPBj.js +0 -1
  88. package/payload/server/public/assets/data-Du79EgR-.js +0 -1
  89. /package/payload/server/public/assets/{useSubAccountSwitcher-CBlKgwms.js → useSubAccountSwitcher-CFa6ZAT1.js} +0 -0
@@ -47,19 +47,95 @@ export async function reconcileOne(session, accountId, booking) {
47
47
  ON CREATE SET r.role = 'booker', r.createdAt = datetime(), r.createdBySource = 'booking-reconcile'`, { meetingId, email: booking.email, accountId, name: booking.name });
48
48
  return { created, meetingId };
49
49
  }
50
+ // ---------------------------------------------------------------------------
51
+ // Task 1806 — one eligibility predicate, shared by the sweep and the audit
52
+ // ---------------------------------------------------------------------------
50
53
  /**
51
- * Compare D1's accepted booking ids against the account's booking :Meeting set.
52
- * pendingUnreconciled — accepted in D1 but no :Meeting yet (reconcile lagging).
53
- * orphanMeetings — a booking :Meeting with no backing D1 row.
54
+ * The single definition of a row the reconcile loop acts on.
55
+ *
56
+ * `status='accepted'` is the row the booking page captured. `kind='booking'` is
57
+ * the row that must become a :Meeting — a `kind='block'` row only reserves a
58
+ * slot and has no meeting by design, so counting it as unreconciled is a false
59
+ * positive that is indistinguishable in the log from a genuinely lost booking.
60
+ *
61
+ * Both call sites build their statements from this constant. Two literals is
62
+ * the defect: the sweep and the audit disagreed about what needed reconciling,
63
+ * and only the sweep's opinion could act.
54
64
  */
55
- export async function auditCounts(session, accountId, d1AcceptedIds) {
65
+ export const ELIGIBLE_BOOKING_PREDICATE = "status='accepted' AND kind='booking'";
66
+ /** The one D1 read that serves both the sweep and the audit. */
67
+ export const ELIGIBLE_BOOKING_SELECT = `SELECT bookingId, slotStart, slotEnd, name, email, note, attempts FROM bookings WHERE ${ELIGIBLE_BOOKING_PREDICATE} ORDER BY id`;
68
+ /** This account's booking :Meeting uids. Read once per run and used for both
69
+ * the sweep's claim decision and the audit's counts. */
70
+ export async function fetchBookingMeetingUids(session, accountId) {
56
71
  const res = await session.run(`MATCH (m:Meeting {accountId: $accountId, source: 'booking'}) RETURN collect(m.uid) AS uids`, { accountId });
57
- const uids = res.records[0]?.get("uids") ?? [];
58
- const uidSet = new Set(uids);
59
- const idSet = new Set(d1AcceptedIds);
60
- const pendingUnreconciled = d1AcceptedIds.filter((id) => !uidSet.has(bookingUid(id))).length;
61
- const orphanMeetings = uids.filter((uid) => !idSet.has(uid.replace(/^booking-/, ""))).length;
62
- return { pendingUnreconciled, orphanMeetings };
72
+ return res.records[0]?.get("uids") ?? [];
73
+ }
74
+ /**
75
+ * The rows the sweep must claim: eligible, and with no :Meeting carrying their
76
+ * uid.
77
+ *
78
+ * Task 1807 — this deliberately does not consult `swept`. `swept` records that
79
+ * a write was attempted, not that a meeting exists; gating retry on it made a
80
+ * booking whose meeting later went missing permanently unreachable, while the
81
+ * audit reported it every tick. Re-entry is safe because `reconcileOne` is
82
+ * idempotent on (accountId, uid) and returns created=false on a re-run.
83
+ */
84
+ export function selectUnreconciled(rows, meetingUids) {
85
+ return rows.filter((r) => !meetingUids.has(bookingUid(String(r.bookingId))));
86
+ }
87
+ /**
88
+ * Compare D1's eligible booking ids against the account's booking :Meeting set.
89
+ * pendingUnreconciled — eligible in D1 but no :Meeting (reconcile lagging or lost).
90
+ * orphanMeetings — a booking :Meeting with no backing eligible D1 row.
91
+ *
92
+ * Pure: both inputs are read by the caller, which uses the same two lists for
93
+ * the sweep, so the sweep and the audit cannot disagree about the population.
94
+ */
95
+ export function auditCounts(eligibleIds, meetingUids) {
96
+ const uidSet = new Set(meetingUids);
97
+ const idSet = new Set(eligibleIds);
98
+ return {
99
+ eligibleExamined: eligibleIds.length,
100
+ pendingUnreconciled: eligibleIds.filter((id) => !uidSet.has(bookingUid(id))).length,
101
+ orphanMeetings: meetingUids.filter((uid) => !idSet.has(uid.replace(/^booking-/, ""))).length,
102
+ };
103
+ }
104
+ // ---------------------------------------------------------------------------
105
+ // Task 1806/1807 — lazy schema migration
106
+ // ---------------------------------------------------------------------------
107
+ /**
108
+ * Statements that bring an already-provisioned bookings table up to the schema
109
+ * the sweep needs. Run once, on the first tick after upgrade, triggered by the
110
+ * eligible-row read failing on the missing column.
111
+ *
112
+ * A template schema change alone cannot reach an existing install: schema.sql
113
+ * is CREATE TABLE IF NOT EXISTS and is applied once at provisioning, so
114
+ * re-applying it to a live database is a no-op. Without this the first
115
+ * kind-qualified read would throw on every existing install and reconcile would
116
+ * stop entirely — strictly worse than the defect being fixed.
117
+ *
118
+ * The third statement classifies rows that predate the column. It is the only
119
+ * place the platform reads a block row's note: the marker is a free-text string
120
+ * owned by an out-of-tree script (Task 1811 absorbs that writer, after which
121
+ * kind='block' is set natively), so it may be used to interpret legacy rows once
122
+ * but never to decide control flow. It is safe to re-run.
123
+ */
124
+ export const BOOKING_SCHEMA_MIGRATION = [
125
+ "ALTER TABLE bookings ADD COLUMN kind TEXT NOT NULL DEFAULT 'booking'",
126
+ "ALTER TABLE bookings ADD COLUMN attempts INTEGER NOT NULL DEFAULT 0",
127
+ "UPDATE bookings SET kind='block' WHERE note='Auto-block: Google Calendar'",
128
+ ];
129
+ /** True when a D1 failure is the absent-column case the migration answers.
130
+ * Every other failure must stay a failure. */
131
+ export function isMissingColumnError(message) {
132
+ return /no such column/i.test(message);
133
+ }
134
+ /** True when an ALTER failed because the column is already there. SQLite has no
135
+ * ADD COLUMN IF NOT EXISTS, so each statement tolerates this independently and
136
+ * a half-migrated table completes rather than aborting. */
137
+ export function isDuplicateColumnError(message) {
138
+ return /duplicate column name/i.test(message);
63
139
  }
64
140
  // ---------------------------------------------------------------------------
65
141
  // Task 1654 — connector calendar → booking availability sync
@@ -76,10 +152,15 @@ export const CONNECTOR_SYNC_PROMPT = "Keep your booking page in step with your r
76
152
  /**
77
153
  * Create-only provisioning of the recurring connector-sync :Event. Idempotent:
78
154
  * the existence probe keys on the `connectorSync` marker so a re-run never
79
- * duplicates the event nor resets its advancing nextRun. Returns 'created' on the
80
- * first arming, 'exists' thereafter.
155
+ * duplicates the event nor resets its advancing nextRun. Returns 'provisioned'
156
+ * on the first arming, 'already-provisioned' thereafter.
157
+ *
158
+ * Task 1809 — `dispatch` is the resolved EXCEPTION destination, not a gate. The
159
+ * caller decides whether to provision from the presence of a calendar
160
+ * connector (the sync's actual input) and resolves where an exception is
161
+ * reported separately; this function writes whichever channel it is handed.
81
162
  */
82
- export async function ensureConnectorSyncEvent(session, accountId, adminPhone) {
163
+ export async function ensureConnectorSyncEvent(session, accountId, dispatch) {
83
164
  const probe = await session.run(`MATCH (e:Event {accountId: $accountId, connectorSync: true})
84
165
  WHERE e.eventStatus IN ['scheduled', 'due']
85
166
  RETURN count(e) AS n`, { accountId });
@@ -88,7 +169,7 @@ export async function ensureConnectorSyncEvent(session, accountId, adminPhone) {
88
169
  ? raw.toNumber()
89
170
  : Number(raw ?? 0);
90
171
  if (n > 0)
91
- return "exists";
172
+ return "already-provisioned";
92
173
  const now = new Date().toISOString();
93
174
  const eventId = randomUUID();
94
175
  await session.run(`CREATE (e:Event {
@@ -111,11 +192,11 @@ export async function ensureConnectorSyncEvent(session, accountId, adminPhone) {
111
192
  accountId,
112
193
  recurrence: "*/30 * * * *",
113
194
  now,
114
- agentChannel: "whatsapp",
115
- agentDestination: adminPhone,
195
+ agentChannel: dispatch.channel,
196
+ agentDestination: dispatch.destination,
116
197
  agentPrompt: CONNECTOR_SYNC_PROMPT,
117
198
  });
118
- return "created";
199
+ return "provisioned";
119
200
  }
120
201
  /** Half-open overlap: [aStart,aEnd) intersects [bStart,bEnd). Edge-touching
121
202
  * intervals (a.end === b.start) do NOT overlap. Any unparseable bound is
@@ -128,16 +209,124 @@ function intervalsOverlap(a, b) {
128
209
  return aS < bE && bS < aE;
129
210
  }
130
211
  /**
131
- * Count published-open booking slots that overlap any connector-busy interval
132
- * the drift the standing audit reports. A nonzero result means the booking page
133
- * is currently offering a slot the connector shows busy.
212
+ * Classify published-open booking slots against the connector-busy set the
213
+ * drift the standing audit reports. `outcome: "verified"` with a nonzero
214
+ * `conflicts` means the booking page is currently offering a slot the connector
215
+ * shows busy. An empty published set reports `no-published-slots` even when the
216
+ * busy set is also empty: the site never publishing is the upstream condition.
134
217
  */
135
- export function countPublishedConflicts(published, busy) {
218
+ export function classifyPublishedDrift(published, busy) {
136
219
  let conflicts = 0;
137
220
  for (const slot of published) {
138
221
  if (busy.some((b) => intervalsOverlap(slot, b)))
139
222
  conflicts++;
140
223
  }
141
- return conflicts;
224
+ const outcome = published.length === 0 ? "no-published-slots" : busy.length === 0 ? "no-busy-data" : "verified";
225
+ return { outcome, published: published.length, busy: busy.length, conflicts };
226
+ }
227
+ /**
228
+ * The published slots to block, given the connector-busy set.
229
+ *
230
+ * The returned slots are drawn from `published` verbatim, never synthesised
231
+ * from `busy`: the edge free/busy Function subtracts taken slots by an exact
232
+ * `slotStart` string match, so a block row keyed on a busy-interval boundary
233
+ * would block nothing.
234
+ *
235
+ * Two exclusions, both carried over from the out-of-tree script this replaces:
236
+ * a slot that has already started (the edge filters past slots anyway, so a
237
+ * row for one is inert churn), and a slot already taken by a real booking
238
+ * (already subtracted at the edge by that booking).
239
+ */
240
+ export function selectBlockedSlots(published, busy, taken, nowMs) {
241
+ return published.filter((slot) => {
242
+ const startMs = Date.parse(slot.start);
243
+ // `<` not `<=`: the edge serves slots with `t >= lower` where lower is now,
244
+ // so a slot starting exactly now IS still offered and must still be blocked.
245
+ if (Number.isNaN(startMs) || startMs < nowMs)
246
+ return false;
247
+ if (taken.has(slot.start))
248
+ return false;
249
+ return busy.some((b) => intervalsOverlap(slot, b));
250
+ });
251
+ }
252
+ /**
253
+ * Reconcile the held block rows to the desired set, keyed on slot start.
254
+ *
255
+ * This replaces the out-of-tree script's delete-and-reinsert. That form
256
+ * rewrote every row on every run and left a window in which a still-busy slot
257
+ * was unblocked; this one writes nothing in the steady state and never
258
+ * unblocks a slot that is staying blocked.
259
+ *
260
+ * A slot held by more than one row (possible after an interrupted run) keeps
261
+ * one and deletes the rest, so a duplicate converges instead of persisting.
262
+ */
263
+ export function diffBlockRows(desired, current) {
264
+ const desiredStarts = new Set(desired.map((s) => s.start));
265
+ const kept = new Set();
266
+ const toDelete = [];
267
+ for (const row of current) {
268
+ if (desiredStarts.has(row.slotStart) && !kept.has(row.slotStart)) {
269
+ kept.add(row.slotStart);
270
+ continue;
271
+ }
272
+ toDelete.push(row.bookingId);
273
+ }
274
+ return { toInsert: desired.filter((s) => !kept.has(s.start)), toDelete };
275
+ }
276
+ /** The block rows this dispatcher owns. `kind='block'` is the whole identity —
277
+ * no free-text marker is consulted, which is the defect Task 1811 removes. */
278
+ export const BLOCK_ROWS_SELECT = "SELECT bookingId, slotStart FROM bookings WHERE kind='block'";
279
+ /** `status='accepted'` so the edge free/busy Function subtracts the slot;
280
+ * `kind='block'` so 1806's sweep and audit both exclude it by construction. */
281
+ export const BLOCK_ROW_INSERT = "INSERT INTO bookings (bookingId,slotStart,slotEnd,name,email,note,status,kind,createdAt) " +
282
+ "VALUES (?1,?2,?3,?4,?5,'','accepted','block',?6)";
283
+ /** The `kind` qualifier is a safety belt, not a filter: even handed the id of a
284
+ * real booking this statement cannot delete it. */
285
+ export const BLOCK_ROW_DELETE = "DELETE FROM bookings WHERE bookingId=?1 AND kind='block'";
286
+ /** Positional parameters for BLOCK_ROW_INSERT. The slot bounds are copied from
287
+ * the published snapshot verbatim — the edge matches `slotStart` exactly. */
288
+ export function blockInsertParams(slot, bookingId, nowIso) {
289
+ return [bookingId, slot.start, slot.end, "Connector busy", "", nowIso];
290
+ }
291
+ /** An interval usable for blocking: both ends parse and start precedes end.
292
+ * `busy.length > 0` is NOT a sufficient liveness test — a set of unparseable
293
+ * intervals overlaps nothing, which would compute an empty desired set and
294
+ * delete every held row. That is the same mass-unblock the empty-set rule
295
+ * exists to prevent, reached with garbage instead of emptiness. */
296
+ function isUsableInterval(iv) {
297
+ const s = Date.parse(iv.start);
298
+ const e = Date.parse(iv.end);
299
+ return !Number.isNaN(s) && !Number.isNaN(e) && s < e;
300
+ }
301
+ const NO_WRITES = { toInsert: [], toDelete: [] };
302
+ /**
303
+ * Decide what the blocking step should write. Pure, so the safety rule below is
304
+ * testable without a live D1 or Neo4j — the rule is the highest-consequence
305
+ * line in this feature and was previously enforced only by an `if` at the call
306
+ * site that no test could reach.
307
+ *
308
+ * Writes happen ONLY on `verified`: the snapshot was read, it holds slots, and
309
+ * the busy set holds at least one usable interval. Every degraded outcome
310
+ * returns no writes, which means an absent or unreadable busy set RETAINS the
311
+ * rows already held rather than clearing them.
312
+ *
313
+ * That asymmetry is deliberate. An empty busy set is ambiguous between a
314
+ * genuinely clear calendar and a sync that failed, never ran, or was
315
+ * deauthorised — `syncConnectorBusy` writes an empty set on the documented
316
+ * deauthorise path. Over-blocking is recoverable by the operator; under-blocking
317
+ * double-books a customer against a real commitment. The retained rows are
318
+ * reported on the `op=cal-block` line so the condition is visible, not silent.
319
+ */
320
+ export function planBlockWrites(input) {
321
+ if (!input.publishedReadable)
322
+ return { outcome: "snapshot-unreadable", diff: NO_WRITES };
323
+ if (!input.busyReadable)
324
+ return { outcome: "error", diff: NO_WRITES };
325
+ if (input.published.length === 0)
326
+ return { outcome: "no-published-slots", diff: NO_WRITES };
327
+ if (!input.busy.some(isUsableInterval))
328
+ return { outcome: "no-busy-data", diff: NO_WRITES };
329
+ const desired = selectBlockedSlots(input.published, input.busy, input.taken, input.nowMs);
330
+ return { outcome: "verified", diff: diffBlockRows(desired, input.current) };
142
331
  }
143
332
  //# sourceMappingURL=booking-reconcile.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"booking-reconcile.js","sourceRoot":"","sources":["../../src/lib/booking-reconcile.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"booking-reconcile.js","sourceRoot":"","sources":["../../src/lib/booking-reconcile.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,109 @@
1
+ import { type AgentChannel } from "./agent-dispatch.js";
2
+ /** A calendar the connector-sync turn can read free/busy from. */
3
+ export type CalendarConnector = "google" | "m365";
4
+ /**
5
+ * The outcome of one account's provisioning pass.
6
+ * - `not-applicable` — no calendar connector, so there is nothing to sync.
7
+ * - `provisioned` — the recurring :Event was created by this pass.
8
+ * - `already-provisioned`— a live :Event was already armed.
9
+ * - `unconfigured` — a connector exists but no exception destination does.
10
+ * - `error` — the pass threw; the reason is on the log line.
11
+ */
12
+ export type ProvisionOutcome = "not-applicable" | "provisioned" | "already-provisioned" | "unconfigured" | "error";
13
+ /** Where a provisioned turn sends an exception it needs the operator to see. */
14
+ export interface DispatchDestination {
15
+ channel: AgentChannel;
16
+ destination: string;
17
+ }
18
+ /**
19
+ * The calendar connectors currently authorised for an account, sorted. An
20
+ * authorised identity is one whose directory holds a `tokens.enc` — a
21
+ * half-registered identity (directory created, consent never completed) is
22
+ * deliberately NOT counted, so provisioning does not arm a turn that has
23
+ * nothing to read.
24
+ */
25
+ export declare function detectCalendarConnectors(accountDir: string): CalendarConnector[];
26
+ /**
27
+ * Why no destination could be resolved. The platform computes the true cause
28
+ * (Task 1439 gave `validateAgentDestination` reason codes precisely so the
29
+ * diagnosis is not guessed); discarding it one frame below the log statement
30
+ * would leave the operator reading `unconfigured` at an account whose
31
+ * `account.json` visibly holds a phone number.
32
+ *
33
+ * - `none-configured` — no WhatsApp phone and no Telegram admin on the account.
34
+ * - `not-registered` — a candidate exists but is in no authoritative list.
35
+ * - `cross-account` — a candidate is registered, but to another account.
36
+ * - `house-unresolvable` — the socket-owning house could not be resolved. This
37
+ * is an INSTALL-WIDE fault, not a per-account configuration gap.
38
+ * - `unreadable-config` — `account.json` is present but unreadable or malformed.
39
+ */
40
+ export type DestinationRefusal = "none-configured" | "not-registered" | "cross-account" | "house-unresolvable" | "unreadable-config";
41
+ export type DispatchResolution = {
42
+ destination: DispatchDestination;
43
+ reason: null;
44
+ } | {
45
+ destination: null;
46
+ reason: DestinationRefusal;
47
+ };
48
+ /**
49
+ * Where this account's connector-sync turn reports an exception, or the reason
50
+ * no channel destination is reachable.
51
+ *
52
+ * WhatsApp is preferred, Telegram is the fallback. Every candidate is passed
53
+ * through `validateAgentDestination` — the same authority the dispatcher's
54
+ * standing audit applies at fire time — so provisioning can never arm a binding
55
+ * the audit would immediately report as stale. `accountsDir` is the parent
56
+ * accounts directory (`$PLATFORM_ROOT/../data/accounts`).
57
+ */
58
+ export declare function resolveDispatchDestination(accountsDir: string, accountId: string): DispatchResolution;
59
+ /** The last outcome reported for one account, and when it was reported.
60
+ *
61
+ * `signature` is the value the change detector compares, not `outcome`: an
62
+ * `error` carries a prefix of its message, so a CHANGED error is reported
63
+ * immediately while an unchanged one heartbeats like any other steady state.
64
+ * Without it a persistently failing account would log every 120 s forever,
65
+ * reproducing the noise this task removes, and its eventual recovery would be
66
+ * invisible because the state map would still hold the pre-error outcome. */
67
+ export interface ProvisionStateEntry {
68
+ outcome: ProvisionOutcome;
69
+ signature: string;
70
+ loggedAtMs: number;
71
+ }
72
+ /** The comparison key for one observation. */
73
+ export declare function provisionSignature(outcome: ProvisionOutcome, detail?: string): string;
74
+ export type ProvisionState = Record<string, ProvisionStateEntry>;
75
+ /**
76
+ * Whether this tick reports the account's outcome.
77
+ *
78
+ * A permanent condition reported at tick rate is noise that hides its own
79
+ * meaning: the evidence for the Task 1809 defect was 5,688 identical lines that
80
+ * everyone read as benign. So a line is emitted when the outcome CHANGES, when
81
+ * the account has never been reported, or when the heartbeat window has
82
+ * elapsed — the heartbeat being what keeps "inert on every account" and "steady
83
+ * state" distinguishable without counting log lines by hand.
84
+ */
85
+ export declare function shouldEmitProvisionLine(prev: ProvisionStateEntry | undefined, nextSignature: string, nowMs: number, heartbeatMs: number): boolean;
86
+ /** How this observation is labelled on the log line. `first` is NOT `change`:
87
+ * labelling a first observation as a change means that any state loss produces
88
+ * a synchronised fleet-wide burst of `emit=change`, indistinguishable from
89
+ * every account genuinely flipping outcome at once — which is a real
90
+ * emergency, and the one moment the label most needs to be trustworthy. */
91
+ export declare function emitLabel(prev: ProvisionStateEntry | undefined, nextSignature: string): "first" | "change" | "heartbeat";
92
+ /** Read the persisted state. An absent or corrupt file reads as empty, which
93
+ * costs one redundant line per account and never suppresses one. A corrupt
94
+ * file is reported: it is the trigger for a fleet-wide `emit=first` burst, and
95
+ * that burst must carry its own explanation. */
96
+ export declare function readProvisionState(path: string): ProvisionState;
97
+ /**
98
+ * Persist the state. Written to a temp path and renamed, so a kill or a hard
99
+ * freeze mid-write cannot leave truncated JSON — which would read back as empty
100
+ * and produce the fleet-wide burst described above.
101
+ *
102
+ * A failed write must never abort the reconcile pass, but it must not be silent
103
+ * either: every realistic cause (disk full, filesystem remounted read-only,
104
+ * mode drift) is PERSISTENT, so the failure degrades emit-on-change back to
105
+ * per-tick logging indefinitely — worse than what it replaced, because every
106
+ * line then falsely claims to be a first observation.
107
+ */
108
+ export declare function writeProvisionState(path: string, state: ProvisionState): void;
109
+ //# sourceMappingURL=connector-sync-provision.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"connector-sync-provision.d.ts","sourceRoot":"","sources":["../../src/lib/connector-sync-provision.ts"],"names":[],"mappings":"AAuBA,OAAO,EAA4B,KAAK,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAElF,kEAAkE;AAClE,MAAM,MAAM,iBAAiB,GAAG,QAAQ,GAAG,MAAM,CAAC;AAElD;;;;;;;GAOG;AACH,MAAM,MAAM,gBAAgB,GACxB,gBAAgB,GAChB,aAAa,GACb,qBAAqB,GACrB,cAAc,GACd,OAAO,CAAC;AAEZ,gFAAgF;AAChF,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,YAAY,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;CACrB;AASD;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CAAC,UAAU,EAAE,MAAM,GAAG,iBAAiB,EAAE,CAwBhF;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,MAAM,kBAAkB,GAC1B,iBAAiB,GACjB,gBAAgB,GAChB,eAAe,GACf,oBAAoB,GACpB,mBAAmB,CAAC;AAExB,MAAM,MAAM,kBAAkB,GAC1B;IAAE,WAAW,EAAE,mBAAmB,CAAC;IAAC,MAAM,EAAE,IAAI,CAAA;CAAE,GAClD;IAAE,WAAW,EAAE,IAAI,CAAC;IAAC,MAAM,EAAE,kBAAkB,CAAA;CAAE,CAAC;AAYtD;;;;;;;;;GASG;AACH,wBAAgB,0BAA0B,CACxC,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,MAAM,GAChB,kBAAkB,CAkCpB;AA0BD;;;;;;;8EAO8E;AAC9E,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,gBAAgB,CAAC;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,8CAA8C;AAC9C,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,gBAAgB,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAErF;AAED,MAAM,MAAM,cAAc,GAAG,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC;AAEjE;;;;;;;;;GASG;AACH,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,mBAAmB,GAAG,SAAS,EACrC,aAAa,EAAE,MAAM,EACrB,KAAK,EAAE,MAAM,EACb,WAAW,EAAE,MAAM,GAClB,OAAO,CAST;AAED;;;;4EAI4E;AAC5E,wBAAgB,SAAS,CAAC,IAAI,EAAE,mBAAmB,GAAG,SAAS,EAAE,aAAa,EAAE,MAAM,GAAG,OAAO,GAAG,QAAQ,GAAG,WAAW,CAGxH;AAED;;;iDAGiD;AACjD,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,cAAc,CAiB/D;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,cAAc,GAAG,IAAI,CAW7E"}
@@ -0,0 +1,213 @@
1
+ /**
2
+ * Task 1809 — the connector-sync provisioning gate.
3
+ *
4
+ * Task 1654 armed the recurring connector-sync turn only when the account
5
+ * carried an admin WhatsApp number, and read that number from a top-level
6
+ * `account.json` `adminPhones` key that exists on no account (the authoritative
7
+ * key is `whatsapp.adminPhones`). The two defects stacked: measured on the
8
+ * laptop on 2026-07-19, the provisioning block had logged `skipped` 7,868 times
9
+ * across four brands and had never once created an event.
10
+ *
11
+ * The gate was also on the wrong thing. The WhatsApp number is where the turn
12
+ * reports an EXCEPTION; it is not an input to the sync. So provisioning is
13
+ * gated here on the presence of a calendar connector — the work's actual input
14
+ * — and the exception destination is resolved separately.
15
+ *
16
+ * Where an account has a connector but no reachable exception destination, it
17
+ * is reported `unconfigured` rather than provisioned: a sync that fails and
18
+ * cannot report is not a clean sync, and arming one would reproduce the silence
19
+ * this task exists to remove. Making that case provisionable needs a
20
+ * destination-free operator route, which is Task 1812 (admin-webchat dispatch).
21
+ */
22
+ import { existsSync, readdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
23
+ import { join } from "node:path";
24
+ import { validateAgentDestination } from "./agent-dispatch.js";
25
+ /** One entry per connector: the directory under `secrets/` whose per-identity
26
+ * subdirectories each hold a `tokens.enc` once that identity is authorised. */
27
+ const CONNECTOR_TOKEN_DIRS = [
28
+ { connector: "google", rel: ["google", "accounts"] },
29
+ { connector: "m365", rel: ["outlook", "mailboxes"] },
30
+ ];
31
+ /**
32
+ * The calendar connectors currently authorised for an account, sorted. An
33
+ * authorised identity is one whose directory holds a `tokens.enc` — a
34
+ * half-registered identity (directory created, consent never completed) is
35
+ * deliberately NOT counted, so provisioning does not arm a turn that has
36
+ * nothing to read.
37
+ */
38
+ export function detectCalendarConnectors(accountDir) {
39
+ const found = [];
40
+ for (const { connector, rel } of CONNECTOR_TOKEN_DIRS) {
41
+ const dir = join(accountDir, "secrets", ...rel);
42
+ // An ABSENT directory means the connector was never registered, which is
43
+ // the benign case. A directory that exists but cannot be read means the
44
+ // gate's input is unknown, NOT empty — reporting that as `not-applicable`
45
+ // would dress a real fault as the boring expected state, which is exactly
46
+ // the pathology Task 1809 exists to remove. So it throws, and the caller
47
+ // reports `error`.
48
+ if (!existsSync(dir))
49
+ continue;
50
+ let identities;
51
+ try {
52
+ identities = readdirSync(dir);
53
+ }
54
+ catch (err) {
55
+ throw new Error(`calendar connector directory ${dir} exists but is unreadable: ${err.message}`);
56
+ }
57
+ if (identities.some((id) => existsSync(join(dir, id, "tokens.enc")))) {
58
+ found.push(connector);
59
+ }
60
+ }
61
+ return found.sort();
62
+ }
63
+ /** Most actionable first: an install-wide fault outranks a per-account one, and
64
+ * a specific rejection outranks the absence of any candidate at all. */
65
+ const REFUSAL_PRECEDENCE = [
66
+ "house-unresolvable",
67
+ "unreadable-config",
68
+ "cross-account",
69
+ "not-registered",
70
+ "none-configured",
71
+ ];
72
+ /**
73
+ * Where this account's connector-sync turn reports an exception, or the reason
74
+ * no channel destination is reachable.
75
+ *
76
+ * WhatsApp is preferred, Telegram is the fallback. Every candidate is passed
77
+ * through `validateAgentDestination` — the same authority the dispatcher's
78
+ * standing audit applies at fire time — so provisioning can never arm a binding
79
+ * the audit would immediately report as stale. `accountsDir` is the parent
80
+ * accounts directory (`$PLATFORM_ROOT/../data/accounts`).
81
+ */
82
+ export function resolveDispatchDestination(accountsDir, accountId) {
83
+ let config;
84
+ try {
85
+ config = JSON.parse(readFileSync(join(accountsDir, accountId, "account.json"), "utf-8"));
86
+ }
87
+ catch {
88
+ // `main` enumerates accounts by the presence of this very file, so by the
89
+ // time we read it "absent" is the least likely cause and "malformed or
90
+ // unreadable" the most likely — the inverse of the obvious reading.
91
+ return { destination: null, reason: "unreadable-config" };
92
+ }
93
+ const refusals = [];
94
+ const wa = config.whatsapp ?? {};
95
+ const phones = Array.isArray(wa.adminPhones) ? wa.adminPhones : [];
96
+ for (const phone of phones) {
97
+ if (typeof phone !== "string" || phone.length === 0)
98
+ continue;
99
+ const check = classify(accountsDir, accountId, "whatsapp", phone);
100
+ if (check === null)
101
+ return { destination: { channel: "whatsapp", destination: phone }, reason: null };
102
+ refusals.push(check);
103
+ }
104
+ const tg = config.telegram ?? {};
105
+ const users = Array.isArray(tg.adminUsers) ? tg.adminUsers : [];
106
+ for (const user of users) {
107
+ if (typeof user !== "number" || !Number.isInteger(user))
108
+ continue;
109
+ const destination = String(user);
110
+ const check = classify(accountsDir, accountId, "telegram", destination);
111
+ if (check === null)
112
+ return { destination: { channel: "telegram", destination }, reason: null };
113
+ refusals.push(check);
114
+ }
115
+ const reason = REFUSAL_PRECEDENCE.find((r) => refusals.includes(r)) ?? "none-configured";
116
+ return { destination: null, reason };
117
+ }
118
+ /** null when the destination validates; otherwise why it did not.
119
+ * `validateAgentDestination` resolves the house from disk and throws when the
120
+ * house is unresolvable. That means "cannot confirm this destination", which
121
+ * fails closed — but it is reported as its own install-wide reason rather than
122
+ * collapsing into a per-account configuration gap. */
123
+ function classify(accountsDir, accountId, channel, destination) {
124
+ let check;
125
+ try {
126
+ check = validateAgentDestination(accountsDir, accountId, channel, destination);
127
+ }
128
+ catch {
129
+ return "house-unresolvable";
130
+ }
131
+ return check.valid ? null : check.reason;
132
+ }
133
+ /** The comparison key for one observation. */
134
+ export function provisionSignature(outcome, detail) {
135
+ return detail ? `${outcome}:${detail.slice(0, 80)}` : outcome;
136
+ }
137
+ /**
138
+ * Whether this tick reports the account's outcome.
139
+ *
140
+ * A permanent condition reported at tick rate is noise that hides its own
141
+ * meaning: the evidence for the Task 1809 defect was 5,688 identical lines that
142
+ * everyone read as benign. So a line is emitted when the outcome CHANGES, when
143
+ * the account has never been reported, or when the heartbeat window has
144
+ * elapsed — the heartbeat being what keeps "inert on every account" and "steady
145
+ * state" distinguishable without counting log lines by hand.
146
+ */
147
+ export function shouldEmitProvisionLine(prev, nextSignature, nowMs, heartbeatMs) {
148
+ if (!prev)
149
+ return true;
150
+ if (prev.signature !== nextSignature)
151
+ return true;
152
+ const elapsed = nowMs - prev.loggedAtMs;
153
+ // A negative elapsed means the clock stepped backwards (an NTP correction on
154
+ // a Pi with no battery-backed RTC). Emitting is the safe side: the heartbeat
155
+ // is the only signal distinguishing "steady state" from "this loop stopped",
156
+ // and suppressing it for hours would hide precisely that.
157
+ return elapsed < 0 || elapsed >= heartbeatMs;
158
+ }
159
+ /** How this observation is labelled on the log line. `first` is NOT `change`:
160
+ * labelling a first observation as a change means that any state loss produces
161
+ * a synchronised fleet-wide burst of `emit=change`, indistinguishable from
162
+ * every account genuinely flipping outcome at once — which is a real
163
+ * emergency, and the one moment the label most needs to be trustworthy. */
164
+ export function emitLabel(prev, nextSignature) {
165
+ if (!prev)
166
+ return "first";
167
+ return prev.signature === nextSignature ? "heartbeat" : "change";
168
+ }
169
+ /** Read the persisted state. An absent or corrupt file reads as empty, which
170
+ * costs one redundant line per account and never suppresses one. A corrupt
171
+ * file is reported: it is the trigger for a fleet-wide `emit=first` burst, and
172
+ * that burst must carry its own explanation. */
173
+ export function readProvisionState(path) {
174
+ let raw;
175
+ try {
176
+ raw = readFileSync(path, "utf-8");
177
+ }
178
+ catch {
179
+ return {}; // absent — the normal first-run case
180
+ }
181
+ try {
182
+ const parsed = JSON.parse(raw);
183
+ return parsed && typeof parsed === "object" ? parsed : {};
184
+ }
185
+ catch (err) {
186
+ console.error(`[calendar-availability] op=cal-sync-state result=unreadable path=${path} ` +
187
+ `reason=${JSON.stringify(err.message)} impact=every-account-reports-first`);
188
+ return {};
189
+ }
190
+ }
191
+ /**
192
+ * Persist the state. Written to a temp path and renamed, so a kill or a hard
193
+ * freeze mid-write cannot leave truncated JSON — which would read back as empty
194
+ * and produce the fleet-wide burst described above.
195
+ *
196
+ * A failed write must never abort the reconcile pass, but it must not be silent
197
+ * either: every realistic cause (disk full, filesystem remounted read-only,
198
+ * mode drift) is PERSISTENT, so the failure degrades emit-on-change back to
199
+ * per-tick logging indefinitely — worse than what it replaced, because every
200
+ * line then falsely claims to be a first observation.
201
+ */
202
+ export function writeProvisionState(path, state) {
203
+ const tmp = `${path}.tmp`;
204
+ try {
205
+ writeFileSync(tmp, JSON.stringify(state));
206
+ renameSync(tmp, path);
207
+ }
208
+ catch (err) {
209
+ console.error(`[calendar-availability] op=cal-sync-state result=write-failed path=${path} ` +
210
+ `reason=${JSON.stringify(err.message)} impact=emit-on-change-degraded-to-per-tick`);
211
+ }
212
+ }
213
+ //# sourceMappingURL=connector-sync-provision.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"connector-sync-provision.js","sourceRoot":"","sources":["../../src/lib/connector-sync-provision.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,YAAY,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC3F,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,wBAAwB,EAAqB,MAAM,qBAAqB,CAAC;AA0BlF;gFACgF;AAChF,MAAM,oBAAoB,GAAmE;IAC3F,EAAE,SAAS,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAC,QAAQ,EAAE,UAAU,CAAC,EAAE;IACpD,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,SAAS,EAAE,WAAW,CAAC,EAAE;CACrD,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,UAAU,wBAAwB,CAAC,UAAkB;IACzD,MAAM,KAAK,GAAwB,EAAE,CAAC;IACtC,KAAK,MAAM,EAAE,SAAS,EAAE,GAAG,EAAE,IAAI,oBAAoB,EAAE,CAAC;QACtD,MAAM,GAAG,GAAG,IAAI,CAAC,UAAU,EAAE,SAAS,EAAE,GAAG,GAAG,CAAC,CAAC;QAChD,yEAAyE;QACzE,wEAAwE;QACxE,0EAA0E;QAC1E,0EAA0E;QAC1E,yEAAyE;QACzE,mBAAmB;QACnB,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;YAAE,SAAS;QAC/B,IAAI,UAAoB,CAAC;QACzB,IAAI,CAAC;YACH,UAAU,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC;QAChC,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,IAAI,KAAK,CACb,gCAAgC,GAAG,8BAA+B,GAAa,CAAC,OAAO,EAAE,CAC1F,CAAC;QACJ,CAAC;QACD,IAAI,UAAU,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,GAAG,EAAE,EAAE,EAAE,YAAY,CAAC,CAAC,CAAC,EAAE,CAAC;YACrE,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QACxB,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC,IAAI,EAAE,CAAC;AACtB,CAAC;AA2BD;yEACyE;AACzE,MAAM,kBAAkB,GAAyB;IAC/C,oBAAoB;IACpB,mBAAmB;IACnB,eAAe;IACf,gBAAgB;IAChB,iBAAiB;CAClB,CAAC;AAEF;;;;;;;;;GASG;AACH,MAAM,UAAU,0BAA0B,CACxC,WAAmB,EACnB,SAAiB;IAEjB,IAAI,MAA+B,CAAC;IACpC,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,CAAC,WAAW,EAAE,SAAS,EAAE,cAAc,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC;IAC3F,CAAC;IAAC,MAAM,CAAC;QACP,0EAA0E;QAC1E,uEAAuE;QACvE,oEAAoE;QACpE,OAAO,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,EAAE,mBAAmB,EAAE,CAAC;IAC5D,CAAC;IAED,MAAM,QAAQ,GAAyB,EAAE,CAAC;IAE1C,MAAM,EAAE,GAAI,MAAM,CAAC,QAAgD,IAAI,EAAE,CAAC;IAC1E,MAAM,MAAM,GAAG,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,CAAC;IACnE,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;YAAE,SAAS;QAC9D,MAAM,KAAK,GAAG,QAAQ,CAAC,WAAW,EAAE,SAAS,EAAE,UAAU,EAAE,KAAK,CAAC,CAAC;QAClE,IAAI,KAAK,KAAK,IAAI;YAAE,OAAO,EAAE,WAAW,EAAE,EAAE,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;QACtG,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACvB,CAAC;IAED,MAAM,EAAE,GAAI,MAAM,CAAC,QAAgD,IAAI,EAAE,CAAC;IAC1E,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,CAAC;IAChE,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC;YAAE,SAAS;QAClE,MAAM,WAAW,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC;QACjC,MAAM,KAAK,GAAG,QAAQ,CAAC,WAAW,EAAE,SAAS,EAAE,UAAU,EAAE,WAAW,CAAC,CAAC;QACxE,IAAI,KAAK,KAAK,IAAI;YAAE,OAAO,EAAE,WAAW,EAAE,EAAE,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;QAC/F,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACvB,CAAC;IAED,MAAM,MAAM,GAAG,kBAAkB,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,iBAAiB,CAAC;IACzF,OAAO,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;AACvC,CAAC;AAED;;;;uDAIuD;AACvD,SAAS,QAAQ,CACf,WAAmB,EACnB,SAAiB,EACjB,OAAqB,EACrB,WAAmB;IAEnB,IAAI,KAAkD,CAAC;IACvD,IAAI,CAAC;QACH,KAAK,GAAG,wBAAwB,CAAC,WAAW,EAAE,SAAS,EAAE,OAAO,EAAE,WAAW,CAAC,CAAC;IACjF,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,oBAAoB,CAAC;IAC9B,CAAC;IACD,OAAO,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC;AAC3C,CAAC;AAoBD,8CAA8C;AAC9C,MAAM,UAAU,kBAAkB,CAAC,OAAyB,EAAE,MAAe;IAC3E,OAAO,MAAM,CAAC,CAAC,CAAC,GAAG,OAAO,IAAI,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC;AAChE,CAAC;AAID;;;;;;;;;GASG;AACH,MAAM,UAAU,uBAAuB,CACrC,IAAqC,EACrC,aAAqB,EACrB,KAAa,EACb,WAAmB;IAEnB,IAAI,CAAC,IAAI;QAAE,OAAO,IAAI,CAAC;IACvB,IAAI,IAAI,CAAC,SAAS,KAAK,aAAa;QAAE,OAAO,IAAI,CAAC;IAClD,MAAM,OAAO,GAAG,KAAK,GAAG,IAAI,CAAC,UAAU,CAAC;IACxC,6EAA6E;IAC7E,6EAA6E;IAC7E,6EAA6E;IAC7E,0DAA0D;IAC1D,OAAO,OAAO,GAAG,CAAC,IAAI,OAAO,IAAI,WAAW,CAAC;AAC/C,CAAC;AAED;;;;4EAI4E;AAC5E,MAAM,UAAU,SAAS,CAAC,IAAqC,EAAE,aAAqB;IACpF,IAAI,CAAC,IAAI;QAAE,OAAO,OAAO,CAAC;IAC1B,OAAO,IAAI,CAAC,SAAS,KAAK,aAAa,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,QAAQ,CAAC;AACnE,CAAC;AAED;;;iDAGiD;AACjD,MAAM,UAAU,kBAAkB,CAAC,IAAY;IAC7C,IAAI,GAAW,CAAC;IAChB,IAAI,CAAC;QACH,GAAG,GAAG,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IACpC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC,CAAC,qCAAqC;IAClD,CAAC;IACD,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAC/B,OAAO,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAE,MAAyB,CAAC,CAAC,CAAC,EAAE,CAAC;IAChF,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,KAAK,CACX,oEAAoE,IAAI,GAAG;YACzE,UAAU,IAAI,CAAC,SAAS,CAAE,GAAa,CAAC,OAAO,CAAC,qCAAqC,CACxF,CAAC;QACF,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,mBAAmB,CAAC,IAAY,EAAE,KAAqB;IACrE,MAAM,GAAG,GAAG,GAAG,IAAI,MAAM,CAAC;IAC1B,IAAI,CAAC;QACH,aAAa,CAAC,GAAG,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC;QAC1C,UAAU,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;IACxB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,KAAK,CACX,sEAAsE,IAAI,GAAG;YAC3E,UAAU,IAAI,CAAC,SAAS,CAAE,GAAa,CAAC,OAAO,CAAC,6CAA6C,CAChG,CAAC;IACJ,CAAC;AACH,CAAC"}