@rubytech/create-maxy-code 0.1.472 → 0.1.473
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/dist/__tests__/google-client-id-env.test.js +12 -10
- package/dist/index.js +6 -29
- package/dist/port-resolution.js +5 -5
- package/package.json +1 -1
- package/payload/platform/config/brand.json +1 -0
- package/payload/platform/docs/superpowers/plans/2026-07-18-ledger-write-path-fix.md +109 -0
- package/payload/platform/lib/ledger-core/dist/reconcile.d.ts +7 -0
- package/payload/platform/lib/ledger-core/dist/reconcile.d.ts.map +1 -1
- package/payload/platform/lib/ledger-core/dist/reconcile.js +5 -2
- package/payload/platform/lib/ledger-core/dist/reconcile.js.map +1 -1
- package/payload/platform/lib/ledger-core/src/__tests__/reconcile.test.ts +20 -0
- package/payload/platform/lib/ledger-core/src/reconcile.ts +12 -2
- package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +24 -5
- package/payload/platform/plugins/docs/references/google-guide.md +10 -3
- package/payload/platform/plugins/docs/references/ledger.md +9 -1
- package/payload/platform/plugins/docs/references/troubleshooting.md +4 -0
- package/payload/platform/plugins/google/.claude-plugin/plugin.json +1 -1
- package/payload/platform/plugins/google/PLUGIN.md +4 -1
- package/payload/platform/plugins/google/mcp/dist/__tests__/account-register.test.js +79 -28
- package/payload/platform/plugins/google/mcp/dist/__tests__/account-register.test.js.map +1 -1
- package/payload/platform/plugins/google/mcp/dist/__tests__/boot.test.d.ts +2 -0
- package/payload/platform/plugins/google/mcp/dist/__tests__/boot.test.d.ts.map +1 -0
- package/payload/platform/plugins/google/mcp/dist/__tests__/boot.test.js +54 -0
- package/payload/platform/plugins/google/mcp/dist/__tests__/boot.test.js.map +1 -0
- package/payload/platform/plugins/google/mcp/dist/__tests__/google-client.test.js +26 -1
- package/payload/platform/plugins/google/mcp/dist/__tests__/google-client.test.js.map +1 -1
- package/payload/platform/plugins/google/mcp/dist/__tests__/index-registration.test.js +3 -2
- package/payload/platform/plugins/google/mcp/dist/__tests__/index-registration.test.js.map +1 -1
- package/payload/platform/plugins/google/mcp/dist/index.d.ts +9 -5
- package/payload/platform/plugins/google/mcp/dist/index.d.ts.map +1 -1
- package/payload/platform/plugins/google/mcp/dist/index.js +38 -37
- package/payload/platform/plugins/google/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/google/mcp/dist/lib/google-client.d.ts +10 -7
- package/payload/platform/plugins/google/mcp/dist/lib/google-client.d.ts.map +1 -1
- package/payload/platform/plugins/google/mcp/dist/lib/google-client.js +25 -13
- package/payload/platform/plugins/google/mcp/dist/lib/google-client.js.map +1 -1
- package/payload/platform/plugins/google/mcp/dist/tools/account-register.d.ts +58 -17
- 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 +143 -37
- package/payload/platform/plugins/google/mcp/dist/tools/account-register.js.map +1 -1
- package/payload/platform/plugins/google/references/auth.md +91 -74
- package/payload/platform/plugins/ledger/PLUGIN.md +13 -1
- package/payload/platform/plugins/ledger/mcp/dist/index.js +23 -15
- package/payload/platform/plugins/ledger/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/ledger/mcp/dist/lib/read.d.ts +11 -1
- package/payload/platform/plugins/ledger/mcp/dist/lib/read.d.ts.map +1 -1
- package/payload/platform/plugins/ledger/mcp/dist/lib/read.js +20 -1
- package/payload/platform/plugins/ledger/mcp/dist/lib/read.js.map +1 -1
- package/payload/platform/plugins/ledger/mcp/dist/lib/types.d.ts +14 -11
- package/payload/platform/plugins/ledger/mcp/dist/lib/types.d.ts.map +1 -1
- package/payload/platform/plugins/ledger/mcp/dist/lib/types.js.map +1 -1
- package/payload/platform/plugins/ledger/mcp/dist/lib/write-rejects.d.ts +12 -0
- package/payload/platform/plugins/ledger/mcp/dist/lib/write-rejects.d.ts.map +1 -0
- package/payload/platform/plugins/ledger/mcp/dist/lib/write-rejects.js +50 -0
- package/payload/platform/plugins/ledger/mcp/dist/lib/write-rejects.js.map +1 -0
- package/payload/platform/plugins/ledger/mcp/dist/tools/cash-record.d.ts +5 -5
- package/payload/platform/plugins/ledger/mcp/dist/tools/cash-record.d.ts.map +1 -1
- package/payload/platform/plugins/ledger/mcp/dist/tools/cash-record.js +11 -11
- package/payload/platform/plugins/ledger/mcp/dist/tools/cash-record.js.map +1 -1
- package/payload/platform/plugins/ledger/mcp/dist/tools/invoice-record.d.ts +7 -2
- package/payload/platform/plugins/ledger/mcp/dist/tools/invoice-record.d.ts.map +1 -1
- package/payload/platform/plugins/ledger/mcp/dist/tools/invoice-record.js +9 -3
- package/payload/platform/plugins/ledger/mcp/dist/tools/invoice-record.js.map +1 -1
- package/payload/platform/plugins/ledger/mcp/dist/tools/payment-record.d.ts +7 -0
- package/payload/platform/plugins/ledger/mcp/dist/tools/payment-record.d.ts.map +1 -1
- package/payload/platform/plugins/ledger/mcp/dist/tools/payment-record.js +3 -4
- package/payload/platform/plugins/ledger/mcp/dist/tools/payment-record.js.map +1 -1
- package/payload/platform/plugins/ledger/mcp/dist/tools/reconcile.d.ts +1 -1
- package/payload/platform/plugins/ledger/mcp/dist/tools/reconcile.d.ts.map +1 -1
- package/payload/platform/plugins/ledger/mcp/dist/tools/reconcile.js +4 -1
- package/payload/platform/plugins/ledger/mcp/dist/tools/reconcile.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/activity-rows.d.ts +27 -2
- 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 +33 -2
- package/payload/platform/services/claude-session-manager/dist/activity-rows.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js +1 -0
- package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/http-server.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/http-server.js +3 -0
- package/payload/platform/services/claude-session-manager/dist/http-server.js.map +1 -1
- package/payload/platform/templates/specialists/agents/personal-assistant.md +1 -1
- package/payload/server/{chunk-BSTIQX52.js → chunk-Y6V45R6M.js} +1 -12
- package/payload/server/maxy-edge.js +1 -1
- package/payload/server/public/activity.html +6 -6
- package/payload/server/public/assets/{AdminLoginScreens-BfamGqS7.js → AdminLoginScreens-B7TQZV8u.js} +1 -1
- package/payload/server/public/assets/{AdminShell-5nHOAzXO.js → AdminShell-DAuri4Yh.js} +1 -1
- package/payload/server/public/assets/{Checkbox-CD6aHwac.js → Checkbox-B1XhnhPk.js} +1 -1
- package/payload/server/public/assets/activity-kRwzxEKU.js +1 -0
- package/payload/server/public/assets/{admin-Cpcw_YE-.js → admin-BdcccSb5.js} +1 -1
- package/payload/server/public/assets/{browser-0znOwk1W.js → browser-B6woQ2qX.js} +1 -1
- package/payload/server/public/assets/{calendar-CykigHdg.js → calendar-CalCeg5T.js} +1 -1
- package/payload/server/public/assets/chat-9s2-L06v.js +1 -0
- package/payload/server/public/assets/chevron-left-C3ho8SnB.js +1 -0
- package/payload/server/public/assets/data-Cfr-7ERI.js +1 -0
- package/payload/server/public/assets/{graph-JV5_xF3V.js → graph-HeZaatSQ.js} +1 -1
- package/payload/server/public/assets/{graph-labels-DPYORjBH.js → graph-labels-4W7JuOgG.js} +1 -1
- package/payload/server/public/assets/{maximize-2-8vKr3h21.js → maximize-2-DKx-aTPb.js} +1 -1
- package/payload/server/public/assets/{operator-Liul1QEq.js → operator-DD9L2VJM.js} +1 -1
- package/payload/server/public/assets/{page-Cg7B1RQx.js → page-Ceado9Kr.js} +1 -1
- package/payload/server/public/assets/{page-CYRA87CB.js → page-tJxw0zNO.js} +1 -1
- package/payload/server/public/assets/{public-B4HMtpjt.js → public-BYFWxyeC.js} +1 -1
- package/payload/server/public/assets/{rotate-ccw-sz5IQSnr.js → rotate-ccw-DrzdV9h7.js} +1 -1
- package/payload/server/public/assets/{tasks-D9oYgEzs.js → tasks-DSwoxGzh.js} +1 -1
- package/payload/server/public/assets/{time-entry-format-CSuCexpM.js → time-entry-format-Bn79w6tq.js} +1 -1
- package/payload/server/public/assets/{triangle-alert-ClS81-CU.js → triangle-alert-CjDVh6r-.js} +1 -1
- package/payload/server/public/assets/{useCopyFeedback-Cn6YgeNx.js → useCopyFeedback-DXHkkJw-.js} +1 -1
- package/payload/server/public/assets/{useSelectionMode-BaD-ZZRk.js → useSelectionMode-D2H8jAhE.js} +1 -1
- package/payload/server/public/assets/{useSubAccountSwitcher-CNZL3PKn.css → useSubAccountSwitcher-Br7Z6Cpu.css} +1 -1
- package/payload/server/public/assets/{useVoiceRecorder-BOzHrDAU.js → useVoiceRecorder-zUvPe55I.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 +1414 -1324
- package/payload/server/public/assets/activity-Dj2nJzPw.js +0 -1
- package/payload/server/public/assets/chat-DIOJZ411.js +0 -1
- package/payload/server/public/assets/chevron-left-Cq7UvOI0.js +0 -1
- package/payload/server/public/assets/data-lHgqlhmu.js +0 -1
- /package/payload/server/public/assets/{useSubAccountSwitcher-CvjWJSGv.js → useSubAccountSwitcher-CUJpgkKI.js} +0 -0
|
@@ -16,8 +16,11 @@ outcome (values themselves deliberately not recorded):
|
|
|
16
16
|
The 604799 figure is the concrete argument for completing brand verification: in Testing,
|
|
17
17
|
every connected account silently re-consents weekly. Verification removes it.
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
The flow below is the ORIGINAL Phase 0 verdict and is retained for its evidence.
|
|
20
|
+
It has since been superseded on two points: the vendor app is a
|
|
21
|
+
Desktop client whose secret ships in brand config, and the redirect is loopback
|
|
22
|
+
rather than a hosted HTTPS callback. Read the Desktop sections further down as
|
|
23
|
+
authoritative where the two differ.
|
|
21
24
|
|
|
22
25
|
## Original spike verdict (documentation-backed, now confirmed)
|
|
23
26
|
|
|
@@ -46,9 +49,11 @@ What is decided from published behaviour:
|
|
|
46
49
|
- The flow is therefore an **operator-assisted authorization-code (PKCE) flow**, and it
|
|
47
50
|
needs a redirect method, because Google's out-of-band ("paste the code") flow is fully
|
|
48
51
|
blocked.
|
|
49
|
-
- The
|
|
50
|
-
Cloudflare tunnel domain
|
|
51
|
-
|
|
52
|
+
- The redirect method recommended at the time was a hosted HTTPS callback on the
|
|
53
|
+
brand's Cloudflare tunnel domain (a "Web application" client). **Superseded by
|
|
54
|
+
the recent decision**: the fallback named here — a Desktop client with a loopback redirect
|
|
55
|
+
and a manual code relay — is what shipped, because only an installed-app secret
|
|
56
|
+
can be distributed to customer devices.
|
|
52
57
|
- **Verification is the light kind.** Calendar is a *sensitive* scope, not a *restricted*
|
|
53
58
|
one, so a brand's app needs only OAuth brand/consent-screen verification, never the
|
|
54
59
|
annual third-party security assessment.
|
|
@@ -109,7 +114,7 @@ that carries the `code` and the process that exchanges it live on different mach
|
|
|
109
114
|
concrete flows bridge that. The recommendation is Option B primary, Option A as the
|
|
110
115
|
secret-less fallback.
|
|
111
116
|
|
|
112
|
-
### Option B (
|
|
117
|
+
### Option B (SUPERSEDED): hosted HTTPS callback on the brand tunnel domain
|
|
113
118
|
|
|
114
119
|
Client type **Web application**. A stable authorized redirect URI on the install's existing
|
|
115
120
|
Cloudflare **public** host, served by the always-running `platform/ui` server (the same
|
|
@@ -203,86 +208,93 @@ on that brand, exactly as `outlookClientId` does. The client id ships as the bra
|
|
|
203
208
|
`GOOGLE_CLIENT_ID` and inherited down the spawn chain (manager unit env → PTY → MCP child).
|
|
204
209
|
|
|
205
210
|
Divergence from Outlook: the recommended Web-application flow (Option B) also needs a
|
|
206
|
-
**client secret**, which
|
|
207
|
-
|
|
211
|
+
**client secret**, which ships in `brand.json` (the house-only file this
|
|
212
|
+
once described is withdrawn). A brand that ships `googleClientId` empty has the plugin inert with an
|
|
208
213
|
actionable signal (`clientIdConfigured=false`), register refuses, and no operator action
|
|
209
214
|
makes it work until the vendor client exists — mirroring Outlook's inert behaviour.
|
|
210
215
|
|
|
211
|
-
###
|
|
216
|
+
### The vendor app is a Desktop client, and its secret ships in brand config
|
|
212
217
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
this file — the refusal is deliberate and happens *before* the user consents,
|
|
217
|
-
so nobody burns a consent on a flow that cannot complete.
|
|
218
|
+
Decided 2026-07-18, superseding the house-only file this section
|
|
219
|
+
used to describe. That model required placing a secret by hand on every install,
|
|
220
|
+
which cannot reach a customer device and contradicts installer-only doctrine.
|
|
218
221
|
|
|
219
|
-
|
|
222
|
+
There is no secret-free alternative: Google requires `client_secret` at the
|
|
223
|
+
token endpoint for **every** client type, including Web clients using PKCE.
|
|
220
224
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
225
|
+
So the vendor app is a **Desktop (installed app)** client, because Google
|
|
226
|
+
explicitly sanctions embedding that secret in distributed software — "the client
|
|
227
|
+
secret is obviously not treated as a secret". `brand.json` therefore carries
|
|
228
|
+
both values and the installer stamps them onto the session-manager unit:
|
|
229
|
+
|
|
230
|
+
```jsonc
|
|
231
|
+
// brands/<brand>/brand.json
|
|
232
|
+
"googleClientId": "<the client id>",
|
|
233
|
+
"googleClientSecret": "<the client secret>"
|
|
230
234
|
```
|
|
231
235
|
|
|
232
|
-
|
|
236
|
+
They stamp **together or not at all**. A client id without its secret builds a
|
|
237
|
+
consent URL the user can complete but an exchange that cannot, which fails later
|
|
238
|
+
and less legibly than not offering the flow.
|
|
233
239
|
|
|
234
|
-
|
|
235
|
-
|
|
240
|
+
The secret ships to a **public** npm registry. That is the accepted trade: the
|
|
241
|
+
residual risk is app impersonation and quota abuse against the brand's client
|
|
242
|
+
id, not token theft, because PKCE still binds each code to its verifier.
|
|
236
243
|
|
|
237
|
-
|
|
238
|
-
grep -m1 'google-mcp. boot' ~/.<brand>-code/logs/server.log
|
|
239
|
-
# ... houseSecretPresent=true
|
|
240
|
-
```
|
|
244
|
+
Rejected, each once and for a reason:
|
|
241
245
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
246
|
+
- **Customer-owned apps** (the QuickBooks pattern, where the operator registers
|
|
247
|
+
their own app). Unlike Intuit, Google demands app verification for the
|
|
248
|
+
sensitive Calendar scope — homepage, privacy policy, domain ownership, demo
|
|
249
|
+
video — which the target customer cannot produce.
|
|
250
|
+
- **A vendor-hosted exchange broker**, where the install posts the code to a
|
|
251
|
+
brand-operated endpoint. Best security and best UX, but it means operating a
|
|
252
|
+
credential-bearing service for every customer.
|
|
253
|
+
- **A Web client with the secret shipped anyway.** A Web-app secret is genuinely
|
|
254
|
+
confidential in Google's model; publishing it would be a real credential leak
|
|
255
|
+
rather than a sanctioned embed.
|
|
245
256
|
|
|
246
|
-
###
|
|
257
|
+
### The redirect is loopback, and the IP literal is mandatory
|
|
247
258
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
259
|
+
Desktop clients permit only loopback redirects. `https` is rejected outright and
|
|
260
|
+
custom URI schemes are withdrawn ("no longer supported due to the risk of app
|
|
261
|
+
impersonation"), so this is the entire option space.
|
|
251
262
|
|
|
252
263
|
```
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
264
|
+
http://127.0.0.1:8571
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
**Never `localhost`.** RFC 8252 §7.3 makes the port irrelevant when matching a
|
|
268
|
+
loopback redirect, but only for the IP literals `127.0.0.1` and `::1`; strict
|
|
269
|
+
servers match the hostname literally. Register `http://127.0.0.1` on the client.
|
|
270
|
+
|
|
271
|
+
Nothing listens on that port, by design. The consenting browser is the user's
|
|
272
|
+
own phone, so the redirect fails to load and the user copies the code out of the
|
|
273
|
+
address bar and pastes it back to `google-account-register-submit`. The port is
|
|
274
|
+
fixed so consent URLs are deterministic and one register's logs compare with
|
|
275
|
+
another's.
|
|
276
|
+
|
|
277
|
+
This is why the hosted HTTPS callback that the earlier plugin built is gone, along with
|
|
278
|
+
its allowlist entry and its refresh route: a Desktop client cannot use it.
|
|
279
|
+
|
|
280
|
+
### Vendor runbook: creating the Desktop client
|
|
281
|
+
|
|
282
|
+
1. In the **existing** Cloud project for the brand — not a new one, so the
|
|
283
|
+
consent screen and any verification progress carry over — go to
|
|
284
|
+
**Credentials → Create credentials → OAuth client ID**.
|
|
285
|
+
2. Application type **Desktop app**. Name it for the brand.
|
|
286
|
+
3. Copy the **Client ID** and **Client secret** into that brand's `brand.json`
|
|
287
|
+
as `googleClientId` and `googleClientSecret`.
|
|
288
|
+
4. Re-bundle and publish. No file is placed on any device by hand.
|
|
289
|
+
|
|
290
|
+
Confirm it took from the boot line, which reports presence and never values:
|
|
291
|
+
|
|
292
|
+
```bash
|
|
293
|
+
grep -m1 'google-mcp. boot' ~/.<brand>-code/logs/server.log
|
|
294
|
+
# ... clientIdConfigured=true clientSecretConfigured=true
|
|
256
295
|
```
|
|
257
296
|
|
|
258
|
-
|
|
259
|
-
`platform/plugins/cloudflare/references/api.md` already establishes this pattern for
|
|
260
|
-
multi-client installs and names SiteDesk as the case: an account-wide credential inside a
|
|
261
|
-
sub-account's secrets file lets that sub-account reach every other client's resources, so
|
|
262
|
-
the master lives house-only at `${PLATFORM_ROOT}/config/cloudflare-house.env`, "a location
|
|
263
|
-
no sub-account spawn sources". `google-house.env` is that file's twin.
|
|
264
|
-
|
|
265
|
-
Rejected, with reasons:
|
|
266
|
-
|
|
267
|
-
- **`brands/<brand>/brand.json`** ships inside the published npm package. It carries
|
|
268
|
-
`googleClientId` (public, exactly like `outlookClientId`, and the value the inert check
|
|
269
|
-
reads) and never the secret.
|
|
270
|
-
- **`~/<brand>-code/data/accounts/<accountId>/secrets`** is wrong-scoped twice: the secret is
|
|
271
|
-
per-brand not per-account, so replicating it multiplies exposure for no benefit, and on a
|
|
272
|
-
multi-tenant install it drops a brand-wide credential into a client sub-account's own
|
|
273
|
-
directory.
|
|
274
|
-
- **A Desktop-type client** (installed-app secret, documented non-confidential) would remove
|
|
275
|
-
the storage question but forces the loopback redirect and its copy-the-code-off-a-404 flow
|
|
276
|
-
on a phone, and discards the already-provisioned, verified Web-application client.
|
|
277
|
-
|
|
278
|
-
House-only is sufficient here rather than merely tidier, because the token exchange runs in
|
|
279
|
-
the `/api/google/oauth/callback` route inside the always-running `platform/ui` server, which
|
|
280
|
-
is house-scoped. The secret never needs to reach a per-account MCP spawn.
|
|
281
|
-
|
|
282
|
-
**It is a boundary of intent, not of permission.** Every account's session runs as the same
|
|
283
|
-
unix user and the house path is deterministic, so an agent holding `Bash` can read the file
|
|
284
|
-
regardless; OS user separation was costed and declined. Never cite house-only placement as
|
|
285
|
-
proof a sub-account cannot reach the secret.
|
|
297
|
+
Either reading `false` on a brand that ships both is the mis-stamp signature.
|
|
286
298
|
|
|
287
299
|
---
|
|
288
300
|
|
|
@@ -312,7 +324,7 @@ follow-up once the plugin lands (mirroring Outlook 1479/1485).
|
|
|
312
324
|
new one."). Capture it at creation into its final destination, or plan to rotate. It is
|
|
313
325
|
never pasted into a chat transcript, a shell command that lands in history, or the repo.
|
|
314
326
|
Rotation is free and has no downtime: **Add secret**, store the new one, delete the old.
|
|
315
|
-
It
|
|
327
|
+
It ships in `brand.json` as `googleClientSecret` — see the Desktop sections above.
|
|
316
328
|
- **Option A (fallback):** application type **Desktop app**. Copy the **Client ID**.
|
|
317
329
|
5. Set `googleClientId` in the brand's `brand.json` and re-bundle:
|
|
318
330
|
```jsonc
|
|
@@ -438,12 +450,17 @@ met and Phases 1–2 (the plugin build) proceed against the confirmed flow.
|
|
|
438
450
|
|
|
439
451
|
| Brand | Client type | Redirect URI | Client ID | Status |
|
|
440
452
|
|---|---|---|---|---|
|
|
441
|
-
| `sitedesk-code` | Web application |
|
|
453
|
+
| `sitedesk-code` | ~~Web application~~ **RETIRED** | ~~`https://public.sitedesk.online/api/google/oauth/callback`~~ | `1032860640027-…` | **Do not use.** A Web client cannot register a loopback redirect, so this id fails `redirect_uri_mismatch` under the Desktop flow. Cleared from `brand.json`; delete the client in the console and revoke its secret. |
|
|
454
|
+
| `sitedesk-code` | Desktop app | `http://127.0.0.1` | *(not yet created)* | Awaiting provisioning in the **existing** `Sitedesk Calendar` project, so consent-screen and verification progress carry over. |
|
|
442
455
|
|
|
443
456
|
The public host was confirmed live and routing to the `platform/ui` server (it serves the
|
|
444
|
-
brand's public page). No secret or token is recorded here. The secret
|
|
445
|
-
|
|
446
|
-
|
|
457
|
+
brand's public page). No secret or token is recorded here. The Web client's secret was exposed to shell history during the manual round-trip.
|
|
458
|
+
That client is retired rather than rotated: delete it in the console, which
|
|
459
|
+
revokes the secret with it.
|
|
460
|
+
|
|
461
|
+
**Devices touched by the earlier plugin may still hold `platform/config/google-house.env`
|
|
462
|
+
containing that live Web-client secret.** Nothing reads it any more and nothing
|
|
463
|
+
removes it. Delete it on each such device as part of retiring the Web client.
|
|
447
464
|
|
|
448
465
|
Per-brand next step for `sitedesk-code`: start **brand/consent-screen verification** to lift
|
|
449
466
|
the 100-test-user cap and the 7-day re-consent measured above. That review is the slow part;
|
|
@@ -63,6 +63,16 @@ An `InvoicePayment` reached from an `Invoice` is money received. The same label
|
|
|
63
63
|
|
|
64
64
|
`CashEntry` does carry `direction`, because it has no parent invoice to derive one from.
|
|
65
65
|
|
|
66
|
+
## The counterparty is passed by id, never by name
|
|
67
|
+
|
|
68
|
+
`ledger-invoice-record` and `ledger-cash-record` take the element id of an existing `Organization` or `Person`, and nothing else about them. The ledger never creates or matches them: look the counterparty up first with `memory-search` or `contact-lookup` and pass the id you find.
|
|
69
|
+
|
|
70
|
+
The display name stored on the invoice is read from that node, not passed as a second argument. One lookup feeds both the stored name and the link, so an invoice cannot read as one company while pointing at another. An id that does not exist on this account is refused with `reason=unknown-counterparty` before anything is written.
|
|
71
|
+
|
|
72
|
+
This is a deliberate boundary, not a convenience gap. The ledger records money; deciding whether "Beagle Direct Ltd" is the same company as an existing "Beagle Direct" is entity resolution, and doing it badly inside a write tool creates duplicate organisations that then split a payment history in two.
|
|
73
|
+
|
|
74
|
+
A `CashEntry` must have a counterparty for a second reason: graph-write refuses a write with no relationship at all, so an unlinked entry cannot be stored.
|
|
75
|
+
|
|
66
76
|
## Over-application is refused
|
|
67
77
|
|
|
68
78
|
A payment that would take applied-to-date above the invoice total is rejected. It is never clamped to the remaining amount and never recorded anyway. The refusal names the figure that would settle the invoice, so the operator can correct it in one step.
|
|
@@ -75,7 +85,9 @@ After writing a payment, the tool re-reads the invoice from the graph and compar
|
|
|
75
85
|
|
|
76
86
|
Four ways this can fail emit no event at all: a write narrated but never landed, a payment with no parent invoice, a stored balance property that has reappeared, and a duplicate payment created under a second id. None of them can be caught by logging an action, because no action occurs.
|
|
77
87
|
|
|
78
|
-
A `[ledger-census]` line is emitted every 15 minutes by the UI server, carrying counts for each, plus any ledger index declared in `schema.cypher` but absent from this database
|
|
88
|
+
A `[ledger-census]` line is emitted every 15 minutes by the UI server, carrying counts for each, plus any ledger index declared in `schema.cypher` but absent from this database, plus `writeRejects24h`.
|
|
89
|
+
|
|
90
|
+
That last counter exists because of a real failure. On 2026-07-18 every ledger write was rejected for a full day while the census reported `invoices=0 payments=0`, which is byte-identical to a healthy ledger nobody has used yet. A rejected write leaves nothing in the graph by definition, so the count is read from the plugin's own log rather than from Neo4j. `alarm=true` on that line means one of them has happened. `ledger-reconcile` is the same check on demand, sharing the same reconcile and the same line formatter.
|
|
79
91
|
|
|
80
92
|
Registration drift between `PLUGIN.md`, the generated canonical-tool-names artifact and the specialist templates is **not** a census signal. It is caught at build time by `platform/scripts/check-canonical-tool-names.mjs` and `check-specialist-tool-surface.mjs`. A runtime counter for it was removed after review: the census runs in the UI server, which has no view of MCP spawns, so the counter could only ever have compared a constant against itself and reported healthy.
|
|
81
93
|
|
|
@@ -4,7 +4,7 @@ import { eagerTool } from "../../../../lib/mcp-eager/dist/index.js";
|
|
|
4
4
|
import { writeNodeWithEdges } from "../../../../lib/graph-write/dist/index.js";
|
|
5
5
|
import { z } from "zod";
|
|
6
6
|
import { closeDriver, getSession } from "./lib/neo4j.js";
|
|
7
|
-
import { readCensus, readInvoice, readStatement, setPaymentStatus, verifyOutstanding, } from "./lib/read.js";
|
|
7
|
+
import { readCensus, readCounterpartyName, readInvoice, readStatement, setPaymentStatus, verifyOutstanding, } from "./lib/read.js";
|
|
8
8
|
import { invoiceRecord } from "./tools/invoice-record.js";
|
|
9
9
|
import { paymentRecord } from "./tools/payment-record.js";
|
|
10
10
|
import { cashRecord } from "./tools/cash-record.js";
|
|
@@ -32,15 +32,21 @@ function refuseNoAccount(toolName) {
|
|
|
32
32
|
isError: true,
|
|
33
33
|
};
|
|
34
34
|
}
|
|
35
|
+
// `agent` stays undefined when AGENT_SLUG is unset rather than defaulting to
|
|
36
|
+
// "". graph-write resolves `agent ?? source ?? "unknown"`, and an empty string
|
|
37
|
+
// is not nullish, so the old default printed a blank `agent=` on every reject
|
|
38
|
+
// and defeated the fallback entirely.
|
|
35
39
|
const createdBy = (tool) => ({
|
|
36
|
-
agent: agentSlug
|
|
37
|
-
session: sessionId
|
|
40
|
+
agent: agentSlug,
|
|
41
|
+
session: sessionId,
|
|
38
42
|
tool,
|
|
43
|
+
source: "ledger",
|
|
39
44
|
});
|
|
40
|
-
//
|
|
41
|
-
//
|
|
42
|
-
//
|
|
43
|
-
|
|
45
|
+
// No cast. Task 1788: the previous version cast writeNodeWithEdges to accept
|
|
46
|
+
// this plugin's own relationship shape, under a comment claiming the params
|
|
47
|
+
// were structurally identical. They were not, and the cast is what let a dead
|
|
48
|
+
// write path ship.
|
|
49
|
+
const write = writeNodeWithEdges;
|
|
44
50
|
async function withSession(fn) {
|
|
45
51
|
const session = getSession();
|
|
46
52
|
try {
|
|
@@ -55,7 +61,10 @@ eagerTool(server, "ledger-invoice-record", "Record an invoice you raised (direct
|
|
|
55
61
|
.enum(["receivable", "payable"])
|
|
56
62
|
.describe("'receivable' is money owed to you; 'payable' is money you owe"),
|
|
57
63
|
confirmationNumber: z.string().min(1).describe("Invoice or bill reference, e.g. INV-2026-014"),
|
|
58
|
-
|
|
64
|
+
counterpartyNodeId: z
|
|
65
|
+
.string()
|
|
66
|
+
.min(1)
|
|
67
|
+
.describe("Element id of the existing Organization. Look it up first (memory-search or contact-lookup) — this tool never creates or matches organisations, so passing a name alone will not work"),
|
|
59
68
|
totalPaymentDue: z.number().positive().describe("Face value of the whole bill"),
|
|
60
69
|
dueDate: z.string().describe("ISO date the payment falls due, e.g. 2026-08-01"),
|
|
61
70
|
description: z.string().optional().describe("What the invoice is for"),
|
|
@@ -65,6 +74,7 @@ eagerTool(server, "ledger-invoice-record", "Record an invoice you raised (direct
|
|
|
65
74
|
return withSession((session) => invoiceRecord({
|
|
66
75
|
session,
|
|
67
76
|
write,
|
|
77
|
+
readName: readCounterpartyName,
|
|
68
78
|
accountId,
|
|
69
79
|
createdBy: createdBy("ledger-invoice-record"),
|
|
70
80
|
...args,
|
|
@@ -98,7 +108,6 @@ eagerTool(server, "ledger-credit-record", "Apply a credit note to an invoice you
|
|
|
98
108
|
}, async (args) => {
|
|
99
109
|
if (accountId === null)
|
|
100
110
|
return refuseNoAccount("ledger-credit-record");
|
|
101
|
-
const { reason: _reason, ...rest } = args;
|
|
102
111
|
return withSession((session) => paymentRecord({
|
|
103
112
|
session,
|
|
104
113
|
read: readInvoice,
|
|
@@ -108,7 +117,7 @@ eagerTool(server, "ledger-credit-record", "Apply a credit note to an invoice you
|
|
|
108
117
|
accountId,
|
|
109
118
|
createdBy: createdBy("ledger-credit-record"),
|
|
110
119
|
kind: "credit",
|
|
111
|
-
...
|
|
120
|
+
...args,
|
|
112
121
|
// After the spread: the credit schema exposes no paymentType, so this
|
|
113
122
|
// is the only source of it and must not be overwritten by the cast.
|
|
114
123
|
paymentType: "credit",
|
|
@@ -119,11 +128,10 @@ eagerTool(server, "ledger-cash-record", "Record money in or out that is not atta
|
|
|
119
128
|
amount: z.number().positive().describe("Amount — always positive; direction carries the sign"),
|
|
120
129
|
date: z.string().describe("ISO date of the movement"),
|
|
121
130
|
description: z.string().min(1).describe("What it was for"),
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
.
|
|
125
|
-
.
|
|
126
|
-
.describe("Whether the counterparty is a person or a business; defaults to organization"),
|
|
131
|
+
counterpartyNodeId: z
|
|
132
|
+
.string()
|
|
133
|
+
.min(1)
|
|
134
|
+
.describe("Element id of the person or organisation this movement was with. Required — an entry linked to nothing cannot be stored"),
|
|
127
135
|
}, async (args) => {
|
|
128
136
|
if (accountId === null)
|
|
129
137
|
return refuseNoAccount("ledger-cash-record");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,kBAAkB,EAAE,MAAM,2CAA2C,CAAC;AAC/E,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AACzD,OAAO,EACL,UAAU,EACV,WAAW,EACX,aAAa,EACb,gBAAgB,EAChB,iBAAiB,GAClB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAC1D,OAAO,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAC1D,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AACpD,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAC7C,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACjD,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -10,6 +10,16 @@ import type { Session } from "neo4j-driver";
|
|
|
10
10
|
import type { InvoiceRead } from "../tools/payment-record.js";
|
|
11
11
|
import type { StatementRows } from "../tools/statement.js";
|
|
12
12
|
import type { LedgerCensusRows } from "../../../../../lib/ledger-core/dist/index.js";
|
|
13
|
+
/**
|
|
14
|
+
* The display name of a counterparty node, or null if that element id does not
|
|
15
|
+
* exist on this account.
|
|
16
|
+
*
|
|
17
|
+
* The invoice stores this name AND links to this node, so both must come from
|
|
18
|
+
* one lookup. Taking the name from a separate argument would let an invoice
|
|
19
|
+
* read as one company while pointing at another, and the account filter is
|
|
20
|
+
* what stops an id from a neighbouring account resolving at all.
|
|
21
|
+
*/
|
|
22
|
+
export declare function readCounterpartyName(session: Session, accountId: string, nodeId: string): Promise<string | null>;
|
|
13
23
|
export declare function readInvoice(session: Session, accountId: string, ref: string): Promise<InvoiceRead | null>;
|
|
14
24
|
/** The op=verify re-read. Deliberately goes back to the graph rather than
|
|
15
25
|
* trusting the figure the write path just computed. */
|
|
@@ -25,5 +35,5 @@ export declare function readStatement(session: Session, accountId: string): Prom
|
|
|
25
35
|
* invoice, payments that look like accidental duplicates, and ledger indexes
|
|
26
36
|
* that are declared in schema.cypher but absent from this database.
|
|
27
37
|
*/
|
|
28
|
-
export declare function readCensus(session: Session, accountId: string): Promise<LedgerCensusRows>;
|
|
38
|
+
export declare function readCensus(session: Session, accountId: string, now: number): Promise<LedgerCensusRows>;
|
|
29
39
|
//# sourceMappingURL=read.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"read.d.ts","sourceRoot":"","sources":["../../src/lib/read.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAC9D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,8CAA8C,CAAC;
|
|
1
|
+
{"version":3,"file":"read.d.ts","sourceRoot":"","sources":["../../src/lib/read.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAC9D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,8CAA8C,CAAC;AAgBrF;;;;;;;;GAQG;AACH,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,OAAO,EAChB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAQxB;AAED,wBAAsB,WAAW,CAC/B,OAAO,EAAE,OAAO,EAChB,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAkC7B;AAED;wDACwD;AACxD,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,OAAO,EAChB,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,MAAM,CAAC,CAIjB;AAED;;4BAE4B;AAC5B,wBAAsB,gBAAgB,CACpC,OAAO,EAAE,OAAO,EAChB,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,MAAM,GAAG,WAAW,GAAG,MAAM,GACpC,OAAO,CAAC,IAAI,CAAC,CAMf;AAED,wBAAsB,aAAa,CACjC,OAAO,EAAE,OAAO,EAChB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,aAAa,CAAC,CA6CxB;AAED;;;;;GAKG;AACH,wBAAsB,UAAU,CAC9B,OAAO,EAAE,OAAO,EAChB,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,gBAAgB,CAAC,CAsD3B"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { LEDGER_INDEX_NAMES, computeOutstanding, } from "../../../../../lib/ledger-core/dist/index.js";
|
|
2
|
+
import { countWriteRejects24h } from "./write-rejects.js";
|
|
2
3
|
/** neo4j-driver returns Integer objects for counts; numbers pass through. */
|
|
3
4
|
function toNum(v) {
|
|
4
5
|
if (typeof v === "number")
|
|
@@ -8,6 +9,21 @@ function toNum(v) {
|
|
|
8
9
|
}
|
|
9
10
|
return 0;
|
|
10
11
|
}
|
|
12
|
+
/**
|
|
13
|
+
* The display name of a counterparty node, or null if that element id does not
|
|
14
|
+
* exist on this account.
|
|
15
|
+
*
|
|
16
|
+
* The invoice stores this name AND links to this node, so both must come from
|
|
17
|
+
* one lookup. Taking the name from a separate argument would let an invoice
|
|
18
|
+
* read as one company while pointing at another, and the account filter is
|
|
19
|
+
* what stops an id from a neighbouring account resolving at all.
|
|
20
|
+
*/
|
|
21
|
+
export async function readCounterpartyName(session, accountId, nodeId) {
|
|
22
|
+
const res = await session.run(`MATCH (o) WHERE elementId(o) = $nodeId AND o.accountId = $accountId
|
|
23
|
+
RETURN coalesce(o.name, trim(coalesce(o.givenName, "") + " " + coalesce(o.familyName, ""))) AS name`, { accountId, nodeId });
|
|
24
|
+
const name = res.records[0]?.get("name");
|
|
25
|
+
return typeof name === "string" && name.trim() !== "" ? name.trim() : null;
|
|
26
|
+
}
|
|
11
27
|
export async function readInvoice(session, accountId, ref) {
|
|
12
28
|
// Staged WITH: payments collapse to one row before the credit match expands
|
|
13
29
|
// again, so the two OPTIONAL MATCHes never form a cartesian product that
|
|
@@ -18,6 +34,7 @@ export async function readInvoice(session, accountId, ref) {
|
|
|
18
34
|
OPTIONAL MATCH (i)-[:HAS_CREDIT]->(c:Credit)
|
|
19
35
|
WITH i, ps, collect(DISTINCT c) AS cs
|
|
20
36
|
RETURN labels(i) AS labels,
|
|
37
|
+
elementId(i) AS nodeId,
|
|
21
38
|
i.confirmationNumber AS invoiceId,
|
|
22
39
|
i.totalPaymentDue AS totalPaymentDue,
|
|
23
40
|
i.dueDate AS dueDate,
|
|
@@ -30,6 +47,7 @@ export async function readInvoice(session, accountId, ref) {
|
|
|
30
47
|
const labels = rec.get("labels");
|
|
31
48
|
return {
|
|
32
49
|
label: labels.includes("InboundInvoice") ? "InboundInvoice" : "Invoice",
|
|
50
|
+
nodeId: rec.get("nodeId"),
|
|
33
51
|
invoiceId: rec.get("invoiceId"),
|
|
34
52
|
totalPaymentDue: toNum(rec.get("totalPaymentDue")),
|
|
35
53
|
dueDateMs: Date.parse(rec.get("dueDate") ?? "") || 0,
|
|
@@ -97,7 +115,7 @@ export async function readStatement(session, accountId) {
|
|
|
97
115
|
* invoice, payments that look like accidental duplicates, and ledger indexes
|
|
98
116
|
* that are declared in schema.cypher but absent from this database.
|
|
99
117
|
*/
|
|
100
|
-
export async function readCensus(session, accountId) {
|
|
118
|
+
export async function readCensus(session, accountId, now) {
|
|
101
119
|
const counts = await session.run(
|
|
102
120
|
// OPTIONAL MATCH, not MATCH: with no payments a plain MATCH eliminates the
|
|
103
121
|
// row, so records[0] is undefined and invoices reports 0 as well. That is
|
|
@@ -133,6 +151,7 @@ export async function readCensus(session, accountId) {
|
|
|
133
151
|
date: r.get("date") ?? "",
|
|
134
152
|
})),
|
|
135
153
|
declaredNotLive,
|
|
154
|
+
writeRejects24h: countWriteRejects24h(process.env.LOG_DIR ?? "", now),
|
|
136
155
|
};
|
|
137
156
|
}
|
|
138
157
|
//# sourceMappingURL=read.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"read.js","sourceRoot":"","sources":["../../src/lib/read.ts"],"names":[],"mappings":"AAYA,OAAO,EACL,kBAAkB,EAClB,kBAAkB,GACnB,MAAM,8CAA8C,CAAC;
|
|
1
|
+
{"version":3,"file":"read.js","sourceRoot":"","sources":["../../src/lib/read.ts"],"names":[],"mappings":"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"}
|
|
@@ -6,17 +6,20 @@
|
|
|
6
6
|
* driver. `index.ts` passes the real `writeNodeWithEdges` and the real reads.
|
|
7
7
|
*/
|
|
8
8
|
import type { Session } from "neo4j-driver";
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
9
|
+
/**
|
|
10
|
+
* Task 1788 — the real graph-write contracts, imported rather than redeclared.
|
|
11
|
+
*
|
|
12
|
+
* This file used to declare its own `GraphRelationship` with
|
|
13
|
+
* `targetLabel`/`targetProps`, and `index.ts` forced it past the type checker
|
|
14
|
+
* with a cast. The real shape needs `targetNodeId`, an element id, so at
|
|
15
|
+
* runtime every relationship carried `undefined` and graph-write rejected every
|
|
16
|
+
* create with `reason=unresolved-target requested=1 found=0`. The whole write
|
|
17
|
+
* path was dead from the day it shipped. Importing the real types is what makes
|
|
18
|
+
* that a compile error instead of a silent runtime rejection, so the redeclared
|
|
19
|
+
* shape must never come back.
|
|
20
|
+
*/
|
|
21
|
+
import type { CreatedBy, GraphRelationship } from "../../../../../lib/graph-write/dist/index.js";
|
|
22
|
+
export type { CreatedBy, GraphRelationship };
|
|
20
23
|
export interface WriteParams {
|
|
21
24
|
session: Session;
|
|
22
25
|
labels: string[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/lib/types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/lib/types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAC5C;;;;;;;;;;;GAWG;AACH,OAAO,KAAK,EACV,SAAS,EACT,iBAAiB,EAClB,MAAM,8CAA8C,CAAC;AAEtD,YAAY,EAAE,SAAS,EAAE,iBAAiB,EAAE,CAAC;AAE7C,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,aAAa,EAAE,iBAAiB,EAAE,CAAC;IACnC,SAAS,EAAE,SAAS,CAAC;CACtB;AAED,MAAM,WAAW,WAAW;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,MAAM,OAAO,GAAG,CAAC,MAAM,EAAE,WAAW,KAAK,OAAO,CAAC,WAAW,CAAC,CAAC;AAEpE,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC/C,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,wBAAgB,EAAE,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,CAE3C;AAED,wBAAgB,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,CAE7C;AAED,6DAA6D;AAC7D,wBAAgB,KAAK,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAEvC;AAED;mEACmE;AACnE,MAAM,MAAM,gBAAgB,GAAG,YAAY,GAAG,SAAS,CAAC;AAExD,wBAAgB,QAAQ,CAAC,SAAS,EAAE,gBAAgB,GAAG,SAAS,GAAG,gBAAgB,CAElF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/lib/types.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/lib/types.ts"],"names":[],"mappings":"AA+CA,MAAM,UAAU,EAAE,CAAC,IAAY;IAC7B,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;AAC/C,CAAC;AAED,MAAM,UAAU,IAAI,CAAC,IAAY;IAC/B,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;AAC9D,CAAC;AAED,6DAA6D;AAC7D,MAAM,UAAU,KAAK,CAAC,CAAS;IAC7B,OAAO,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;AACtB,CAAC;AAMD,MAAM,UAAU,QAAQ,CAAC,SAA2B;IAClD,OAAO,SAAS,KAAK,YAAY,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,gBAAgB,CAAC;AACnE,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Counts `[graph-write] reject` lines across the ledger MCP tee logs modified
|
|
3
|
+
* within the trailing 24 hours.
|
|
4
|
+
*
|
|
5
|
+
* Uses file mtime rather than parsing per-line timestamps: the tee log carries
|
|
6
|
+
* no timestamp of its own, and a file untouched for a day cannot hold a reject
|
|
7
|
+
* from the last one. Best-effort — an unreadable log directory yields 0 rather
|
|
8
|
+
* than throwing, because a census that dies on a missing directory tells the
|
|
9
|
+
* operator nothing at all.
|
|
10
|
+
*/
|
|
11
|
+
export declare function countWriteRejects24h(logDir: string, now: number): number;
|
|
12
|
+
//# sourceMappingURL=write-rejects.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"write-rejects.d.ts","sourceRoot":"","sources":["../../src/lib/write-rejects.ts"],"names":[],"mappings":"AAeA;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAwBxE"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Task 1788 — count recent write rejections from the ledger's own MCP logs.
|
|
3
|
+
*
|
|
4
|
+
* A rejected write cannot record its own failure in the graph, because the
|
|
5
|
+
* whole point is that nothing was written. The only durable trace is the
|
|
6
|
+
* stderr line graph-write emits, which `mcp-spawn-tee` captures per session.
|
|
7
|
+
* Counting those lines is what lets the census distinguish a healthy empty
|
|
8
|
+
* ledger from one whose write path is dead — on 2026-07-18 those two states
|
|
9
|
+
* printed identically for a full day.
|
|
10
|
+
*/
|
|
11
|
+
import { readdirSync, readFileSync, statSync } from "node:fs";
|
|
12
|
+
import { join } from "node:path";
|
|
13
|
+
const DAY_MS = 86_400_000;
|
|
14
|
+
/**
|
|
15
|
+
* Counts `[graph-write] reject` lines across the ledger MCP tee logs modified
|
|
16
|
+
* within the trailing 24 hours.
|
|
17
|
+
*
|
|
18
|
+
* Uses file mtime rather than parsing per-line timestamps: the tee log carries
|
|
19
|
+
* no timestamp of its own, and a file untouched for a day cannot hold a reject
|
|
20
|
+
* from the last one. Best-effort — an unreadable log directory yields 0 rather
|
|
21
|
+
* than throwing, because a census that dies on a missing directory tells the
|
|
22
|
+
* operator nothing at all.
|
|
23
|
+
*/
|
|
24
|
+
export function countWriteRejects24h(logDir, now) {
|
|
25
|
+
let entries;
|
|
26
|
+
try {
|
|
27
|
+
entries = readdirSync(logDir).filter((f) => f.startsWith("mcp-ledger-") && f.endsWith(".log"));
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return 0;
|
|
31
|
+
}
|
|
32
|
+
let total = 0;
|
|
33
|
+
for (const name of entries) {
|
|
34
|
+
const path = join(logDir, name);
|
|
35
|
+
try {
|
|
36
|
+
if (now - statSync(path).mtimeMs > DAY_MS)
|
|
37
|
+
continue;
|
|
38
|
+
for (const line of readFileSync(path, "utf8").split("\n")) {
|
|
39
|
+
if (line.includes("[graph-write] reject"))
|
|
40
|
+
total += 1;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
// A log rotated away mid-read is not a census failure.
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return total;
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=write-rejects.js.map
|