@rubytech/create-maxy-code 0.1.474 → 0.1.476

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 (138) 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 +18 -9
  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/docs/references/google-guide.md +15 -6
  10. package/payload/platform/plugins/google/.claude-plugin/plugin.json +1 -1
  11. package/payload/platform/plugins/google/PLUGIN.md +60 -12
  12. package/payload/platform/plugins/google/mcp/dist/__tests__/account-audit.test.d.ts +2 -0
  13. package/payload/platform/plugins/google/mcp/dist/__tests__/account-audit.test.d.ts.map +1 -0
  14. package/payload/platform/plugins/google/mcp/dist/__tests__/account-audit.test.js +229 -0
  15. package/payload/platform/plugins/google/mcp/dist/__tests__/account-audit.test.js.map +1 -0
  16. package/payload/platform/plugins/google/mcp/dist/__tests__/account-register.test.js +9 -9
  17. package/payload/platform/plugins/google/mcp/dist/__tests__/account-register.test.js.map +1 -1
  18. package/payload/platform/plugins/google/mcp/dist/__tests__/consent-url-redirect.test.d.ts +2 -0
  19. package/payload/platform/plugins/google/mcp/dist/__tests__/consent-url-redirect.test.d.ts.map +1 -0
  20. package/payload/platform/plugins/google/mcp/dist/__tests__/consent-url-redirect.test.js +17 -0
  21. package/payload/platform/plugins/google/mcp/dist/__tests__/consent-url-redirect.test.js.map +1 -0
  22. package/payload/platform/plugins/google/mcp/dist/__tests__/loopback-listener.test.d.ts +2 -0
  23. package/payload/platform/plugins/google/mcp/dist/__tests__/loopback-listener.test.d.ts.map +1 -0
  24. package/payload/platform/plugins/google/mcp/dist/__tests__/loopback-listener.test.js +146 -0
  25. package/payload/platform/plugins/google/mcp/dist/__tests__/loopback-listener.test.js.map +1 -0
  26. package/payload/platform/plugins/google/mcp/dist/__tests__/pending-redirect.test.d.ts +2 -0
  27. package/payload/platform/plugins/google/mcp/dist/__tests__/pending-redirect.test.d.ts.map +1 -0
  28. package/payload/platform/plugins/google/mcp/dist/__tests__/pending-redirect.test.js +19 -0
  29. package/payload/platform/plugins/google/mcp/dist/__tests__/pending-redirect.test.js.map +1 -0
  30. package/payload/platform/plugins/google/mcp/dist/__tests__/register-redirect-chain.test.d.ts +2 -0
  31. package/payload/platform/plugins/google/mcp/dist/__tests__/register-redirect-chain.test.d.ts.map +1 -0
  32. package/payload/platform/plugins/google/mcp/dist/__tests__/register-redirect-chain.test.js +41 -0
  33. package/payload/platform/plugins/google/mcp/dist/__tests__/register-redirect-chain.test.js.map +1 -0
  34. package/payload/platform/plugins/google/mcp/dist/auth/account-audit.d.ts +44 -0
  35. package/payload/platform/plugins/google/mcp/dist/auth/account-audit.d.ts.map +1 -0
  36. package/payload/platform/plugins/google/mcp/dist/auth/account-audit.js +112 -0
  37. package/payload/platform/plugins/google/mcp/dist/auth/account-audit.js.map +1 -0
  38. package/payload/platform/plugins/google/mcp/dist/auth/loopback-listener.d.ts +17 -0
  39. package/payload/platform/plugins/google/mcp/dist/auth/loopback-listener.d.ts.map +1 -0
  40. package/payload/platform/plugins/google/mcp/dist/auth/loopback-listener.js +143 -0
  41. package/payload/platform/plugins/google/mcp/dist/auth/loopback-listener.js.map +1 -0
  42. package/payload/platform/plugins/google/mcp/dist/auth/pending-store.d.ts +9 -0
  43. package/payload/platform/plugins/google/mcp/dist/auth/pending-store.d.ts.map +1 -1
  44. package/payload/platform/plugins/google/mcp/dist/auth/pending-store.js.map +1 -1
  45. package/payload/platform/plugins/google/mcp/dist/index.js +73 -5
  46. package/payload/platform/plugins/google/mcp/dist/index.js.map +1 -1
  47. package/payload/platform/plugins/google/mcp/dist/scripts/account-audit.d.ts +2 -0
  48. package/payload/platform/plugins/google/mcp/dist/scripts/account-audit.d.ts.map +1 -0
  49. package/payload/platform/plugins/google/mcp/dist/scripts/account-audit.js +37 -0
  50. package/payload/platform/plugins/google/mcp/dist/scripts/account-audit.js.map +1 -0
  51. package/payload/platform/plugins/google/mcp/dist/tools/account-register.d.ts +20 -4
  52. package/payload/platform/plugins/google/mcp/dist/tools/account-register.d.ts.map +1 -1
  53. package/payload/platform/plugins/google/mcp/dist/tools/account-register.js +19 -9
  54. package/payload/platform/plugins/google/mcp/dist/tools/account-register.js.map +1 -1
  55. package/payload/platform/plugins/google/references/auth.md +93 -14
  56. package/payload/platform/plugins/scheduling/PLUGIN.md +2 -2
  57. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/booking-reconcile.test.js +158 -23
  58. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/booking-reconcile.test.js.map +1 -1
  59. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/booking-schema-migration.test.d.ts +2 -0
  60. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/booking-schema-migration.test.d.ts.map +1 -0
  61. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/booking-schema-migration.test.js +200 -0
  62. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/booking-schema-migration.test.js.map +1 -0
  63. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/connector-sync-provision.test.d.ts +2 -0
  64. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/connector-sync-provision.test.d.ts.map +1 -0
  65. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/connector-sync-provision.test.js +309 -0
  66. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/connector-sync-provision.test.js.map +1 -0
  67. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/d1-command.test.js +219 -19
  68. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/d1-command.test.js.map +1 -1
  69. package/payload/platform/plugins/scheduling/mcp/dist/lib/booking-reconcile.d.ts +94 -11
  70. package/payload/platform/plugins/scheduling/mcp/dist/lib/booking-reconcile.d.ts.map +1 -1
  71. package/payload/platform/plugins/scheduling/mcp/dist/lib/booking-reconcile.js +106 -22
  72. package/payload/platform/plugins/scheduling/mcp/dist/lib/booking-reconcile.js.map +1 -1
  73. package/payload/platform/plugins/scheduling/mcp/dist/lib/connector-sync-provision.d.ts +109 -0
  74. package/payload/platform/plugins/scheduling/mcp/dist/lib/connector-sync-provision.d.ts.map +1 -0
  75. package/payload/platform/plugins/scheduling/mcp/dist/lib/connector-sync-provision.js +213 -0
  76. package/payload/platform/plugins/scheduling/mcp/dist/lib/connector-sync-provision.js.map +1 -0
  77. package/payload/platform/plugins/scheduling/mcp/dist/lib/d1-command.d.ts +80 -8
  78. package/payload/platform/plugins/scheduling/mcp/dist/lib/d1-command.d.ts.map +1 -1
  79. package/payload/platform/plugins/scheduling/mcp/dist/lib/d1-command.js +202 -8
  80. package/payload/platform/plugins/scheduling/mcp/dist/lib/d1-command.js.map +1 -1
  81. package/payload/platform/plugins/scheduling/mcp/dist/scripts/reconcile-bookings.d.ts +6 -4
  82. package/payload/platform/plugins/scheduling/mcp/dist/scripts/reconcile-bookings.d.ts.map +1 -1
  83. package/payload/platform/plugins/scheduling/mcp/dist/scripts/reconcile-bookings.js +334 -142
  84. package/payload/platform/plugins/scheduling/mcp/dist/scripts/reconcile-bookings.js.map +1 -1
  85. package/payload/platform/services/claude-session-manager/dist/activity-rows.d.ts +21 -3
  86. package/payload/platform/services/claude-session-manager/dist/activity-rows.d.ts.map +1 -1
  87. package/payload/platform/services/claude-session-manager/dist/activity-rows.js +6 -2
  88. package/payload/platform/services/claude-session-manager/dist/activity-rows.js.map +1 -1
  89. package/payload/platform/services/claude-session-manager/dist/fs-watcher.d.ts.map +1 -1
  90. package/payload/platform/services/claude-session-manager/dist/fs-watcher.js +43 -5
  91. package/payload/platform/services/claude-session-manager/dist/fs-watcher.js.map +1 -1
  92. package/payload/platform/services/claude-session-manager/dist/jsonl-enumerator.d.ts +9 -0
  93. package/payload/platform/services/claude-session-manager/dist/jsonl-enumerator.d.ts.map +1 -1
  94. package/payload/platform/services/claude-session-manager/dist/jsonl-enumerator.js +78 -0
  95. package/payload/platform/services/claude-session-manager/dist/jsonl-enumerator.js.map +1 -1
  96. package/payload/server/{chunk-Y6V45R6M.js → chunk-LBCMFD4O.js} +173 -29
  97. package/payload/server/maxy-edge.js +1 -1
  98. package/payload/server/public/activity.html +6 -6
  99. package/payload/server/public/assets/{AdminLoginScreens-B44KbEuC.js → AdminLoginScreens-DximwPlS.js} +1 -1
  100. package/payload/server/public/assets/{AdminShell-BjxV2qkb.js → AdminShell-BehbH-Oa.js} +1 -1
  101. package/payload/server/public/assets/{Checkbox-DSeZNGNA.js → Checkbox-DM-eHqOS.js} +1 -1
  102. package/payload/server/public/assets/activity-hDwUeQd6.js +1 -0
  103. package/payload/server/public/assets/{admin-B4LpTCur.js → admin-CAkDnGdk.js} +1 -1
  104. package/payload/server/public/assets/{browser-CEFvaWwI.js → browser-DXl8hOoi.js} +1 -1
  105. package/payload/server/public/assets/{calendar-B3zo6V34.js → calendar-DP6hn4-6.js} +1 -1
  106. package/payload/server/public/assets/chat-DhOfVGYM.js +1 -0
  107. package/payload/server/public/assets/chevron-left-DYNBoLii.js +1 -0
  108. package/payload/server/public/assets/data-bq8fTo_s.js +1 -0
  109. package/payload/server/public/assets/{graph-RaOrnSf2.js → graph-CHcYoEJ5.js} +1 -1
  110. package/payload/server/public/assets/{graph-labels-GlTEIYJj.js → graph-labels-CZykslZM.js} +1 -1
  111. package/payload/server/public/assets/{maximize-2-CHDa8p5h.js → maximize-2-31AZEcMS.js} +1 -1
  112. package/payload/server/public/assets/{operator-DtqHY4jV.js → operator-BGGpdsO_.js} +1 -1
  113. package/payload/server/public/assets/{page-CkhFj8GQ.js → page-BloC6ygA.js} +1 -1
  114. package/payload/server/public/assets/{page-DxnWgPtd.js → page-DOeiiqbR.js} +1 -1
  115. package/payload/server/public/assets/{public-D5TUfQ3I.js → public-CGgO6IZv.js} +1 -1
  116. package/payload/server/public/assets/{rotate-ccw-BJjVAQQY.js → rotate-ccw-BlAi6iFg.js} +1 -1
  117. package/payload/server/public/assets/{tasks-CM9pYYG1.js → tasks-C1giFRRL.js} +1 -1
  118. package/payload/server/public/assets/{time-entry-format-C5RYEYjM.js → time-entry-format-CiUUQdq7.js} +1 -1
  119. package/payload/server/public/assets/{triangle-alert-DLfIsuo_.js → triangle-alert-CVqKuLoa.js} +1 -1
  120. package/payload/server/public/assets/{useCopyFeedback-7yKeiAzV.js → useCopyFeedback-ksyUe_g1.js} +1 -1
  121. package/payload/server/public/assets/{useSelectionMode-CWlh6AEq.js → useSelectionMode-BPrBkdTd.js} +1 -1
  122. package/payload/server/public/assets/{useSubAccountSwitcher-B2n9QPVI.css → useSubAccountSwitcher-K4kYeN6i.css} +1 -1
  123. package/payload/server/public/assets/{useVoiceRecorder-j3ZArZ2H.js → useVoiceRecorder-CTXP8oTW.js} +1 -1
  124. package/payload/server/public/browser.html +5 -5
  125. package/payload/server/public/calendar.html +6 -6
  126. package/payload/server/public/chat.html +12 -12
  127. package/payload/server/public/data.html +11 -11
  128. package/payload/server/public/graph.html +11 -11
  129. package/payload/server/public/index.html +14 -14
  130. package/payload/server/public/operator.html +14 -14
  131. package/payload/server/public/public.html +12 -12
  132. package/payload/server/public/tasks.html +5 -5
  133. package/payload/server/server.js +547 -423
  134. package/payload/server/public/assets/activity-CdipMTDp.js +0 -1
  135. package/payload/server/public/assets/chat-5tJdo9i_.js +0 -1
  136. package/payload/server/public/assets/chevron-left-BJllrPBj.js +0 -1
  137. package/payload/server/public/assets/data-Du79EgR-.js +0 -1
  138. /package/payload/server/public/assets/{useSubAccountSwitcher-CBlKgwms.js → useSubAccountSwitcher-DzgzTRRn.js} +0 -0
