@rubytech/create-maxy-code 0.1.498 → 0.1.499

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/package.json +1 -1
  2. package/payload/platform/lib/active-rules/dist/index.d.ts +3 -7
  3. package/payload/platform/lib/active-rules/dist/index.d.ts.map +1 -1
  4. package/payload/platform/lib/active-rules/dist/index.js +20 -52
  5. package/payload/platform/lib/active-rules/dist/index.js.map +1 -1
  6. package/payload/platform/lib/active-rules/src/index.test.ts +35 -69
  7. package/payload/platform/lib/active-rules/src/index.ts +17 -58
  8. package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +3 -3
  9. package/payload/platform/plugins/docs/references/internals.md +2 -2
  10. package/payload/platform/plugins/email/mcp/dist/__tests__/compose-signature.test.d.ts +2 -0
  11. package/payload/platform/plugins/email/mcp/dist/__tests__/compose-signature.test.d.ts.map +1 -0
  12. package/payload/platform/plugins/email/mcp/dist/__tests__/compose-signature.test.js +53 -0
  13. package/payload/platform/plugins/email/mcp/dist/__tests__/compose-signature.test.js.map +1 -0
  14. package/payload/platform/plugins/email/mcp/dist/__tests__/signature.test.d.ts +2 -0
  15. package/payload/platform/plugins/email/mcp/dist/__tests__/signature.test.d.ts.map +1 -0
  16. package/payload/platform/plugins/email/mcp/dist/__tests__/signature.test.js +50 -0
  17. package/payload/platform/plugins/email/mcp/dist/__tests__/signature.test.js.map +1 -0
  18. package/payload/platform/plugins/email/mcp/dist/lib/compose.d.ts.map +1 -1
  19. package/payload/platform/plugins/email/mcp/dist/lib/compose.js +15 -2
  20. package/payload/platform/plugins/email/mcp/dist/lib/compose.js.map +1 -1
  21. package/payload/platform/plugins/email/mcp/dist/lib/signature.d.ts +29 -0
  22. package/payload/platform/plugins/email/mcp/dist/lib/signature.d.ts.map +1 -0
  23. package/payload/platform/plugins/email/mcp/dist/lib/signature.js +55 -0
  24. package/payload/platform/plugins/email/mcp/dist/lib/signature.js.map +1 -0
  25. package/payload/platform/plugins/email/references/email-reference.md +10 -0
  26. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/profile-read-active-rules.test.js +10 -9
  27. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/profile-read-active-rules.test.js.map +1 -1
  28. package/payload/platform/plugins/memory/skills/conversational-memory/SKILL.md +2 -0
  29. package/payload/platform/plugins/memory/skills/preference-audit/SKILL.md +16 -0
  30. package/payload/platform/services/claude-session-manager/dist/preference-reconciliation-audit.d.ts +19 -19
  31. package/payload/platform/services/claude-session-manager/dist/preference-reconciliation-audit.d.ts.map +1 -1
  32. package/payload/platform/services/claude-session-manager/dist/preference-reconciliation-audit.js +28 -34
  33. package/payload/platform/services/claude-session-manager/dist/preference-reconciliation-audit.js.map +1 -1
  34. package/payload/server/public/assets/{chat-qtOiJX-Y.js → chat-MvnxPJZ5.js} +1 -1
  35. package/payload/server/public/assets/{operator-icJwMkZF.js → operator-DCNWUKP_.js} +1 -1
  36. package/payload/server/public/assets/{page-iG57nKOg.js → page-C4TrTU6r.js} +1 -1
  37. package/payload/server/public/assets/{public-CkOumzpf.js → public-DXJXQPwe.js} +1 -1
  38. package/payload/server/public/chat.html +2 -2
  39. package/payload/server/public/operator.html +2 -2
  40. package/payload/server/public/public.html +2 -2
  41. package/payload/server/server.js +47 -57
