@snoai/memory 1.2.3 → 1.2.4

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 (142) hide show
  1. package/README.md +1 -1
  2. package/dist/{access-tracker-BNqlgChq.d.ts → access-tracker-D20cgP0X.d.ts} +3 -3
  3. package/dist/{adapter-J6IQ0rvr.d.ts → adapter-Dx92oSuo.d.ts} +2 -2
  4. package/dist/{agent-llm-port-5oXgBpzJ.d.ts → agent-llm-port-DY2290EK.d.ts} +2 -0
  5. package/dist/{atomic-memory-extraction-By0axKHc.js → atomic-memory-extraction-BWYEdBhl.js} +4 -4
  6. package/dist/{atomic-memory-extraction-BT8lFE5t.d.ts → atomic-memory-extraction-Dz-fe3h0.d.ts} +4 -4
  7. package/dist/{b-profile-section-dictionary-provider-DGtF4dB7.d.ts → b-profile-section-dictionary-provider-D-VJRSOs.d.ts} +1 -1
  8. package/dist/{capture-policy-detector-CN3vfdNU.d.ts → capture-policy-detector-D7rPVLFp.d.ts} +1 -1
  9. package/dist/client.d.ts +1 -1
  10. package/dist/{config-CyH1VuJc.js → config-D3Hc1__G.js} +60 -1
  11. package/dist/{derived-suppression-cache-BZdBZSdK.d.ts → derived-suppression-cache-BGcKAg2p.d.ts} +1 -1
  12. package/dist/diagnostic-encoder.js +1 -1
  13. package/dist/{embedding-provider-client-Q_hJonK9.js → embedding-provider-client-40tpzKou.js} +5 -1
  14. package/dist/{index-DtqgCT8u.d.ts → index-CgqHi0Rw.d.ts} +2 -2
  15. package/dist/index.d.ts +10 -10
  16. package/dist/index.js +4 -4
  17. package/dist/internal/config/plugin-config-schema.d.ts +1 -1
  18. package/dist/internal/config/product-mode.d.ts +1 -1
  19. package/dist/internal/config/settings.d.ts +1 -1
  20. package/dist/internal/config/skin-defaults.d.ts +1 -1
  21. package/dist/internal/contract/profile.d.ts +1 -1
  22. package/dist/internal/contract/routes.d.ts +1 -1
  23. package/dist/internal/engine/bindings/memory-cli-shared.d.ts +4 -4
  24. package/dist/internal/engine/bindings/memory-forget-tool.js +1 -1
  25. package/dist/internal/engine/bindings/memory-list-tool.js +1 -1
  26. package/dist/internal/engine/bindings/memory-recall-tool.js +1 -1
  27. package/dist/internal/engine/bindings/memory-reflection-resolve-tool.js +1 -1
  28. package/dist/internal/engine/bindings/memory-stats-tool.js +1 -1
  29. package/dist/internal/engine/bindings/memory-store-tool.js +3 -3
  30. package/dist/internal/engine/bindings/memory-tool-access.d.ts +1 -1
  31. package/dist/internal/engine/bindings/memory-tool-access.js +1 -1
  32. package/dist/internal/engine/bindings/memory-tool-results.d.ts +1 -1
  33. package/dist/internal/engine/bindings/memory-tool-results.js +1 -1
  34. package/dist/internal/engine/bindings/memory-tool-schemas.d.ts +5 -5
  35. package/dist/internal/engine/bindings/memory-tool-schemas.js +1 -1
  36. package/dist/internal/engine/bindings/memory-update-tool.js +2 -2
  37. package/dist/internal/engine/bindings/sno-station-mem-ambient-learning-hook.d.ts +4 -4
  38. package/dist/internal/engine/bindings/sno-station-mem-ambient-learning-hook.js +2 -2
  39. package/dist/internal/engine/bindings/sno-station-mem-auto-recall-hook.d.ts +4 -4
  40. package/dist/internal/engine/bindings/sno-station-mem-auto-recall-hook.js +1 -1
  41. package/dist/internal/engine/bindings/sno-station-mem-insight-distill-factory.d.ts +6 -6
  42. package/dist/internal/engine/bindings/sno-station-mem-insight-distill-factory.js +2 -2
  43. package/dist/internal/engine/bindings/sno-station-mem-message-transcript.js +2 -2
  44. package/dist/internal/engine/bindings/sno-station-mem-reset-hook.d.ts +3 -3
  45. package/dist/internal/engine/bindings/sno-station-mem-runtime-mode.d.ts +1 -1
  46. package/dist/internal/engine/extraction/atomic-memory-extraction.d.ts +1 -1
  47. package/dist/internal/engine/extraction/atomic-memory-extraction.js +1 -1
  48. package/dist/internal/engine/extraction/b-profile-section-dictionary-provider.d.ts +1 -1
  49. package/dist/internal/engine/extraction/b-profile-section-rekey.d.ts +5 -5
  50. package/dist/internal/engine/extraction/capture-policy-detector.d.ts +1 -1
  51. package/dist/internal/engine/extraction/embedding-provider-client.js +1 -1
  52. package/dist/internal/engine/extraction/memory-metadata-codec.d.ts +2 -2
  53. package/dist/internal/engine/extraction/memory-temporality-classifier.d.ts +1 -1
  54. package/dist/internal/engine/extraction/memory-temporality-classifier.js +1 -1
  55. package/dist/internal/engine/observability/adapter.d.ts +1 -1
  56. package/dist/internal/engine/observability/memory-snapshot.d.ts +1 -1
  57. package/dist/internal/engine/observability/observable-embedder.d.ts +1 -1
  58. package/dist/internal/engine/observability/observable-embedder.js +1 -1
  59. package/dist/internal/engine/observability/observable-llm-client.d.ts +1 -1
  60. package/dist/internal/engine/observability/observable-memory-store.d.ts +1 -1
  61. package/dist/internal/engine/observability/observable-memory-store.js +1 -1
  62. package/dist/internal/engine/observability/observable-retriever.d.ts +1 -1
  63. package/dist/internal/engine/observability/runtime-diagnostics.d.ts +1 -1
  64. package/dist/internal/engine/observability/runtime-diagnostics.js +1 -1
  65. package/dist/internal/engine/operations/memory-tier-promoter.d.ts +1 -1
  66. package/dist/internal/engine/operations/runtime-audit-log.d.ts +2 -2
  67. package/dist/internal/engine/provider/provider-registration.d.ts +4 -4
  68. package/dist/internal/engine/provider/provider-row-renderer.d.ts +1 -1
  69. package/dist/internal/engine/reflection/reflection-command-hooks.d.ts +1 -1
  70. package/dist/internal/engine/reflection/reflection-command-hooks.js +1 -1
  71. package/dist/internal/engine/reflection/reflection-deps.d.ts +1 -1
  72. package/dist/internal/engine/reflection/reflection-embedded-generator.d.ts +1 -1
  73. package/dist/internal/engine/reflection/reflection-embedded-generator.js +1 -1
  74. package/dist/internal/engine/reflection/reflection-injection-hooks.d.ts +1 -1
  75. package/dist/internal/engine/reflection/reflection-injection-hooks.js +1 -1
  76. package/dist/internal/engine/reflection/reflection-lifecycle-hooks.d.ts +1 -1
  77. package/dist/internal/engine/reflection/reflection-lifecycle-hooks.js +1 -1
  78. package/dist/internal/engine/reflection/strategy-hook-runner.d.ts +3 -3
  79. package/dist/internal/engine/reflection/strategy-hook-runner.js +2 -2
  80. package/dist/internal/engine/retrieval/access-tracker.d.ts +1 -1
  81. package/dist/internal/engine/retrieval/retriever.d.ts +1 -1
  82. package/dist/internal/engine/shared/memory-kind-policy.d.ts +1 -1
  83. package/dist/internal/engine/shared/paths.d.ts +2 -2
  84. package/dist/internal/engine/shared/types.d.ts +2 -2
  85. package/dist/internal/engine/telemetry/observe-ledger.d.ts +1 -1
  86. package/dist/internal/model/agent-llm-port.d.ts +1 -1
  87. package/dist/internal/model/llm-client.d.ts +2 -2
  88. package/dist/internal/model/llm-client.js +1 -1
  89. package/dist/internal/model/llm-mode-routing.d.ts +1 -1
  90. package/dist/internal/store/maintenance.d.ts +2 -2
  91. package/dist/internal/store/maintenance.js +21 -7
  92. package/dist/internal/store/store.d.ts +2 -2
  93. package/dist/internal/store/store.js +1 -1
  94. package/dist/{llm-client-BhkzlTnH.js → llm-client-BOkokxti.js} +1 -0
  95. package/dist/{llm-client-PC0yQuCq.d.ts → llm-client-CtNzuoRc.d.ts} +2 -1
  96. package/dist/{log-site-catalog.generated-NI-E9zjv.js → log-site-catalog.generated-DoCYZhJF.js} +101 -101
  97. package/dist/log-site-catalog.json +1 -1
  98. package/dist/{memory-kind-policy-DnHY7l0n.d.ts → memory-kind-policy-iKHUsuwu.d.ts} +1 -1
  99. package/dist/{memory-metadata-codec-DbrRyhCg.d.ts → memory-metadata-codec-n-CFKYjn.d.ts} +1 -1
  100. package/dist/{memory-runtime-BKBFbD1E.js → memory-runtime-CVbnnCfB.js} +144 -105
  101. package/dist/{memory-snapshot-shzK-0C8.d.ts → memory-snapshot-DhtNqVPs.d.ts} +2 -2
  102. package/dist/{memory-store-base-Dx6oSqiv.d.ts → memory-store-base-QoyY3sxH.d.ts} +2 -2
  103. package/dist/{memory-temporality-classifier-D91grvZ3.d.ts → memory-temporality-classifier-Bon6IpEm.d.ts} +1 -1
  104. package/dist/{memory-temporality-classifier-AetVby94.js → memory-temporality-classifier-C6MW_0i4.js} +1 -1
  105. package/dist/{memory-tier-promoter-DzX7GMZ2.d.ts → memory-tier-promoter-DeJbiH1w.d.ts} +1 -1
  106. package/dist/memory-tool-dependencies-CDvVqE6_.d.ts +14 -0
  107. package/dist/{memory-tool-dependencies-4Vl4QwBY.js → memory-tool-dependencies-CiofG6Lj.js} +1 -1
  108. package/dist/{observable-embedder-rKGU0LqW.d.ts → observable-embedder-B5ZLQv4-.d.ts} +1 -1
  109. package/dist/{observable-llm-client-Cw04DElM.d.ts → observable-llm-client-D65rHe8y.d.ts} +3 -3
  110. package/dist/{observable-memory-store-CHLQvW9E.d.ts → observable-memory-store-CAdLg_OF.d.ts} +4 -4
  111. package/dist/{observable-retriever-DJKBEkBC.d.ts → observable-retriever-DPPwzSY1.d.ts} +5 -5
  112. package/dist/paths-AoHWqjCM.d.ts +1 -0
  113. package/dist/{plugin-config-schema-CCwv_K7R.d.ts → plugin-config-schema-DmbzYMYe.d.ts} +1 -1
  114. package/dist/{profile-DbIDAmnc.d.ts → profile-C2e9mF3H.d.ts} +1 -1
  115. package/dist/{profile-section-writer-BV605K78.js → profile-section-writer-qPqgOH-a.js} +2 -2
  116. package/dist/{reflection-command-hooks-CTYkYqM3.js → reflection-command-hooks-Ctumt-yD.js} +3 -3
  117. package/dist/{reflection-command-hooks-h2CbYuXx.d.ts → reflection-command-hooks-xeMMA4Ik.d.ts} +4 -4
  118. package/dist/{reflection-deps-MWVeBZcs.d.ts → reflection-deps-Btr849dc.d.ts} +3 -3
  119. package/dist/{reflection-embedded-generator-D5OhLLVw.js → reflection-embedded-generator-B5K0O1sJ.js} +1 -1
  120. package/dist/{reflection-embedded-generator-Dk7sL8uy.d.ts → reflection-embedded-generator-W6MWE9e3.d.ts} +4 -4
  121. package/dist/{reflection-injection-hooks-B0fGkENX.d.ts → reflection-injection-hooks-BR-bvDmD.d.ts} +4 -4
  122. package/dist/{reflection-lifecycle-hooks-Bg_JBS6c.d.ts → reflection-lifecycle-hooks-DnQp5cvp.d.ts} +3 -3
  123. package/dist/rem-batch-executor-C7iItB5x.js +2 -0
  124. package/dist/{rem-batch-executor-vssXCmmE.js → rem-trigger-BEIQodYk.js} +855 -138
  125. package/dist/{retriever-CXeB7-A2.d.ts → retriever-DgVRB1Zq.d.ts} +6 -6
  126. package/dist/{runtime-audit-log-CIYiNvwf.d.ts → runtime-audit-log-DNPzk6Wy.d.ts} +1 -1
  127. package/dist/{server-BkDfjx4w.js → server-DyWtDUHh.js} +11 -11
  128. package/dist/sidecar/main.js +2 -3
  129. package/dist/{sno-station-mem-runtime-dependencies-DEdpOj7z.js → sno-station-mem-runtime-dependencies-D47FAvbm.js} +5 -5
  130. package/dist/sno-station-mem-runtime-dependencies-EbtwfQh0.d.ts +31 -0
  131. package/dist/{store-D3n1P7Xn.js → store-ByUy8ukD.js} +1 -1
  132. package/dist/{store-Dbo7MIzt.d.ts → store-DmFEfRbK.d.ts} +1 -1
  133. package/dist/{strategy-hook-runner-C6qUCnzY.d.ts → strategy-hook-runner-BdG_ktbm.d.ts} +7 -7
  134. package/dist/{strategy-hook-runner-CHpG8vXD.js → strategy-hook-runner-CD5X8gs3.js} +1 -1
  135. package/dist/{types-DvQ6nTNC.d.ts → types-CCPXb_iJ.d.ts} +1 -1
  136. package/package.json +1 -1
  137. package/dist/memory-tool-dependencies-BGFjBpkG.d.ts +0 -14
  138. package/dist/paths-DHU342hf.d.ts +0 -1
  139. package/dist/rem-batch-executor-_J7QoDuG.js +0 -2
  140. package/dist/rem-trigger-BZBf_rmt.js +0 -730
  141. package/dist/routes-DLHT__Ay.js +0 -60
  142. package/dist/sno-station-mem-runtime-dependencies-DjA-KpD8.d.ts +0 -31
