@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 @@
1
+ {"version":3,"file":"booking-schema-migration.test.js","sourceRoot":"","sources":["../../../src/lib/__tests__/booking-schema-migration.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=connector-sync-provision.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"connector-sync-provision.test.d.ts","sourceRoot":"","sources":["../../../src/lib/__tests__/connector-sync-provision.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,309 @@
1
+ // Task 1809 — the connector-sync provisioning gate.
2
+ //
3
+ // The defect these tests pin had two stacked causes. `readAdminPhone` read a
4
+ // top-level `adminPhones` key that exists on no account (the authoritative key
5
+ // is `whatsapp.adminPhones`), so the gate never opened anywhere; and the gate
6
+ // was on a reply destination rather than on the work's own input, so an account
7
+ // with a calendar and no WhatsApp number could never sync even once the field
8
+ // path was right.
9
+ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
10
+ import { mkdtempSync, mkdirSync, writeFileSync, chmodSync, existsSync } from "node:fs";
11
+ import { tmpdir } from "node:os";
12
+ import { join } from "node:path";
13
+ import { detectCalendarConnectors, resolveDispatchDestination, shouldEmitProvisionLine, provisionSignature, emitLabel, readProvisionState, writeProvisionState, } from "../connector-sync-provision.js";
14
+ const HOUSE = "11111111-1111-1111-1111-111111111111";
15
+ /** A temp accounts dir holding one account with the given account.json. */
16
+ function accountsDirWith(accountId, config) {
17
+ const dir = mkdtempSync(join(tmpdir(), "cal-sync-prov-"));
18
+ const accountDir = join(dir, accountId);
19
+ mkdirSync(accountDir, { recursive: true });
20
+ writeFileSync(join(accountDir, "account.json"), JSON.stringify(config));
21
+ return dir;
22
+ }
23
+ function writeConnector(accountDir, rel, key) {
24
+ const dir = join(accountDir, "secrets", ...rel, key);
25
+ mkdirSync(dir, { recursive: true });
26
+ writeFileSync(join(dir, "tokens.enc"), "cipher");
27
+ }
28
+ describe("detectCalendarConnectors", () => {
29
+ it("reports google from an authorised google account token", () => {
30
+ const dir = accountsDirWith(HOUSE, {});
31
+ const accountDir = join(dir, HOUSE);
32
+ writeConnector(accountDir, ["google", "accounts"], "someone_gmail.com");
33
+ expect(detectCalendarConnectors(accountDir)).toEqual(["google"]);
34
+ });
35
+ it("reports m365 from an authorised outlook mailbox token", () => {
36
+ const dir = accountsDirWith(HOUSE, {});
37
+ const accountDir = join(dir, HOUSE);
38
+ writeConnector(accountDir, ["outlook", "mailboxes"], "60273abb803be05b");
39
+ expect(detectCalendarConnectors(accountDir)).toEqual(["m365"]);
40
+ });
41
+ it("reports both, sorted, when both are authorised", () => {
42
+ const dir = accountsDirWith(HOUSE, {});
43
+ const accountDir = join(dir, HOUSE);
44
+ writeConnector(accountDir, ["google", "accounts"], "someone_gmail.com");
45
+ writeConnector(accountDir, ["outlook", "mailboxes"], "60273abb803be05b");
46
+ expect(detectCalendarConnectors(accountDir)).toEqual(["google", "m365"]);
47
+ });
48
+ it("reports none when the connector directory exists but holds no token", () => {
49
+ const dir = accountsDirWith(HOUSE, {});
50
+ const accountDir = join(dir, HOUSE);
51
+ mkdirSync(join(accountDir, "secrets", "google", "accounts", "half-registered"), { recursive: true });
52
+ expect(detectCalendarConnectors(accountDir)).toEqual([]);
53
+ });
54
+ it("reports none when the account has no secrets directory at all", () => {
55
+ const dir = accountsDirWith(HOUSE, {});
56
+ expect(detectCalendarConnectors(join(dir, HOUSE))).toEqual([]);
57
+ });
58
+ // An unreadable connector directory means the gate's input is UNKNOWN, not
59
+ // empty. Reporting it as `not-applicable` would dress a real fault as the
60
+ // boring expected state — the exact reading that let 7,868 `skipped` lines
61
+ // pass as benign for months.
62
+ it("throws rather than reporting none when the directory exists but cannot be read", () => {
63
+ const dir = accountsDirWith(HOUSE, {});
64
+ const accountDir = join(dir, HOUSE);
65
+ const connectorDir = join(accountDir, "secrets", "google", "accounts");
66
+ mkdirSync(connectorDir, { recursive: true });
67
+ chmodSync(connectorDir, 0o000);
68
+ try {
69
+ expect(() => detectCalendarConnectors(accountDir)).toThrow(/unreadable/);
70
+ }
71
+ finally {
72
+ chmodSync(connectorDir, 0o755);
73
+ }
74
+ });
75
+ });
76
+ describe("resolveDispatchDestination", () => {
77
+ // The house is the socket-owning authority for WhatsApp, so a house account's
78
+ // own `whatsapp.adminPhones` is the authoritative list and validates.
79
+ it("resolves the house's first registered WhatsApp admin phone", () => {
80
+ const dir = accountsDirWith(HOUSE, {
81
+ role: "house",
82
+ whatsapp: { adminPhones: ["+447504472444"] },
83
+ });
84
+ expect(resolveDispatchDestination(dir, HOUSE)).toEqual({
85
+ destination: { channel: "whatsapp", destination: "+447504472444" },
86
+ reason: null,
87
+ });
88
+ });
89
+ // THE GATE-IS-GONE TEST. Before Task 1809 an account with no WhatsApp number
90
+ // was skipped outright, whatever else it had. A Telegram admin is a perfectly
91
+ // good exception destination and must now provision.
92
+ it("falls back to Telegram when the account has no WhatsApp admin phone", () => {
93
+ const dir = accountsDirWith(HOUSE, {
94
+ role: "house",
95
+ telegram: { adminUsers: [123456789] },
96
+ });
97
+ expect(resolveDispatchDestination(dir, HOUSE)).toEqual({
98
+ destination: { channel: "telegram", destination: "123456789" },
99
+ reason: null,
100
+ });
101
+ });
102
+ // Task 1812 must not silently move existing accounts onto a new surface.
103
+ it("prefers WhatsApp when both channels are configured", () => {
104
+ const dir = accountsDirWith(HOUSE, {
105
+ role: "house",
106
+ whatsapp: { adminPhones: ["+447504472444"] },
107
+ telegram: { adminUsers: [123456789] },
108
+ });
109
+ expect(resolveDispatchDestination(dir, HOUSE).destination?.channel).toBe("whatsapp");
110
+ });
111
+ // THE FIELD-PATH TEST. `readAdminPhone` read this key. It exists on no
112
+ // account on the install, which is why every tick logged `skipped`. Reading
113
+ // it must not resolve a destination — otherwise the fix is cosmetic.
114
+ it("does not resolve from the legacy top-level adminPhones key", () => {
115
+ const dir = accountsDirWith(HOUSE, {
116
+ role: "house",
117
+ adminPhones: ["+447504472444"],
118
+ });
119
+ expect(resolveDispatchDestination(dir, HOUSE)).toEqual({
120
+ destination: null,
121
+ reason: "none-configured",
122
+ });
123
+ });
124
+ it("resolves nothing when neither channel is configured", () => {
125
+ const dir = accountsDirWith(HOUSE, { role: "house" });
126
+ expect(resolveDispatchDestination(dir, HOUSE)).toEqual({
127
+ destination: null,
128
+ reason: "none-configured",
129
+ });
130
+ });
131
+ // A destination the standing audit would flag as stale on the very next tick
132
+ // must never be armed: provisioning and the audit share one authority. The
133
+ // reason must survive to the caller — an operator told `unconfigured` at an
134
+ // account whose account.json visibly holds a phone number has no path from
135
+ // the log line to the cause.
136
+ it("refuses a WhatsApp number that is registered to no account, and says why", () => {
137
+ const dir = accountsDirWith(HOUSE, {
138
+ role: "house",
139
+ whatsapp: { adminPhones: ["not-a-phone"] },
140
+ });
141
+ expect(resolveDispatchDestination(dir, HOUSE)).toEqual({
142
+ destination: null,
143
+ reason: "not-registered",
144
+ });
145
+ });
146
+ // A number registered to a DIFFERENT account is a distinct, more actionable
147
+ // diagnosis than "not registered", and validateAgentDestination computes it.
148
+ it("reports cross-account rather than collapsing it into not-registered", () => {
149
+ const SUB = "22222222-2222-2222-2222-222222222222";
150
+ const OTHER = "33333333-3333-3333-3333-333333333333";
151
+ const dir = accountsDirWith(HOUSE, {
152
+ role: "house",
153
+ whatsapp: { adminPhones: [], accountManagers: { "+447700900123": OTHER } },
154
+ });
155
+ const subDir = join(dir, SUB);
156
+ mkdirSync(subDir, { recursive: true });
157
+ writeFileSync(join(subDir, "account.json"), JSON.stringify({ role: "client", whatsapp: { adminPhones: ["+447700900123"] } }));
158
+ expect(resolveDispatchDestination(dir, SUB)).toEqual({
159
+ destination: null,
160
+ reason: "cross-account",
161
+ });
162
+ });
163
+ // main() enumerates accounts by the presence of account.json, so by the time
164
+ // this runs "malformed" is the likely cause and "absent" the unlikely one.
165
+ // Reporting a corrupt config as "no destination configured" tells the
166
+ // operator to add a phone number when the real fact is far larger.
167
+ it("distinguishes an unreadable account.json from an unconfigured one", () => {
168
+ const dir = accountsDirWith(HOUSE, { role: "house" });
169
+ writeFileSync(join(dir, HOUSE, "account.json"), "{not json");
170
+ expect(resolveDispatchDestination(dir, HOUSE)).toEqual({
171
+ destination: null,
172
+ reason: "unreadable-config",
173
+ });
174
+ });
175
+ });
176
+ describe("shouldEmitProvisionLine", () => {
177
+ const HOUR = 60 * 60 * 1000;
178
+ const HEARTBEAT = 6 * HOUR;
179
+ const entry = (outcome, loggedAtMs, detail) => ({
180
+ outcome,
181
+ signature: provisionSignature(outcome, detail),
182
+ loggedAtMs,
183
+ });
184
+ it("emits when there is no prior state for the account", () => {
185
+ expect(shouldEmitProvisionLine(undefined, "not-applicable", 1_000, HEARTBEAT)).toBe(true);
186
+ });
187
+ it("emits when the outcome changed", () => {
188
+ expect(shouldEmitProvisionLine(entry("unconfigured", 1_000), "provisioned", 1_000 + HOUR, HEARTBEAT)).toBe(true);
189
+ });
190
+ // The whole point: 5,688 identical lines is noise that hides its own meaning.
191
+ it("stays silent on an unchanged outcome inside the heartbeat window", () => {
192
+ expect(shouldEmitProvisionLine(entry("already-provisioned", 1_000), "already-provisioned", 1_000 + HOUR, HEARTBEAT)).toBe(false);
193
+ });
194
+ it("emits again once the heartbeat window has elapsed", () => {
195
+ expect(shouldEmitProvisionLine(entry("already-provisioned", 1_000), "already-provisioned", 1_000 + HEARTBEAT, HEARTBEAT)).toBe(true);
196
+ });
197
+ // The heartbeat is the only signal separating "steady state" from "this loop
198
+ // stopped running". An NTP step backwards on an RTC-less Pi must not mute it.
199
+ it("emits rather than muting the heartbeat when the clock steps backwards", () => {
200
+ expect(shouldEmitProvisionLine(entry("already-provisioned", 10 * HOUR), "already-provisioned", 1_000, HEARTBEAT)).toBe(true);
201
+ });
202
+ // A persistent Neo4j fault must not log at tick rate forever — that is the
203
+ // volume profile this task exists to remove — but a CHANGED error is news.
204
+ it("treats an unchanged error as a steady state and a changed one as news", () => {
205
+ const prev = entry("error", 1_000, "Neo4j unreachable");
206
+ expect(shouldEmitProvisionLine(prev, provisionSignature("error", "Neo4j unreachable"), 1_000 + HOUR, HEARTBEAT)).toBe(false);
207
+ expect(shouldEmitProvisionLine(prev, provisionSignature("error", "constraint violation"), 1_000 + HOUR, HEARTBEAT)).toBe(true);
208
+ });
209
+ // Recovery must be visible. Before errors entered the state map, an account
210
+ // returning to health read as "unchanged" and went silent for six hours.
211
+ it("reports recovery from an error immediately", () => {
212
+ const prev = entry("error", 1_000, "Neo4j unreachable");
213
+ expect(shouldEmitProvisionLine(prev, provisionSignature("already-provisioned"), 1_000 + HOUR, HEARTBEAT)).toBe(true);
214
+ });
215
+ });
216
+ describe("emitLabel", () => {
217
+ // A first observation is not a change. Labelling it as one means any state
218
+ // loss produces a synchronised fleet-wide burst of `emit=change`, which is
219
+ // indistinguishable from every account genuinely flipping at once.
220
+ it("labels a first observation `first`, never `change`", () => {
221
+ expect(emitLabel(undefined, "not-applicable")).toBe("first");
222
+ });
223
+ it("labels a genuine outcome change `change`", () => {
224
+ expect(emitLabel({ outcome: "unconfigured", signature: "unconfigured", loggedAtMs: 1 }, "provisioned")).toBe("change");
225
+ });
226
+ it("labels an unchanged outcome `heartbeat`", () => {
227
+ expect(emitLabel({ outcome: "provisioned", signature: "provisioned", loggedAtMs: 1 }, "provisioned")).toBe("heartbeat");
228
+ });
229
+ });
230
+ describe("provision state round-trip", () => {
231
+ let dir;
232
+ beforeEach(() => {
233
+ dir = mkdtempSync(join(tmpdir(), "cal-sync-state-"));
234
+ });
235
+ const ENTRY = { outcome: "provisioned", signature: "provisioned", loggedAtMs: 42 };
236
+ it("round-trips per-account outcomes", () => {
237
+ const path = join(dir, "cal-sync-provision-state.json");
238
+ writeProvisionState(path, { [HOUSE]: ENTRY });
239
+ expect(readProvisionState(path)).toEqual({ [HOUSE]: ENTRY });
240
+ });
241
+ it("leaves no temp file behind, so the write is atomic", () => {
242
+ const path = join(dir, "cal-sync-provision-state.json");
243
+ writeProvisionState(path, { [HOUSE]: ENTRY });
244
+ expect(existsSync(`${path}.tmp`)).toBe(false);
245
+ });
246
+ it("reads an absent state file as empty and silently — that is the first run", () => {
247
+ const spy = vi.spyOn(console, "error").mockImplementation(() => { });
248
+ expect(readProvisionState(join(dir, "missing.json"))).toEqual({});
249
+ expect(spy).not.toHaveBeenCalled();
250
+ });
251
+ // A corrupt state file triggers a fleet-wide `emit=first` burst. That burst
252
+ // must carry its own explanation, or it reads as every account flipping at once.
253
+ it("reads a corrupt state file as empty but says so", () => {
254
+ const spy = vi.spyOn(console, "error").mockImplementation(() => { });
255
+ const bad = join(dir, "bad.json");
256
+ writeFileSync(bad, "{not json");
257
+ expect(readProvisionState(bad)).toEqual({});
258
+ expect(spy).toHaveBeenCalledWith(expect.stringContaining("result=unreadable"));
259
+ });
260
+ // Every realistic cause of a write failure is persistent, so the failure
261
+ // degrades emit-on-change back to per-tick logging indefinitely. Silence
262
+ // here would make that degradation undetectable.
263
+ it("reports a failed write and names the impact rather than swallowing it", () => {
264
+ const spy = vi.spyOn(console, "error").mockImplementation(() => { });
265
+ const readOnly = join(dir, "read-only");
266
+ mkdirSync(readOnly);
267
+ chmodSync(readOnly, 0o500);
268
+ try {
269
+ writeProvisionState(join(readOnly, "state.json"), { [HOUSE]: ENTRY });
270
+ expect(spy).toHaveBeenCalledWith(expect.stringContaining("result=write-failed"));
271
+ expect(spy).toHaveBeenCalledWith(expect.stringContaining("impact=emit-on-change-degraded-to-per-tick"));
272
+ }
273
+ finally {
274
+ chmodSync(readOnly, 0o755);
275
+ }
276
+ });
277
+ });
278
+ // The mutation check the task calls for. A permanently unconfigured account is
279
+ // the case most at risk of being silenced by the very change that quietens the
280
+ // per-tick noise. This asserts the outcome reaches the operator on change, and
281
+ // the guard below proves the assertion has teeth.
282
+ describe("an unconfigured account is reported, not swallowed", () => {
283
+ const HEARTBEAT = 6 * 60 * 60 * 1000;
284
+ let emitted;
285
+ beforeEach(() => {
286
+ emitted = [];
287
+ });
288
+ afterEach(() => {
289
+ vi.restoreAllMocks();
290
+ });
291
+ /** The emit decision exactly as reconcileAccount applies it. */
292
+ function tick(prevState, nowMs) {
293
+ const outcome = "unconfigured";
294
+ const signature = provisionSignature(outcome, "none-configured");
295
+ if (shouldEmitProvisionLine(prevState[HOUSE], signature, nowMs, HEARTBEAT)) {
296
+ emitted.push(outcome);
297
+ prevState[HOUSE] = { outcome, signature, loggedAtMs: nowMs };
298
+ }
299
+ }
300
+ it("emits once on first observation and stays silent for the next 100 ticks", () => {
301
+ const state = {};
302
+ // 101 ticks at 120s is under 3.4h, inside the 6h heartbeat window.
303
+ for (let i = 0; i < 101; i++)
304
+ tick(state, i * 120_000);
305
+ // One line, not 101 — and crucially not zero.
306
+ expect(emitted).toEqual(["unconfigured"]);
307
+ });
308
+ });
309
+ //# sourceMappingURL=connector-sync-provision.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"connector-sync-provision.test.js","sourceRoot":"","sources":["../../../src/lib/__tests__/connector-sync-provision.test.ts"],"names":[],"mappings":"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"}
@@ -1,23 +1,223 @@
1
- // Regression guard for Task 1350. The brand server PATH carries no global
2
- // `wrangler`; only `npx wrangler` resolves, the same form the proven
3
- // forward-path availability publisher uses. A bare-`wrangler` spawn head
4
- // returns ENOENT on every reconcile tick, so booking->calendar reverse-sync
5
- // silently fails. This pins the resolved command to the npx form.
6
- import { describe, it, expect } from "vitest";
7
- import { buildD1Command } from "../d1-command.js";
8
- describe("buildD1Command", () => {
9
- it("invokes wrangler via npx, not a bare wrangler binary", () => {
10
- const { command, args } = buildD1Command("mydb", "SELECT 1");
11
- expect(command).toBe("npx");
12
- expect(args[0]).toBe("wrangler");
13
- expect(command).not.toBe("wrangler");
14
- });
15
- it("preserves the d1 execute argv shape and the SQL/db it was given", () => {
16
- const { args } = buildD1Command("mydb", "SELECT 1");
17
- expect(args).toEqual([
18
- "wrangler", "d1", "execute", "mydb",
19
- "--remote", "--json", "--command", "SELECT 1",
1
+ // Task 1805 the transport is HTTP, not a process per statement. These pin the
2
+ // requests it composes, the rows it parses, the one-lookup-per-run cache, and the
3
+ // two bounds (60 s request, 16 MB streamed response) inherited from the spawnSync
4
+ // call this replaced. Task 1350's npx-head guard is retired with the spawn it guarded.
5
+ import { describe, it, expect, vi } from "vitest";
6
+ import { buildD1LookupRequest, buildD1QueryRequest, createD1Client, D1_RESPONSE_CAP_BYTES, D1_TIMEOUT_MS, } from "../d1-command.js";
7
+ const ACC = "acc123";
8
+ const DB_ID = "11111111-2222-3333-4444-555555555555";
9
+ function bodyOf(text) {
10
+ return (async function* () {
11
+ yield new TextEncoder().encode(text);
12
+ })();
13
+ }
14
+ /** A fetch seam whose responses are scripted per call, recording what it received. */
15
+ function fakeFetch(responses) {
16
+ const calls = [];
17
+ let i = 0;
18
+ const fn = async (url, init) => {
19
+ calls.push({ url, init });
20
+ const r = responses[i++];
21
+ if (!r)
22
+ throw new Error(`unexpected extra fetch to ${url}`);
23
+ return { ok: r.ok ?? true, status: r.status ?? 200, chunks: bodyOf(r.text) };
24
+ };
25
+ return { fn, calls };
26
+ }
27
+ const lookupOk = (name) => JSON.stringify({ success: true, result: [{ uuid: DB_ID, name }] });
28
+ const queryOk = (rows) => JSON.stringify({ success: true, result: [{ results: rows, success: true }] });
29
+ describe("buildD1LookupRequest", () => {
30
+ it("addresses the account's D1 list endpoint filtered by the database name", () => {
31
+ const { url, init } = buildD1LookupRequest(ACC, "book db", "tok");
32
+ expect(url).toBe(`https://api.cloudflare.com/client/v4/accounts/${ACC}/d1/database?name=book+db`);
33
+ expect(init.method).toBe("GET");
34
+ });
35
+ it("carries a bearer Authorization header", () => {
36
+ const { init } = buildD1LookupRequest(ACC, "mydb", "tok");
37
+ expect(Object.keys(init.headers)).toContain("Authorization");
38
+ expect(init.headers.Authorization.startsWith("Bearer ")).toBe(true);
39
+ });
40
+ });
41
+ describe("buildD1QueryRequest", () => {
42
+ it("posts the SQL to the database's query endpoint", () => {
43
+ const { url, init } = buildD1QueryRequest(ACC, DB_ID, "SELECT 1", "tok");
44
+ expect(url).toBe(`https://api.cloudflare.com/client/v4/accounts/${ACC}/d1/database/${DB_ID}/query`);
45
+ expect(init.method).toBe("POST");
46
+ expect(init.headers["Content-Type"]).toBe("application/json");
47
+ expect(JSON.parse(init.body)).toEqual({ sql: "SELECT 1", params: [] });
48
+ });
49
+ it("sends values as bound parameters rather than folding them into the SQL", () => {
50
+ const { init } = buildD1QueryRequest(ACC, DB_ID, "UPDATE bookings SET swept=1 WHERE bookingId=?1", "tok", ["b'1"]);
51
+ const sent = JSON.parse(init.body);
52
+ expect(sent.params).toEqual(["b'1"]);
53
+ expect(sent.sql).not.toContain("b'1");
54
+ });
55
+ it("carries a bearer Authorization header", () => {
56
+ const { init } = buildD1QueryRequest(ACC, DB_ID, "SELECT 1", "tok");
57
+ expect(Object.keys(init.headers)).toContain("Authorization");
58
+ expect(init.headers.Authorization.startsWith("Bearer ")).toBe(true);
59
+ });
60
+ it("starts no process — the module exports no argv builder", async () => {
61
+ const mod = await import("../d1-command.js");
62
+ expect("buildD1Command" in mod).toBe(false);
63
+ });
64
+ });
65
+ describe("createD1Client", () => {
66
+ it("resolves the database by name then returns the query's rows", async () => {
67
+ const { fn, calls } = fakeFetch([
68
+ { text: lookupOk("mydb") },
69
+ { text: queryOk([{ bookingId: "b1" }]) },
20
70
  ]);
71
+ const client = createD1Client({ accountId: ACC, token: "tok", fetchFn: fn });
72
+ await expect(client.query("mydb", "SELECT 1")).resolves.toEqual([{ bookingId: "b1" }]);
73
+ expect(calls[0].url).toContain("/d1/database?name=mydb");
74
+ expect(calls[1].url).toContain(`/d1/database/${DB_ID}/query`);
75
+ });
76
+ it("returns an empty row set when the statement produced no results", async () => {
77
+ const { fn } = fakeFetch([{ text: lookupOk("mydb") }, { text: queryOk([]) }]);
78
+ const client = createD1Client({ accountId: ACC, token: "tok", fetchFn: fn });
79
+ await expect(client.query("mydb", "UPDATE t SET x=1")).resolves.toEqual([]);
80
+ });
81
+ it("passes a caller's parameters through to the request body", async () => {
82
+ const { fn, calls } = fakeFetch([{ text: lookupOk("mydb") }, { text: queryOk([]) }]);
83
+ const client = createD1Client({ accountId: ACC, token: "tok", fetchFn: fn });
84
+ await client.query("mydb", "UPDATE t SET x=1 WHERE id=?1", ["abc"]);
85
+ expect(JSON.parse(calls[1].init.body).params).toEqual(["abc"]);
86
+ });
87
+ it("looks the database up once per run and reuses the id for later statements", async () => {
88
+ const { fn, calls } = fakeFetch([
89
+ { text: lookupOk("mydb") },
90
+ { text: queryOk([]) },
91
+ { text: queryOk([]) },
92
+ ]);
93
+ const client = createD1Client({ accountId: ACC, token: "tok", fetchFn: fn });
94
+ await client.query("mydb", "SELECT 1");
95
+ await client.query("mydb", "SELECT 2");
96
+ expect(calls.filter((c) => c.url.includes("?name=")).length).toBe(1);
97
+ expect(calls.length).toBe(3);
98
+ });
99
+ it("counts the statements it issued and the time it spent", async () => {
100
+ const { fn } = fakeFetch([
101
+ { text: lookupOk("mydb") },
102
+ { text: queryOk([]) },
103
+ { text: queryOk([]) },
104
+ ]);
105
+ const client = createD1Client({ accountId: ACC, token: "tok", fetchFn: fn });
106
+ await client.query("mydb", "SELECT 1");
107
+ await client.query("mydb", "SELECT 2");
108
+ expect(client.statements).toBe(2);
109
+ expect(client.elapsedMs).toBeGreaterThanOrEqual(0);
110
+ });
111
+ it("refuses a name the account does not carry rather than querying a guessed id", async () => {
112
+ const { fn } = fakeFetch([
113
+ { text: JSON.stringify({ success: true, result: [{ uuid: DB_ID, name: "otherdb" }] }) },
114
+ ]);
115
+ const client = createD1Client({ accountId: ACC, token: "tok", fetchFn: fn });
116
+ await expect(client.query("mydb", "SELECT 1")).rejects.toThrow(/mydb/);
117
+ });
118
+ it("surfaces a non-2xx with its status and body", async () => {
119
+ const { fn } = fakeFetch([{ ok: false, status: 403, text: '{"errors":[{"code":10000}]}' }]);
120
+ const client = createD1Client({ accountId: ACC, token: "tok", fetchFn: fn });
121
+ await expect(client.query("mydb", "SELECT 1")).rejects.toThrow(/403[\s\S]*10000/);
122
+ });
123
+ it("surfaces a 2xx that is not a success envelope with its body", async () => {
124
+ const { fn } = fakeFetch([
125
+ { text: lookupOk("mydb") },
126
+ { text: JSON.stringify({ success: false, errors: [{ message: "no such table" }] }) },
127
+ ]);
128
+ const client = createD1Client({ accountId: ACC, token: "tok", fetchFn: fn });
129
+ await expect(client.query("mydb", "SELECT 1")).rejects.toThrow(/no such table/);
130
+ });
131
+ it("surfaces a statement that reports its own failure inside a success envelope", async () => {
132
+ // The REST API's per-statement `success` semantics are undocumented, so this
133
+ // refuses to let an unmeasured fact decide correctness. If a failed sweep can
134
+ // arrive this way, treating it as success would leave the row unswept while
135
+ // the caller believed it had swept — a silent no-op on the write path.
136
+ const { fn } = fakeFetch([
137
+ { text: lookupOk("mydb") },
138
+ {
139
+ text: JSON.stringify({
140
+ success: true,
141
+ result: [{ success: false, results: [], error: "no such table: bookings" }],
142
+ }),
143
+ },
144
+ ]);
145
+ const client = createD1Client({ accountId: ACC, token: "tok", fetchFn: fn });
146
+ await expect(client.query("mydb", "UPDATE bookings SET swept=1")).rejects.toThrow(/no such table/);
147
+ });
148
+ it("accepts a statement that omits the success flag rather than requiring it", async () => {
149
+ // Absent is not false. Requiring an undocumented field to be present would
150
+ // reject healthy responses that simply do not carry it.
151
+ const { fn } = fakeFetch([
152
+ { text: lookupOk("mydb") },
153
+ { text: JSON.stringify({ success: true, result: [{ results: [{ n: 1 }] }] }) },
154
+ ]);
155
+ const client = createD1Client({ accountId: ACC, token: "tok", fetchFn: fn });
156
+ await expect(client.query("mydb", "SELECT 1")).resolves.toEqual([{ n: 1 }]);
157
+ });
158
+ it("counts a statement that was issued and then failed, not only ones that succeeded", async () => {
159
+ // `statements` is what reached D1. Counting successes only would report 0 for
160
+ // both "the name lookup failed so nothing was sent" and "one statement was
161
+ // sent and errored", which the op=d1 line needs to tell apart.
162
+ const { fn } = fakeFetch([
163
+ { text: lookupOk("mydb") },
164
+ { ok: false, status: 500, text: "boom" },
165
+ ]);
166
+ const client = createD1Client({ accountId: ACC, token: "tok", fetchFn: fn });
167
+ await expect(client.query("mydb", "SELECT 1")).rejects.toThrow(/500/);
168
+ expect(client.statements).toBe(1);
169
+ });
170
+ it("counts no statement when the name lookup itself failed", async () => {
171
+ const { fn } = fakeFetch([{ ok: false, status: 403, text: "denied" }]);
172
+ const client = createD1Client({ accountId: ACC, token: "tok", fetchFn: fn });
173
+ await expect(client.query("mydb", "SELECT 1")).rejects.toThrow(/403/);
174
+ expect(client.statements).toBe(0);
175
+ });
176
+ it("surfaces a 2xx that is not JSON with its body", async () => {
177
+ const { fn } = fakeFetch([{ text: "<html>gateway</html>" }]);
178
+ const client = createD1Client({ accountId: ACC, token: "tok", fetchFn: fn });
179
+ await expect(client.query("mydb", "SELECT 1")).rejects.toThrow(/gateway/);
180
+ });
181
+ it("aborts a response past the cap while streaming, not after buffering it", async () => {
182
+ // Three chunks of 8 MB. The cap is 16 MB, so the third crosses it. Counting
183
+ // the chunks consumed proves the read stopped rather than materialising the
184
+ // whole body and checking its length afterwards.
185
+ let consumed = 0;
186
+ const chunk = new Uint8Array(8 * 1024 * 1024);
187
+ const fn = async () => ({
188
+ ok: true,
189
+ status: 200,
190
+ chunks: (async function* () {
191
+ for (let i = 0; i < 5; i++) {
192
+ consumed++;
193
+ yield chunk;
194
+ }
195
+ })(),
196
+ });
197
+ const client = createD1Client({ accountId: ACC, token: "tok", fetchFn: fn });
198
+ await expect(client.query("mydb", "SELECT 1")).rejects.toThrow(/cap/);
199
+ expect(consumed).toBe(3);
200
+ expect(D1_RESPONSE_CAP_BYTES).toBe(16 * 1024 * 1024);
201
+ });
202
+ it("rejects at the 60 s bound rather than hanging when the endpoint stalls", async () => {
203
+ vi.useFakeTimers();
204
+ try {
205
+ // Honours the signal the client attached; resolves never otherwise. This is
206
+ // the property the PID lock depends on: a stalled Cloudflare must not wedge
207
+ // the run and hold the lock.
208
+ const fn = (_url, init) => new Promise((_resolve, reject) => {
209
+ init.signal.addEventListener("abort", () => reject(init.signal.reason));
210
+ });
211
+ const client = createD1Client({ accountId: ACC, token: "tok", fetchFn: fn });
212
+ const pending = client.query("mydb", "SELECT 1");
213
+ const assertion = expect(pending).rejects.toThrow(/timed out after 60000ms/);
214
+ await vi.advanceTimersByTimeAsync(D1_TIMEOUT_MS + 1);
215
+ await assertion;
216
+ expect(D1_TIMEOUT_MS).toBe(60_000);
217
+ }
218
+ finally {
219
+ vi.useRealTimers();
220
+ }
21
221
  });
22
222
  });
23
223
  //# sourceMappingURL=d1-command.test.js.map