@rubytech/create-maxy-code 0.1.409 → 0.1.411
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/plugins/scheduling/PLUGIN.md +2 -2
- package/payload/platform/plugins/scheduling/mcp/dist/__tests__/agent-dispatch-manager-grounding.test.js +5 -5
- package/payload/platform/plugins/scheduling/mcp/dist/__tests__/agent-dispatch-manager-grounding.test.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/agent-dispatch.test.js +103 -34
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/agent-dispatch.test.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/lib/agent-dispatch.d.ts +45 -11
- package/payload/platform/plugins/scheduling/mcp/dist/lib/agent-dispatch.d.ts.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/lib/agent-dispatch.js +74 -23
- package/payload/platform/plugins/scheduling/mcp/dist/lib/agent-dispatch.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/standing-audit.test.js +48 -9
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/standing-audit.test.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/agent-turn-dispatch.d.ts +9 -5
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/agent-turn-dispatch.d.ts.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/agent-turn-dispatch.js +53 -18
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/agent-turn-dispatch.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-event-agent.test.js +24 -4
- package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-event-agent.test.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-update-agent.test.js +6 -4
- package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-update-agent.test.js.map +1 -1
- package/payload/platform/plugins/whatsapp/references/channels-whatsapp.md +2 -0
- package/payload/server/public/assets/{AdminLoginScreens-D36zz_SY.js → AdminLoginScreens-BRQgI2lW.js} +1 -1
- package/payload/server/public/assets/AdminShell-By9n5icG.js +2 -0
- package/payload/server/public/assets/{Checkbox-54KtArF3.js → Checkbox-DjEWgYu1.js} +1 -1
- package/payload/server/public/assets/{Transcript-BCpiXt4J.js → Transcript-BejWvOaO.js} +1 -1
- package/payload/server/public/assets/{admin-C0FCHcJj.js → admin-BFEMryQm.js} +1 -1
- package/payload/server/public/assets/{browser-bKeZ5iLx.js → browser-CMJMEUz6.js} +1 -1
- package/payload/server/public/assets/{calendar-BDPBArFA.js → calendar-C6ZGlHEP.js} +1 -1
- package/payload/server/public/assets/chat-B7s62Nnp.js +1 -0
- package/payload/server/public/assets/chevron-left-Dfjcaiyr.js +1 -0
- package/payload/server/public/assets/data-741ZbBwR.js +1 -0
- package/payload/server/public/assets/{graph-Dvru9CW9.js → graph-DIrXtPLv.js} +1 -1
- package/payload/server/public/assets/{graph-labels-DLhR6Rit.js → graph-labels-BtPf1Jom.js} +1 -1
- package/payload/server/public/assets/{maximize-2-CyPKq7HZ.js → maximize-2-DeJ4K8kL.js} +1 -1
- package/payload/server/public/assets/{operator-Ql5iQXB-.js → operator-B8QhqzLI.js} +1 -1
- package/payload/server/public/assets/{page-YsTtZsn6.js → page-Df6KYkf4.js} +1 -1
- package/payload/server/public/assets/{page-ClJOW2fk.js → page-iFOy0dFx.js} +1 -1
- package/payload/server/public/assets/{public-BlDxwcmS.js → public-BrUIRQZd.js} +1 -1
- package/payload/server/public/assets/{rotate-ccw-DSynJPZX.js → rotate-ccw-CYfntpwu.js} +1 -1
- package/payload/server/public/assets/useSubAccountSwitcher-Cvnc_fwQ.css +1 -0
- package/payload/server/public/browser.html +4 -4
- package/payload/server/public/calendar.html +5 -5
- package/payload/server/public/chat.html +9 -9
- package/payload/server/public/data.html +8 -8
- package/payload/server/public/graph.html +9 -9
- package/payload/server/public/index.html +11 -11
- package/payload/server/public/operator.html +11 -11
- package/payload/server/public/public.html +9 -9
- package/payload/server/public/assets/AdminShell-DH6Lcj-d.js +0 -1
- package/payload/server/public/assets/chat-Cp_1ebzU.js +0 -1
- package/payload/server/public/assets/chevron-left-S7Nk-se6.js +0 -1
- package/payload/server/public/assets/data-BshlTgIW.js +0 -1
- package/payload/server/public/assets/useSubAccountSwitcher-CJelLQK-.css +0 -1
- /package/payload/server/public/assets/{useSubAccountSwitcher-DGKQxXDD.js → useSubAccountSwitcher-BK_KfOLC.js} +0 -0
package/package.json
CHANGED
|
@@ -124,14 +124,14 @@ An `action` invokes one pre-encoded MCP tool with no reasoning. When a due event
|
|
|
124
124
|
```
|
|
125
125
|
|
|
126
126
|
- `channel` — `whatsapp` or `telegram`.
|
|
127
|
-
- `destination` — the E.164 phone (WhatsApp) or chat id (Telegram) to run the turn for and reply to. It **must already be a registered admin or account-manager** on that channel; a destination that is not is rejected when the event is created.
|
|
127
|
+
- `destination` — the E.164 phone (WhatsApp) or chat id (Telegram) to run the turn for and reply to. It **must already be a registered admin or account-manager** on that channel; a destination that is not is rejected when the event is created. For WhatsApp the authoritative admin/manager lists are read from the socket-owning **house** account (never the event's own account), and the destination's registered account must match the account that created the schedule: an admin belongs to the house, a manager to its bound sub-account. The house/owner account may target any registered admin or manager; a sub-account is locked to its own manager(s). A refusal names the true reason — `not-registered` (in no house list) or `cross-account` (registered to a different account, naming both accounts).
|
|
128
128
|
- `prompt` — the instruction the agent runs.
|
|
129
129
|
|
|
130
130
|
An event carries **either** an `action` **or** an `agentDispatch`, never both — setting one clears the other. Agent dispatch is stored as `agentChannel`, `agentDestination`, `agentPrompt`. Use `schedule-update` with `agentDispatch` to modify or `agentDispatch: null` to clear. On WhatsApp, a destination bound as an account-manager scopes the spawned session to its sub-account; on Telegram the session runs on the house account.
|
|
131
131
|
|
|
132
132
|
A WhatsApp `agentDispatch` destination may therefore be an account manager, not only an admin phone — this is the supported way to send a manager a proactive or scheduled WhatsApp message. The reply is delivered over the house's paired line regardless of which sub-account the session is scoped to; socket ownership never restricts which number the dispatch can reach.
|
|
133
133
|
|
|
134
|
-
Dispatch results are recorded on the Event node (`lastDispatchResult`, `lastDispatchAt`) exactly as for tool actions. Every heartbeat tick also audits stored agent dispatches and logs `[schedule-audit] op=stale-dispatch`
|
|
134
|
+
Dispatch results are recorded on the Event node (`lastDispatchResult`, `lastDispatchAt`) exactly as for tool actions. Every heartbeat tick also audits stored agent dispatches against the same house-authority rule used at creation, and logs `[schedule-audit] op=stale-dispatch` with the identical reason codes — `not-registered` (destination left the house lists), `cross-account` (rebound to a different account than the one that created the schedule), or `sub-account-missing` (bound sub-account deleted) — so a binding that rots between scheduling and firing is surfaced with its true cause before it mis-dispatches.
|
|
135
135
|
|
|
136
136
|
## Reconciling public bookings (D1 → graph)
|
|
137
137
|
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// Task 1435 — content-presence gate for the scheduling side of the WhatsApp
|
|
2
|
-
// deliverability grounding. `
|
|
3
|
-
// manager (adminPhones OR accountManagers), so scheduled dispatch is
|
|
4
|
-
// supported way to reach a manager. PLUGIN.md must say so plainly, and
|
|
5
|
-
// over-the-house-line must be stated, so the agent cites it instead of
|
|
6
|
-
// a line-ownership restriction. Mirrors time-resolve-description.test.ts.
|
|
2
|
+
// deliverability grounding. `validateAgentDestination` accepts an account
|
|
3
|
+
// manager (the house's adminPhones OR accountManagers), so scheduled dispatch is
|
|
4
|
+
// the supported way to reach a manager. PLUGIN.md must say so plainly, and
|
|
5
|
+
// deliver-over-the-house-line must be stated, so the agent cites it instead of
|
|
6
|
+
// inventing a line-ownership restriction. Mirrors time-resolve-description.test.ts.
|
|
7
7
|
import { describe, it, expect } from "vitest";
|
|
8
8
|
import { readFileSync } from "fs";
|
|
9
9
|
import { resolve } from "path";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent-dispatch-manager-grounding.test.js","sourceRoot":"","sources":["../../src/__tests__/agent-dispatch-manager-grounding.test.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,
|
|
1
|
+
{"version":3,"file":"agent-dispatch-manager-grounding.test.js","sourceRoot":"","sources":["../../src/__tests__/agent-dispatch-manager-grounding.test.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,0EAA0E;AAC1E,iFAAiF;AACjF,2EAA2E;AAC3E,+EAA+E;AAC/E,oFAAoF;AACpF,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,YAAY,EAAE,MAAM,IAAI,CAAC;AAClC,OAAO,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAE/B,MAAM,MAAM,GAAG,YAAY,CAAC,OAAO,CAAC,SAAS,EAAE,oBAAoB,CAAC,EAAE,MAAM,CAAC,CAAC;AAE9E,QAAQ,CAAC,0CAA0C,EAAE,GAAG,EAAE;IACxD,EAAE,CAAC,4EAA4E,EAAE,GAAG,EAAE;QACpF,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,CACtB,4EAA4E,CAC7E,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,sEAAsE,EAAE,GAAG,EAAE;QAC9E,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,CACtB,sEAAsE,CACvE,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -1,10 +1,27 @@
|
|
|
1
1
|
import { describe, it, expect, afterEach } from "vitest";
|
|
2
|
-
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { mkdtempSync, rmSync, mkdirSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { tmpdir } from "node:os";
|
|
4
4
|
import { join } from "node:path";
|
|
5
|
-
import { normalizeE164, phonesMatch,
|
|
6
|
-
|
|
5
|
+
import { normalizeE164, phonesMatch, validateAgentDestination, boundSubAccountFor, } from "../agent-dispatch.js";
|
|
6
|
+
// The house-authority validator resolves the socket-owning house from the
|
|
7
|
+
// PARENT accounts dir (via account-enumeration), so fixtures must use
|
|
8
|
+
// UUID-named account dirs carrying role:"house" (the resolver's contract).
|
|
9
|
+
const HOUSE = "11111111-1111-4111-8111-111111111111";
|
|
10
|
+
const SUB_A = "22222222-2222-4222-8222-222222222222";
|
|
11
|
+
const SUB_B = "33333333-3333-4333-8333-333333333333";
|
|
12
|
+
function makeAccountsDir(seed) {
|
|
7
13
|
const dir = mkdtempSync(join(tmpdir(), "agent-dispatch-"));
|
|
14
|
+
seed(dir);
|
|
15
|
+
return dir;
|
|
16
|
+
}
|
|
17
|
+
function seedAccount(accountsDir, id, cfg) {
|
|
18
|
+
const d = join(accountsDir, id);
|
|
19
|
+
mkdirSync(d, { recursive: true });
|
|
20
|
+
writeFileSync(join(d, "account.json"), JSON.stringify(cfg), "utf-8");
|
|
21
|
+
}
|
|
22
|
+
// A single account dir (for the direct-read helpers boundSubAccountFor tests).
|
|
23
|
+
function makeAccountDir(config) {
|
|
24
|
+
const dir = mkdtempSync(join(tmpdir(), "agent-dispatch-one-"));
|
|
8
25
|
writeFileSync(join(dir, "account.json"), JSON.stringify(config), "utf-8");
|
|
9
26
|
return dir;
|
|
10
27
|
}
|
|
@@ -23,62 +40,114 @@ describe("normalizeE164 / phonesMatch (replica of platform normalize.ts)", () =>
|
|
|
23
40
|
expect(phonesMatch("", "")).toBe(false);
|
|
24
41
|
});
|
|
25
42
|
});
|
|
26
|
-
describe("
|
|
43
|
+
describe("validateAgentDestination — whatsapp house-authority + creating-account match", () => {
|
|
27
44
|
let dirs = [];
|
|
28
45
|
afterEach(() => {
|
|
29
46
|
for (const d of dirs)
|
|
30
47
|
rmSync(d, { recursive: true, force: true });
|
|
31
48
|
dirs = [];
|
|
32
49
|
});
|
|
33
|
-
it("
|
|
34
|
-
const
|
|
35
|
-
dirs.push(
|
|
36
|
-
|
|
37
|
-
expect(isValidAgentDestination(dir, "whatsapp", "+44 7504 472444")).toEqual({ valid: true });
|
|
50
|
+
it("house-created dispatch to a house admin is valid (spaced form matches stored bare form)", () => {
|
|
51
|
+
const acc = makeAccountsDir((a) => seedAccount(a, HOUSE, { role: "house", whatsapp: { adminPhones: ["+447504472444"] } }));
|
|
52
|
+
dirs.push(acc);
|
|
53
|
+
expect(validateAgentDestination(acc, HOUSE, "whatsapp", "+44 7504 472444")).toEqual({ valid: true });
|
|
38
54
|
});
|
|
39
|
-
it("
|
|
40
|
-
const
|
|
41
|
-
|
|
55
|
+
it("sub-account-created dispatch to its own manager is valid", () => {
|
|
56
|
+
const acc = makeAccountsDir((a) => {
|
|
57
|
+
seedAccount(a, HOUSE, { role: "house", whatsapp: { accountManagers: { "447958391734": SUB_A } } });
|
|
58
|
+
seedAccount(a, SUB_A, {});
|
|
42
59
|
});
|
|
43
|
-
dirs.push(
|
|
44
|
-
expect(
|
|
60
|
+
dirs.push(acc);
|
|
61
|
+
expect(validateAgentDestination(acc, SUB_A, "whatsapp", "+44 7958 391734")).toEqual({ valid: true });
|
|
45
62
|
});
|
|
46
|
-
it("
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
63
|
+
it("sub-account-created dispatch to ANOTHER sub-account's manager is cross-account", () => {
|
|
64
|
+
const acc = makeAccountsDir((a) => {
|
|
65
|
+
seedAccount(a, HOUSE, { role: "house", whatsapp: { accountManagers: { "447958391734": SUB_A } } });
|
|
66
|
+
seedAccount(a, SUB_A, {});
|
|
67
|
+
seedAccount(a, SUB_B, {});
|
|
68
|
+
});
|
|
69
|
+
dirs.push(acc);
|
|
70
|
+
expect(validateAgentDestination(acc, SUB_B, "whatsapp", "+447958391734")).toEqual({
|
|
50
71
|
valid: false,
|
|
51
|
-
reason: "
|
|
72
|
+
reason: "cross-account",
|
|
73
|
+
destinationAccount: SUB_A,
|
|
74
|
+
scheduleAccount: SUB_B,
|
|
52
75
|
});
|
|
53
76
|
});
|
|
54
|
-
it("
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
77
|
+
it("sub-account-created dispatch to a house admin is cross-account (sub locked to own managers)", () => {
|
|
78
|
+
const acc = makeAccountsDir((a) => {
|
|
79
|
+
seedAccount(a, HOUSE, { role: "house", whatsapp: { adminPhones: ["+447504472444"] } });
|
|
80
|
+
seedAccount(a, SUB_A, {});
|
|
81
|
+
});
|
|
82
|
+
dirs.push(acc);
|
|
83
|
+
expect(validateAgentDestination(acc, SUB_A, "whatsapp", "+447504472444")).toEqual({
|
|
84
|
+
valid: false,
|
|
85
|
+
reason: "cross-account",
|
|
86
|
+
destinationAccount: HOUSE,
|
|
87
|
+
scheduleAccount: SUB_A,
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
it("house-created dispatch to any sub-account manager is valid (owner exception)", () => {
|
|
91
|
+
const acc = makeAccountsDir((a) => {
|
|
92
|
+
seedAccount(a, HOUSE, { role: "house", whatsapp: { accountManagers: { "447958391734": SUB_A } } });
|
|
93
|
+
seedAccount(a, SUB_A, {});
|
|
94
|
+
});
|
|
95
|
+
dirs.push(acc);
|
|
96
|
+
expect(validateAgentDestination(acc, HOUSE, "whatsapp", "+447958391734")).toEqual({ valid: true });
|
|
97
|
+
});
|
|
98
|
+
it("unregistered number is not-registered", () => {
|
|
99
|
+
const acc = makeAccountsDir((a) => seedAccount(a, HOUSE, { role: "house", whatsapp: { adminPhones: ["+447504472444"] } }));
|
|
100
|
+
dirs.push(acc);
|
|
101
|
+
expect(validateAgentDestination(acc, HOUSE, "whatsapp", "+447999999999")).toEqual({
|
|
102
|
+
valid: false,
|
|
103
|
+
reason: "not-registered",
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
it("reads the HOUSE list, not the creating sub-account's own account.json (the 1439 bug)", () => {
|
|
107
|
+
// The manager map lives ONLY on the house; the sub-account carries none.
|
|
108
|
+
// Pre-fix this read the sub-account and returned not-registered.
|
|
109
|
+
const acc = makeAccountsDir((a) => {
|
|
110
|
+
seedAccount(a, HOUSE, { role: "house", whatsapp: { accountManagers: { "447958391734": SUB_A } } });
|
|
111
|
+
seedAccount(a, SUB_A, { whatsapp: {} });
|
|
112
|
+
});
|
|
113
|
+
dirs.push(acc);
|
|
114
|
+
expect(validateAgentDestination(acc, SUB_A, "whatsapp", "+447958391734")).toEqual({ valid: true });
|
|
115
|
+
});
|
|
116
|
+
it("a number that is both a house admin and a sub-account's manager binds to the sub-account (manager wins)", () => {
|
|
117
|
+
const acc = makeAccountsDir((a) => {
|
|
118
|
+
seedAccount(a, HOUSE, {
|
|
119
|
+
role: "house",
|
|
120
|
+
whatsapp: { adminPhones: ["+447958391734"], accountManagers: { "447958391734": SUB_A } },
|
|
121
|
+
});
|
|
122
|
+
seedAccount(a, SUB_A, {});
|
|
123
|
+
});
|
|
124
|
+
dirs.push(acc);
|
|
125
|
+
// created in SUB_A → its own manager → valid
|
|
126
|
+
expect(validateAgentDestination(acc, SUB_A, "whatsapp", "+447958391734")).toEqual({ valid: true });
|
|
58
127
|
});
|
|
59
128
|
});
|
|
60
|
-
describe("
|
|
129
|
+
describe("validateAgentDestination — telegram unchanged (creating-account dir, house-only)", () => {
|
|
61
130
|
let dirs = [];
|
|
62
131
|
afterEach(() => {
|
|
63
132
|
for (const d of dirs)
|
|
64
133
|
rmSync(d, { recursive: true, force: true });
|
|
65
134
|
dirs = [];
|
|
66
135
|
});
|
|
67
|
-
it("accepts a chat id in telegram.adminUsers", () => {
|
|
68
|
-
const
|
|
69
|
-
dirs.push(
|
|
70
|
-
expect(
|
|
136
|
+
it("accepts a chat id in the creating account's telegram.adminUsers", () => {
|
|
137
|
+
const acc = makeAccountsDir((a) => seedAccount(a, HOUSE, { role: "house", telegram: { adminUsers: [12345678] } }));
|
|
138
|
+
dirs.push(acc);
|
|
139
|
+
expect(validateAgentDestination(acc, HOUSE, "telegram", "12345678")).toEqual({ valid: true });
|
|
71
140
|
});
|
|
72
|
-
it("rejects a chat id
|
|
73
|
-
const
|
|
74
|
-
dirs.push(
|
|
75
|
-
expect(
|
|
141
|
+
it("rejects a chat id absent from adminUsers as not-registered", () => {
|
|
142
|
+
const acc = makeAccountsDir((a) => seedAccount(a, HOUSE, { role: "house", telegram: { adminUsers: [12345678] } }));
|
|
143
|
+
dirs.push(acc);
|
|
144
|
+
expect(validateAgentDestination(acc, HOUSE, "telegram", "99999999")).toEqual({
|
|
76
145
|
valid: false,
|
|
77
|
-
reason: "
|
|
146
|
+
reason: "not-registered",
|
|
78
147
|
});
|
|
79
148
|
});
|
|
80
149
|
});
|
|
81
|
-
describe("boundSubAccountFor — whatsapp accountManagers only", () => {
|
|
150
|
+
describe("boundSubAccountFor — whatsapp accountManagers only (direct single-dir read)", () => {
|
|
82
151
|
let dirs = [];
|
|
83
152
|
afterEach(() => {
|
|
84
153
|
for (const d of dirs)
|
package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/agent-dispatch.test.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent-dispatch.test.js","sourceRoot":"","sources":["../../../src/lib/__tests__/agent-dispatch.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"agent-dispatch.test.js","sourceRoot":"","sources":["../../../src/lib/__tests__/agent-dispatch.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,9 +1,23 @@
|
|
|
1
1
|
export type AgentChannel = "whatsapp" | "telegram";
|
|
2
|
+
/**
|
|
3
|
+
* Reason codes are the true cause, not a symptom (Task 1439 observability):
|
|
4
|
+
* - `not-registered`: the destination is in no authoritative list (house's
|
|
5
|
+
* `adminPhones`/`accountManagers` for WhatsApp; the creating account's
|
|
6
|
+
* `telegram.adminUsers` for Telegram).
|
|
7
|
+
* - `cross-account`: the destination IS registered, but to an account other
|
|
8
|
+
* than the one that created the schedule (and the creator is not the house).
|
|
9
|
+
* Names both accounts so the mismatch is explained, not guessed.
|
|
10
|
+
*/
|
|
2
11
|
export type DestinationValidity = {
|
|
3
12
|
valid: true;
|
|
4
13
|
} | {
|
|
5
14
|
valid: false;
|
|
6
|
-
reason: "
|
|
15
|
+
reason: "not-registered";
|
|
16
|
+
} | {
|
|
17
|
+
valid: false;
|
|
18
|
+
reason: "cross-account";
|
|
19
|
+
destinationAccount: string;
|
|
20
|
+
scheduleAccount: string;
|
|
7
21
|
};
|
|
8
22
|
/**
|
|
9
23
|
* Replica of `normalize.ts:normalizeE164` — strip whitespace/dashes/parens, a
|
|
@@ -24,20 +38,40 @@ export declare function readChannelAdmins(accountDir: string, channel: "telegram
|
|
|
24
38
|
adminUsers: number[];
|
|
25
39
|
};
|
|
26
40
|
/**
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
41
|
+
* The account a WhatsApp `destination` is registered to on the HOUSE's lists,
|
|
42
|
+
* or null if it is in neither list. A manager binding wins over an admin match:
|
|
43
|
+
* if the number is an `accountManagers` key its registered account is the bound
|
|
44
|
+
* sub-account (the isolation-relevant fact); otherwise if it is an `adminPhones`
|
|
45
|
+
* entry its registered account is the house. `houseDir` is the house account's
|
|
46
|
+
* directory; `houseAccountId` its id.
|
|
47
|
+
*/
|
|
48
|
+
export declare function registeredAccountForDestination(houseDir: string, houseAccountId: string, destination: string): string | null;
|
|
49
|
+
/**
|
|
50
|
+
* Validate a dispatch destination under the Task 1439 two-part rule.
|
|
51
|
+
* `accountsDir` is the PARENT accounts directory
|
|
52
|
+
* (`$PLATFORM_ROOT/../data/accounts`); `creatingAccountId` is the account that
|
|
53
|
+
* created (or owns) the schedule.
|
|
54
|
+
*
|
|
55
|
+
* - whatsapp: resolve the socket-owning house, read ITS authoritative lists,
|
|
56
|
+
* and compute the destination's registered account. `not-registered` if it
|
|
57
|
+
* is in no house list. Otherwise valid iff the creator IS the house (owner
|
|
58
|
+
* exception: it owns the single line and may target any registered
|
|
59
|
+
* admin/manager) OR the registered account equals the creating account.
|
|
60
|
+
* Any other case is `cross-account`.
|
|
61
|
+
* - telegram: house-only (Task 1378; 1380 deferred). Reads the creating
|
|
62
|
+
* account's own `telegram.adminUsers`; there is no manager surface and thus
|
|
63
|
+
* no cross-account concept — an integer chat id present in the list is valid,
|
|
64
|
+
* anything else is `not-registered`.
|
|
32
65
|
*/
|
|
33
|
-
export declare function
|
|
66
|
+
export declare function validateAgentDestination(accountsDir: string, creatingAccountId: string, channel: AgentChannel, destination: string): DestinationValidity;
|
|
34
67
|
/**
|
|
35
68
|
* Validate an `agentDispatch` at write time (shared by `schedule-event` and
|
|
36
69
|
* `schedule-update` so the two write paths cannot drift on the rule): reject an
|
|
37
|
-
* empty prompt, and reject a destination that
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
* `$PLATFORM_ROOT/../data/accounts
|
|
70
|
+
* empty prompt, and reject a destination that fails the two-part house-authority
|
|
71
|
+
* rule. Throws with a caller-facing message naming the true reason; returns on
|
|
72
|
+
* success. `accountId` is the account that created the schedule — the authority
|
|
73
|
+
* lists are read from the house under `$PLATFORM_ROOT/../data/accounts`, and the
|
|
74
|
+
* destination's registered account must match `accountId` (house/owner aside).
|
|
41
75
|
*/
|
|
42
76
|
export declare function validateAgentDispatch(accountId: string, agentDispatch: {
|
|
43
77
|
channel: AgentChannel;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent-dispatch.d.ts","sourceRoot":"","sources":["../../src/lib/agent-dispatch.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"agent-dispatch.d.ts","sourceRoot":"","sources":["../../src/lib/agent-dispatch.ts"],"names":[],"mappings":"AAqBA,MAAM,MAAM,YAAY,GAAG,UAAU,GAAG,UAAU,CAAC;AAEnD;;;;;;;;GAQG;AACH,MAAM,MAAM,mBAAmB,GAC3B;IAAE,KAAK,EAAE,IAAI,CAAA;CAAE,GACf;IAAE,KAAK,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,gBAAgB,CAAA;CAAE,GAC1C;IAAE,KAAK,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,eAAe,CAAC;IAAC,kBAAkB,EAAE,MAAM,CAAC;IAAC,eAAe,EAAE,MAAM,CAAA;CAAE,CAAC;AAEnG;;;GAGG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAOnD;AAED,8EAA8E;AAC9E,wBAAgB,WAAW,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAIzD;AAED,UAAU,cAAc;IACtB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACzC;AAED;;uCAEuC;AACvC,wBAAgB,iBAAiB,CAC/B,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,UAAU,GAClB,cAAc,CAAC;AAClB,wBAAgB,iBAAiB,CAC/B,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,UAAU,GAClB;IAAE,UAAU,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC;AA8B5B;;;;;;;GAOG;AACH,wBAAgB,+BAA+B,CAC7C,QAAQ,EAAE,MAAM,EAChB,cAAc,EAAE,MAAM,EACtB,WAAW,EAAE,MAAM,GAClB,MAAM,GAAG,IAAI,CAOf;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,wBAAwB,CACtC,WAAW,EAAE,MAAM,EACnB,iBAAiB,EAAE,MAAM,EACzB,OAAO,EAAE,YAAY,EACrB,WAAW,EAAE,MAAM,GAClB,mBAAmB,CAmBrB;AAED;;;;;;;;GAQG;AACH,wBAAgB,qBAAqB,CACnC,SAAS,EAAE,MAAM,EACjB,aAAa,EAAE;IAAE,OAAO,EAAE,YAAY,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAC5E,IAAI,CAsBN;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAMzF"}
|
|
@@ -1,16 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Task 1378 — agent-bearing scheduled dispatch: destination validation.
|
|
3
|
+
* Task 1439 — house single-authority + creating-account match.
|
|
3
4
|
*
|
|
4
5
|
* The scheduling MCP cannot import `platform/ui` (plugin boundary), so this
|
|
5
6
|
* module replicates the minimal E.164 matching from
|
|
6
|
-
* `platform/ui/app/lib/whatsapp/normalize.ts
|
|
7
|
-
*
|
|
7
|
+
* `platform/ui/app/lib/whatsapp/normalize.ts`. For WhatsApp the authoritative
|
|
8
|
+
* `adminPhones` + `accountManagers` lists live ONLY on the socket-owning house
|
|
9
|
+
* account (Tasks 1377/1382), so the validator resolves the house via
|
|
10
|
+
* `resolveHouseOrSoleAccountId` — the same authority the inbound gate uses — and
|
|
11
|
+
* reads its config, NOT the event's own account. It then requires the
|
|
12
|
+
* destination's registered account to match the account that created the
|
|
13
|
+
* schedule (the house/owner account excepted), so a schedule created in account
|
|
14
|
+
* X cannot message another account's people. One `validateAgentDestination` is
|
|
8
15
|
* shared by `schedule-event`, `schedule-update`, and the dispatcher's standing
|
|
9
|
-
* audit so
|
|
10
|
-
* drift between the write path and the audit.
|
|
16
|
+
* audit so create-time and fire-time cannot diverge on rule or reason codes.
|
|
11
17
|
*/
|
|
12
18
|
import { readFileSync } from "node:fs";
|
|
13
19
|
import { resolve } from "node:path";
|
|
20
|
+
import { resolveHouseOrSoleAccountId } from "../../../../../lib/account-enumeration/dist/index.js";
|
|
14
21
|
/**
|
|
15
22
|
* Replica of `normalize.ts:normalizeE164` — strip whitespace/dashes/parens, a
|
|
16
23
|
* `whatsapp:` prefix, and a leading `+` or `00`; return digits-only or `''`.
|
|
@@ -60,31 +67,71 @@ export function readChannelAdmins(accountDir, channel) {
|
|
|
60
67
|
return { adminUsers };
|
|
61
68
|
}
|
|
62
69
|
/**
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
70
|
+
* The account a WhatsApp `destination` is registered to on the HOUSE's lists,
|
|
71
|
+
* or null if it is in neither list. A manager binding wins over an admin match:
|
|
72
|
+
* if the number is an `accountManagers` key its registered account is the bound
|
|
73
|
+
* sub-account (the isolation-relevant fact); otherwise if it is an `adminPhones`
|
|
74
|
+
* entry its registered account is the house. `houseDir` is the house account's
|
|
75
|
+
* directory; `houseAccountId` its id.
|
|
68
76
|
*/
|
|
69
|
-
export function
|
|
77
|
+
export function registeredAccountForDestination(houseDir, houseAccountId, destination) {
|
|
78
|
+
const { adminPhones, accountManagers } = readChannelAdmins(houseDir, "whatsapp");
|
|
79
|
+
for (const [managerPhone, subAccountId] of Object.entries(accountManagers)) {
|
|
80
|
+
if (subAccountId && phonesMatch(managerPhone, destination))
|
|
81
|
+
return subAccountId;
|
|
82
|
+
}
|
|
83
|
+
if (adminPhones.some((p) => phonesMatch(p, destination)))
|
|
84
|
+
return houseAccountId;
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Validate a dispatch destination under the Task 1439 two-part rule.
|
|
89
|
+
* `accountsDir` is the PARENT accounts directory
|
|
90
|
+
* (`$PLATFORM_ROOT/../data/accounts`); `creatingAccountId` is the account that
|
|
91
|
+
* created (or owns) the schedule.
|
|
92
|
+
*
|
|
93
|
+
* - whatsapp: resolve the socket-owning house, read ITS authoritative lists,
|
|
94
|
+
* and compute the destination's registered account. `not-registered` if it
|
|
95
|
+
* is in no house list. Otherwise valid iff the creator IS the house (owner
|
|
96
|
+
* exception: it owns the single line and may target any registered
|
|
97
|
+
* admin/manager) OR the registered account equals the creating account.
|
|
98
|
+
* Any other case is `cross-account`.
|
|
99
|
+
* - telegram: house-only (Task 1378; 1380 deferred). Reads the creating
|
|
100
|
+
* account's own `telegram.adminUsers`; there is no manager surface and thus
|
|
101
|
+
* no cross-account concept — an integer chat id present in the list is valid,
|
|
102
|
+
* anything else is `not-registered`.
|
|
103
|
+
*/
|
|
104
|
+
export function validateAgentDestination(accountsDir, creatingAccountId, channel, destination) {
|
|
70
105
|
if (channel === "whatsapp") {
|
|
71
|
-
const
|
|
72
|
-
const
|
|
73
|
-
const
|
|
74
|
-
|
|
106
|
+
const houseAccountId = resolveHouseOrSoleAccountId(accountsDir);
|
|
107
|
+
const houseDir = resolve(accountsDir, houseAccountId);
|
|
108
|
+
const registered = registeredAccountForDestination(houseDir, houseAccountId, destination);
|
|
109
|
+
if (registered === null)
|
|
110
|
+
return { valid: false, reason: "not-registered" };
|
|
111
|
+
if (creatingAccountId === houseAccountId)
|
|
112
|
+
return { valid: true };
|
|
113
|
+
if (registered === creatingAccountId)
|
|
114
|
+
return { valid: true };
|
|
115
|
+
return {
|
|
116
|
+
valid: false,
|
|
117
|
+
reason: "cross-account",
|
|
118
|
+
destinationAccount: registered,
|
|
119
|
+
scheduleAccount: creatingAccountId,
|
|
120
|
+
};
|
|
75
121
|
}
|
|
76
|
-
const { adminUsers } = readChannelAdmins(
|
|
122
|
+
const { adminUsers } = readChannelAdmins(resolve(accountsDir, creatingAccountId), "telegram");
|
|
77
123
|
const asNumber = Number(destination);
|
|
78
124
|
const ok = Number.isInteger(asNumber) && adminUsers.includes(asNumber);
|
|
79
|
-
return ok ? { valid: true } : { valid: false, reason: "
|
|
125
|
+
return ok ? { valid: true } : { valid: false, reason: "not-registered" };
|
|
80
126
|
}
|
|
81
127
|
/**
|
|
82
128
|
* Validate an `agentDispatch` at write time (shared by `schedule-event` and
|
|
83
129
|
* `schedule-update` so the two write paths cannot drift on the rule): reject an
|
|
84
|
-
* empty prompt, and reject a destination that
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
* `$PLATFORM_ROOT/../data/accounts
|
|
130
|
+
* empty prompt, and reject a destination that fails the two-part house-authority
|
|
131
|
+
* rule. Throws with a caller-facing message naming the true reason; returns on
|
|
132
|
+
* success. `accountId` is the account that created the schedule — the authority
|
|
133
|
+
* lists are read from the house under `$PLATFORM_ROOT/../data/accounts`, and the
|
|
134
|
+
* destination's registered account must match `accountId` (house/owner aside).
|
|
88
135
|
*/
|
|
89
136
|
export function validateAgentDispatch(accountId, agentDispatch) {
|
|
90
137
|
const { channel, destination, prompt } = agentDispatch;
|
|
@@ -95,10 +142,14 @@ export function validateAgentDispatch(accountId, agentDispatch) {
|
|
|
95
142
|
if (!platformRoot) {
|
|
96
143
|
throw new Error("PLATFORM_ROOT environment variable is required to validate the agentDispatch destination");
|
|
97
144
|
}
|
|
98
|
-
const
|
|
99
|
-
const check =
|
|
145
|
+
const accountsDir = resolve(platformRoot, "..", "data/accounts");
|
|
146
|
+
const check = validateAgentDestination(accountsDir, accountId, channel, destination);
|
|
100
147
|
if (!check.valid) {
|
|
101
|
-
|
|
148
|
+
if (check.reason === "cross-account") {
|
|
149
|
+
throw new Error(`agentDispatch destination "${destination}" belongs to a different account on ${channel} — ` +
|
|
150
|
+
`reason=cross-account destinationAccount=${check.destinationAccount} scheduleAccount=${check.scheduleAccount}`);
|
|
151
|
+
}
|
|
152
|
+
throw new Error(`agentDispatch destination "${destination}" is not a registered admin/account-manager on ${channel} — reason=not-registered`);
|
|
102
153
|
}
|
|
103
154
|
}
|
|
104
155
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent-dispatch.js","sourceRoot":"","sources":["../../src/lib/agent-dispatch.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"agent-dispatch.js","sourceRoot":"","sources":["../../src/lib/agent-dispatch.ts"],"names":[],"mappings":"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"}
|
package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/standing-audit.test.js
CHANGED
|
@@ -13,12 +13,16 @@ function driverReturning(rows) {
|
|
|
13
13
|
}),
|
|
14
14
|
};
|
|
15
15
|
}
|
|
16
|
+
// The audit now reads the authoritative lists from the socket-owning house, so
|
|
17
|
+
// the house dir must be UUID-named and carry role:"house" (resolver contract).
|
|
16
18
|
let accountsDir;
|
|
17
|
-
const HOUSE = "
|
|
19
|
+
const HOUSE = "11111111-1111-4111-8111-111111111111";
|
|
20
|
+
const SUB_OK = "44444444-4444-4444-8444-444444444444";
|
|
21
|
+
const SUB_OTHER = "55555555-5555-4555-8555-555555555555";
|
|
18
22
|
function seedHouse(waConfig) {
|
|
19
23
|
const dir = join(accountsDir, HOUSE);
|
|
20
24
|
mkdirSync(dir, { recursive: true });
|
|
21
|
-
writeFileSync(join(dir, "account.json"), JSON.stringify({ whatsapp: waConfig }), "utf-8");
|
|
25
|
+
writeFileSync(join(dir, "account.json"), JSON.stringify({ role: "house", whatsapp: waConfig }), "utf-8");
|
|
22
26
|
}
|
|
23
27
|
let errSpy;
|
|
24
28
|
function auditLines() {
|
|
@@ -36,18 +40,31 @@ afterEach(() => {
|
|
|
36
40
|
vi.clearAllMocks();
|
|
37
41
|
});
|
|
38
42
|
describe("runStandingAudit", () => {
|
|
39
|
-
it("emits
|
|
43
|
+
it("emits not-registered when the destination is in no house list", async () => {
|
|
40
44
|
seedHouse({ adminPhones: ["+447111111111"] }); // destination not present
|
|
41
45
|
const driver = driverReturning([
|
|
42
46
|
{ eventId: "e1", accountId: HOUSE, agentChannel: "whatsapp", agentDestination: "+447504472444" },
|
|
43
47
|
]);
|
|
44
48
|
await runStandingAudit(driver, [HOUSE], accountsDir);
|
|
45
49
|
expect(auditLines()).toEqual([
|
|
46
|
-
expect.stringContaining("op=stale-dispatch eventId=e1 reason=
|
|
50
|
+
expect.stringContaining("op=stale-dispatch eventId=e1 reason=not-registered"),
|
|
51
|
+
]);
|
|
52
|
+
});
|
|
53
|
+
it("emits cross-account when a house event targets a manager bound to a different sub-account", async () => {
|
|
54
|
+
// The manager is bound to SUB_OK, but the event was created in SUB_OTHER.
|
|
55
|
+
seedHouse({ accountManagers: { "447504472444": SUB_OK } });
|
|
56
|
+
mkdirSync(join(accountsDir, SUB_OK), { recursive: true });
|
|
57
|
+
mkdirSync(join(accountsDir, SUB_OTHER), { recursive: true });
|
|
58
|
+
const driver = driverReturning([
|
|
59
|
+
{ eventId: "e5", accountId: SUB_OTHER, agentChannel: "whatsapp", agentDestination: "+447504472444" },
|
|
60
|
+
]);
|
|
61
|
+
await runStandingAudit(driver, [SUB_OTHER], accountsDir);
|
|
62
|
+
expect(auditLines()).toEqual([
|
|
63
|
+
expect.stringContaining(`op=stale-dispatch eventId=e5 reason=cross-account destinationAccount=${SUB_OK} scheduleAccount=${SUB_OTHER}`),
|
|
47
64
|
]);
|
|
48
65
|
});
|
|
49
66
|
it("emits sub-account-missing when a manager binding points at a deleted sub-account", async () => {
|
|
50
|
-
seedHouse({ accountManagers: { "447504472444": "
|
|
67
|
+
seedHouse({ accountManagers: { "447504472444": "66666666-6666-4666-8666-666666666666" } }); // sub dir never created
|
|
51
68
|
const driver = driverReturning([
|
|
52
69
|
{ eventId: "e2", accountId: HOUSE, agentChannel: "whatsapp", agentDestination: "+447504472444" },
|
|
53
70
|
]);
|
|
@@ -56,16 +73,16 @@ describe("runStandingAudit", () => {
|
|
|
56
73
|
expect.stringContaining("op=stale-dispatch eventId=e2 reason=sub-account-missing"),
|
|
57
74
|
]);
|
|
58
75
|
});
|
|
59
|
-
it("emits nothing for a healthy binding (manager + present sub-account)", async () => {
|
|
60
|
-
seedHouse({ accountManagers: { "447504472444":
|
|
61
|
-
mkdirSync(join(accountsDir,
|
|
76
|
+
it("emits nothing for a healthy binding (house event, manager + present sub-account)", async () => {
|
|
77
|
+
seedHouse({ accountManagers: { "447504472444": SUB_OK } });
|
|
78
|
+
mkdirSync(join(accountsDir, SUB_OK), { recursive: true });
|
|
62
79
|
const driver = driverReturning([
|
|
63
80
|
{ eventId: "e3", accountId: HOUSE, agentChannel: "whatsapp", agentDestination: "+447504472444" },
|
|
64
81
|
]);
|
|
65
82
|
await runStandingAudit(driver, [HOUSE], accountsDir);
|
|
66
83
|
expect(auditLines()).toEqual([]);
|
|
67
84
|
});
|
|
68
|
-
it("emits nothing for a healthy plain admin destination", async () => {
|
|
85
|
+
it("emits nothing for a healthy plain admin destination (house event)", async () => {
|
|
69
86
|
seedHouse({ adminPhones: ["+447504472444"] });
|
|
70
87
|
const driver = driverReturning([
|
|
71
88
|
{ eventId: "e4", accountId: HOUSE, agentChannel: "whatsapp", agentDestination: "+447504472444" },
|
|
@@ -73,5 +90,27 @@ describe("runStandingAudit", () => {
|
|
|
73
90
|
await runStandingAudit(driver, [HOUSE], accountsDir);
|
|
74
91
|
expect(auditLines()).toEqual([]);
|
|
75
92
|
});
|
|
93
|
+
it("emits nothing when a sub-account event targets its OWN manager", async () => {
|
|
94
|
+
seedHouse({ accountManagers: { "447504472444": SUB_OK } });
|
|
95
|
+
mkdirSync(join(accountsDir, SUB_OK), { recursive: true });
|
|
96
|
+
const driver = driverReturning([
|
|
97
|
+
{ eventId: "e6", accountId: SUB_OK, agentChannel: "whatsapp", agentDestination: "+447504472444" },
|
|
98
|
+
]);
|
|
99
|
+
await runStandingAudit(driver, [SUB_OK], accountsDir);
|
|
100
|
+
expect(auditLines()).toEqual([]);
|
|
101
|
+
});
|
|
102
|
+
it("never throws on house drift (multi-house): returns with op=house-unresolved", async () => {
|
|
103
|
+
// Two role:"house" dirs — the resolver loud-fails. The audit must not throw
|
|
104
|
+
// into the sweep; it emits one diagnostic and returns.
|
|
105
|
+
seedHouse({ adminPhones: ["+447504472444"] });
|
|
106
|
+
const secondHouse = "77777777-7777-4777-8777-777777777777";
|
|
107
|
+
mkdirSync(join(accountsDir, secondHouse), { recursive: true });
|
|
108
|
+
writeFileSync(join(accountsDir, secondHouse, "account.json"), JSON.stringify({ role: "house", whatsapp: {} }), "utf-8");
|
|
109
|
+
const driver = driverReturning([
|
|
110
|
+
{ eventId: "e7", accountId: HOUSE, agentChannel: "whatsapp", agentDestination: "+447504472444" },
|
|
111
|
+
]);
|
|
112
|
+
await expect(runStandingAudit(driver, [HOUSE], accountsDir)).resolves.toBeUndefined();
|
|
113
|
+
expect(auditLines().some((l) => l.includes("op=house-unresolved"))).toBe(true);
|
|
114
|
+
});
|
|
76
115
|
});
|
|
77
116
|
//# sourceMappingURL=standing-audit.test.js.map
|