@@ -1,730 +0,0 @@
1
- import { $ as appendAuditEntryStrict, r as enumerateRemCandidateScopes, tt as getAuditPath } from "./rem-batch-executor-vssXCmmE.js";
2
- import { s as REM_SIDECAR_TOKEN_HEADER } from "./routes-DLHT__Ay.js";
3
- import { d as getRemDiscoveryPath, o as REM_RUN_PATH, r as REM_CORRELATION_ID_HEADER, s as REM_SIDECAR_HOST } from "./config-CyH1VuJc.js";
4
- import { createLogger } from "@snoai/utils/logger";
5
- import path from "node:path";
6
- import { z } from "zod";
7
- import { createHash, randomUUID } from "node:crypto";
8
- import { mkdir, open, readFile, rename, rm } from "node:fs/promises";
9
- import { withLogContext as withLogContext$1 } from "@snoai/utils/log-context";
10
- //#region src/contract/error.ts
11
- const DEGRADED_REASONS = [
12
- "sidecar-unreachable",
13
- "sidecar-unresponsive",
14
- "principal-mismatch",
15
- "store-mismatch",
16
- "no-agent-endpoint",
17
- "invalid-input",
18
- "timeout",
19
- "storage-unavailable",
20
- "engine-failed"
21
- ];
22
- var ContractError = class extends Error {
23
- reason;
24
- degraded = true;
25
- constructor(reason, message = reason) {
26
- super(message);
27
- this.reason = reason;
28
- this.name = "ContractError";
29
- }
30
- };
31
- //#endregion
32
- //#region src/sidecar/rem-trigger-state.ts
33
- /** @file rem-trigger-state.ts
34
- * @purpose Persists the automatic REM scheduler's strict per-scope state.
35
- * @boundary One atomically replaced JSON file under the sno-station-mem state directory.
36
- */
37
- const remTriggerScopeStateSchema = z.object({
38
- last_pass_at: z.string().datetime(),
39
- schedule_zone: z.string().min(1).refine(isValidScheduleZone, "invalid IANA time zone"),
40
- last_covered_count: z.number().int().nonnegative(),
41
- last_volume_pass_date: z.string().regex(/^\d{4}-\d{2}-\d{2}$/u).nullable(),
42
- missed_window: z.object({
43
- due_at: z.string().datetime(),
44
- trigger: z.enum(["daily", "volume"]),
45
- recorded_at: z.string().datetime()
46
- }).strict().nullable().default(null),
47
- attempts: z.object({
48
- identity: z.string().min(1).nullable(),
49
- count: z.number().int().min(0).max(3)
50
- }).strict()
51
- }).strict();
52
- const remTriggerStateDocumentSchema = z.object({
53
- version: z.literal(1),
54
- scopes: z.record(z.string().min(1), remTriggerScopeStateSchema)
55
- }).strict();
56
- var RemTriggerStateError = class extends Error {
57
- statePath;
58
- constructor(statePath, cause) {
59
- super(`REM trigger state at ${statePath} is invalid or unreadable: ${errorMessage$1(cause)}`, { cause: cause instanceof Error ? cause : void 0 });
60
- this.statePath = statePath;
61
- this.name = "RemTriggerStateError";
62
- }
63
- };
64
- function remTriggerStatePath(stateDir) {
65
- return path.join(stateDir, "rem-trigger-state.json");
66
- }
67
- async function loadRemTriggerState(stateDir) {
68
- const statePath = remTriggerStatePath(stateDir);
69
- let raw;
70
- try {
71
- raw = await readFile(statePath, "utf8");
72
- } catch (error) {
73
- if (isNodeError$1(error) && error.code === "ENOENT") return {
74
- version: 1,
75
- scopes: {}
76
- };
77
- throw new RemTriggerStateError(statePath, error);
78
- }
79
- try {
80
- return remTriggerStateDocumentSchema.parse(JSON.parse(raw));
81
- } catch (error) {
82
- throw new RemTriggerStateError(statePath, error);
83
- }
84
- }
85
- function ensureRemTriggerScope(state, input) {
86
- const existing = state.scopes[input.scope];
87
- if (existing !== void 0) return {
88
- state,
89
- scopeState: existing,
90
- initialized: false
91
- };
92
- if (input.scope.trim().length === 0) throw new Error("REM trigger scope is required");
93
- if (!Number.isInteger(input.candidateCount) || input.candidateCount < 0) throw new Error("REM trigger candidate count must be a nonnegative integer");
94
- if (Number.isNaN(input.now.getTime())) throw new Error("REM trigger initialization time is invalid");
95
- const scheduleZone = (input.resolveScheduleZone ?? resolveHostScheduleZone)();
96
- if (!isValidScheduleZone(scheduleZone)) throw new Error(`invalid REM schedule zone: ${scheduleZone}`);
97
- const scopeState = {
98
- last_pass_at: input.now.toISOString(),
99
- schedule_zone: scheduleZone,
100
- last_covered_count: input.candidateCount,
101
- last_volume_pass_date: null,
102
- missed_window: null,
103
- attempts: {
104
- identity: null,
105
- count: 0
106
- }
107
- };
108
- return {
109
- state: {
110
- version: 1,
111
- scopes: {
112
- ...state.scopes,
113
- [input.scope]: scopeState
114
- }
115
- },
116
- scopeState,
117
- initialized: true
118
- };
119
- }
120
- async function writeRemTriggerStateAtomic$1(stateDir, state) {
121
- const validated = remTriggerStateDocumentSchema.parse(state);
122
- await mkdir(stateDir, {
123
- recursive: true,
124
- mode: 448
125
- });
126
- const statePath = remTriggerStatePath(stateDir);
127
- const temporaryPath = `${statePath}.${process.pid}.${randomUUID()}.tmp`;
128
- try {
129
- const handle = await open(temporaryPath, "wx", 384);
130
- try {
131
- await handle.writeFile(JSON.stringify(validated), "utf8");
132
- await handle.sync();
133
- } finally {
134
- await handle.close();
135
- }
136
- await rename(temporaryPath, statePath);
137
- await syncDirectory(stateDir);
138
- } catch (error) {
139
- await rm(temporaryPath, { force: true });
140
- throw error;
141
- }
142
- }
143
- function resolveHostScheduleZone() {
144
- return Intl.DateTimeFormat().resolvedOptions().timeZone;
145
- }
146
- function isValidScheduleZone(value) {
147
- if (value.trim().length === 0) return false;
148
- try {
149
- new Intl.DateTimeFormat("en-US", { timeZone: value }).format(0);
150
- return true;
151
- } catch {
152
- return false;
153
- }
154
- }
155
- async function syncDirectory(directory) {
156
- if (process.platform === "win32") return;
157
- const handle = await open(directory, "r");
158
- try {
159
- await handle.sync();
160
- } finally {
161
- await handle.close();
162
- }
163
- }
164
- function isNodeError$1(error) {
165
- return error instanceof Error && "code" in error;
166
- }
167
- function errorMessage$1(error) {
168
- return error instanceof Error ? error.message : String(error);
169
- }
170
- const REM_COMPLETION_AUDIT_TAIL_BYTES = 16777216;
171
- const log = createLogger("sno-station-mem:rem-trigger");
172
- function remNeedsHost(mode, modelCalls) {
173
- return Object.keys(modelCalls).some((id) => id.startsWith("REM") && modelCalls[id][mode] === "host");
174
- }
175
- const discoverySchema = z.object({
176
- pid: z.number().int().positive(),
177
- port: z.number().int().min(1).max(65535),
178
- token: z.string().min(1)
179
- }).strict();
180
- const idleEvaluations = /* @__PURE__ */ new Map();
181
- async function evaluateRemAutomaticTriggers(input) {
182
- const now = input.now ?? /* @__PURE__ */ new Date();
183
- if (!Number.isInteger(input.volumeThreshold ?? 100) || (input.volumeThreshold ?? 100) < 1) throw new Error("REM volume threshold must be a positive integer");
184
- const auditStateDir = input.auditStateDir ?? input.stateDir;
185
- if (Number.isNaN(now.getTime())) throw new Error("REM trigger evaluation time is invalid");
186
- if (input.requestedOperations.length === 0) {
187
- await recordDecision(auditStateDir, void 0, "skipped", {
188
- row: "automatic-trigger-skipped",
189
- reason: "product-mode-disabled"
190
- });
191
- return {
192
- evaluations: 0,
193
- dispatches: 0
194
- };
195
- }
196
- if (input.mode && remNeedsHost(input.mode, input.modelCalls) && !input.hasConnectedHost?.()) {
197
- await recordDecision(auditStateDir, void 0, "skipped", {
198
- row: "automatic-trigger-skipped",
199
- reason: "skipped: no host model connected"
200
- });
201
- return {
202
- evaluations: 0,
203
- dispatches: 0
204
- };
205
- }
206
- let scopes;
207
- try {
208
- scopes = enumerateRemCandidateScopes(input.database);
209
- } catch (error) {
210
- await recordDecision(auditStateDir, void 0, "error", {
211
- row: "enumeration-failed",
212
- cause: errorMessage(error)
213
- });
214
- return {
215
- evaluations: 1,
216
- dispatches: 0
217
- };
218
- }
219
- if (scopes.length === 0) {
220
- await recordDecision(auditStateDir, void 0, "ok", { row: "no-scopes" });
221
- return {
222
- evaluations: 1,
223
- dispatches: 0
224
- };
225
- }
226
- let state;
227
- let lostState = false;
228
- try {
229
- state = await loadRemTriggerState(input.stateDir);
230
- } catch (error) {
231
- for (const { scope, candidateCount } of scopes) {
232
- const consecutiveIdle = (idleEvaluations.get(scope) ?? 0) + 1;
233
- idleEvaluations.set(scope, consecutiveIdle);
234
- await recordDecision(auditStateDir, scope, "error", {
235
- row: "state-unreadable",
236
- candidate_count: candidateCount,
237
- cause: errorMessage(error),
238
- consecutive_idle: consecutiveIdle
239
- });
240
- }
241
- log.error("REM trigger state unavailable; continuing with due work", { cause: errorMessage(error) }, {
242
- event_name: "memory.rem.trigger.state.unavailable",
243
- file: "packages/memory/src/sidecar/rem-trigger.ts",
244
- function: "evaluateRemAutomaticTriggers",
245
- site_id: "memory.rem.trigger.state.unavailable"
246
- });
247
- state = {
248
- version: 1,
249
- scopes: {}
250
- };
251
- lostState = true;
252
- }
253
- state = await applyCompletedBaselines(auditStateDir, input.stateDir, state);
254
- let dispatches = 0;
255
- for (const { scope, candidateCount } of scopes) try {
256
- const ensured = ensureRemTriggerScope(state, {
257
- scope,
258
- now: lostState ? previousDay(now) : now,
259
- candidateCount,
260
- resolveScheduleZone: input.resolveScheduleZone
261
- });
262
- state = ensured.state;
263
- if (ensured.initialized) await writeRemTriggerStateAtomic(input.stateDir, state);
264
- const scopeResult = await evaluateScope(input, auditStateDir, state, scope, candidateCount, now);
265
- state = scopeResult.state;
266
- dispatches += scopeResult.dispatched ? 1 : 0;
267
- } catch (error) {
268
- log.error("REM automatic scope evaluation failed", {
269
- scope,
270
- error
271
- }, {
272
- event_name: "sno_station_mem.rem-trigger.rem.automatic.scope.evaluation.failed",
273
- file: "packages/memory/src/sidecar/rem-trigger.ts",
274
- function: "evaluateRemAutomaticTriggers",
275
- site_id: "rem-trigger.evaluateRemAutomaticTriggers.1d59077a5c"
276
- });
277
- try {
278
- await recordDecision(auditStateDir, scope, "error", {
279
- row: "scope-evaluation-failed",
280
- cause: errorMessage(error)
281
- });
282
- } catch (auditError) {
283
- log.warn("REM automatic scope failure audit failed", {
284
- scope,
285
- error: auditError
286
- }, {
287
- event_name: "sno_station_mem.rem-trigger.rem.automatic.scope.failure.audit.failed",
288
- file: "packages/memory/src/sidecar/rem-trigger.ts",
289
- function: "evaluateRemAutomaticTriggers",
290
- site_id: "rem-trigger.evaluateRemAutomaticTriggers.ebe1cdd204"
291
- });
292
- }
293
- try {
294
- state = await loadRemTriggerState(input.stateDir);
295
- } catch (stateError) {
296
- log.warn("REM automatic trigger state reload failed", {
297
- scope,
298
- error: stateError
299
- }, {
300
- event_name: "sno_station_mem.rem-trigger.rem.automatic.trigger.state.reload.failed",
301
- file: "packages/memory/src/sidecar/rem-trigger.ts",
302
- function: "evaluateRemAutomaticTriggers",
303
- site_id: "rem-trigger.evaluateRemAutomaticTriggers.46669e66ff"
304
- });
305
- break;
306
- }
307
- }
308
- return {
309
- evaluations: scopes.length,
310
- dispatches
311
- };
312
- }
313
- function previousDay(now) {
314
- const day = new Date(now);
315
- day.setUTCDate(day.getUTCDate() - 1);
316
- return day;
317
- }
318
- async function writeRemTriggerStateAtomic(stateDir, state) {
319
- try {
320
- await writeRemTriggerStateAtomic$1(stateDir, state);
321
- } catch (error) {
322
- log.error("REM trigger state write failed; continuing dispatch", { cause: errorMessage(error) }, {
323
- event_name: "memory.rem.trigger.state.write.failed",
324
- file: "packages/memory/src/sidecar/rem-trigger.ts",
325
- function: "evaluateRemAutomaticTriggers",
326
- site_id: "memory.rem.trigger.state.write.failed"
327
- });
328
- }
329
- }
330
- async function evaluateScope(input, auditStateDir, state, scope, candidateCount, now) {
331
- const scopeState = requiredScopeState(state, scope);
332
- const zone = scopeState.schedule_zone;
333
- const nextDue = nextDailyDue(scopeState);
334
- const localDate = localDateAt(now, zone);
335
- const growth = candidateCount - scopeState.last_covered_count;
336
- const scheduledDue = computeRemDailyDue(scopeState.last_pass_at, zone);
337
- const pendingVolumeToday = scopeState.attempts.identity === remAutomaticCorrelationId("volume", scope, localDate);
338
- const pendingDailyToday = localDateAt(scheduledDue, zone) === localDate && scopeState.attempts.identity === remAutomaticCorrelationId("daily", scope, scheduledDue.toISOString());
339
- const dailyDue = now.getTime() >= nextDue.getTime() && !pendingVolumeToday;
340
- const volumeDue = growth >= (input.volumeThreshold ?? 100) && scopeState.last_volume_pass_date !== localDate && !pendingDailyToday;
341
- if (!dailyDue && !volumeDue) {
342
- const consecutiveIdle = (idleEvaluations.get(scope) ?? 0) + 1;
343
- idleEvaluations.set(scope, consecutiveIdle);
344
- await recordDecision(auditStateDir, scope, "ok", {
345
- row: "waiting-for-schedule",
346
- next_due: nextDue.toISOString(),
347
- milliseconds_until_due: Math.max(0, nextDue.getTime() - now.getTime()),
348
- growth: growthDetails(candidateCount, scopeState.last_covered_count, input.volumeThreshold ?? 100),
349
- consecutive_idle: consecutiveIdle
350
- });
351
- return {
352
- state,
353
- dispatched: false
354
- };
355
- }
356
- const trigger = dailyDue ? "daily" : "volume";
357
- if (input.tickEnabled === false) {
358
- const nextState = replaceScopeState(state, scope, {
359
- ...scopeState,
360
- missed_window: {
361
- due_at: dailyDue ? nextDue.toISOString() : now.toISOString(),
362
- trigger,
363
- recorded_at: now.toISOString()
364
- }
365
- });
366
- await writeRemTriggerStateAtomic(input.stateDir, nextState);
367
- return {
368
- state: nextState,
369
- dispatched: false
370
- };
371
- }
372
- const correlationId = remAutomaticCorrelationId(trigger, scope, trigger === "daily" ? nextDue.toISOString() : localDate);
373
- const priorAttempts = scopeState.attempts.identity === correlationId ? scopeState.attempts.count : 0;
374
- await recordDecision(auditStateDir, scope, "ok", {
375
- row: "dispatch",
376
- trigger,
377
- next_due: nextDue.toISOString(),
378
- pass_at: now.toISOString(),
379
- local_date: localDate,
380
- growth: growthDetails(candidateCount, scopeState.last_covered_count, input.volumeThreshold ?? 100),
381
- correlation_id: correlationId,
382
- consecutive_idle: 0
383
- });
384
- idleEvaluations.set(scope, 0);
385
- let nextState = replaceScopeState(state, scope, {
386
- ...scopeState,
387
- attempts: {
388
- identity: correlationId,
389
- count: Math.min(priorAttempts + 1, 3)
390
- }
391
- });
392
- await writeRemTriggerStateAtomic(input.stateDir, nextState);
393
- let cause;
394
- try {
395
- await dispatchRemWave(input, scope, correlationId);
396
- } catch (error) {
397
- cause = errorMessage(error);
398
- }
399
- if (cause === void 0) {
400
- nextState = replaceScopeState(nextState, scope, {
401
- ...requiredScopeState(nextState, scope),
402
- missed_window: null
403
- });
404
- await writeRemTriggerStateAtomic(input.stateDir, nextState);
405
- return {
406
- state: nextState,
407
- dispatched: true
408
- };
409
- }
410
- await appendAuditEntryStrict(auditStateDir, {
411
- event: "rem_failed",
412
- resultStatus: "error",
413
- scope,
414
- details: {
415
- outcome: "dispatch-failed",
416
- cause,
417
- correlation_id: correlationId,
418
- source: "gateway-trigger"
419
- }
420
- });
421
- return {
422
- state: nextState,
423
- dispatched: true
424
- };
425
- }
426
- async function dispatchRemWave(input, scope, correlationId) {
427
- const started = performance.now();
428
- const discovery = discoverySchema.parse(JSON.parse(await readFile(input.discoveryPath ?? getRemDiscoveryPath(), "utf8")));
429
- const response = await fetch(`http://${REM_SIDECAR_HOST}:${discovery.port}${REM_RUN_PATH}`, {
430
- method: "POST",
431
- headers: {
432
- "content-type": "application/json",
433
- [REM_SIDECAR_TOKEN_HEADER]: discovery.token,
434
- [REM_CORRELATION_ID_HEADER]: correlationId
435
- },
436
- body: JSON.stringify({
437
- types: input.requestedOperations,
438
- scope
439
- }),
440
- signal: AbortSignal.timeout(input.dispatchTimeoutMs ?? 1e4)
441
- });
442
- if (response.status !== 202) throw new Error(`REM sidecar returned HTTP ${response.status}`);
443
- const body = z.object({
444
- job_id: z.string().min(1),
445
- waveId: z.string().min(1)
446
- }).passthrough().parse(await response.json());
447
- if (body.job_id !== body.waveId) throw new Error("REM sidecar confirmation identities disagree");
448
- withLogContext$1({
449
- operation_id: body.job_id,
450
- job_id: body.job_id,
451
- session_reference: scope,
452
- external_reference: correlationId
453
- }, () => {
454
- log.info("REM dispatch accepted", {
455
- outcome: "success",
456
- job_id: body.job_id,
457
- status: response.status,
458
- duration_ms: performance.now() - started
459
- }, {
460
- event_name: "sidecar.trigger.dispatch.accepted",
461
- file: "packages/memory/src/sidecar/rem-trigger.ts",
462
- function: "dispatchRemWave",
463
- site_id: "sidecar.trigger.dispatch.accepted"
464
- });
465
- });
466
- }
467
- async function applyCompletedBaselines(auditStateDir, triggerStateDir, state) {
468
- let content;
469
- try {
470
- content = await readAuditTail(getAuditPath(auditStateDir));
471
- } catch (error) {
472
- if (isNodeError(error) && error.code === "ENOENT") return state;
473
- log.warn("REM completion audit could not be read", {
474
- error,
475
- outcome: "skipped",
476
- reason_code: "audit_read_failed"
477
- }, {
478
- event_name: "sidecar.trigger.audit_read.failed",
479
- file: "packages/memory/src/sidecar/rem-trigger.ts",
480
- function: "applyCompletedBaselines",
481
- site_id: "sidecar.trigger.audit_read.failed"
482
- });
483
- return state;
484
- }
485
- const dispatched = /* @__PURE__ */ new Map();
486
- let nextState = state;
487
- let malformedCount = 0;
488
- for (const line of content.split("\n")) {
489
- if (line.trim() === "") continue;
490
- try {
491
- const entry = JSON.parse(line);
492
- const details = asRecord(entry["details"]);
493
- const correlationId = details?.["correlation_id"];
494
- const scope = entry["scope"];
495
- if (entry["event"] === "rem_trigger_evaluated" && details?.["row"] === "dispatch" && typeof correlationId === "string" && typeof scope === "string") {
496
- const trigger = details["trigger"];
497
- const passAt = details["pass_at"];
498
- const localDate = details["local_date"];
499
- dispatched.set(correlationId, {
500
- scope,
501
- ...trigger === "daily" || trigger === "volume" ? { trigger } : {},
502
- ...typeof passAt === "string" ? { passAt } : {},
503
- ...typeof localDate === "string" ? { localDate } : {}
504
- });
505
- continue;
506
- }
507
- if (entry["event"] === "rem_skipped" && typeof correlationId === "string") {
508
- const skipped = dispatched.get(correlationId);
509
- if (skipped && nextState.scopes[skipped.scope]?.attempts.identity === correlationId) nextState = replaceScopeState(nextState, skipped.scope, {
510
- ...requiredScopeState(nextState, skipped.scope),
511
- attempts: {
512
- identity: null,
513
- count: 0
514
- }
515
- });
516
- continue;
517
- }
518
- if (entry["event"] !== "rem_completed" || typeof correlationId !== "string") continue;
519
- const dispatch = dispatched.get(correlationId);
520
- if (dispatch === void 0 || state.scopes[dispatch.scope] === void 0 || entry["scope"] !== dispatch.scope) continue;
521
- const currentScopeState = requiredScopeState(nextState, dispatch.scope);
522
- let completedScopeState = {
523
- ...currentScopeState,
524
- ...dispatch.trigger === "daily" && isValidInstant(dispatch.passAt) ? { last_pass_at: dispatch.passAt } : {},
525
- ...dispatch.trigger === "daily" && isLocalDate(dispatch.localDate) ? { last_volume_pass_date: dispatch.localDate } : {},
526
- ...dispatch.trigger === "volume" && isLocalDate(dispatch.localDate) ? { last_volume_pass_date: dispatch.localDate } : {},
527
- ...currentScopeState.attempts.identity === correlationId ? { attempts: {
528
- identity: null,
529
- count: 0
530
- } } : {}
531
- };
532
- const measured = asRecord(asRecord(details?.["stats"])?.["measured"]);
533
- const rowsConsidered = measured?.["rows_considered"];
534
- if (measured?.["pair_cap_binding"] === false && Number.isInteger(rowsConsidered) && Number(rowsConsidered) >= 0) completedScopeState = {
535
- ...completedScopeState,
536
- last_covered_count: Number(rowsConsidered)
537
- };
538
- if (JSON.stringify(completedScopeState) === JSON.stringify(currentScopeState)) continue;
539
- nextState = replaceScopeState(nextState, dispatch.scope, completedScopeState);
540
- } catch {
541
- malformedCount += 1;
542
- }
543
- }
544
- if (malformedCount > 0) log.warn("Malformed REM audit records skipped", {
545
- malformed_count: malformedCount,
546
- outcome: "partial"
547
- }, {
548
- event_name: "sidecar.trigger.audit_records.skipped",
549
- file: "packages/memory/src/sidecar/rem-trigger.ts",
550
- function: "applyCompletedBaselines",
551
- site_id: "sidecar.trigger.audit_records.skipped"
552
- });
553
- if (nextState !== state) await writeRemTriggerStateAtomic(triggerStateDir, nextState);
554
- return nextState;
555
- }
556
- async function readAuditTail(auditPath) {
557
- const handle = await open(auditPath, "r");
558
- try {
559
- const size = (await handle.stat()).size;
560
- const bytesToRead = Math.min(size, REM_COMPLETION_AUDIT_TAIL_BYTES);
561
- if (bytesToRead === 0) return "";
562
- const buffer = Buffer.alloc(bytesToRead);
563
- const { bytesRead } = await handle.read(buffer, 0, bytesToRead, size - bytesToRead);
564
- let content = buffer.subarray(0, bytesRead).toString("utf8");
565
- if (size > bytesToRead) {
566
- const firstLineEnd = content.indexOf("\n");
567
- content = firstLineEnd === -1 ? "" : content.slice(firstLineEnd + 1);
568
- }
569
- return content;
570
- } finally {
571
- await handle.close();
572
- }
573
- }
574
- function remAutomaticCorrelationId(trigger, scope, triggerKey) {
575
- return `rem-auto-${trigger}-${createHash("sha256").update(JSON.stringify([
576
- "rem-automatic-v1",
577
- trigger,
578
- scope,
579
- triggerKey
580
- ])).digest("hex")}`;
581
- }
582
- /** The daily due time, pushed to the schedule after the last used-up local day. */
583
- function nextDailyDue(scopeState) {
584
- const scheduled = computeRemDailyDue(scopeState.last_pass_at, scopeState.schedule_zone);
585
- const volumeDate = scopeState.last_volume_pass_date;
586
- if (volumeDate === null) return scheduled;
587
- const parsed = /^(\d{4})-(\d{2})-(\d{2})$/u.exec(volumeDate);
588
- if (parsed === null) return scheduled;
589
- const following = addCalendarDays({
590
- year: Number(parsed[1]),
591
- month: Number(parsed[2]),
592
- day: Number(parsed[3])
593
- }, 1);
594
- const afterVolume = zonedInstant(scopeState.schedule_zone, following.year, following.month, following.day);
595
- return afterVolume.getTime() > scheduled.getTime() ? afterVolume : scheduled;
596
- }
597
- function computeRemDailyDue(lastPassAt, scheduleZone) {
598
- const lastPass = new Date(lastPassAt);
599
- if (Number.isNaN(lastPass.getTime())) throw new Error("REM last pass time is invalid");
600
- const localDate = localDateParts(lastPass, scheduleZone);
601
- let due = zonedInstant(scheduleZone, localDate.year, localDate.month, localDate.day);
602
- if (due.getTime() <= lastPass.getTime()) {
603
- const nextDate = addCalendarDays(localDate, 1);
604
- due = zonedInstant(scheduleZone, nextDate.year, nextDate.month, nextDate.day);
605
- }
606
- return due;
607
- }
608
- function zonedInstant(zone, year, month, day) {
609
- const target = Date.UTC(year, month - 1, day, 3, 0, 0, 0);
610
- let instant = target;
611
- let firstExistingAfter;
612
- for (let iteration = 0; iteration < 5; iteration++) {
613
- const parts = zonedDateTimeParts(new Date(instant), zone);
614
- const represented = Date.UTC(parts.year, parts.month - 1, parts.day, parts.hour, parts.minute, parts.second);
615
- const adjustment = target - represented;
616
- if (adjustment === 0) return new Date(instant);
617
- if (represented > target && represented < (firstExistingAfter?.represented ?? Number.POSITIVE_INFINITY)) firstExistingAfter = {
618
- instant,
619
- represented
620
- };
621
- instant += adjustment;
622
- }
623
- if (firstExistingAfter !== void 0) return new Date(firstExistingAfter.instant);
624
- throw new Error(`REM scheduled instant cannot be resolved in zone ${zone}`);
625
- }
626
- function localDateAt(instant, zone) {
627
- const parts = localDateParts(instant, zone);
628
- return `${parts.year.toString().padStart(4, "0")}-${parts.month.toString().padStart(2, "0")}-${parts.day.toString().padStart(2, "0")}`;
629
- }
630
- function localDateParts(instant, zone) {
631
- const parts = zonedDateTimeParts(instant, zone);
632
- return {
633
- year: parts.year,
634
- month: parts.month,
635
- day: parts.day
636
- };
637
- }
638
- function zonedDateTimeParts(instant, zone) {
639
- const values = {};
640
- const formatter = new Intl.DateTimeFormat("en-CA", {
641
- timeZone: zone,
642
- year: "numeric",
643
- month: "2-digit",
644
- day: "2-digit",
645
- hour: "2-digit",
646
- minute: "2-digit",
647
- second: "2-digit",
648
- hourCycle: "h23"
649
- });
650
- for (const part of formatter.formatToParts(instant)) if (part.type !== "literal") values[part.type] = Number(part.value);
651
- const year = values["year"];
652
- const month = values["month"];
653
- const day = values["day"];
654
- const hour = values["hour"];
655
- const minute = values["minute"];
656
- const second = values["second"];
657
- if ([
658
- year,
659
- month,
660
- day,
661
- hour,
662
- minute,
663
- second
664
- ].some((value) => value === void 0)) throw new Error(`REM schedule zone ${zone} did not produce complete date parts`);
665
- return {
666
- year: Number(year),
667
- month: Number(month),
668
- day: Number(day),
669
- hour: Number(hour),
670
- minute: Number(minute),
671
- second: Number(second)
672
- };
673
- }
674
- function addCalendarDays(date, days) {
675
- const instant = new Date(Date.UTC(date.year, date.month - 1, date.day + days));
676
- return {
677
- year: instant.getUTCFullYear(),
678
- month: instant.getUTCMonth() + 1,
679
- day: instant.getUTCDate()
680
- };
681
- }
682
- function growthDetails(candidateCount, coveredCount, threshold) {
683
- const growth = candidateCount - coveredCount;
684
- return {
685
- candidate_count: candidateCount,
686
- last_covered_count: coveredCount,
687
- delta: growth,
688
- threshold,
689
- remaining: Math.max(0, threshold - growth)
690
- };
691
- }
692
- async function recordDecision(stateDir, scope, resultStatus, details) {
693
- await appendAuditEntryStrict(stateDir, {
694
- event: "rem_trigger_evaluated",
695
- resultStatus,
696
- ...scope === void 0 ? {} : { scope },
697
- details
698
- });
699
- }
700
- function requiredScopeState(state, scope) {
701
- const scopeState = state.scopes[scope];
702
- if (scopeState === void 0) throw new Error(`REM trigger state missing scope ${scope}`);
703
- return scopeState;
704
- }
705
- function replaceScopeState(state, scope, scopeState) {
706
- return {
707
- version: 1,
708
- scopes: {
709
- ...state.scopes,
710
- [scope]: scopeState
711
- }
712
- };
713
- }
714
- function asRecord(value) {
715
- return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
716
- }
717
- function isValidInstant(value) {
718
- return value !== void 0 && !Number.isNaN(new Date(value).getTime());
719
- }
720
- function isLocalDate(value) {
721
- return value !== void 0 && /^\d{4}-\d{2}-\d{2}$/u.test(value);
722
- }
723
- function isNodeError(error) {
724
- return error instanceof Error && "code" in error;
725
- }
726
- function errorMessage(error) {
727
- return error instanceof Error ? error.message : String(error);
728
- }
729
- //#endregion
730
- export { DEGRADED_REASONS as i, remNeedsHost as n, ContractError as r, evaluateRemAutomaticTriggers as t };