@@ -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":"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"}
@@ -1,12 +1,84 @@
1
1
  /**
2
- * Build the argv for a D1 SQL read. Resolves wrangler via `npx` — the brand
3
- * server PATH carries no global `wrangler`, only `npx wrangler` resolves, the
4
- * same form the proven forward-path availability publisher uses. Keeping this
5
- * pure lets the argv be asserted without importing the side-effectful
6
- * dispatcher script (Task 1350).
2
+ * D1 access for booking reconcile, over Cloudflare's HTTP query API.
3
+ *
4
+ * Task 1805 this module used to return an argv for `npx wrangler d1 execute`,
5
+ * which `reconcile-bookings.ts` ran through spawnSync once per SQL statement. On
6
+ * a four-brand laptop that was a continuous ~0.6 of a core: every statement paid
7
+ * a cold start of npm, then node, then wrangler's cli.js and its esbuild service.
8
+ * Nothing was failing; the cost was the transport choice, and it scaled with
9
+ * accounts x statements x tick rate. Task 1350's ENOENT finding (only `npx
10
+ * wrangler` resolves on the brand server PATH) is not reopened — it is retired,
11
+ * because no binary is resolved here at all.
12
+ *
13
+ * The two bounds the spawn carried are preserved deliberately, not incidentally:
14
+ * its `timeout: 60_000` becomes a request bound, and its `maxBuffer: 16MB`
15
+ * becomes a cap enforced *while streaming* the response. Both exist so a stalled
16
+ * Cloudflare cannot wedge a run that is holding the reconcile PID lock.
17
+ *
18
+ * The endpoint is keyed by database uuid but every caller holds a database name,
19
+ * so the client resolves name -> uuid once per run and caches it.
7
20
  */