@@ -0,0 +1 @@
1
+ {"version":3,"file":"signature.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/signature.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,50 @@
1
+ import { afterEach, beforeEach, describe, expect, it } from 'vitest';
2
+ import { mkdtempSync, writeFileSync, rmSync, realpathSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ import { tmpdir } from 'node:os';
5
+ import { loadSignature, applySignature } from '../lib/signature.js';
6
+ describe('Task 1952 — email signature load + apply', () => {
7
+ let dir = '';
8
+ beforeEach(() => { dir = realpathSync(mkdtempSync(join(tmpdir(), 'sig-'))); });
9
+ afterEach(() => { if (dir)
10
+ rmSync(dir, { recursive: true, force: true }); });
11
+ it('loadSignature returns null fields when no files exist', () => {
12
+ expect(loadSignature(dir)).toEqual({ text: null, html: null, font: null });
13
+ });
14
+ it('loadSignature returns null fields when accountDir is undefined', () => {
15
+ expect(loadSignature(undefined)).toEqual({ text: null, html: null, font: null });
16
+ });
17
+ it('loadSignature reads text and html verbatim and trims font', () => {
18
+ writeFileSync(join(dir, 'email-signature.txt'), 'Regards,\nDale');
19
+ writeFileSync(join(dir, 'email-signature.html'), '<p>Regards,<br>Dale</p>');
20
+ writeFileSync(join(dir, 'email-signature.font'), 'Georgia, serif\n');
21
+ expect(loadSignature(dir)).toEqual({
22
+ text: 'Regards,\nDale',
23
+ html: '<p>Regards,<br>Dale</p>',
24
+ font: 'Georgia, serif',
25
+ });
26
+ });
27
+ it('applySignature appends text to body byte-for-byte', () => {
28
+ const out = applySignature({ body: 'Hello.' }, { text: 'Regards,\nDale', html: null, font: null });
29
+ expect(out.body).toBe('Hello.\n\nRegards,\nDale');
30
+ expect(out.html).toBeUndefined();
31
+ expect(out.applied).toBe(true);
32
+ });
33
+ it('applySignature appends html sig then wraps in font when html present', () => {
34
+ const out = applySignature({ body: 'Hello.', html: '<p>Hello.</p>' }, { text: null, html: '<p>Regards</p>', font: 'Georgia, serif' });
35
+ expect(out.html).toBe('<div style="font-family: Georgia, serif"><p>Hello.</p><p>Regards</p></div>');
36
+ expect(out.applied).toBe(true);
37
+ });
38
+ it('applySignature leaves html unset when caller gave no html', () => {
39
+ const out = applySignature({ body: 'Hi' }, { text: null, html: '<p>Sig</p>', font: 'Arial' });
40
+ expect(out.html).toBeUndefined();
41
+ expect(out.applied).toBe(false);
42
+ });
43
+ it('applySignature applied=false when no signature files present', () => {
44
+ const out = applySignature({ body: 'Hi', html: '<p>Hi</p>' }, { text: null, html: null, font: null });
45
+ expect(out.body).toBe('Hi');
46
+ expect(out.html).toBe('<p>Hi</p>');
47
+ expect(out.applied).toBe(false);
48
+ });
49
+ });
50
+ //# sourceMappingURL=signature.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"signature.test.js","sourceRoot":"","sources":["../../src/__tests__/signature.test.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"compose.d.ts","sourceRoot":"","sources":["../../src/lib/compose.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAE,gBAAgB,EAAkC,MAAM,WAAW,CAAC;AAI7E,6FAA6F;AAC7F,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,EAAE,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,mEAAmE;IACnE,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,eAAe,EAAE,MAAM,CAAC;IACxB,sDAAsD;IACtD,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC;;;OAGG;IACH,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE,KAAK,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACxD;AAED;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,UAAU,CAAC,OAAO,gBAAgB,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,CASxF;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,YAAY,CAChC,MAAM,EAAE,WAAW,EACnB,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,kBAAkB,GACzB,OAAO,CAAC,aAAa,CAAC,CAgDxB"}
1
+ {"version":3,"file":"compose.d.ts","sourceRoot":"","sources":["../../src/lib/compose.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAE,gBAAgB,EAAkC,MAAM,WAAW,CAAC;AAK7E,6FAA6F;AAC7F,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,EAAE,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,mEAAmE;IACnE,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,eAAe,EAAE,MAAM,CAAC;IACxB,sDAAsD;IACtD,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC;;;OAGG;IACH,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE,KAAK,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACxD;AAED;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,UAAU,CAAC,OAAO,gBAAgB,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,CASxF;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,YAAY,CAChC,MAAM,EAAE,WAAW,EACnB,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,kBAAkB,GACzB,OAAO,CAAC,aAAa,CAAC,CA8DxB"}
@@ -1,8 +1,10 @@
1
1
  import { randomUUID } from "node:crypto";
2
+ import { join } from "node:path";
2
3
  import MailComposer from "nodemailer/lib/mail-composer/index.js";
3
4
  import { buildMailOptions, replySubject } from "./smtp.js";
4
5
  import { fetchEnvelopeByMessageId } from "./imap.js";
5
6
  import { resolveOutboundAttachments, accountDirFor } from "./attachment-resolve.js";
7
+ import { loadSignature, applySignature } from "./signature.js";
6
8
  /**
7
9
  * Compile the shared mail options into raw RFC822 bytes via nodemailer's own composer.
8
10
  *
@@ -64,11 +66,22 @@ export async function composeDraft(config, password, accountId, params) {
64
66
  // value is the source of truth an edit re-resolves the draft by.
65
67
  const host = (config.agentAddress || config.email).split("@")[1] || "localhost";
66
68
  const draftMessageId = `<${randomUUID()}@${host}>`;
69
+ // Apply the account's saved signature deterministically, before the MIME is
70
+ // built, so every draft carries it without the model adding signature text.
71
+ // Resolve the account dir the same way the attachment path does: the param
72
+ // accountId (the sending account) is authoritative, ACCOUNT_DIR is the
73
+ // fallback, so the signature reads from the same account root the attachments
74
+ // are validated against.
75
+ const sigDir = accountDirFor(accountId) || process.env.ACCOUNT_DIR;
76
+ const sig = loadSignature(sigDir);
77
+ const signed = applySignature({ body: params.body, html: params.html }, sig);
78
+ const assetNote = sigDir ? join(sigDir, "email-signature") : "none";
79
+ console.error(`[email-draft] op=signature accountId=${accountId.slice(0, 8)} applied=${signed.applied} asset=${assetNote}`);
67
80
  const sendOptions = {
68
81
  to: params.to,
69
82
  subject: resolvedSubject,
70
- body: params.body,
71
- html: params.html,
83
+ body: signed.body,
84
+ html: signed.html,
72
85
  cc: params.cc,
73
86
  bcc: params.bcc,
74
87
  attachments,
@@ -1 +1 @@
1
- {"version":3,"file":"compose.js","sourceRoot":"","sources":["../../src/lib/compose.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"compose.js","sourceRoot":"","sources":["../../src/lib/compose.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ export interface Signature {
2
+ text: string | null;
3
+ html: string | null;
4
+ font: string | null;
5
+ }
6
+ /**
7
+ * Load an account's saved email signature from the fixed account-dir convention.
8
+ * `.txt` and `.html` are returned verbatim (appended byte-for-byte). `.font` is a
9
+ * single CSS font-family value, trimmed of surrounding whitespace. A missing file
10
+ * is null, never an error: a draft still composes without a signature.
11
+ */
12
+ export declare function loadSignature(accountDir: string | undefined): Signature;
13
+ /**
14
+ * Append the signature to a draft's body/html deterministically, with no model
15
+ * judgement. The plain-text signature is appended to body whenever present. The
16
+ * html signature and the font wrap apply only when the caller supplied html:
17
+ * font is HTML-only and has no plain-text analogue. `applied` is true when at
18
+ * least one signature part (text or html) was appended; the font wrap alone does
19
+ * not set it.
20
+ */
21
+ export declare function applySignature(input: {
22
+ body: string;
23
+ html?: string;
24
+ }, sig: Signature): {
25
+ body: string;
26
+ html?: string;
27
+ applied: boolean;
28
+ };
29
+ //# sourceMappingURL=signature.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"signature.d.ts","sourceRoot":"","sources":["../../src/lib/signature.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CACrB;AAUD;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS,CASvE;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAC5B,KAAK,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,EACtC,GAAG,EAAE,SAAS,GACb;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CAkBnD"}
@@ -0,0 +1,55 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ function readIf(path) {
4
+ try {
5
+ return readFileSync(path, "utf8");
6
+ }
7
+ catch {
8
+ return null;
9
+ }
10
+ }
11
+ /**
12
+ * Load an account's saved email signature from the fixed account-dir convention.
13
+ * `.txt` and `.html` are returned verbatim (appended byte-for-byte). `.font` is a
14
+ * single CSS font-family value, trimmed of surrounding whitespace. A missing file
15
+ * is null, never an error: a draft still composes without a signature.
16
+ */
17
+ export function loadSignature(accountDir) {
18
+ if (!accountDir)
19
+ return { text: null, html: null, font: null };
20
+ const base = join(accountDir, "email-signature");
21
+ const font = readIf(`${base}.font`);
22
+ return {
23
+ text: readIf(`${base}.txt`),
24
+ html: readIf(`${base}.html`),
25
+ font: font ? font.trim() : null,
26
+ };
27
+ }
28
+ /**
29
+ * Append the signature to a draft's body/html deterministically, with no model
30
+ * judgement. The plain-text signature is appended to body whenever present. The
31
+ * html signature and the font wrap apply only when the caller supplied html:
32
+ * font is HTML-only and has no plain-text analogue. `applied` is true when at
33
+ * least one signature part (text or html) was appended; the font wrap alone does
34
+ * not set it.
35
+ */
36
+ export function applySignature(input, sig) {
37
+ let body = input.body;
38
+ let html = input.html;
39
+ let applied = false;
40
+ if (sig.text !== null) {
41
+ body = `${body}\n\n${sig.text}`;
42
+ applied = true;
43
+ }
44
+ if (html !== undefined) {
45
+ if (sig.html !== null) {
46
+ html = `${html}${sig.html}`;
47
+ applied = true;
48
+ }
49
+ if (sig.font !== null) {
50
+ html = `<div style="font-family: ${sig.font}">${html}</div>`;
51
+ }
52
+ }
53
+ return { body, html, applied };
54
+ }
55
+ //# sourceMappingURL=signature.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"signature.js","sourceRoot":"","sources":["../../src/lib/signature.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAQjC,SAAS,MAAM,CAAC,IAAY;IAC1B,IAAI,CAAC;QACH,OAAO,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IACpC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,aAAa,CAAC,UAA8B;IAC1D,IAAI,CAAC,UAAU;QAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;IAC/D,MAAM,IAAI,GAAG,IAAI,CAAC,UAAU,EAAE,iBAAiB,CAAC,CAAC;IACjD,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,IAAI,OAAO,CAAC,CAAC;IACpC,OAAO;QACL,IAAI,EAAE,MAAM,CAAC,GAAG,IAAI,MAAM,CAAC;QAC3B,IAAI,EAAE,MAAM,CAAC,GAAG,IAAI,OAAO,CAAC;QAC5B,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI;KAChC,CAAC;AACJ,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,cAAc,CAC5B,KAAsC,EACtC,GAAc;IAEd,IAAI,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC;IACtB,IAAI,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC;IACtB,IAAI,OAAO,GAAG,KAAK,CAAC;IACpB,IAAI,GAAG,CAAC,IAAI,KAAK,IAAI,EAAE,CAAC;QACtB,IAAI,GAAG,GAAG,IAAI,OAAO,GAAG,CAAC,IAAI,EAAE,CAAC;QAChC,OAAO,GAAG,IAAI,CAAC;IACjB,CAAC;IACD,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QACvB,IAAI,GAAG,CAAC,IAAI,KAAK,IAAI,EAAE,CAAC;YACtB,IAAI,GAAG,GAAG,IAAI,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC;YAC5B,OAAO,GAAG,IAAI,CAAC;QACjB,CAAC;QACD,IAAI,GAAG,CAAC,IAAI,KAAK,IAAI,EAAE,CAAC;YACtB,IAAI,GAAG,4BAA4B,GAAG,CAAC,IAAI,KAAK,IAAI,QAAQ,CAAC;QAC/D,CAAC;IACH,CAAC;IACD,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;AACjC,CAAC"}
@@ -169,6 +169,16 @@ To get this resilience, pass the `draftMessageId` from the prior confirmation ba
169
169
 
170
170
  If neither the UID nor the Message-ID resolves a live draft, the prior draft is genuinely gone. The edit does not fail — it saves the replacement as a fresh draft and says so (`previous draft no longer present; saved a fresh draft`). The atomicity rule still holds: the replacement is always written before any old copy is removed, so a draft is never left with zero copies.
171
171
 
172
+ ### Signature
173
+
174
+ `email-draft` and `email-draft-edit` apply the account's saved signature to every draft deterministically, so the model never adds signature text itself. The signature lives at a fixed path under the account directory:
175
+
176
+ - `email-signature.txt` is the plain-text signature, appended to the message body.
177
+ - `email-signature.html` is the HTML signature block, appended to the message html.
178
+ - `email-signature.font` is an optional single-line CSS font-family; when present the html part is wrapped in one `font-family` div.
179
+
180
+ The plain-text signature is appended whenever it exists. The html signature and the font wrap apply only when the caller supplied html, because font has no plain-text analogue. The caller passes body and html without the signature, and the tool appends it. Each draft compose logs `[email-draft] op=signature accountId=<id8> applied=<bool> asset=<path>`; `applied=false` on a turn that produced a draft is the failure signal, meaning the asset was missing or the account directory did not resolve.
181
+
172
182
  ### Transient connection drops
173
183
 
174
184
  A single transient transport drop (socket timeout, unexpected close, connection-not-available) during a draft write is retried once on a fresh connection. Authentication failures and other permanent errors are not retried — they surface immediately.
@@ -1,8 +1,7 @@
1
- // Task 1486 — profile-read exposes the account's active, de-conflicted, bounded
2
- // rule-set (the same set injection uses). Near-duplicate subjects collapse to
3
- // one; the count never exceeds the injection cap.
1
+ // Task 1486 / 1951 — profile-read exposes the account's active rule-set (the
2
+ // same set injection uses). Injection is uncapped and never dedups: every stored
3
+ // active owner preference is returned verbatim, in confidence/observedAt order.
4
4
  import { describe, it, expect, beforeEach, vi } from "vitest";
5
- import { INJECTION_RULE_CAP } from "../../../../../../lib/active-rules/dist/index.js";
6
5
  const mockSession = {
7
6
  run: vi.fn(),
8
7
  close: vi.fn().mockResolvedValue(undefined),
@@ -29,7 +28,9 @@ function wire(activeCandidates) {
29
28
  }
30
29
  describe("profile-read — activeRules", () => {
31
30
  beforeEach(() => wire([]));
32
- it("collapses same-subject candidates and returns activeRules", async () => {
31
+ it("returns same-subject candidates verbatim, without dedup", async () => {
32
+ // 'a' and 'b' are near-duplicates that the old read-side dedup would have
33
+ // folded; injection no longer dedups, so BOTH are returned.
33
34
  wire([
34
35
  { preferenceId: "a", category: "content", key: "header_a", value: "use the standard header", confidence: 0.8, observedAt: "2026-07-01T00:00:00Z", embedding: [1, 0, 0, 0] },
35
36
  { preferenceId: "b", category: "content", key: "header_b", value: "standard header restated", confidence: 0.6, observedAt: "2026-07-02T00:00:00Z", embedding: [0.99, 0.01, 0, 0] },
@@ -37,9 +38,9 @@ describe("profile-read — activeRules", () => {
37
38
  ]);
38
39
  const res = await profileRead({ accountId: "acct-1", userId: "user-1" });
39
40
  const ids = res.activeRules.map((r) => r.preferenceId);
40
- expect(ids).toEqual(["a", "c"]); // b folds into a
41
+ expect(ids).toEqual(["a", "b", "c"]);
41
42
  });
42
- it("never exceeds the injection cap", async () => {
43
+ it("returns the uncapped set — all 20 candidates, no truncation", async () => {
43
44
  const many = Array.from({ length: 20 }, (_, i) => ({
44
45
  preferenceId: `p${i}`, category: "content", key: `k${i}`, value: `rule ${i}`,
45
46
  confidence: 1 - i * 0.01, observedAt: "2026-07-01T00:00:00Z",
@@ -47,8 +48,8 @@ describe("profile-read — activeRules", () => {
47
48
  }));
48
49
  wire(many);
49
50
  const res = await profileRead({ accountId: "acct-1", userId: "user-1" });
50
- const rules = res.activeRules;
51
- expect(rules.length).toBeLessThanOrEqual(INJECTION_RULE_CAP);
51
+ const ids = res.activeRules.map((r) => r.preferenceId);
52
+ expect(ids).toEqual(many.map((m) => m.preferenceId));
52
53
  });
53
54
  });
54
55
  //# sourceMappingURL=profile-read-active-rules.test.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"profile-read-active-rules.test.js","sourceRoot":"","sources":["../../../src/tools/__tests__/profile-read-active-rules.test.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,8EAA8E;AAC9E,kDAAkD;AAElD,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAC9D,OAAO,EAAE,kBAAkB,EAAE,MAAM,kDAAkD,CAAC;AAEtF,MAAM,WAAW,GAAG;IAClB,GAAG,EAAE,EAAE,CAAC,EAAE,EAAE;IACZ,KAAK,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,iBAAiB,CAAC,SAAS,CAAC;CAC5C,CAAC;AAEF,EAAE,CAAC,IAAI,CAAC,oBAAoB,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,UAAU,EAAE,GAAG,EAAE,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC;AAEzE,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAEjD,MAAM,GAAG,GAAG,CAAC,GAA4B,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,CAAS,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;AAE/E,MAAM,MAAM,GAAG,EAAE,UAAU,EAAE,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,CAAC;AAEzF,SAAS,IAAI,CAAC,gBAAgD;IAC5D,WAAW,CAAC,GAAG,CAAC,SAAS,EAAE,CAAC;IAC5B,WAAW,CAAC,GAAG,CAAC,kBAAkB,CAAC,KAAK,EAAE,CAAS,EAAE,EAAE;QACrD,iEAAiE;QACjE,sEAAsE;QACtE,IAAI,iBAAiB,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;YAC9B,OAAO,EAAE,OAAO,EAAE,CAAC,GAAG,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,CAAC,EAAE,CAAC;QAClD,CAAC;QACD,4EAA4E;QAC5E,IAAI,CAAC,CAAC,QAAQ,CAAC,6BAA6B,CAAC,EAAE,CAAC;YAC9C,OAAO,EAAE,OAAO,EAAE,gBAAgB,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;QAChD,CAAC;QACD,sDAAsD;QACtD,OAAO,EAAE,OAAO,EAAE,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC;IACrE,CAAC,CAAC,CAAC;AACL,CAAC;AAED,QAAQ,CAAC,4BAA4B,EAAE,GAAG,EAAE;IAC1C,UAAU,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;IAE3B,EAAE,CAAC,2DAA2D,EAAE,KAAK,IAAI,EAAE;QACzE,IAAI,CAAC;YACH,EAAE,YAAY,EAAE,GAAG,EAAE,QAAQ,EAAE,SAAS,EAAE,GAAG,EAAE,UAAU,EAAE,KAAK,EAAE,yBAAyB,EAAE,UAAU,EAAE,GAAG,EAAE,UAAU,EAAE,sBAAsB,EAAE,SAAS,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE;YAC3K,EAAE,YAAY,EAAE,GAAG,EAAE,QAAQ,EAAE,SAAS,EAAE,GAAG,EAAE,UAAU,EAAE,KAAK,EAAE,0BAA0B,EAAE,UAAU,EAAE,GAAG,EAAE,UAAU,EAAE,sBAAsB,EAAE,SAAS,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE;YAClL,EAAE,YAAY,EAAE,GAAG,EAAE,QAAQ,EAAE,eAAe,EAAE,GAAG,EAAE,SAAS,EAAE,KAAK,EAAE,gBAAgB,EAAE,UAAU,EAAE,GAAG,EAAE,UAAU,EAAE,sBAAsB,EAAE,SAAS,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE;SACxK,CAAC,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,WAAW,CAAC,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,CAAC;QACzE,MAAM,GAAG,GAAI,GAAwD,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC;QAC7G,MAAM,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,iBAAiB;IACpD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,iCAAiC,EAAE,KAAK,IAAI,EAAE;QAC/C,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;YACjD,YAAY,EAAE,IAAI,CAAC,EAAE,EAAE,QAAQ,EAAE,SAAS,EAAE,GAAG,EAAE,IAAI,CAAC,EAAE,EAAE,KAAK,EAAE,QAAQ,CAAC,EAAE;YAC5E,UAAU,EAAE,CAAC,GAAG,CAAC,GAAG,IAAI,EAAE,UAAU,EAAE,sBAAsB;YAC5D,SAAS,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;SAC5C,CAAC,CAAC,CAAC;QACJ,IAAI,CAAC,IAAI,CAAC,CAAC;QACX,MAAM,GAAG,GAAG,MAAM,WAAW,CAAC,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,CAAC;QACzE,MAAM,KAAK,GAAI,GAAkC,CAAC,WAAW,CAAC;QAC9D,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,mBAAmB,CAAC,kBAAkB,CAAC,CAAC;IAC/D,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
1
+ {"version":3,"file":"profile-read-active-rules.test.js","sourceRoot":"","sources":["../../../src/tools/__tests__/profile-read-active-rules.test.ts"],"names":[],"mappings":"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"}
@@ -13,6 +13,8 @@ Memory accumulates from conversation — never from questionnaires, quizzes, or
13
13
 
14
14
  Every active preference is prepended to the agent's context on **every single turn**, for the life of the account. A verbose preference is not a one-time cost. It is paid again on every future turn, and it crowds out the live conversation. Concision here is not a matter of style. It is a hard, non-negotiable requirement: a preference that is not precise and concise is a defect.
15
15
 
16
+ Injection surfaces every stored active preference verbatim. Nothing de-duplicates or trims at injection time, so a redundant or bloated preference you store is injected on every future turn exactly as written. Write-time discipline is the only guard: keep each value minimal, and fold an overlapping preference into the existing one rather than storing a second.
17
+
16
18
  Each `value` is one precise imperative: the rule itself and nothing else. Strip everything that is not the rule.
17
19
 
18
20
  - No provenance or history ("Set 09/07/2026 after X went wrong", "SUPERSEDES the old rule", "Confirmed by Dale").
@@ -30,6 +30,16 @@ A stored preference is drifted when its substance maps to an installed capabilit
30
30
 
31
31
  Do not treat this as a fixed keyword list. Check each preference's substance against the capabilities this install actually has. When one maps, restate the preference to route there, or convert it to a real job and retire it.
32
32
 
33
+ ## Invariant versus soft classification
34
+
35
+ Beyond the four moves, classify each preference as invariant or soft.
36
+
37
+ A preference is invariant-class when its value describes a document layout, a naming convention, a template, or a fixed numeric or style default (a font, a header, a signature block, a numbering scheme, a filing structure). An invariant does not belong in the injected store as prose the model must read and obey. It belongs in the skill that owns the document or process, applied by that skill's template or tool step. For each invariant, name the candidate owning skill: quotes, works-orders, valuations, invoices, filing, or email.
38
+
39
+ A preference is soft when it is a genuine standing choice with no owning skill to enforce it: a contact, a timezone, a one-invoice-per-message rule, a skip-size numeral, a no-proactive-reminders rule. Soft preferences stay in the injected store.
40
+
41
+ For every invariant, the plan names the owning skill and whether that skill already applies it deterministically or a migration is still pending. A preference whose owning skill you cannot name is reported as unassigned rather than silently left in place. The email signature is the worked first instance: `email-draft` and `email-draft-edit` apply the account's saved signature asset deterministically, so its preference is retired once the asset is in place.
42
+
33
43
  ## Dry run first, always
34
44
 
35
45
  The audit never mutates before the operator sees the plan. In order:
@@ -48,3 +58,9 @@ Close every audit with one line, whether it was a dry run or applied:
48
58
  `[preference-audit] account=<id> reworded=<n> merged=<n> retired=<n> restated-to-capability=<n>`
49
59
 
50
60
  Count what the plan proposes on a dry run, and what was applied on a real run.
61
+
62
+ The classification pass emits its own line on the audit's periodic tick, so an invariant-shaped preference accumulating unmigrated surfaces on its own and not only on demand:
63
+
64
+ `[preference-audit] op=classify account=<id8> invariant=<n> soft=<m> unassigned=<k>`
65
+
66
+ `unassigned>0` names a preference the audit could not place under an owning skill.
@@ -7,24 +7,24 @@ export interface AuditSessionLike {
7
7
  }
8
8
  export interface AuditFinding {
9
9
  accountId: string;
10
- /** Active (non-trashed, not-declined, above-floor) preference rows stored.
11
- * Task 1564 queryActiveRuleCandidates is owner-scoped, so this counts the
12
- * owner's rows only; duplicate piles stranded on a non-owner profile are
13
- * neither counted nor injected, so there is no injection-drift to flag. */
14
- storedActive: number;
15
- /** Rows injection would surface after semantic de-confliction. */
16
- injectedActive: number;
17
- /** storedActive − injectedActive: unreconciled same-subject duplicates. */
18
- duplicateClusters: number;
10
+ /** Active (non-trashed, not-declined, above-floor) owner preference rows
11
+ * storedthe count injection is expected to surface verbatim. Task 1564:
12
+ * owner-scoped, so rows stranded on a non-owner profile are neither counted
13
+ * nor injected. */
14
+ stored: number;
15
+ /** Rows injection actually surfaces (resolveActiveRules, the exact injection
16
+ * path). Equal to `stored` unless injection dropped rows. */
17
+ injected: number;
19
18
  }
20
- /** One drift line per flagged account. accountId truncated to 8 for readability;
21
- * the three counts are greppable fields. */
19
+ /** One reconcile line per flagged account. accountId truncated to 8 for
20
+ * readability; the two counts are greppable fields. */
22
21
  export declare function formatAuditLine(f: AuditFinding): string;
23
- /** For every account that holds active preferences, measure stored-vs-injected
24
- * divergence. Returns a finding ONLY for accounts where the two differ (i.e.
25
- * the store holds unreconciled same-subject duplicates). Measures against the
26
- * exact injection definition (queryActiveRuleCandidates + deconflictBySubject),
27
- * so the audit and injection can never disagree about what "active" means. */
22
+ /** For every account that holds active preferences, compare the stored active
23
+ * count against the count injection surfaces. Returns a finding ONLY where the
24
+ * two differ injection is meant to surface every stored active preference
25
+ * verbatim (Task 1951), so a gap is a dropped-row regression. Measures against
26
+ * the exact injection path (resolveActiveRules), so the audit and injection can
27
+ * never disagree about what "injected" means. */
28
28
  export declare function auditPreferenceReconciliation(session: AuditSessionLike): Promise<AuditFinding[]>;
29
29
  export interface PreferenceAuditDeps {
30
30
  neo4jUri: string;
@@ -32,10 +32,10 @@ export interface PreferenceAuditDeps {
32
32
  neo4jPassword: string;
33
33
  logger: (line: string) => void;
34
34
  }
35
- /** Run one audit pass and log a drift line per flagged account. Best-effort
35
+ /** Run one audit pass and log a reconcile line per flagged account. Best-effort
36
36
  * Neo4j — absent env or an unreachable/erroring read logs `unavailable` and
37
- * never throws. A clean pass logs a single `op=ok` line so the audit's own
38
- * liveness is visible. */
37
+ * never throws. A clean pass logs a single `op=reconcile drifted=0` line so the
38
+ * audit's own liveness is visible. */
39
39
  export declare function emitPreferenceAudit(deps: PreferenceAuditDeps): Promise<void>;
40
40
  export interface PreferenceAudit {
41
41
  start(): void;
@@ -1 +1 @@
1
- {"version":3,"file":"preference-reconciliation-audit.d.ts","sourceRoot":"","sources":["../src/preference-reconciliation-audit.ts"],"names":[],"mappings":"AAuBA,MAAM,WAAW,gBAAgB;IAC/B,GAAG,CACD,KAAK,EAAE,MAAM,EACb,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC/B,OAAO,CAAC;QAAE,OAAO,EAAE,KAAK,CAAC;YAAE,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;SAAE,CAAC,CAAA;KAAE,CAAC,CAAA;CAC9D;AAED,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,MAAM,CAAA;IACjB;;;gFAG4E;IAC5E,YAAY,EAAE,MAAM,CAAA;IACpB,kEAAkE;IAClE,cAAc,EAAE,MAAM,CAAA;IACtB,2EAA2E;IAC3E,iBAAiB,EAAE,MAAM,CAAA;CAC1B;AAED;6CAC6C;AAC7C,wBAAgB,eAAe,CAAC,CAAC,EAAE,YAAY,GAAG,MAAM,CAEvD;AAED;;;;+EAI+E;AAC/E,wBAAsB,6BAA6B,CACjD,OAAO,EAAE,gBAAgB,GACxB,OAAO,CAAC,YAAY,EAAE,CAAC,CAoBzB;AAED,MAAM,WAAW,mBAAmB;IAClC,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;IACjB,aAAa,EAAE,MAAM,CAAA;IACrB,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;CAC/B;AAED;;;2BAG2B;AAC3B,wBAAsB,mBAAmB,CAAC,IAAI,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC,CAoBlF;AAED,MAAM,WAAW,eAAe;IAC9B,KAAK,IAAI,IAAI,CAAA;IACb,IAAI,IAAI,IAAI,CAAA;CACb;AAED;sCACsC;AACtC,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,mBAAmB,GAAG;IAAE,UAAU,EAAE,MAAM,CAAA;CAAE,GACjD,eAAe,CAiBjB"}
1
+ {"version":3,"file":"preference-reconciliation-audit.d.ts","sourceRoot":"","sources":["../src/preference-reconciliation-audit.ts"],"names":[],"mappings":"AAkBA,MAAM,WAAW,gBAAgB;IAC/B,GAAG,CACD,KAAK,EAAE,MAAM,EACb,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC/B,OAAO,CAAC;QAAE,OAAO,EAAE,KAAK,CAAC;YAAE,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;SAAE,CAAC,CAAA;KAAE,CAAC,CAAA;CAC9D;AAED,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,MAAM,CAAA;IACjB;;;wBAGoB;IACpB,MAAM,EAAE,MAAM,CAAA;IACd;kEAC8D;IAC9D,QAAQ,EAAE,MAAM,CAAA;CACjB;AAED;wDACwD;AACxD,wBAAgB,eAAe,CAAC,CAAC,EAAE,YAAY,GAAG,MAAM,CAEvD;AAED;;;;;kDAKkD;AAClD,wBAAsB,6BAA6B,CACjD,OAAO,EAAE,gBAAgB,GACxB,OAAO,CAAC,YAAY,EAAE,CAAC,CAkBzB;AAED,MAAM,WAAW,mBAAmB;IAClC,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;IACjB,aAAa,EAAE,MAAM,CAAA;IACrB,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;CAC/B;AAED;;;uCAGuC;AACvC,wBAAsB,mBAAmB,CAAC,IAAI,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC,CAoBlF;AAED,MAAM,WAAW,eAAe;IAC9B,KAAK,IAAI,IAAI,CAAA;IACb,IAAI,IAAI,IAAI,CAAA;CACb;AAED;sCACsC;AACtC,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,mBAAmB,GAAG;IAAE,UAAU,EAAE,MAAM,CAAA;CAAE,GACjD,eAAe,CAiBjB"}
@@ -1,30 +1,26 @@
1
- // Task 1486 — standing preference-reconciliation audit. The failure this task
2
- // fixes emitted NO signal: rules piled up as unreconciled same-subject
3
- // duplicates and the only detector was the operator complaining. Write-side
4
- // reconcile collapses FUTURE writes, but a legacy store (the 24 GL Smith rows)
5
- // keeps its rephrasings until a backfill that is out of scope. So an independent
6
- // periodic read is the only signal that the store and what injection surfaces
7
- // have drifted apart.
8
- //
9
- // For each account with active preferences it compares the stored active count
10
- // against the de-conflicted count that injection would actually surface (via the
11
- // same @maxy/active-rules definition). A positive gap is unreconciled
12
- // same-subject duplicates. Best-effort by construction: a Neo4j-unreachable read
13
- // logs and returns nothing rather than crashing the manager (Neo4j may not be up
14
- // at boot). Shape mirrors rootless-client-audit.ts.
1
+ // Task 1486 / 1951 — standing preference-reconciliation audit. Injection now
2
+ // surfaces every stored active preference verbatim (Task 1951), so the invariant
3
+ // is stored == injected. This audit is the tripwire: for each account it compares
4
+ // the stored active-candidate count against the count injection actually surfaces
5
+ // (resolveActiveRules, the exact injection path). A gap means injection dropped
6
+ // rows a reintroduced cap, filter, or truncation caught here without
7
+ // reproducing a turn. Best-effort by construction: a Neo4j-unreachable read logs
8
+ // and returns nothing rather than crashing the manager (Neo4j may not be up at
9
+ // boot). Shape mirrors rootless-client-audit.ts.
15
10
  import neo4j from 'neo4j-driver';
16
- import { queryActiveRuleCandidates, deconflictBySubject, } from '../../../lib/active-rules/dist/index.js';
17
- const TAG = '[preference-audit]';
18
- /** One drift line per flagged account. accountId truncated to 8 for readability;
19
- * the three counts are greppable fields. */
11
+ import { queryActiveRuleCandidates, resolveActiveRules, } from '../../../lib/active-rules/dist/index.js';
12
+ const TAG = '[preference-inject]';
13
+ /** One reconcile line per flagged account. accountId truncated to 8 for
14
+ * readability; the two counts are greppable fields. */
20
15
  export function formatAuditLine(f) {
21
- return `${TAG} op=drift accountId=${f.accountId.slice(0, 8)} storedActive=${f.storedActive} injectedActive=${f.injectedActive} duplicateClusters=${f.duplicateClusters}`;
16
+ return `${TAG} op=reconcile accountId=${f.accountId.slice(0, 8)} stored=${f.stored} injected=${f.injected}`;
22
17
  }
23
- /** For every account that holds active preferences, measure stored-vs-injected
24
- * divergence. Returns a finding ONLY for accounts where the two differ (i.e.
25
- * the store holds unreconciled same-subject duplicates). Measures against the
26
- * exact injection definition (queryActiveRuleCandidates + deconflictBySubject),
27
- * so the audit and injection can never disagree about what "active" means. */
18
+ /** For every account that holds active preferences, compare the stored active
19
+ * count against the count injection surfaces. Returns a finding ONLY where the
20
+ * two differ injection is meant to surface every stored active preference
21
+ * verbatim (Task 1951), so a gap is a dropped-row regression. Measures against
22
+ * the exact injection path (resolveActiveRules), so the audit and injection can
23
+ * never disagree about what "injected" means. */
28
24
  export async function auditPreferenceReconciliation(session) {
29
25
  const accountsRes = await session.run(`MATCH (pref:Preference {scope: 'admin'})
30
26
  WHERE NOT pref:Trashed AND pref.deletedAt IS NULL
@@ -33,20 +29,18 @@ export async function auditPreferenceReconciliation(session) {
33
29
  const findings = [];
34
30
  for (const rec of accountsRes.records) {
35
31
  const accountId = String(rec.get('accountId'));
36
- const candidates = await queryActiveRuleCandidates(session, accountId);
37
- const injectedActive = deconflictBySubject(candidates).length;
38
- const storedActive = candidates.length;
39
- const duplicateClusters = storedActive - injectedActive;
40
- if (duplicateClusters > 0) {
41
- findings.push({ accountId, storedActive, injectedActive, duplicateClusters });
32
+ const stored = (await queryActiveRuleCandidates(session, accountId)).length;
33
+ const injected = (await resolveActiveRules(session, accountId)).rules.length;
34
+ if (stored !== injected) {
35
+ findings.push({ accountId, stored, injected });
42
36
  }
43
37
  }
44
38
  return findings;
45
39
  }
46
- /** Run one audit pass and log a drift line per flagged account. Best-effort
40
+ /** Run one audit pass and log a reconcile line per flagged account. Best-effort
47
41
  * Neo4j — absent env or an unreachable/erroring read logs `unavailable` and
48
- * never throws. A clean pass logs a single `op=ok` line so the audit's own
49
- * liveness is visible. */
42
+ * never throws. A clean pass logs a single `op=reconcile drifted=0` line so the
43
+ * audit's own liveness is visible. */
50
44
  export async function emitPreferenceAudit(deps) {
51
45
  if (!deps.neo4jUri || !deps.neo4jPassword) {
52
46
  deps.logger(`${TAG} op=unavailable reason=no-neo4j-env`);
@@ -57,7 +51,7 @@ export async function emitPreferenceAudit(deps) {
57
51
  try {
58
52
  const findings = await auditPreferenceReconciliation(session);
59
53
  if (findings.length === 0) {
60
- deps.logger(`${TAG} op=ok drifted=0`);
54
+ deps.logger(`${TAG} op=reconcile drifted=0`);
61
55
  return;
62
56
  }
63
57
  for (const f of findings)
@@ -1 +1 @@
1
- {"version":3,"file":"preference-reconciliation-audit.js","sourceRoot":"","sources":["../src/preference-reconciliation-audit.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"preference-reconciliation-audit.js","sourceRoot":"","sources":["../src/preference-reconciliation-audit.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- import{B as e,U as t}from"./useSubAccountSwitcher-Bc7XHr0L.js";import"./useVoiceRecorder-Cu6zvuFx.js";import"./AdminShell-BFVVkWFC.js";import{n}from"./page-iG57nKOg.js";import"./useCopyFeedback-DHCtz8qX.js";var r=t(),i=e();(0,r.createRoot)(document.getElementById(`root`)).render((0,i.jsx)(n,{}));
1
+ import{B as e,U as t}from"./useSubAccountSwitcher-Bc7XHr0L.js";import"./useVoiceRecorder-Cu6zvuFx.js";import"./AdminShell-BFVVkWFC.js";import{n}from"./page-C4TrTU6r.js";import"./useCopyFeedback-DHCtz8qX.js";var r=t(),i=e();(0,r.createRoot)(document.getElementById(`root`)).render((0,i.jsx)(n,{}));
@@ -1 +1 @@
1
- import{o as e}from"./chunk-fQC6bEKA.js";import{B as t,G as n,U as r}from"./useSubAccountSwitcher-Bc7XHr0L.js";import{n as i,t as a}from"./AdminLoginScreens-Ce5aBBWF.js";import"./useVoiceRecorder-Cu6zvuFx.js";import"./AdminShell-BFVVkWFC.js";import{t as o}from"./page-iG57nKOg.js";import"./useCopyFeedback-DHCtz8qX.js";var s=r(),c=e(n(),1),l=t();function u(){let e=i(`operator`),t=(0,c.useRef)(null);return(0,c.useEffect)(()=>{e.appState!==`chat`&&t.current!==e.appState&&(t.current=e.appState,console.info(`[operator-ui] op=auth-gate state=${e.appState}`))},[e.appState]),e.appState===`chat`?(0,l.jsx)(o,{variant:`operator`,forceCanonicalSession:window.location.pathname!==`/chat`}):(0,l.jsx)(a,{auth:e})}(0,s.createRoot)(document.getElementById(`root`)).render((0,l.jsx)(u,{}));
1
+ import{o as e}from"./chunk-fQC6bEKA.js";import{B as t,G as n,U as r}from"./useSubAccountSwitcher-Bc7XHr0L.js";import{n as i,t as a}from"./AdminLoginScreens-Ce5aBBWF.js";import"./useVoiceRecorder-Cu6zvuFx.js";import"./AdminShell-BFVVkWFC.js";import{t as o}from"./page-C4TrTU6r.js";import"./useCopyFeedback-DHCtz8qX.js";var s=r(),c=e(n(),1),l=t();function u(){let e=i(`operator`),t=(0,c.useRef)(null);return(0,c.useEffect)(()=>{e.appState!==`chat`&&t.current!==e.appState&&(t.current=e.appState,console.info(`[operator-ui] op=auth-gate state=${e.appState}`))},[e.appState]),e.appState===`chat`?(0,l.jsx)(o,{variant:`operator`,forceCanonicalSession:window.location.pathname!==`/chat`}):(0,l.jsx)(a,{auth:e})}(0,s.createRoot)(document.getElementById(`root`)).render((0,l.jsx)(u,{}));