@rubytech/create-maxy-code 0.1.458 → 0.1.460
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/__tests__/cf-exec.test.js +298 -3
- package/payload/platform/lib/storage-broker/dist/__tests__/cf-exec.test.js.map +1 -1
- package/payload/platform/lib/storage-broker/dist/__tests__/object-limits.test.d.ts +2 -0
- package/payload/platform/lib/storage-broker/dist/__tests__/object-limits.test.d.ts.map +1 -0
- package/payload/platform/lib/storage-broker/dist/__tests__/object-limits.test.js +79 -0
- package/payload/platform/lib/storage-broker/dist/__tests__/object-limits.test.js.map +1 -0
- package/payload/platform/lib/storage-broker/dist/__tests__/resource-limits.test.d.ts +2 -0
- package/payload/platform/lib/storage-broker/dist/__tests__/resource-limits.test.d.ts.map +1 -0
- package/payload/platform/lib/storage-broker/dist/__tests__/resource-limits.test.js +41 -0
- package/payload/platform/lib/storage-broker/dist/__tests__/resource-limits.test.js.map +1 -0
- package/payload/platform/lib/storage-broker/dist/cf-exec.d.ts +13 -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 +165 -0
- package/payload/platform/lib/storage-broker/dist/cf-exec.js.map +1 -1
- package/payload/platform/lib/storage-broker/dist/house-credential.d.ts.map +1 -1
- package/payload/platform/lib/storage-broker/dist/house-credential.js +4 -2
- package/payload/platform/lib/storage-broker/dist/house-credential.js.map +1 -1
- package/payload/platform/lib/storage-broker/dist/index.d.ts +2 -0
- package/payload/platform/lib/storage-broker/dist/index.d.ts.map +1 -1
- package/payload/platform/lib/storage-broker/dist/index.js +2 -0
- package/payload/platform/lib/storage-broker/dist/index.js.map +1 -1
- package/payload/platform/lib/storage-broker/dist/object-limits.d.ts +80 -0
- package/payload/platform/lib/storage-broker/dist/object-limits.d.ts.map +1 -0
- package/payload/platform/lib/storage-broker/dist/object-limits.js +92 -0
- package/payload/platform/lib/storage-broker/dist/object-limits.js.map +1 -0
- package/payload/platform/lib/storage-broker/dist/resource-limits.d.ts +102 -0
- package/payload/platform/lib/storage-broker/dist/resource-limits.d.ts.map +1 -0
- package/payload/platform/lib/storage-broker/dist/resource-limits.js +105 -0
- package/payload/platform/lib/storage-broker/dist/resource-limits.js.map +1 -0
- package/payload/platform/lib/storage-broker/src/__tests__/cf-exec.test.ts +334 -5
- package/payload/platform/lib/storage-broker/src/__tests__/object-limits.test.ts +106 -0
- package/payload/platform/lib/storage-broker/src/__tests__/resource-limits.test.ts +49 -0
- package/payload/platform/lib/storage-broker/src/cf-exec.ts +221 -6
- package/payload/platform/lib/storage-broker/src/house-credential.ts +4 -2
- package/payload/platform/lib/storage-broker/src/index.ts +2 -0
- package/payload/platform/lib/storage-broker/src/object-limits.ts +95 -0
- package/payload/platform/lib/storage-broker/src/resource-limits.ts +105 -0
- package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +7 -6
- package/payload/platform/plugins/cloudflare/PLUGIN.md +3 -1
- package/payload/platform/plugins/cloudflare/bin/cf-token.sh +4 -3
- package/payload/platform/plugins/cloudflare/mcp/__tests__/auth-route.test.ts +206 -0
- package/payload/platform/plugins/cloudflare/mcp/__tests__/authorize.test.ts +201 -0
- package/payload/platform/plugins/cloudflare/mcp/__tests__/file-routes.test.ts +178 -0
- package/payload/platform/plugins/cloudflare/mcp/__tests__/harness.test.ts +32 -0
- package/payload/platform/plugins/cloudflare/mcp/__tests__/passcode.test.ts +74 -0
- package/payload/platform/plugins/cloudflare/mcp/__tests__/ratelimit.test.ts +77 -0
- package/payload/platform/plugins/cloudflare/mcp/__tests__/skill-contract.test.ts +46 -0
- package/payload/platform/plugins/cloudflare/mcp/__tests__/template-config.test.ts +55 -0
- package/payload/platform/plugins/cloudflare/mcp/__tests__/upload-route.test.ts +255 -0
- package/payload/platform/plugins/cloudflare/mcp/package.json +5 -2
- package/payload/platform/plugins/cloudflare/references/api.md +1 -1
- package/payload/platform/plugins/cloudflare/references/r2-object-storage.md +46 -0
- package/payload/platform/plugins/cloudflare/skills/data-portal/SKILL.md +123 -0
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/_lib/authorize.ts +32 -0
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/_lib/log.ts +12 -0
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/_lib/passcode.ts +73 -0
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/_lib/ratelimit.ts +55 -0
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/_lib/session.ts +85 -0
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/_lib/types.ts +44 -0
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/auth.ts +113 -0
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/delete.ts +89 -0
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/download.ts +70 -0
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/files.ts +41 -0
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/upload.ts +151 -0
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/public/index.html +42 -0
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/public/portal.css +113 -0
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/public/portal.js +155 -0
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/schema.sql +64 -0
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/wrangler.toml +21 -0
- package/payload/platform/plugins/cloudflare/skills/site-deploy/SKILL.md +8 -0
- package/payload/platform/plugins/docs/references/outlook-guide.md +5 -5
- package/payload/platform/plugins/docs/references/voice-mirror-guide.md +1 -0
- package/payload/platform/plugins/filesystem/PLUGIN.md +0 -1
- package/payload/platform/plugins/filesystem/mcp/dist/lib/scope.d.ts +14 -10
- package/payload/platform/plugins/filesystem/mcp/dist/lib/scope.d.ts.map +1 -1
- package/payload/platform/plugins/filesystem/mcp/dist/lib/scope.js +14 -10
- package/payload/platform/plugins/filesystem/mcp/dist/lib/scope.js.map +1 -1
- package/payload/platform/plugins/outlook/.claude-plugin/plugin.json +1 -1
- package/payload/platform/plugins/outlook/PLUGIN.md +3 -5
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/attach.test.d.ts +2 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/attach.test.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/attach.test.js +138 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/attach.test.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/detach.test.d.ts +2 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/detach.test.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/detach.test.js +75 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/detach.test.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/draft-attachments.test.d.ts +2 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/draft-attachments.test.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/draft-attachments.test.js +121 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/draft-attachments.test.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/draft-edit-attachments.test.d.ts +2 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/draft-edit-attachments.test.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/draft-edit-attachments.test.js +102 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/draft-edit-attachments.test.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/draft-edit-removal.test.d.ts +2 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/draft-edit-removal.test.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/draft-edit-removal.test.js +187 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/draft-edit-removal.test.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/draft-edit.test.js +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/draft-edit.test.js.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/mail-send-attachments.test.d.ts +2 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/mail-send-attachments.test.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/mail-send-attachments.test.js +209 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/mail-send-attachments.test.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/index.js +12 -8
- package/payload/platform/plugins/outlook/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/lib/attach.d.ts +37 -0
- package/payload/platform/plugins/outlook/mcp/dist/lib/attach.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/lib/attach.js +79 -0
- package/payload/platform/plugins/outlook/mcp/dist/lib/attach.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/lib/attachment-list.d.ts +43 -0
- package/payload/platform/plugins/outlook/mcp/dist/lib/attachment-list.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/lib/attachment-list.js +65 -0
- package/payload/platform/plugins/outlook/mcp/dist/lib/attachment-list.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/lib/detach.d.ts +65 -0
- package/payload/platform/plugins/outlook/mcp/dist/lib/detach.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/lib/detach.js +99 -0
- package/payload/platform/plugins/outlook/mcp/dist/lib/detach.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/lib/message.d.ts +8 -0
- package/payload/platform/plugins/outlook/mcp/dist/lib/message.d.ts.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/lib/message.js +4 -0
- package/payload/platform/plugins/outlook/mcp/dist/lib/message.js.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/lib/outbound-attachments.d.ts +36 -8
- package/payload/platform/plugins/outlook/mcp/dist/lib/outbound-attachments.d.ts.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/lib/outbound-attachments.js +40 -13
- package/payload/platform/plugins/outlook/mcp/dist/lib/outbound-attachments.js.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/lib/upload-session.d.ts +9 -5
- package/payload/platform/plugins/outlook/mcp/dist/lib/upload-session.d.ts.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/lib/upload-session.js +12 -8
- package/payload/platform/plugins/outlook/mcp/dist/lib/upload-session.js.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/tools/draft-edit.d.ts +49 -4
- package/payload/platform/plugins/outlook/mcp/dist/tools/draft-edit.d.ts.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/tools/draft-edit.js +82 -13
- package/payload/platform/plugins/outlook/mcp/dist/tools/draft-edit.js.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/tools/draft.d.ts +8 -2
- package/payload/platform/plugins/outlook/mcp/dist/tools/draft.d.ts.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/tools/draft.js +13 -4
- package/payload/platform/plugins/outlook/mcp/dist/tools/draft.js.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/tools/mail-attachment.d.ts +15 -16
- package/payload/platform/plugins/outlook/mcp/dist/tools/mail-attachment.d.ts.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/tools/mail-attachment.js +8 -41
- package/payload/platform/plugins/outlook/mcp/dist/tools/mail-attachment.js.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/tools/mail-reply.d.ts.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/tools/mail-reply.js +3 -20
- package/payload/platform/plugins/outlook/mcp/dist/tools/mail-reply.js.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/tools/mail-send.d.ts +27 -3
- package/payload/platform/plugins/outlook/mcp/dist/tools/mail-send.d.ts.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/tools/mail-send.js +53 -8
- package/payload/platform/plugins/outlook/mcp/dist/tools/mail-send.js.map +1 -1
- package/payload/platform/plugins/outlook/references/auth.md +1 -1
- package/payload/platform/plugins/outlook/references/graph-surfaces.md +33 -6
- package/payload/platform/plugins/outlook/skills/outlook/SKILL.md +22 -5
- package/payload/platform/plugins/storage-broker/PLUGIN.md +41 -2
- package/payload/platform/plugins/storage-broker/mcp/dist/__tests__/confine-path.test.d.ts +2 -0
- package/payload/platform/plugins/storage-broker/mcp/dist/__tests__/confine-path.test.d.ts.map +1 -0
- package/payload/platform/plugins/storage-broker/mcp/dist/__tests__/confine-path.test.js +85 -0
- package/payload/platform/plugins/storage-broker/mcp/dist/__tests__/confine-path.test.js.map +1 -0
- package/payload/platform/plugins/storage-broker/mcp/dist/__tests__/put-source.test.d.ts +2 -0
- package/payload/platform/plugins/storage-broker/mcp/dist/__tests__/put-source.test.d.ts.map +1 -0
- package/payload/platform/plugins/storage-broker/mcp/dist/__tests__/put-source.test.js +77 -0
- package/payload/platform/plugins/storage-broker/mcp/dist/__tests__/put-source.test.js.map +1 -0
- package/payload/platform/plugins/storage-broker/mcp/dist/confine-path.d.ts +12 -0
- package/payload/platform/plugins/storage-broker/mcp/dist/confine-path.d.ts.map +1 -0
- package/payload/platform/plugins/storage-broker/mcp/dist/confine-path.js +107 -0
- package/payload/platform/plugins/storage-broker/mcp/dist/confine-path.js.map +1 -0
- package/payload/platform/plugins/storage-broker/mcp/dist/index.js +86 -0
- package/payload/platform/plugins/storage-broker/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/storage-broker/mcp/dist/put-source.d.ts +11 -0
- package/payload/platform/plugins/storage-broker/mcp/dist/put-source.d.ts.map +1 -0
- package/payload/platform/plugins/storage-broker/mcp/dist/put-source.js +35 -0
- package/payload/platform/plugins/storage-broker/mcp/dist/put-source.js.map +1 -0
- package/payload/platform/plugins/voice-mirror/PLUGIN.md +19 -7
- package/payload/platform/plugins/voice-mirror/mcp/dist/index.d.ts.map +1 -1
- package/payload/platform/plugins/voice-mirror/mcp/dist/index.js +35 -16
- package/payload/platform/plugins/voice-mirror/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/voice-mirror/mcp/dist/lib/admin-user.d.ts +31 -0
- package/payload/platform/plugins/voice-mirror/mcp/dist/lib/admin-user.d.ts.map +1 -0
- package/payload/platform/plugins/voice-mirror/mcp/dist/lib/admin-user.js +87 -0
- package/payload/platform/plugins/voice-mirror/mcp/dist/lib/admin-user.js.map +1 -0
- package/payload/platform/plugins/voice-mirror/mcp/dist/tools/voice-distil-profile.d.ts.map +1 -1
- package/payload/platform/plugins/voice-mirror/mcp/dist/tools/voice-distil-profile.js +13 -3
- package/payload/platform/plugins/voice-mirror/mcp/dist/tools/voice-distil-profile.js.map +1 -1
- package/payload/platform/plugins/voice-mirror/mcp/dist/tools/voice-ingest-session-text.d.ts.map +1 -1
- package/payload/platform/plugins/voice-mirror/mcp/dist/tools/voice-ingest-session-text.js +2 -1
- package/payload/platform/plugins/voice-mirror/mcp/dist/tools/voice-ingest-session-text.js.map +1 -1
- package/payload/platform/plugins/voice-mirror/mcp/dist/tools/voice-record-feedback.d.ts.map +1 -1
- package/payload/platform/plugins/voice-mirror/mcp/dist/tools/voice-record-feedback.js +2 -1
- package/payload/platform/plugins/voice-mirror/mcp/dist/tools/voice-record-feedback.js.map +1 -1
- package/payload/platform/plugins/voice-mirror/mcp/dist/tools/voice-tag-content.d.ts +2 -2
- package/payload/platform/plugins/voice-mirror/mcp/dist/tools/voice-tag-content.d.ts.map +1 -1
- package/payload/platform/plugins/voice-mirror/mcp/dist/tools/voice-tag-content.js +12 -3
- package/payload/platform/plugins/voice-mirror/mcp/dist/tools/voice-tag-content.js.map +1 -1
- package/payload/platform/plugins/voice-mirror/mcp/scripts/smoke.mjs +334 -0
- package/payload/platform/plugins/voice-mirror/skills/voice-mirror/SKILL.md +13 -0
- package/payload/platform/scripts/logs-read-jsonl.test.sh +118 -0
- package/payload/platform/scripts/logs-read.sh +63 -9
- package/payload/platform/scripts/voice-author-key-audit.sh +155 -0
- 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 +4 -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/mcp-descriptor-resolve.d.ts +17 -0
- package/payload/platform/services/claude-session-manager/dist/mcp-descriptor-resolve.d.ts.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/mcp-descriptor-resolve.js +88 -0
- package/payload/platform/services/claude-session-manager/dist/mcp-descriptor-resolve.js.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts +0 -1
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.js +34 -23
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.js.map +1 -1
- package/payload/server/{chunk-64FGYKNZ.js → chunk-JXWFVE5X.js} +148 -0
- package/payload/server/{chunk-F4D35LKL.js → chunk-POBXIHOF.js} +34 -0
- package/payload/server/maxy-edge.js +1 -1
- package/payload/server/server.js +412 -37
- package/payload/server/{src-S7C4P6TT.js → src-4F37OHLK.js} +19 -1
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/pkce-flow.test.d.ts +0 -2
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/pkce-flow.test.js +0 -215
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/pkce-flow.test.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"voice-tag-content.js","sourceRoot":"","sources":["../../src/tools/voice-tag-content.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EACL,uBAAuB,EACvB,aAAa,GAEd,MAAM,wBAAwB,CAAC;AAShC,MAAM,CAAC,MAAM,gBAAgB,GAAgC,IAAI,GAAG,CAAC;IACnE,YAAY;IACZ,0BAA0B;IAC1B,0BAA0B;IAC1B,YAAY;IACZ,SAAS;CACV,CAAC,CAAC;AAEH;;;;;;;;;GASG;AACH,MAAM,UAAU,kBAAkB,CAChC,MAA0B,EAC1B,cAA6B;IAE7B,MAAM,QAAQ,GAAG,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IACvC,IAAI,QAAQ;QAAE,OAAO,QAAQ,CAAC;IAC9B,IAAI,cAAc;QAAE,OAAO,cAAc,CAAC;IAC1C,MAAM,IAAI,KAAK,CACb,
|
|
1
|
+
{"version":3,"file":"voice-tag-content.js","sourceRoot":"","sources":["../../src/tools/voice-tag-content.ts"],"names":[],"mappings":"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"}
|
|
@@ -33,6 +33,7 @@ import { readFileSync } from "node:fs";
|
|
|
33
33
|
import { join, dirname } from "node:path";
|
|
34
34
|
import { fileURLToPath } from "node:url";
|
|
35
35
|
import { readdirSync } from "node:fs";
|
|
36
|
+
import { spawn } from "node:child_process";
|
|
36
37
|
|
|
37
38
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
38
39
|
const TOOLS_SRC = join(__dirname, "../src/tools");
|
|
@@ -410,6 +411,339 @@ await check(
|
|
|
410
411
|
},
|
|
411
412
|
);
|
|
412
413
|
|
|
414
|
+
// ---------------------------------------------------------------------------
|
|
415
|
+
// 12. Boot identity resolution (Task 1699). The server resolves the operator
|
|
416
|
+
// from USER_ID — the variable the PTY spawn stamps and MCP children
|
|
417
|
+
// inherit. ADMIN_USER_ID was never emitted by the platform, so every boot
|
|
418
|
+
// logged userId=null and four of five tools refused.
|
|
419
|
+
//
|
|
420
|
+
// Spawning the built server is the only faithful test: userId is read at
|
|
421
|
+
// module scope, and the boot line is the contract the operator diagnoses
|
|
422
|
+
// from. An empty USER_ID (silent-compaction / recorder / public spawns
|
|
423
|
+
// legitimately stamp empty) must resolve to null, not "" — an empty string
|
|
424
|
+
// is truthy enough to reach voiceRetrieveConditioning's `userId` and would
|
|
425
|
+
// become a profile key.
|
|
426
|
+
// ---------------------------------------------------------------------------
|
|
427
|
+
console.log("\n-- boot identity --");
|
|
428
|
+
|
|
429
|
+
const DIST_ENTRY = join(__dirname, "../dist/index.js");
|
|
430
|
+
|
|
431
|
+
function bootUserId(env) {
|
|
432
|
+
return new Promise((resolve, reject) => {
|
|
433
|
+
const child = spawn(process.execPath, [DIST_ENTRY], {
|
|
434
|
+
env: { PATH: process.env.PATH, ACCOUNT_ID: "acct-smoke", ...env },
|
|
435
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
436
|
+
});
|
|
437
|
+
let stderr = "";
|
|
438
|
+
const timer = setTimeout(() => {
|
|
439
|
+
child.kill("SIGKILL");
|
|
440
|
+
reject(new Error(`no boot line within 10s; stderr=${stderr.slice(0, 300)}`));
|
|
441
|
+
}, 10_000);
|
|
442
|
+
child.stderr.on("data", (chunk) => {
|
|
443
|
+
stderr += chunk.toString();
|
|
444
|
+
const m = stderr.match(/\[voice-mirror\] boot accountId=(\S+) userId=(\S+)/);
|
|
445
|
+
if (m) {
|
|
446
|
+
clearTimeout(timer);
|
|
447
|
+
child.kill("SIGKILL");
|
|
448
|
+
resolve({ accountId: m[1], userId: m[2] });
|
|
449
|
+
}
|
|
450
|
+
});
|
|
451
|
+
child.on("error", (err) => {
|
|
452
|
+
clearTimeout(timer);
|
|
453
|
+
reject(err);
|
|
454
|
+
});
|
|
455
|
+
// A child that dies before printing the boot line would otherwise sit here
|
|
456
|
+
// until the timeout and report "no boot line", hiding the real cause.
|
|
457
|
+
child.on("exit", (code, signal) => {
|
|
458
|
+
if (signal === "SIGKILL") return; // our own kill, after a successful match
|
|
459
|
+
clearTimeout(timer);
|
|
460
|
+
reject(new Error(`exited code=${code} before boot line; stderr=${stderr.slice(0, 300)}`));
|
|
461
|
+
});
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
await check("boot resolves userId from USER_ID (the var the platform emits)", async () => {
|
|
466
|
+
const { accountId, userId } = await bootUserId({ USER_ID: "436fdf2d-cfa7-4836-9ffa-77d91cef15f9" });
|
|
467
|
+
assert.equal(accountId, "acct-smoke");
|
|
468
|
+
assert.equal(userId, "436fdf2d-cfa7-4836-9ffa-77d91cef15f9");
|
|
469
|
+
});
|
|
470
|
+
|
|
471
|
+
await check("boot reports userId=null when USER_ID is absent", async () => {
|
|
472
|
+
const { userId } = await bootUserId({});
|
|
473
|
+
assert.equal(userId, "null");
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
await check("boot reports userId=null when USER_ID is empty (not \"\")", async () => {
|
|
477
|
+
const { userId } = await bootUserId({ USER_ID: "" });
|
|
478
|
+
assert.equal(userId, "null");
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
await check("ADMIN_USER_ID is inert — it is not an identity source", async () => {
|
|
482
|
+
const { userId } = await bootUserId({ ADMIN_USER_ID: "should-be-ignored" });
|
|
483
|
+
assert.equal(userId, "null");
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
// ---------------------------------------------------------------------------
|
|
487
|
+
// 12. Identity gate (Task 1701).
|
|
488
|
+
// A userId / voiceAuthor that names no :AdminUser on the calling account
|
|
489
|
+
// must never become a profile key or an author stamp. The reserved org
|
|
490
|
+
// sentinel legitimately names no :AdminUser and must bypass (Tasks 676, 975).
|
|
491
|
+
// ---------------------------------------------------------------------------
|
|
492
|
+
console.log("\n-- identity gate --");
|
|
493
|
+
|
|
494
|
+
const adminUserMod = await import("../dist/lib/admin-user.js");
|
|
495
|
+
|
|
496
|
+
check("unknownAdminUserMessage names the tool, the rejected key and the account", () => {
|
|
497
|
+
const msg = adminUserMod.unknownAdminUserMessage(
|
|
498
|
+
"voice-tag-content",
|
|
499
|
+
"rob-mead",
|
|
500
|
+
"68b50910",
|
|
501
|
+
);
|
|
502
|
+
assert.ok(msg.includes("rob-mead"), `names the rejected key: ${msg}`);
|
|
503
|
+
assert.ok(msg.includes("68b50910"), `names the account: ${msg}`);
|
|
504
|
+
assert.ok(msg.includes("voice-tag-content"), `names the tool: ${msg}`);
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
check("refusal wording does not invite the caller to guess another userId", () => {
|
|
508
|
+
// The prior text told callers to supply userId explicitly. An agent with no
|
|
509
|
+
// way to learn a real id supplied a plausible-looking name instead, which is
|
|
510
|
+
// how `rob-mead` reached a live client account.
|
|
511
|
+
const msg = adminUserMod.unknownAdminUserMessage(
|
|
512
|
+
"voice-tag-content",
|
|
513
|
+
"rob-mead",
|
|
514
|
+
"68b50910",
|
|
515
|
+
);
|
|
516
|
+
assert.ok(
|
|
517
|
+
!/supply\s+\S*\s*userId|pass\s+\S*\s*userId\s+explicitly|provide\s+a\s+userId/i.test(
|
|
518
|
+
msg,
|
|
519
|
+
),
|
|
520
|
+
`must not invite the caller to supply a userId: ${msg}`,
|
|
521
|
+
);
|
|
522
|
+
});
|
|
523
|
+
|
|
524
|
+
check("VoiceIdentityError carries the rejected key and account", () => {
|
|
525
|
+
const err = new adminUserMod.VoiceIdentityError(
|
|
526
|
+
"voice-tag-content",
|
|
527
|
+
"rob-mead",
|
|
528
|
+
"68b50910",
|
|
529
|
+
);
|
|
530
|
+
assert.ok(err instanceof Error);
|
|
531
|
+
assert.equal(err.name, "VoiceIdentityError");
|
|
532
|
+
assert.equal(err.userId, "rob-mead");
|
|
533
|
+
assert.equal(err.accountId, "68b50910");
|
|
534
|
+
});
|
|
535
|
+
|
|
536
|
+
await check("ORG_USER_ID bypasses the gate without opening a session", async () => {
|
|
537
|
+
// With NEO4J_URI unset, getDriver() throws. Returning normally proves the
|
|
538
|
+
// org sentinel short-circuits before any session is opened, so an org
|
|
539
|
+
// distil/retrieve still works on a spawn with no personal identity.
|
|
540
|
+
const saved = process.env.NEO4J_URI;
|
|
541
|
+
delete process.env.NEO4J_URI;
|
|
542
|
+
try {
|
|
543
|
+
await adminUserMod.assertAdminUserOnAccount(
|
|
544
|
+
"voice-distil-profile",
|
|
545
|
+
corpusMod.ORG_USER_ID,
|
|
546
|
+
"acct-1",
|
|
547
|
+
);
|
|
548
|
+
} finally {
|
|
549
|
+
if (saved !== undefined) process.env.NEO4J_URI = saved;
|
|
550
|
+
}
|
|
551
|
+
});
|
|
552
|
+
|
|
553
|
+
await check("a non-org key is looked up, never passed through (fail-closed)", async () => {
|
|
554
|
+
// The converse of the bypass: a real key must reach the lookup. With
|
|
555
|
+
// NEO4J_URI unset that surfaces as a driver throw. A gate that returned
|
|
556
|
+
// normally here would be failing open on every unverified key.
|
|
557
|
+
const saved = process.env.NEO4J_URI;
|
|
558
|
+
delete process.env.NEO4J_URI;
|
|
559
|
+
try {
|
|
560
|
+
await assert.rejects(
|
|
561
|
+
() =>
|
|
562
|
+
adminUserMod.assertAdminUserOnAccount(
|
|
563
|
+
"voice-tag-content",
|
|
564
|
+
"rob-mead",
|
|
565
|
+
"68b50910",
|
|
566
|
+
),
|
|
567
|
+
/NEO4J_URI unset/,
|
|
568
|
+
"must attempt the lookup rather than silently accepting the key",
|
|
569
|
+
);
|
|
570
|
+
} finally {
|
|
571
|
+
if (saved !== undefined) process.env.NEO4J_URI = saved;
|
|
572
|
+
}
|
|
573
|
+
});
|
|
574
|
+
|
|
575
|
+
// ---------------------------------------------------------------------------
|
|
576
|
+
// 13. Identity-gate wiring (Task 1701).
|
|
577
|
+
// The gate's own behaviour is covered above against the helper. These are
|
|
578
|
+
// structural invariants on the call sites: a live Neo4j is needed to tell
|
|
579
|
+
// "gate refused" from "write failed" at runtime (both surface as the same
|
|
580
|
+
// driver error), so ordering and presence are asserted on the source —
|
|
581
|
+
// the same idiom as the Cypher forbidden-pattern gate above.
|
|
582
|
+
// ---------------------------------------------------------------------------
|
|
583
|
+
console.log("\n-- identity-gate wiring --");
|
|
584
|
+
|
|
585
|
+
const tagSrc = readFileSync(join(TOOLS_SRC, "voice-tag-content.ts"), "utf-8");
|
|
586
|
+
|
|
587
|
+
check("voice-tag-content gates the resolved voiceAuthor", () => {
|
|
588
|
+
assert.ok(
|
|
589
|
+
/assertAdminUserOnAccount\(/.test(tagSrc),
|
|
590
|
+
"voice-tag-content must gate voiceAuthor via assertAdminUserOnAccount",
|
|
591
|
+
);
|
|
592
|
+
});
|
|
593
|
+
|
|
594
|
+
check("voice-tag-content gates before it stamps voiceAuthor", () => {
|
|
595
|
+
// The stamp is the write this task exists to stop. A gate ordered after it
|
|
596
|
+
// would refuse only once the dead data was already on the node.
|
|
597
|
+
const gateAt = tagSrc.indexOf("assertAdminUserOnAccount(");
|
|
598
|
+
const writeAt = tagSrc.indexOf("n.voiceAuthor = $voiceAuthor");
|
|
599
|
+
assert.ok(gateAt > -1, "gate present");
|
|
600
|
+
assert.ok(writeAt > -1, "voiceAuthor write present");
|
|
601
|
+
assert.ok(gateAt < writeAt, "identity gate must run before the voiceAuthor write");
|
|
602
|
+
});
|
|
603
|
+
|
|
604
|
+
await check("voice-tag-content: empty nodeIds stays a no-op (no identity lookup)", async () => {
|
|
605
|
+
// An empty tag has nothing to attribute, so it must not reach the driver.
|
|
606
|
+
// Guards the ordering of the early return against the new gate.
|
|
607
|
+
const saved = process.env.NEO4J_URI;
|
|
608
|
+
delete process.env.NEO4J_URI;
|
|
609
|
+
try {
|
|
610
|
+
const result = await tagMod.voiceTagContent({
|
|
611
|
+
nodeIds: [],
|
|
612
|
+
mode: "human-only",
|
|
613
|
+
accountId: "acct-1",
|
|
614
|
+
format: "text",
|
|
615
|
+
voiceAuthor: "rob-mead",
|
|
616
|
+
});
|
|
617
|
+
assert.equal(result.updated, 0);
|
|
618
|
+
assert.equal(result.skipped, 0);
|
|
619
|
+
} finally {
|
|
620
|
+
if (saved !== undefined) process.env.NEO4J_URI = saved;
|
|
621
|
+
}
|
|
622
|
+
});
|
|
623
|
+
|
|
624
|
+
const distilSrc = readFileSync(join(TOOLS_SRC, "voice-distil-profile.ts"), "utf-8");
|
|
625
|
+
|
|
626
|
+
check("voice-distil-profile gates the profile key at its single entry point", () => {
|
|
627
|
+
// Gating the exported entry rather than distilForFormat covers sample, amend,
|
|
628
|
+
// amend-write and multi-format enumeration with one lookup, instead of one
|
|
629
|
+
// per format.
|
|
630
|
+
const entryAt = distilSrc.indexOf("export async function voiceDistilProfile");
|
|
631
|
+
assert.ok(entryAt > -1, "exported entry present");
|
|
632
|
+
assert.ok(
|
|
633
|
+
/assertAdminUserOnAccount\(/.test(distilSrc.slice(entryAt)),
|
|
634
|
+
"the exported entry must gate the profile key",
|
|
635
|
+
);
|
|
636
|
+
});
|
|
637
|
+
|
|
638
|
+
check("voice-distil-profile keeps both structural write anchors", () => {
|
|
639
|
+
// The anchor MATCH binds `anchor` for MERGE (anchor)-[:HAS_VOICE_PROFILE]->(p).
|
|
640
|
+
// The entry gate is additive; removing these would drop the edge.
|
|
641
|
+
const anchors = distilSrc.match(
|
|
642
|
+
/MATCH \(anchor:AdminUser \{accountId: \$accountId, userId: \$profileUserId\}\)/g,
|
|
643
|
+
);
|
|
644
|
+
assert.equal(anchors?.length, 2, "both write-path anchors retained");
|
|
645
|
+
});
|
|
646
|
+
|
|
647
|
+
const retrieveSrc = readFileSync(
|
|
648
|
+
join(TOOLS_SRC, "voice-retrieve-conditioning.ts"),
|
|
649
|
+
"utf-8",
|
|
650
|
+
);
|
|
651
|
+
|
|
652
|
+
check("voice-retrieve-conditioning does NOT gate identity (it writes nothing)", () => {
|
|
653
|
+
// Deliberately ungated. Retrieve mutates nothing, so it sits outside the
|
|
654
|
+
// "validate before any write keyed on it" rule, and gating it broke the
|
|
655
|
+
// personal->org fallback below. Invented keys are stopped at
|
|
656
|
+
// voice-tag-content, which is the only path that puts one in the graph.
|
|
657
|
+
assert.ok(
|
|
658
|
+
!/assertAdminUserOnAccount\(/.test(retrieveSrc),
|
|
659
|
+
"retrieve must not gate identity: it regresses the personal->org fallback",
|
|
660
|
+
);
|
|
661
|
+
});
|
|
662
|
+
|
|
663
|
+
check("voice-retrieve-conditioning keeps the personal->org fallback", () => {
|
|
664
|
+
// The regression guard. A personal lookup with no data must borrow the org
|
|
665
|
+
// (house) voice, not degrade to the default register. A house operator
|
|
666
|
+
// scoped into a client sub-account has no :AdminUser on that account, so
|
|
667
|
+
// this fallback is the only thing that puts the client's house card into
|
|
668
|
+
// their drafts.
|
|
669
|
+
assert.ok(
|
|
670
|
+
/const org = await orgLookup\(\);/.test(retrieveSrc),
|
|
671
|
+
"personal miss must fall back to orgLookup()",
|
|
672
|
+
);
|
|
673
|
+
assert.ok(
|
|
674
|
+
/status = hasData\(org\) \? "fallback-org" : "no-data";/.test(retrieveSrc),
|
|
675
|
+
"personal miss must report fallback-org when the org voice resolves",
|
|
676
|
+
);
|
|
677
|
+
});
|
|
678
|
+
|
|
679
|
+
check("voice-retrieve-conditioning never sets isError", () => {
|
|
680
|
+
// Match the property assignment, not the word: prose about isError is fine,
|
|
681
|
+
// setting it is not.
|
|
682
|
+
assert.ok(
|
|
683
|
+
!/isError\s*[:=]/.test(retrieveSrc),
|
|
684
|
+
"retrieve degrades via status, never isError",
|
|
685
|
+
);
|
|
686
|
+
});
|
|
687
|
+
|
|
688
|
+
// ---------------------------------------------------------------------------
|
|
689
|
+
// 14. One refusal wording across every gate (Task 1701).
|
|
690
|
+
// record-feedback and ingest already enforced the rule via an inline
|
|
691
|
+
// MATCH (a:AdminUser {accountId, userId}). That match is structural — it
|
|
692
|
+
// binds `a` for the AUTHORED edge — so it stays exactly where it is and
|
|
693
|
+
// only the wording is shared.
|
|
694
|
+
// ---------------------------------------------------------------------------
|
|
695
|
+
console.log("\n-- shared refusal wording --");
|
|
696
|
+
|
|
697
|
+
for (const file of [
|
|
698
|
+
"voice-record-feedback.ts",
|
|
699
|
+
"voice-ingest-session-text.ts",
|
|
700
|
+
"voice-distil-profile.ts",
|
|
701
|
+
]) {
|
|
702
|
+
const src = readFileSync(join(TOOLS_SRC, file), "utf-8");
|
|
703
|
+
check(`${file} refuses with the shared wording`, () => {
|
|
704
|
+
assert.ok(
|
|
705
|
+
/unknownAdminUserMessage\(/.test(src),
|
|
706
|
+
`${file} must use the shared refusal wording`,
|
|
707
|
+
);
|
|
708
|
+
});
|
|
709
|
+
check(`${file} drops the un-actionable onboarding wording`, () => {
|
|
710
|
+
// "Onboarding must promote the operator to :AdminUser" names a remedy the
|
|
711
|
+
// caller cannot perform and never names the account.
|
|
712
|
+
assert.ok(
|
|
713
|
+
!/Onboarding must promote the operator/.test(src),
|
|
714
|
+
`${file} still carries the old onboarding wording`,
|
|
715
|
+
);
|
|
716
|
+
});
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
check("record-feedback keeps its structural AdminUser match (binds the AUTHORED edge)", () => {
|
|
720
|
+
const fb = readFileSync(join(TOOLS_SRC, "voice-record-feedback.ts"), "utf-8");
|
|
721
|
+
assert.ok(
|
|
722
|
+
/MATCH \(a:AdminUser \{accountId: \$accountId, userId: \$userId\}\)/.test(fb),
|
|
723
|
+
"structural match removed — the AUTHORED edge would be dropped",
|
|
724
|
+
);
|
|
725
|
+
assert.ok(/CREATE \(a\)-\[:AUTHORED\]->\(e\)/.test(fb), "AUTHORED edge retained");
|
|
726
|
+
});
|
|
727
|
+
|
|
728
|
+
check("ingest keeps its structural AdminUser match (binds the AUTHORED edge)", () => {
|
|
729
|
+
const ing = readFileSync(join(TOOLS_SRC, "voice-ingest-session-text.ts"), "utf-8");
|
|
730
|
+
assert.ok(
|
|
731
|
+
/MATCH \(a:AdminUser \{accountId: \$accountId, userId: \$userId\}\)/.test(ing),
|
|
732
|
+
"structural match removed — the AUTHORED edge would be dropped",
|
|
733
|
+
);
|
|
734
|
+
assert.ok(/MERGE \(a\)-\[:AUTHORED\]->\(n\)/.test(ing), "AUTHORED edge retained");
|
|
735
|
+
});
|
|
736
|
+
|
|
737
|
+
check("distil keeps the :LocalBusiness wording for the org branch", () => {
|
|
738
|
+
// An org distil failing on a missing business node is a different failure
|
|
739
|
+
// from an unknown personal key; it must not collapse into the shared wording.
|
|
740
|
+
const src = readFileSync(join(TOOLS_SRC, "voice-distil-profile.ts"), "utf-8");
|
|
741
|
+
assert.ok(
|
|
742
|
+
/:LocalBusiness \{accountId='\$\{accountId\}'\} not found/.test(src),
|
|
743
|
+
"org branch must keep its own :LocalBusiness message",
|
|
744
|
+
);
|
|
745
|
+
});
|
|
746
|
+
|
|
413
747
|
// ---------------------------------------------------------------------------
|
|
414
748
|
// Final summary.
|
|
415
749
|
// ---------------------------------------------------------------------------
|
|
@@ -201,6 +201,18 @@ Which voice a draft uses is the `scope` on the retrieval brief: `"my voice"` →
|
|
|
201
201
|
|
|
202
202
|
**Single-operator accounts are unchanged.** With author omitted at tag time, the sole operator is the implicit author, so their personal corpus equals the account-wide corpus and personal distillation reproduces the prior single profile. Legacy content carries no `voiceAuthor`; it appears only in the account-wide org walk until re-tagged, and personal drafts fall back to the org voice in the meantime.
|
|
203
203
|
|
|
204
|
+
## Identity is never guessed
|
|
205
|
+
|
|
206
|
+
Every `userId` and every `author` is an `:AdminUser` id that already exists on the account being operated. It is never a person's name, and never a slug built from one. `rob-mead` is not an identity.
|
|
207
|
+
|
|
208
|
+
A value naming no `:AdminUser` on the account is refused, and nothing is written. This is not a formality. A profile keyed on an admin who does not exist cannot be read back by any seat that resolves identity properly, so it becomes dead data that looks like a real operator profile. A `voiceAuthor` stamped the same way attributes writing to somebody who does not exist.
|
|
209
|
+
|
|
210
|
+
The tools resolve identity from the spawn environment on their own. Pass `author:` only to attribute content to a different operator on a multi-operator account, and only with that operator's real `:AdminUser` id.
|
|
211
|
+
|
|
212
|
+
If a tool refuses, the id is wrong. Do not retry with a different guess. The account's real admin identity is the operator's to supply.
|
|
213
|
+
|
|
214
|
+
`__org__` is exempt, because the house voice anchors on the business rather than on a person.
|
|
215
|
+
|
|
204
216
|
## Observability
|
|
205
217
|
|
|
206
218
|
| Tag | When |
|
|
@@ -213,6 +225,7 @@ Which voice a draft uses is the `scope` on the retrieval brief: `"my voice"` →
|
|
|
213
225
|
| `[voice-retrieve] scope=<scope> format=<format> status=<ok\|fallback-org\|no-data\|error> styleCardBytes=<n> exemplarCount=<k> tokenBudget=<n>` | Each retrieval. `status=fallback-org` proves the personal→org fallback fired. |
|
|
214
226
|
| `[voice-record-feedback] scope=<scope> userId=<id\|__org__> format=<format> intent="<haiku-summary>" diffBytes=<n>` | Each feedback write. |
|
|
215
227
|
| `[voice-ingest-session-text] sessionId=<id> adminUser=<id> turns=<n> bytes=<n> skipped=<m>` | On-demand session-text capture. |
|
|
228
|
+
| `[voice-identity] refuse tool=<tool> reason=unknown-admin-user userId=<key> accountId=<account>` | A key naming no `:AdminUser` on the account was refused. Names the rejected key and the account, so an invented key is visible in the logs instead of only in the graph. |
|
|
216
229
|
|
|
217
230
|
**Confirms working:** `[voice-retrieve] exemplarCount≥1` precedes every drafting-skill prompt assembly when the calling skill has not opted out. `[voice-distil]` appears at least once per 30-day window per active format per operator. `[voice-ingest-session-text] turns≥1` appears when the operator asks to capture a session's voice.
|
|
218
231
|
|
|
@@ -87,6 +87,52 @@ write_parent_with_error() {
|
|
|
87
87
|
} > "$f"
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
+
# Write a parent JSONL carrying BOTH harness rejections and the classes that
|
|
91
|
+
# must NOT be treated as rejections. A rejection is a call the CLI refused to
|
|
92
|
+
# dispatch, so the tool never ran; everything else here either ran and failed or
|
|
93
|
+
# is a different fault. Both InputValidationError sub-classes are covered:
|
|
94
|
+
# unparseable JSON (carries __unparsedToolInput) and schema-valid JSON that
|
|
95
|
+
# failed validation (no such marker, and the majority class in practice).
|
|
96
|
+
write_parent_with_rejections() {
|
|
97
|
+
local cfg="$1" uuid="$2"
|
|
98
|
+
local f="$cfg/projects/-fake-cwd/${uuid}.jsonl"
|
|
99
|
+
{
|
|
100
|
+
printf '%s\n' '{"type":"user","timestamp":"2026-07-16T15:59:00.000Z","uuid":"r0","message":{"role":"user","content":"send it"}}'
|
|
101
|
+
# 2 x rejection sub-class A (unparseable JSON) on ONE tool -> census x2
|
|
102
|
+
printf '%s\n' '{"type":"assistant","timestamp":"2026-07-16T15:59:01.000Z","uuid":"r1","message":{"role":"assistant","content":[{"type":"tool_use","id":"toolu_R1","name":"outlook-mail-send","input":{"__unparsedToolInput":"{\"to\": bare@x.co}"}}]}}'
|
|
103
|
+
printf '%s\n' '{"type":"user","timestamp":"2026-07-16T15:59:02.000Z","uuid":"r2","message":{"role":"user","content":[{"type":"tool_result","tool_use_id":"toolu_R1","is_error":true,"content":"<tool_use_error>InputValidationError: outlook-mail-send was called with input that could not be parsed as JSON.\nYou sent (first 200 of 670 bytes): {\"to\": bare@x.co} REJECT_A_ONE</tool_use_error>"}]}}'
|
|
104
|
+
printf '%s\n' '{"type":"assistant","timestamp":"2026-07-16T15:59:03.000Z","uuid":"r3","message":{"role":"assistant","content":[{"type":"tool_use","id":"toolu_R2","name":"outlook-mail-send","input":{"__unparsedToolInput":"{\"to\": bare@x.co}"}}]}}'
|
|
105
|
+
printf '%s\n' '{"type":"user","timestamp":"2026-07-16T15:59:04.000Z","uuid":"r4","message":{"role":"user","content":[{"type":"tool_result","tool_use_id":"toolu_R2","is_error":true,"content":"<tool_use_error>InputValidationError: outlook-mail-send was called with input that could not be parsed as JSON.\nYou sent (first 200 of 670 bytes): {\"to\": bare@x.co} REJECT_A_TWO</tool_use_error>"}]}}'
|
|
106
|
+
# rejection sub-class B: JSON parsed fine, schema validation failed
|
|
107
|
+
printf '%s\n' '{"type":"assistant","timestamp":"2026-07-16T15:59:05.000Z","uuid":"r5","message":{"role":"assistant","content":[{"type":"tool_use","id":"toolu_R3","name":"Read","input":{"offset":"x"}}]}}'
|
|
108
|
+
printf '%s\n' '{"type":"user","timestamp":"2026-07-16T15:59:06.000Z","uuid":"r6","message":{"role":"user","content":[{"type":"tool_result","tool_use_id":"toolu_R3","is_error":true,"content":"<tool_use_error>InputValidationError: Read failed due to the following issue: The parameter `offset` type is expected as `number` REJECT_B_ONE</tool_use_error>"}]}}'
|
|
109
|
+
# NOT a rejection: unknown tool (different fault, has its own PostToolUse hook)
|
|
110
|
+
printf '%s\n' '{"type":"assistant","timestamp":"2026-07-16T15:59:07.000Z","uuid":"r7","message":{"role":"assistant","content":[{"type":"tool_use","id":"toolu_N1","name":"work-create","input":{}}]}}'
|
|
111
|
+
printf '%s\n' '{"type":"user","timestamp":"2026-07-16T15:59:08.000Z","uuid":"r8","message":{"role":"user","content":[{"type":"tool_result","tool_use_id":"toolu_N1","is_error":true,"content":"<tool_use_error>Error: No such tool available: work-create NOTREJ_NOTOOL</tool_use_error>"}]}}'
|
|
112
|
+
# NOT a rejection: MCP server-side validation (the call DID reach the server)
|
|
113
|
+
printf '%s\n' '{"type":"assistant","timestamp":"2026-07-16T15:59:09.000Z","uuid":"r9","message":{"role":"assistant","content":[{"type":"tool_use","id":"toolu_N2","name":"outlook-mail-fetch-body","input":{}}]}}'
|
|
114
|
+
printf '%s\n' '{"type":"user","timestamp":"2026-07-16T15:59:10.000Z","uuid":"rA","message":{"role":"user","content":[{"type":"tool_result","tool_use_id":"toolu_N2","is_error":true,"content":"MCP error -32602: Input validation error: id: Required NOTREJ_32602"}]}}'
|
|
115
|
+
# NOT a rejection: built-in tool that RAN and failed its precondition
|
|
116
|
+
printf '%s\n' '{"type":"assistant","timestamp":"2026-07-16T15:59:11.000Z","uuid":"rB","message":{"role":"assistant","content":[{"type":"tool_use","id":"toolu_N3","name":"Edit","input":{}}]}}'
|
|
117
|
+
printf '%s\n' '{"type":"user","timestamp":"2026-07-16T15:59:12.000Z","uuid":"rC","message":{"role":"user","content":[{"type":"tool_result","tool_use_id":"toolu_N3","is_error":true,"content":"<tool_use_error>File has not been read yet. Read it first before writing to it. NOTREJ_UNREAD</tool_use_error>"}]}}'
|
|
118
|
+
} > "$f"
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
# Write a subagent transcript whose ONLY error is a harness rejection. Used to
|
|
122
|
+
# prove --scan-subagent-errors does not report a rejection-only subagent as an
|
|
123
|
+
# error-carrying one: the subagent did not fail, a call was never dispatched.
|
|
124
|
+
write_subagent_rejection_only() {
|
|
125
|
+
local cfg="$1" parent_uuid="$2" agent="$3" atype="$4"
|
|
126
|
+
local d="$cfg/projects/-fake-cwd/${parent_uuid}/subagents"
|
|
127
|
+
mkdir -p "$d"
|
|
128
|
+
{
|
|
129
|
+
printf '%s\n' '{"type":"user","timestamp":"2026-07-16T16:00:00.000Z","uuid":"j1","message":{"role":"user","content":"read it"}}'
|
|
130
|
+
printf '%s\n' '{"type":"assistant","timestamp":"2026-07-16T16:00:01.000Z","uuid":"j2","message":{"role":"assistant","content":[{"type":"tool_use","id":"toolu_SR","name":"Read","input":{"__unparsedToolInput":"{\"file_path\": /x}"}}]}}'
|
|
131
|
+
printf '%s\n' '{"type":"user","timestamp":"2026-07-16T16:00:02.000Z","uuid":"j3","message":{"role":"user","content":[{"type":"tool_result","tool_use_id":"toolu_SR","is_error":true,"content":"<tool_use_error>InputValidationError: Read was called with input that could not be parsed as JSON.\nYou sent (first 200 of 30 bytes): {\"file_path\": /x} SUBREJ_ONLY</tool_use_error>"}]}}'
|
|
132
|
+
} > "$d/${agent}.jsonl"
|
|
133
|
+
printf '{"agentType":"%s","description":"read a file"}\n' "$atype" > "$d/${agent}.meta.json"
|
|
134
|
+
}
|
|
135
|
+
|
|
90
136
|
# Write a CLEAN subagent transcript (no errors) + its meta.
|
|
91
137
|
write_subagent_clean() {
|
|
92
138
|
local cfg="$1" parent_uuid="$2" agent="$3" atype="$4"
|
|
@@ -248,6 +294,75 @@ case_parent_error_not_subagent() {
|
|
|
248
294
|
return 0
|
|
249
295
|
}
|
|
250
296
|
|
|
297
|
+
# --- Case J: harness rejection is distinguished from a tool that RAN and failed ---
|
|
298
|
+
# The 1692 defect: a call the CLI refused to dispatch rendered identically to a
|
|
299
|
+
# tool that ran and returned a failure, so a rejection loop read as "the tool is
|
|
300
|
+
# broken" while the tool was healthy. Only InputValidationError may be relabelled.
|
|
301
|
+
case_rejection_classified_and_counted() {
|
|
302
|
+
local root="/tmp/maxy-1692-J-$$"; local out
|
|
303
|
+
out=$(setup_tree "$root"); local script="${out% *}"; local cfg="${out#* }"
|
|
304
|
+
write_parent_with_rejections "$cfg" "$UUID_A"
|
|
305
|
+
write_pidfile "$cfg" 4255 "$UUID_A" "$BRIDGE"
|
|
306
|
+
|
|
307
|
+
invoke "$script" "$cfg" "session_${BRIDGE}"
|
|
308
|
+
cleanup_tree "$root"
|
|
309
|
+
|
|
310
|
+
[[ $RC -eq 0 ]] || { echo " expected exit 0, got $RC; err=$ERR"; return 1; }
|
|
311
|
+
|
|
312
|
+
# Both InputValidationError sub-classes are rejections (neither dispatched).
|
|
313
|
+
[[ "$OUT" == *"REJECTED"*"REJECT_A_ONE"* ]] || { echo " sub-class A (unparseable) not flagged REJECTED"; return 1; }
|
|
314
|
+
[[ "$OUT" == *"REJECTED"*"REJECT_B_ONE"* ]] || { echo " sub-class B (schema-invalid) not flagged REJECTED"; return 1; }
|
|
315
|
+
|
|
316
|
+
# The exclusions MUST keep the plain tool-error rendering. Widening the
|
|
317
|
+
# predicate to the bare <tool_use_error> wrapper is the regression this pins.
|
|
318
|
+
local l
|
|
319
|
+
for l in NOTREJ_NOTOOL NOTREJ_32602 NOTREJ_UNREAD; do
|
|
320
|
+
grep -q "tool error.*$l" <<<"$OUT" || { echo " $l lost its '‼ tool error' rendering"; return 1; }
|
|
321
|
+
grep -q "REJECTED.*$l" <<<"$OUT" && { echo " $l wrongly flagged REJECTED"; return 1; }
|
|
322
|
+
done
|
|
323
|
+
|
|
324
|
+
# Repetition is the diagnostic fact: 2 identical calls on one tool, counted.
|
|
325
|
+
[[ "$OUT" == *"rejections: 3"* ]] || { echo " census missing or miscounted (want 3)"; return 1; }
|
|
326
|
+
[[ "$OUT" == *"outlook-mail-send x2"* ]] || { echo " census does not count the repeat per tool"; return 1; }
|
|
327
|
+
return 0
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
# --- Case K: a clean session emits NO census line (non-regression) ---
|
|
331
|
+
case_no_rejections_no_census() {
|
|
332
|
+
local root="/tmp/maxy-1692-K-$$"; local out
|
|
333
|
+
out=$(setup_tree "$root"); local script="${out% *}"; local cfg="${out#* }"
|
|
334
|
+
write_parent_with_error "$cfg" "$UUID_A"
|
|
335
|
+
write_pidfile "$cfg" 4256 "$UUID_A" "$BRIDGE"
|
|
336
|
+
|
|
337
|
+
invoke "$script" "$cfg" "session_${BRIDGE}"
|
|
338
|
+
cleanup_tree "$root"
|
|
339
|
+
|
|
340
|
+
[[ $RC -eq 0 ]] || { echo " expected exit 0, got $RC"; return 1; }
|
|
341
|
+
[[ "$OUT" != *"rejections:"* ]] || { echo " census line emitted for a session with no rejections"; return 1; }
|
|
342
|
+
[[ "$OUT" != *"REJECTED"* ]] || { echo " genuine tool error wrongly flagged REJECTED"; return 1; }
|
|
343
|
+
return 0
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
# --- Case L: --scan-subagent-errors does not report a rejection-only subagent ---
|
|
347
|
+
# A rejection is not a subagent failure: the subagent did not fail, a call was
|
|
348
|
+
# never dispatched. Counting it re-creates the 1692 conflation one mode over.
|
|
349
|
+
case_scan_ignores_rejection_only_subagent() {
|
|
350
|
+
local root="/tmp/maxy-1692-L-$$"; local out
|
|
351
|
+
out=$(setup_tree "$root"); local script="${out% *}"; local cfg="${out#* }"
|
|
352
|
+
write_parent "$cfg" "$UUID_A"
|
|
353
|
+
write_subagent_rejection_only "$cfg" "$UUID_A" "agent-aREJ00001" "content-producer"
|
|
354
|
+
write_subagent_failed "$cfg" "$UUID_A" "agent-aFAIL0001" "pdf-renderer"
|
|
355
|
+
|
|
356
|
+
invoke "$script" "$cfg" --scan-subagent-errors
|
|
357
|
+
cleanup_tree "$root"
|
|
358
|
+
|
|
359
|
+
[[ $RC -eq 0 ]] || { echo " expected exit 0, got $RC; err=$ERR"; return 1; }
|
|
360
|
+
[[ "$OUT" == *"aFAIL0001"* ]] || { echo " genuinely failed subagent missing from scan"; return 1; }
|
|
361
|
+
[[ "$OUT" != *"SUBREJ_ONLY"* ]] || { echo " rejection-only subagent reported as error-carrying"; return 1; }
|
|
362
|
+
[[ "$OUT" != *"aREJ00001"* ]] || { echo " rejection-only subagent listed by scan"; return 1; }
|
|
363
|
+
return 0
|
|
364
|
+
}
|
|
365
|
+
|
|
251
366
|
# --- Case I: content-grep resolves a unique body match (last-resort tier) ---
|
|
252
367
|
case_content_grep_single() {
|
|
253
368
|
local root="/tmp/maxy-585-I-$$"; local out
|
|
@@ -299,6 +414,9 @@ run_case "parent's own tool error not mislabelled SUBAGENT ERROR" case_pare
|
|
|
299
414
|
run_case "content-grep resolves a unique body match" case_content_grep_single
|
|
300
415
|
run_case "content-grep refuses an ambiguous (multi) body match" case_content_grep_ambiguous_refuses
|
|
301
416
|
run_case "unresolvable key → exit 1" case_unresolvable_miss
|
|
417
|
+
run_case "harness rejection classified + counted, exclusions intact" case_rejection_classified_and_counted
|
|
418
|
+
run_case "no rejections → no census line" case_no_rejections_no_census
|
|
419
|
+
run_case "scan ignores a rejection-only subagent" case_scan_ignores_rejection_only_subagent
|
|
302
420
|
|
|
303
421
|
echo ""
|
|
304
422
|
echo "================================================"
|
|
@@ -627,7 +627,7 @@ tail_mode() {
|
|
|
627
627
|
# non-interactive SSH PATH. argv[1] selects the mode (timeline|scan); the
|
|
628
628
|
# program is fed via -c so logs-read.sh stays a single self-contained file.
|
|
629
629
|
JSONL_PY=$(cat <<'PY'
|
|
630
|
-
import sys, os, re, json, glob
|
|
630
|
+
import sys, os, re, json, glob, collections
|
|
631
631
|
|
|
632
632
|
MODE = sys.argv[1] if len(sys.argv) > 1 else ''
|
|
633
633
|
|
|
@@ -776,7 +776,40 @@ def subagent_dir(projects_root, uuid):
|
|
|
776
776
|
# failure (the task's target, flagged 'SUBAGENT ERROR' and counted) from the
|
|
777
777
|
# parent session's own tool error (shown plainly, never counted as a subagent
|
|
778
778
|
# error). Returns the count of SUBAGENT errors found (0 for the parent).
|
|
779
|
-
|
|
779
|
+
# A harness rejection is a call that NEVER REACHED its tool: the CLI refused the
|
|
780
|
+
# model's arguments, so no MCP dispatch happened and no plugin line exists
|
|
781
|
+
# anywhere (not in server.log, not in mcp-<server>-<sessionId>.log, not in
|
|
782
|
+
# mcp-lifeline). Rendering it as a plain tool error reads as "the tool is broken"
|
|
783
|
+
# when the tool was never called -- the confusion that produced a false
|
|
784
|
+
# customer-facing fault report (task 1692).
|
|
785
|
+
#
|
|
786
|
+
# InputValidationError has two sub-classes and BOTH are rejections, because
|
|
787
|
+
# neither dispatches. The label says 'never dispatched', not 'malformed', because
|
|
788
|
+
# only the first is malformed:
|
|
789
|
+
# A. "...could not be parsed as JSON" -- args unparseable; the tool_use block
|
|
790
|
+
# carries __unparsedToolInput. 52 of 170 measured 2026-07-16.
|
|
791
|
+
# B. "...failed due to the following issue(s)" -- the JSON parsed fine and
|
|
792
|
+
# violated the schema (wrong type, missing required). No __unparsedToolInput.
|
|
793
|
+
# 118 of 170, so the majority; calling it 'malformed' would be wrong.
|
|
794
|
+
#
|
|
795
|
+
# This is the ONLY class relabelled. Deliberately NOT covered, all of which keep
|
|
796
|
+
# rendering as '‼ tool error':
|
|
797
|
+
# 'Error: No such tool available:' / 'Unknown skill:' -- different fault, and
|
|
798
|
+
# the missing-tool case already has a PostToolUse hook (mcp-tool-missing.sh)
|
|
799
|
+
# 'MCP error -32602' -- reached the server; its SDK rejected it
|
|
800
|
+
# every unwrapped is_error payload -- the tool ran and returned a failure
|
|
801
|
+
#
|
|
802
|
+
# The <tool_use_error> wrapper alone is NOT a rejection discriminator: measured
|
|
803
|
+
# 2026-07-16 across 733 SiteDesk transcripts it wraps mostly built-in tools that
|
|
804
|
+
# RAN and failed ('File has not been read yet' x60, 'File has been modified
|
|
805
|
+
# since read' x16). Match the error class, not the wrapper.
|
|
806
|
+
def is_rejection(text):
|
|
807
|
+
if not isinstance(text, str):
|
|
808
|
+
return False
|
|
809
|
+
return text.lstrip().startswith('<tool_use_error>InputValidationError:')
|
|
810
|
+
|
|
811
|
+
|
|
812
|
+
def collect_events(path, source, events, is_sub, rejects=None):
|
|
780
813
|
rows = load_jsonl(path)
|
|
781
814
|
id2tool = {}
|
|
782
815
|
for r in rows:
|
|
@@ -812,10 +845,18 @@ def collect_events(path, source, events, is_sub):
|
|
|
812
845
|
for b in content:
|
|
813
846
|
if isinstance(b, dict) and b.get('type') == 'tool_result' and b.get('is_error'):
|
|
814
847
|
tool = id2tool.get(b.get('tool_use_id'))
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
848
|
+
body = result_text(b, r)
|
|
849
|
+
if is_rejection(body):
|
|
850
|
+
# Not a tool failure: the call never ran. Never
|
|
851
|
+
# counted as a subagent error for the same reason.
|
|
852
|
+
flag = '‼ REJECTED (never dispatched)'
|
|
853
|
+
if rejects is not None:
|
|
854
|
+
rejects[tool or '?'] += 1
|
|
855
|
+
else:
|
|
856
|
+
flag = '‼ SUBAGENT ERROR' if is_sub else '‼ tool error'
|
|
857
|
+
if is_sub:
|
|
858
|
+
n_err += 1
|
|
859
|
+
events.append((ts, source, 'error', '%s tool=%s error="%s"' % (flag, tool or '?', clip(body, 200))))
|
|
819
860
|
elif isinstance(content, str) and content.strip():
|
|
820
861
|
events.append((ts, source, 'text', 'user: ' + clip(content, 160)))
|
|
821
862
|
return n_err
|
|
@@ -826,18 +867,25 @@ def timeline(projects_root, sessions_dir, key):
|
|
|
826
867
|
sys.stderr.write('-- trailer: key=%s resolved=no reason=%s\n' % (key, via))
|
|
827
868
|
return 1
|
|
828
869
|
events = []
|
|
870
|
+
rejects = collections.Counter()
|
|
829
871
|
if parent_path and os.path.exists(parent_path):
|
|
830
|
-
collect_events(parent_path, 'parent', events, False)
|
|
872
|
+
collect_events(parent_path, 'parent', events, False, rejects)
|
|
831
873
|
sdir = subagent_dir(projects_root, uuid)
|
|
832
874
|
sub_count = err_count = 0
|
|
833
875
|
if sdir:
|
|
834
876
|
for af in sorted(glob.glob(os.path.join(sdir, 'agent-*.jsonl'))):
|
|
835
877
|
sub_count += 1
|
|
836
|
-
err_count += collect_events(af, agent_type(af) or os.path.basename(af)[:-6], events, True)
|
|
878
|
+
err_count += collect_events(af, agent_type(af) or os.path.basename(af)[:-6], events, True, rejects)
|
|
837
879
|
events.sort(key=lambda e: e[0])
|
|
838
880
|
print('# session %s (resolved via %s)' % (uuid, via))
|
|
839
881
|
print('# parent: %s' % (parent_path or '(no parent transcript on disk)'))
|
|
840
882
|
print('# subagents: %d subagent-errors: %d' % (sub_count, err_count))
|
|
883
|
+
# Repetition is the diagnostic fact: one fat-fingered call is a typo, N
|
|
884
|
+
# identical ones against a single tool is a wedged loop. Counting it here
|
|
885
|
+
# makes that one greppable line instead of N unrelated ones.
|
|
886
|
+
if rejects:
|
|
887
|
+
top = ' '.join('%s x%d' % (t, n) for t, n in rejects.most_common())
|
|
888
|
+
print('# rejections: %d (%s)' % (sum(rejects.values()), top))
|
|
841
889
|
print('')
|
|
842
890
|
for ts, source, _kind, text in events:
|
|
843
891
|
print('%s [%s] %s' % (ts or ('-' * 24), source, text))
|
|
@@ -871,7 +919,13 @@ def scan(projects_root, limit):
|
|
|
871
919
|
if isinstance(content, list):
|
|
872
920
|
for b in content:
|
|
873
921
|
if isinstance(b, dict) and b.get('type') == 'tool_result' and b.get('is_error'):
|
|
874
|
-
|
|
922
|
+
body = result_text(b, r)
|
|
923
|
+
# A rejection is not a subagent failure: the subagent did
|
|
924
|
+
# not fail, a call was never dispatched. Counting it here
|
|
925
|
+
# would re-create the conflation the timeline just fixed.
|
|
926
|
+
if is_rejection(body):
|
|
927
|
+
continue
|
|
928
|
+
errs.append((id2tool.get(b.get('tool_use_id')), body))
|
|
875
929
|
if errs:
|
|
876
930
|
parent_uuid = os.path.basename(os.path.dirname(os.path.dirname(af)))
|
|
877
931
|
findings.append((af, agent_type(af), parent_uuid, errs))
|