8
- export declare function buildD1Command(dbName: string, sql: string): {
9
- command: string;
10
- args: string[];
21
+ /** The bound each request runs under. Inherited from the spawnSync `timeout`. */
22
+ export declare const D1_TIMEOUT_MS = 60000;
23
+ /** The most response bytes we will read. Inherited from the spawnSync `maxBuffer`. */
24
+ export declare const D1_RESPONSE_CAP_BYTES: number;
25
+ export interface D1Response {
26
+ ok: boolean;
27
+ status: number;
28
+ /**
29
+ * The body as byte chunks. Deliberately not a `text()`: the cap has to be able
30
+ * to stop the read part-way, which is the property `maxBuffer` provided. A
31
+ * whole-body accessor could only check the size after paying for it.
32
+ */
33
+ chunks: AsyncIterable<Uint8Array>;
34
+ }
35
+ export interface D1RequestInit {
36
+ method: string;
37
+ headers: Record<string, string>;
38
+ body?: string;
39
+ signal: AbortSignal;
40
+ }
41
+ /** The HTTP seam. Injectable so the transport is unit-testable without a network. */
42
+ export type D1Fetch = (url: string, init: D1RequestInit) => Promise<D1Response>;
43
+ /** Compose the request that resolves a database name to its uuid. Pure. */
44
+ export declare function buildD1LookupRequest(accountId: string, dbName: string, token: string): {
45
+ url: string;
46
+ init: Omit<D1RequestInit, "signal">;
11
47
  };
48
+ /**
49
+ * Compose the request that runs one SQL statement. Pure.
50
+ *
51
+ * `params` binds the statement's values server-side (`?1`, `?2`, … in the SQL).
52
+ * The spawn this replaced had no parameter channel, so every value reached D1
53
+ * folded into the statement text; the HTTP API takes them separately, so the
54
+ * sweep's bookingId is bound rather than interpolated.
55
+ *
56
+ * Typed `string[]` because the API documents this field as an array of string.
57
+ * A wider type would let a caller pass a value the endpoint rejects at runtime,
58
+ * on a periodic path where that surfaces as a failed tick rather than a build error.
59
+ */
60
+ export declare function buildD1QueryRequest(accountId: string, databaseId: string, sql: string, token: string, params?: string[]): {
61
+ url: string;
62
+ init: Omit<D1RequestInit, "signal">;
63
+ };
64
+ export interface D1Client {
65
+ /**
66
+ * Run one SQL statement against the named database and return its rows.
67
+ * Values go in `params` (`?1`, `?2`, … in the SQL), never in the statement text.
68
+ */
69
+ query(dbName: string, sql: string, params?: string[]): Promise<Record<string, unknown>[]>;
70
+ /** SQL statements issued so far. Name lookups are not statements and are excluded. */
71
+ readonly statements: number;
72
+ /** Total wall time spent in this client, lookups included. */
73
+ readonly elapsedMs: number;
74
+ }
75
+ /**
76
+ * One client per account run. It owns the name -> uuid cache, so a tick pays one
77
+ * lookup for its database and then issues statements against the resolved id.
78
+ */
79
+ export declare function createD1Client(opts: {
80
+ accountId: string;
81
+ token: string;
82
+ fetchFn?: D1Fetch;
83
+ }): D1Client;
12
84
  //# sourceMappingURL=d1-command.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"d1-command.d.ts","sourceRoot":"","sources":["../../src/lib/d1-command.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,wBAAgB,cAAc,CAC5B,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,MAAM,GACV;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,EAAE,CAAA;CAAE,CAKrC"}
1
+ {"version":3,"file":"d1-command.d.ts","sourceRoot":"","sources":["../../src/lib/d1-command.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,iFAAiF;AACjF,eAAO,MAAM,aAAa,QAAS,CAAC;AAEpC,sFAAsF;AACtF,eAAO,MAAM,qBAAqB,QAAmB,CAAC;AAItD,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf;;;;OAIG;IACH,MAAM,EAAE,aAAa,CAAC,UAAU,CAAC,CAAC;CACnC;AAED,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,WAAW,CAAC;CACrB;AAED,qFAAqF;AACrF,MAAM,MAAM,OAAO,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,aAAa,KAAK,OAAO,CAAC,UAAU,CAAC,CAAC;AAehF,2EAA2E;AAC3E,wBAAgB,oBAAoB,CAClC,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,GACZ;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,IAAI,CAAC,aAAa,EAAE,QAAQ,CAAC,CAAA;CAAE,CAMtD;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,mBAAmB,CACjC,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,EAClB,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,MAAM,EACb,MAAM,GAAE,MAAM,EAAO,GACpB;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,IAAI,CAAC,aAAa,EAAE,QAAQ,CAAC,CAAA;CAAE,CAStD;AAkFD,MAAM,WAAW,QAAQ;IACvB;;;OAGG;IACH,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC,CAAC;IAC1F,sFAAsF;IACtF,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,8DAA8D;IAC9D,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE;IACnC,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB,GAAG,QAAQ,CAwEX"}
@@ -1,14 +1,208 @@
1
1
  /**
2
- * Build the argv for a D1 SQL read. Resolves wrangler via `npx` — the brand
3
- * server PATH carries no global `wrangler`, only `npx wrangler` resolves, the
4
- * same form the proven forward-path availability publisher uses. Keeping this
5
- * pure lets the argv be asserted without importing the side-effectful
6
- * dispatcher script (Task 1350).
2
+ * D1 access for booking reconcile, over Cloudflare's HTTP query API.
3
+ *
4
+ * Task 1805 this module used to return an argv for `npx wrangler d1 execute`,
5
+ * which `reconcile-bookings.ts` ran through spawnSync once per SQL statement. On
6
+ * a four-brand laptop that was a continuous ~0.6 of a core: every statement paid
7
+ * a cold start of npm, then node, then wrangler's cli.js and its esbuild service.
8
+ * Nothing was failing; the cost was the transport choice, and it scaled with
9
+ * accounts x statements x tick rate. Task 1350's ENOENT finding (only `npx
10
+ * wrangler` resolves on the brand server PATH) is not reopened — it is retired,
11
+ * because no binary is resolved here at all.
12
+ *
13
+ * The two bounds the spawn carried are preserved deliberately, not incidentally:
14
+ * its `timeout: 60_000` becomes a request bound, and its `maxBuffer: 16MB`
15
+ * becomes a cap enforced *while streaming* the response. Both exist so a stalled
16
+ * Cloudflare cannot wedge a run that is holding the reconcile PID lock.
17
+ *
18
+ * The endpoint is keyed by database uuid but every caller holds a database name,
19
+ * so the client resolves name -> uuid once per run and caches it.
7
20
  */
8
- export function buildD1Command(dbName, sql) {
21
+ /** The bound each request runs under. Inherited from the spawnSync `timeout`. */
22
+ export const D1_TIMEOUT_MS = 60_000;
23
+ /** The most response bytes we will read. Inherited from the spawnSync `maxBuffer`. */
24
+ export const D1_RESPONSE_CAP_BYTES = 16 * 1024 * 1024;
25
+ const API = "https://api.cloudflare.com/client/v4";
26
+ const defaultFetch = async (url, init) => {
27
+ const res = await fetch(url, init);
28
+ return { ok: res.ok, status: res.status, chunks: bodyChunks(res) };
29
+ };
30
+ async function* bodyChunks(res) {
31
+ // A bodyless response (204, or a HEAD-shaped reply) yields nothing. That reads
32
+ // as an empty body, which the envelope guard below rejects with its status —
33
+ // never as a silently empty row set.
34
+ if (!res.body)
35
+ return;
36
+ for await (const chunk of res.body)
37
+ yield chunk;
38
+ }
39
+ /** Compose the request that resolves a database name to its uuid. Pure. */
40
+ export function buildD1LookupRequest(accountId, dbName, token) {
41
+ const qs = new URLSearchParams({ name: dbName });
42
+ return {
43
+ url: `${API}/accounts/${accountId}/d1/database?${qs}`,
44
+ init: { method: "GET", headers: { Authorization: `Bearer ${token}` } },
45
+ };
46
+ }
47
+ /**
48
+ * Compose the request that runs one SQL statement. Pure.
49
+ *
50
+ * `params` binds the statement's values server-side (`?1`, `?2`, … in the SQL).
51
+ * The spawn this replaced had no parameter channel, so every value reached D1
52
+ * folded into the statement text; the HTTP API takes them separately, so the
53
+ * sweep's bookingId is bound rather than interpolated.
54
+ *
55
+ * Typed `string[]` because the API documents this field as an array of string.
56
+ * A wider type would let a caller pass a value the endpoint rejects at runtime,
57
+ * on a periodic path where that surfaces as a failed tick rather than a build error.
58
+ */
59
+ export function buildD1QueryRequest(accountId, databaseId, sql, token, params = []) {
60
+ return {
61
+ url: `${API}/accounts/${accountId}/d1/database/${databaseId}/query`,
62
+ init: {
63
+ method: "POST",
64
+ headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" },
65
+ body: JSON.stringify({ sql, params }),
66
+ },
67
+ };
68
+ }
69
+ /** Read the body, refusing to hold more than the cap. Stops mid-stream. */
70
+ async function readCapped(chunks) {
71
+ const parts = [];
72
+ let total = 0;
73
+ for await (const chunk of chunks) {
74
+ total += chunk.byteLength;
75
+ if (total > D1_RESPONSE_CAP_BYTES) {
76
+ throw new Error(`response exceeded the ${D1_RESPONSE_CAP_BYTES}-byte cap`);
77
+ }
78
+ parts.push(chunk);
79
+ }
80
+ return Buffer.concat(parts).toString("utf-8");
81
+ }
82
+ /**
83
+ * Issue one request under the bound and return its parsed success envelope.
84
+ *
85
+ * Every failure surfaces the body it came with. A 2xx that is not a well-formed
86
+ * `success:true` envelope is an anomaly to report, never a silently empty result:
87
+ * this transport's callers read row sets, and an empty one means "no bookings".
88
+ */
89
+ async function d1ApiJson(fetchFn, url, init, context) {
90
+ // An explicit controller rather than `AbortSignal.timeout`, for two reasons.
91
+ // It is cancellable, so a request that finishes early clears its timer instead
92
+ // of leaving a 60 s handle holding this short-lived dispatcher's event loop
93
+ // open past its work; and its timer is the ordinary global one, so the bound is
94
+ // reachable from a test rather than only from a real 60 s wall-clock wait.
95
+ const controller = new AbortController();
96
+ const timer = setTimeout(() => controller.abort(new Error(`scheduling: ${context} timed out after ${D1_TIMEOUT_MS}ms`)), D1_TIMEOUT_MS);
97
+ try {
98
+ let res;
99
+ try {
100
+ res = await fetchFn(url, { ...init, signal: controller.signal });
101
+ }
102
+ catch (err) {
103
+ // The bound is load-bearing for the PID lock, so name it explicitly rather
104
+ // than relaying a bare DOMException the journal cannot be read for.
105
+ if (controller.signal.aborted) {
106
+ throw new Error(`scheduling: ${context} timed out after ${D1_TIMEOUT_MS}ms`);
107
+ }
108
+ throw new Error(`scheduling: ${context} failed: ${err.message}`);
109
+ }
110
+ let body;
111
+ try {
112
+ // Inside the bound deliberately: a response whose headers arrive promptly
113
+ // but whose body then stalls would otherwise hang here unbounded, which is
114
+ // the same wedge the timeout exists to prevent.
115
+ body = await readCapped(res.chunks);
116
+ }
117
+ catch (err) {
118
+ if (controller.signal.aborted) {
119
+ throw new Error(`scheduling: ${context} timed out after ${D1_TIMEOUT_MS}ms`);
120
+ }
121
+ // Keep the status: without it a capped error response is unattributable.
122
+ throw new Error(`scheduling: ${context} (status ${res.status}) ${err.message}`);
123
+ }
124
+ if (!res.ok) {
125
+ throw new Error(`scheduling: ${context} failed: ${res.status}\n${body}`);
126
+ }
127
+ let parsed;
128
+ try {
129
+ parsed = JSON.parse(body);
130
+ }
131
+ catch {
132
+ throw new Error(`scheduling: ${context} returned non-JSON: ${body}`);
133
+ }
134
+ if (parsed?.success !== true) {
135
+ throw new Error(`scheduling: ${context} API error: ${body}`);
136
+ }
137
+ return parsed;
138
+ }
139
+ finally {
140
+ clearTimeout(timer);
141
+ }
142
+ }
143
+ /**
144
+ * One client per account run. It owns the name -> uuid cache, so a tick pays one
145
+ * lookup for its database and then issues statements against the resolved id.
146
+ */
147
+ export function createD1Client(opts) {
148
+ const fetchFn = opts.fetchFn ?? defaultFetch;
149
+ const ids = new Map();
150
+ let statements = 0;
151
+ let elapsedMs = 0;
152
+ async function resolveId(dbName) {
153
+ const cached = ids.get(dbName);
154
+ if (cached !== undefined)
155
+ return cached;
156
+ const { url, init } = buildD1LookupRequest(opts.accountId, dbName, opts.token);
157
+ const parsed = (await d1ApiJson(fetchFn, url, init, "d1 database lookup"));
158
+ // Exact match on the name, never "the first result". `?name=` is a filter
159
+ // whose matching rule is not doc-confirmed; taking position on trust would
160
+ // let a near-name run this account's statements against the wrong database.
161
+ const hit = (parsed.result ?? []).find((d) => d.name === dbName);
162
+ if (!hit || typeof hit.uuid !== "string") {
163
+ throw new Error(`scheduling: no D1 database named "${dbName}" in account ${opts.accountId}`);
164
+ }
165
+ ids.set(dbName, hit.uuid);
166
+ return hit.uuid;
167
+ }
9
168
  return {
10
- command: "npx",
11
- args: ["wrangler", "d1", "execute", dbName, "--remote", "--json", "--command", sql],
169
+ async query(dbName, sql, params = []) {
170
+ const started = Date.now();
171
+ try {
172
+ const databaseId = await resolveId(dbName);
173
+ const { url, init } = buildD1QueryRequest(opts.accountId, databaseId, sql, opts.token, params);
174
+ // Counted before the await, not after: `statements` is what reached D1,
175
+ // and a statement that was issued and then errored still went. Counting
176
+ // successes only would report 0 both for "the lookup failed so nothing
177
+ // was sent" and for "one statement was sent and errored", which is
178
+ // exactly the distinction the op=d1 line exists to make.
179
+ statements++;
180
+ const parsed = (await d1ApiJson(fetchFn, url, init, "d1 query"));
181
+ // Same shape wrangler --json printed: an array of per-statement results,
182
+ // each carrying its own `results` row array. One statement in, so [0].
183
+ const first = parsed.result?.[0];
184
+ // The per-statement flag is authoritative *when present*. Its REST
185
+ // semantics are undocumented, so this refuses to let an unmeasured fact
186
+ // decide correctness: if a failed statement can arrive inside a
187
+ // success:true envelope, reading it as success would make a failed sweep
188
+ // look swept and leave the row behind silently. Absent is not false — an
189
+ // envelope that omits the flag is accepted, since requiring an
190
+ // undocumented field would reject healthy responses.
191
+ if (first?.success === false) {
192
+ throw new Error(`scheduling: d1 query statement failed: ${first.error ?? JSON.stringify(first)}`);
193
+ }
194
+ return (Array.isArray(first?.results) ? first.results : []);
195
+ }
196
+ finally {
197
+ elapsedMs += Date.now() - started;
198
+ }
199
+ },
200
+ get statements() {
201
+ return statements;
202
+ },
203
+ get elapsedMs() {
204
+ return elapsedMs;
205
+ },
12
206
  };
13
207
  }
14
208
  //# sourceMappingURL=d1-command.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"d1-command.js","sourceRoot":"","sources":["../../src/lib/d1-command.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,UAAU,cAAc,CAC5B,MAAc,EACd,GAAW;IAEX,OAAO;QACL,OAAO,EAAE,KAAK;QACd,IAAI,EAAE,CAAC,UAAU,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,QAAQ,EAAE,WAAW,EAAE,GAAG,CAAC;KACpF,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"d1-command.js","sourceRoot":"","sources":["../../src/lib/d1-command.ts"],"names":[],"mappings":"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"}
@@ -1,11 +1,13 @@
1
1
  /**
2
2
  * Dispatcher script: reconcile-bookings
3
3
  *
4
- * Self-contained, like check-due-events. Pulls accepted booking rows from each
4
+ * Self-contained, like check-due-events. Pulls eligible booking rows from each
5
5
  * account's Cloudflare D1 `bookings` table (written by the booking site's Pages
6
- * Function), writes each into Neo4j as a :Meeting so it appears on the admin
7
- * calendar, links the booker as a :Person, and notifies the admin. Marks each
8
- * row swept so it reconciles exactly once. Ends with the standing audit.
6
+ * Function), claims the ones with no :Meeting in the graph, writes each into
7
+ * Neo4j as a :Meeting so it appears on the admin calendar, links the booker as
8
+ * a :Person, and notifies the admin. A row is claimed on the absence of its
9
+ * meeting, not on a flag, so a meeting that later goes missing self-heals.
10
+ * Ends with the standing audit over that same population.
9
11
  *
10
12
  * Task 1654 — for each booking-site account it also create-only provisions the
11
13
  * recurring connector-sync :Event (the admin turn that reads the calendar connector
@@ -1 +1 @@
1
- {"version":3,"file":"reconcile-bookings.d.ts","sourceRoot":"","sources":["../../src/scripts/reconcile-bookings.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG"}
1
+ {"version":3,"file":"reconcile-bookings.d.ts","sourceRoot":"","sources":["../../src/scripts/reconcile-bookings.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG"}