@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.
- package/package.json +1 -1
- package/payload/platform/lib/storage-broker/dist/cf-exec.d.ts.map +1 -1
- package/payload/platform/lib/storage-broker/dist/cf-exec.js +3 -2
- package/payload/platform/lib/storage-broker/dist/cf-exec.js.map +1 -1
- package/payload/platform/lib/storage-broker/src/cf-exec.ts +3 -2
- package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +18 -9
- package/payload/platform/plugins/cloudflare/skills/calendar-site/template/schema.sql +13 -3
- package/payload/platform/plugins/docs/references/calendar-booking.md +2 -2
- package/payload/platform/plugins/docs/references/google-guide.md +15 -6
- package/payload/platform/plugins/google/.claude-plugin/plugin.json +1 -1
- package/payload/platform/plugins/google/PLUGIN.md +60 -12
- package/payload/platform/plugins/google/mcp/dist/__tests__/account-audit.test.d.ts +2 -0
- package/payload/platform/plugins/google/mcp/dist/__tests__/account-audit.test.d.ts.map +1 -0
- package/payload/platform/plugins/google/mcp/dist/__tests__/account-audit.test.js +229 -0
- package/payload/platform/plugins/google/mcp/dist/__tests__/account-audit.test.js.map +1 -0
- package/payload/platform/plugins/google/mcp/dist/__tests__/account-register.test.js +9 -9
- package/payload/platform/plugins/google/mcp/dist/__tests__/account-register.test.js.map +1 -1
- package/payload/platform/plugins/google/mcp/dist/__tests__/consent-url-redirect.test.d.ts +2 -0
- package/payload/platform/plugins/google/mcp/dist/__tests__/consent-url-redirect.test.d.ts.map +1 -0
- package/payload/platform/plugins/google/mcp/dist/__tests__/consent-url-redirect.test.js +17 -0
- package/payload/platform/plugins/google/mcp/dist/__tests__/consent-url-redirect.test.js.map +1 -0
- package/payload/platform/plugins/google/mcp/dist/__tests__/loopback-listener.test.d.ts +2 -0
- package/payload/platform/plugins/google/mcp/dist/__tests__/loopback-listener.test.d.ts.map +1 -0
- package/payload/platform/plugins/google/mcp/dist/__tests__/loopback-listener.test.js +146 -0
- package/payload/platform/plugins/google/mcp/dist/__tests__/loopback-listener.test.js.map +1 -0
- package/payload/platform/plugins/google/mcp/dist/__tests__/pending-redirect.test.d.ts +2 -0
- package/payload/platform/plugins/google/mcp/dist/__tests__/pending-redirect.test.d.ts.map +1 -0
- package/payload/platform/plugins/google/mcp/dist/__tests__/pending-redirect.test.js +19 -0
- package/payload/platform/plugins/google/mcp/dist/__tests__/pending-redirect.test.js.map +1 -0
- package/payload/platform/plugins/google/mcp/dist/__tests__/register-redirect-chain.test.d.ts +2 -0
- package/payload/platform/plugins/google/mcp/dist/__tests__/register-redirect-chain.test.d.ts.map +1 -0
- package/payload/platform/plugins/google/mcp/dist/__tests__/register-redirect-chain.test.js +41 -0
- package/payload/platform/plugins/google/mcp/dist/__tests__/register-redirect-chain.test.js.map +1 -0
- package/payload/platform/plugins/google/mcp/dist/auth/account-audit.d.ts +44 -0
- package/payload/platform/plugins/google/mcp/dist/auth/account-audit.d.ts.map +1 -0
- package/payload/platform/plugins/google/mcp/dist/auth/account-audit.js +112 -0
- package/payload/platform/plugins/google/mcp/dist/auth/account-audit.js.map +1 -0
- package/payload/platform/plugins/google/mcp/dist/auth/loopback-listener.d.ts +17 -0
- package/payload/platform/plugins/google/mcp/dist/auth/loopback-listener.d.ts.map +1 -0
- package/payload/platform/plugins/google/mcp/dist/auth/loopback-listener.js +143 -0
- package/payload/platform/plugins/google/mcp/dist/auth/loopback-listener.js.map +1 -0
- package/payload/platform/plugins/google/mcp/dist/auth/pending-store.d.ts +9 -0
- package/payload/platform/plugins/google/mcp/dist/auth/pending-store.d.ts.map +1 -1
- package/payload/platform/plugins/google/mcp/dist/auth/pending-store.js.map +1 -1
- package/payload/platform/plugins/google/mcp/dist/index.js +73 -5
- package/payload/platform/plugins/google/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/google/mcp/dist/scripts/account-audit.d.ts +2 -0
- package/payload/platform/plugins/google/mcp/dist/scripts/account-audit.d.ts.map +1 -0
- package/payload/platform/plugins/google/mcp/dist/scripts/account-audit.js +37 -0
- package/payload/platform/plugins/google/mcp/dist/scripts/account-audit.js.map +1 -0
- package/payload/platform/plugins/google/mcp/dist/tools/account-register.d.ts +20 -4
- package/payload/platform/plugins/google/mcp/dist/tools/account-register.d.ts.map +1 -1
- package/payload/platform/plugins/google/mcp/dist/tools/account-register.js +19 -9
- package/payload/platform/plugins/google/mcp/dist/tools/account-register.js.map +1 -1
- package/payload/platform/plugins/google/references/auth.md +93 -14
- package/payload/platform/plugins/scheduling/PLUGIN.md +2 -2
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/booking-reconcile.test.js +158 -23
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/booking-reconcile.test.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/booking-schema-migration.test.d.ts +2 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/booking-schema-migration.test.d.ts.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/booking-schema-migration.test.js +200 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/booking-schema-migration.test.js.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/connector-sync-provision.test.d.ts +2 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/connector-sync-provision.test.d.ts.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/connector-sync-provision.test.js +309 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/connector-sync-provision.test.js.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/d1-command.test.js +219 -19
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/d1-command.test.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/lib/booking-reconcile.d.ts +94 -11
- package/payload/platform/plugins/scheduling/mcp/dist/lib/booking-reconcile.d.ts.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/lib/booking-reconcile.js +106 -22
- package/payload/platform/plugins/scheduling/mcp/dist/lib/booking-reconcile.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/lib/connector-sync-provision.d.ts +109 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/connector-sync-provision.d.ts.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/connector-sync-provision.js +213 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/connector-sync-provision.js.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/d1-command.d.ts +80 -8
- package/payload/platform/plugins/scheduling/mcp/dist/lib/d1-command.d.ts.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/lib/d1-command.js +202 -8
- package/payload/platform/plugins/scheduling/mcp/dist/lib/d1-command.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/reconcile-bookings.d.ts +6 -4
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/reconcile-bookings.d.ts.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/reconcile-bookings.js +334 -142
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/reconcile-bookings.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/activity-rows.d.ts +21 -3
- package/payload/platform/services/claude-session-manager/dist/activity-rows.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/activity-rows.js +6 -2
- package/payload/platform/services/claude-session-manager/dist/activity-rows.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/fs-watcher.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/fs-watcher.js +43 -5
- package/payload/platform/services/claude-session-manager/dist/fs-watcher.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/jsonl-enumerator.d.ts +9 -0
- package/payload/platform/services/claude-session-manager/dist/jsonl-enumerator.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/jsonl-enumerator.js +78 -0
- package/payload/platform/services/claude-session-manager/dist/jsonl-enumerator.js.map +1 -1
- package/payload/server/{chunk-Y6V45R6M.js → chunk-LBCMFD4O.js} +173 -29
- package/payload/server/maxy-edge.js +1 -1
- package/payload/server/public/activity.html +6 -6
- package/payload/server/public/assets/{AdminLoginScreens-B44KbEuC.js → AdminLoginScreens-DximwPlS.js} +1 -1
- package/payload/server/public/assets/{AdminShell-BjxV2qkb.js → AdminShell-BehbH-Oa.js} +1 -1
- package/payload/server/public/assets/{Checkbox-DSeZNGNA.js → Checkbox-DM-eHqOS.js} +1 -1
- package/payload/server/public/assets/activity-hDwUeQd6.js +1 -0
- package/payload/server/public/assets/{admin-B4LpTCur.js → admin-CAkDnGdk.js} +1 -1
- package/payload/server/public/assets/{browser-CEFvaWwI.js → browser-DXl8hOoi.js} +1 -1
- package/payload/server/public/assets/{calendar-B3zo6V34.js → calendar-DP6hn4-6.js} +1 -1
- package/payload/server/public/assets/chat-DhOfVGYM.js +1 -0
- package/payload/server/public/assets/chevron-left-DYNBoLii.js +1 -0
- package/payload/server/public/assets/data-bq8fTo_s.js +1 -0
- package/payload/server/public/assets/{graph-RaOrnSf2.js → graph-CHcYoEJ5.js} +1 -1
- package/payload/server/public/assets/{graph-labels-GlTEIYJj.js → graph-labels-CZykslZM.js} +1 -1
- package/payload/server/public/assets/{maximize-2-CHDa8p5h.js → maximize-2-31AZEcMS.js} +1 -1
- package/payload/server/public/assets/{operator-DtqHY4jV.js → operator-BGGpdsO_.js} +1 -1
- package/payload/server/public/assets/{page-CkhFj8GQ.js → page-BloC6ygA.js} +1 -1
- package/payload/server/public/assets/{page-DxnWgPtd.js → page-DOeiiqbR.js} +1 -1
- package/payload/server/public/assets/{public-D5TUfQ3I.js → public-CGgO6IZv.js} +1 -1
- package/payload/server/public/assets/{rotate-ccw-BJjVAQQY.js → rotate-ccw-BlAi6iFg.js} +1 -1
- package/payload/server/public/assets/{tasks-CM9pYYG1.js → tasks-C1giFRRL.js} +1 -1
- package/payload/server/public/assets/{time-entry-format-C5RYEYjM.js → time-entry-format-CiUUQdq7.js} +1 -1
- package/payload/server/public/assets/{triangle-alert-DLfIsuo_.js → triangle-alert-CVqKuLoa.js} +1 -1
- package/payload/server/public/assets/{useCopyFeedback-7yKeiAzV.js → useCopyFeedback-ksyUe_g1.js} +1 -1
- package/payload/server/public/assets/{useSelectionMode-CWlh6AEq.js → useSelectionMode-BPrBkdTd.js} +1 -1
- package/payload/server/public/assets/{useSubAccountSwitcher-B2n9QPVI.css → useSubAccountSwitcher-K4kYeN6i.css} +1 -1
- package/payload/server/public/assets/{useVoiceRecorder-j3ZArZ2H.js → useVoiceRecorder-CTXP8oTW.js} +1 -1
- package/payload/server/public/browser.html +5 -5
- package/payload/server/public/calendar.html +6 -6
- package/payload/server/public/chat.html +12 -12
- package/payload/server/public/data.html +11 -11
- package/payload/server/public/graph.html +11 -11
- package/payload/server/public/index.html +14 -14
- package/payload/server/public/operator.html +14 -14
- package/payload/server/public/public.html +12 -12
- package/payload/server/public/tasks.html +5 -5
- package/payload/server/server.js +547 -423
- package/payload/server/public/assets/activity-CdipMTDp.js +0 -1
- package/payload/server/public/assets/chat-5tJdo9i_.js +0 -1
- package/payload/server/public/assets/chevron-left-BJllrPBj.js +0 -1
- package/payload/server/public/assets/data-Du79EgR-.js +0 -1
- /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
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
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
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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
|
|
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
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
11
|
-
|
|
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
|
|
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
|
|
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),
|
|
7
|
-
*
|
|
8
|
-
*
|
|
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
|
|
1
|
+
{"version":3,"file":"reconcile-bookings.d.ts","sourceRoot":"","sources":["../../src/scripts/reconcile-bookings.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG"}
|