@snoai/memory 1.2.2 → 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.
- package/README.md +1 -1
- package/dist/{access-tracker-BNqlgChq.d.ts → access-tracker-D20cgP0X.d.ts} +3 -3
- package/dist/{adapter-CeQ7rViY.d.ts → adapter-Dx92oSuo.d.ts} +3 -2
- package/dist/{adapter-XaRHZWZ2.js → adapter-RxQ9CJ7f.js} +1 -1
- package/dist/{agent-llm-port-5oXgBpzJ.d.ts → agent-llm-port-DY2290EK.d.ts} +2 -0
- package/dist/{atomic-memory-extraction-By0axKHc.js → atomic-memory-extraction-BWYEdBhl.js} +4 -4
- package/dist/{atomic-memory-extraction-BT8lFE5t.d.ts → atomic-memory-extraction-Dz-fe3h0.d.ts} +4 -4
- package/dist/{b-profile-section-dictionary-provider-DGtF4dB7.d.ts → b-profile-section-dictionary-provider-D-VJRSOs.d.ts} +1 -1
- package/dist/{capture-policy-detector-CN3vfdNU.d.ts → capture-policy-detector-D7rPVLFp.d.ts} +1 -1
- package/dist/client.d.ts +1 -1
- package/dist/{config-CyH1VuJc.js → config-D3Hc1__G.js} +60 -1
- package/dist/{derived-suppression-cache-BZdBZSdK.d.ts → derived-suppression-cache-BGcKAg2p.d.ts} +1 -1
- package/dist/diagnostic-encoder.js +1 -1
- package/dist/{embedding-provider-client-Q_hJonK9.js → embedding-provider-client-40tpzKou.js} +5 -1
- package/dist/{index-DtqgCT8u.d.ts → index-CgqHi0Rw.d.ts} +2 -2
- package/dist/index.d.ts +10 -10
- package/dist/index.js +6 -5
- package/dist/internal/config/plugin-config-schema.d.ts +1 -1
- package/dist/internal/config/plugin-config-schema.js +1 -1
- package/dist/internal/config/product-mode.d.ts +1 -1
- package/dist/internal/config/settings.d.ts +1 -1
- package/dist/internal/config/settings.js +1 -1
- package/dist/internal/config/skin-defaults.d.ts +1 -1
- package/dist/internal/contract/profile.d.ts +1 -1
- package/dist/internal/contract/routes.d.ts +1 -1
- package/dist/internal/engine/bindings/memory-cli-shared.d.ts +4 -4
- package/dist/internal/engine/bindings/memory-forget-tool.js +1 -1
- package/dist/internal/engine/bindings/memory-list-tool.js +1 -1
- package/dist/internal/engine/bindings/memory-recall-tool.js +1 -1
- package/dist/internal/engine/bindings/memory-reflection-resolve-tool.js +1 -1
- package/dist/internal/engine/bindings/memory-stats-tool.js +1 -1
- package/dist/internal/engine/bindings/memory-store-tool.js +3 -3
- package/dist/internal/engine/bindings/memory-tool-access.d.ts +1 -1
- package/dist/internal/engine/bindings/memory-tool-access.js +1 -1
- package/dist/internal/engine/bindings/memory-tool-results.d.ts +1 -1
- package/dist/internal/engine/bindings/memory-tool-results.js +1 -1
- package/dist/internal/engine/bindings/memory-tool-schemas.d.ts +5 -5
- package/dist/internal/engine/bindings/memory-tool-schemas.js +1 -1
- package/dist/internal/engine/bindings/memory-update-tool.js +2 -2
- package/dist/internal/engine/bindings/sno-station-mem-ambient-learning-hook.d.ts +4 -4
- package/dist/internal/engine/bindings/sno-station-mem-ambient-learning-hook.js +2 -2
- package/dist/internal/engine/bindings/sno-station-mem-auto-recall-hook.d.ts +4 -4
- package/dist/internal/engine/bindings/sno-station-mem-auto-recall-hook.js +1 -1
- package/dist/internal/engine/bindings/sno-station-mem-insight-distill-factory.d.ts +6 -6
- package/dist/internal/engine/bindings/sno-station-mem-insight-distill-factory.js +2 -2
- package/dist/internal/engine/bindings/sno-station-mem-message-transcript.js +2 -2
- package/dist/internal/engine/bindings/sno-station-mem-reset-hook.d.ts +3 -3
- package/dist/internal/engine/bindings/sno-station-mem-runtime-mode.d.ts +1 -1
- package/dist/internal/engine/extraction/atomic-memory-extraction.d.ts +1 -1
- package/dist/internal/engine/extraction/atomic-memory-extraction.js +1 -1
- package/dist/internal/engine/extraction/b-profile-section-dictionary-provider.d.ts +1 -1
- package/dist/internal/engine/extraction/b-profile-section-rekey.d.ts +5 -5
- package/dist/internal/engine/extraction/capture-policy-detector.d.ts +1 -1
- package/dist/internal/engine/extraction/embedding-provider-client.js +1 -1
- package/dist/internal/engine/extraction/memory-metadata-codec.d.ts +2 -2
- package/dist/internal/engine/extraction/memory-temporality-classifier.d.ts +1 -1
- package/dist/internal/engine/extraction/memory-temporality-classifier.js +1 -1
- package/dist/internal/engine/observability/adapter.d.ts +1 -1
- package/dist/internal/engine/observability/adapter.js +1 -1
- package/dist/internal/engine/observability/memory-snapshot.d.ts +1 -1
- package/dist/internal/engine/observability/observable-embedder.d.ts +1 -1
- package/dist/internal/engine/observability/observable-embedder.js +1 -1
- package/dist/internal/engine/observability/observable-llm-client.d.ts +1 -1
- package/dist/internal/engine/observability/observable-memory-store.d.ts +1 -1
- package/dist/internal/engine/observability/observable-memory-store.js +1 -1
- package/dist/internal/engine/observability/observable-retriever.d.ts +1 -1
- package/dist/internal/engine/observability/runtime-diagnostics.d.ts +1 -1
- package/dist/internal/engine/observability/runtime-diagnostics.js +1 -1
- package/dist/internal/engine/operations/memory-tier-promoter.d.ts +1 -1
- package/dist/internal/engine/operations/runtime-audit-log.d.ts +2 -2
- package/dist/internal/engine/provider/provider-registration.d.ts +4 -4
- package/dist/internal/engine/provider/provider-row-renderer.d.ts +1 -1
- package/dist/internal/engine/reflection/reflection-command-hooks.d.ts +1 -1
- package/dist/internal/engine/reflection/reflection-command-hooks.js +1 -1
- package/dist/internal/engine/reflection/reflection-deps.d.ts +1 -1
- package/dist/internal/engine/reflection/reflection-embedded-generator.d.ts +1 -1
- package/dist/internal/engine/reflection/reflection-embedded-generator.js +1 -1
- package/dist/internal/engine/reflection/reflection-injection-hooks.d.ts +1 -1
- package/dist/internal/engine/reflection/reflection-injection-hooks.js +1 -1
- package/dist/internal/engine/reflection/reflection-lifecycle-hooks.d.ts +1 -1
- package/dist/internal/engine/reflection/reflection-lifecycle-hooks.js +1 -1
- package/dist/internal/engine/reflection/strategy-hook-runner.d.ts +3 -3
- package/dist/internal/engine/reflection/strategy-hook-runner.js +2 -2
- package/dist/internal/engine/retrieval/access-tracker.d.ts +1 -1
- package/dist/internal/engine/retrieval/retriever.d.ts +1 -1
- package/dist/internal/engine/shared/memory-kind-policy.d.ts +1 -1
- package/dist/internal/engine/shared/paths.d.ts +2 -2
- package/dist/internal/engine/shared/types.d.ts +2 -2
- package/dist/internal/engine/shared/types.js +1 -1
- package/dist/internal/engine/telemetry/observe-ledger.d.ts +3 -2
- package/dist/internal/engine/telemetry/observe-ledger.js +3 -0
- package/dist/internal/model/agent-llm-port.d.ts +1 -1
- package/dist/internal/model/llm-client.d.ts +2 -2
- package/dist/internal/model/llm-client.js +1 -1
- package/dist/internal/model/llm-mode-routing.d.ts +1 -1
- package/dist/internal/store/maintenance.d.ts +2 -2
- package/dist/internal/store/maintenance.js +21 -7
- package/dist/internal/store/store.d.ts +2 -2
- package/dist/internal/store/store.js +1 -1
- package/dist/{llm-client-BhkzlTnH.js → llm-client-BOkokxti.js} +1 -0
- package/dist/{llm-client-PC0yQuCq.d.ts → llm-client-CtNzuoRc.d.ts} +2 -1
- package/dist/{log-site-catalog.generated-DTY6bcLM.js → log-site-catalog.generated-DoCYZhJF.js} +115 -115
- package/dist/log-site-catalog.json +1 -1
- package/dist/{memory-kind-policy-DnHY7l0n.d.ts → memory-kind-policy-iKHUsuwu.d.ts} +1 -1
- package/dist/{memory-metadata-codec-DbrRyhCg.d.ts → memory-metadata-codec-n-CFKYjn.d.ts} +1 -1
- package/dist/{memory-runtime-ChU1i-bO.js → memory-runtime-CVbnnCfB.js} +159 -120
- package/dist/{memory-snapshot-shzK-0C8.d.ts → memory-snapshot-DhtNqVPs.d.ts} +2 -2
- package/dist/{memory-store-base-Dx6oSqiv.d.ts → memory-store-base-QoyY3sxH.d.ts} +2 -2
- package/dist/{memory-temporality-classifier-D91grvZ3.d.ts → memory-temporality-classifier-Bon6IpEm.d.ts} +1 -1
- package/dist/{memory-temporality-classifier-AetVby94.js → memory-temporality-classifier-C6MW_0i4.js} +1 -1
- package/dist/{memory-tier-promoter-DzX7GMZ2.d.ts → memory-tier-promoter-DeJbiH1w.d.ts} +1 -1
- package/dist/memory-tool-dependencies-CDvVqE6_.d.ts +14 -0
- package/dist/{memory-tool-dependencies-4Vl4QwBY.js → memory-tool-dependencies-CiofG6Lj.js} +1 -1
- package/dist/{observable-embedder-CbFylzsx.d.ts → observable-embedder-B5ZLQv4-.d.ts} +1 -1
- package/dist/{observable-llm-client-BC6Bc8t3.d.ts → observable-llm-client-D65rHe8y.d.ts} +3 -3
- package/dist/{observable-memory-store-BykVMQXt.d.ts → observable-memory-store-CAdLg_OF.d.ts} +4 -4
- package/dist/{observable-retriever-C-c9O83s.d.ts → observable-retriever-DPPwzSY1.d.ts} +5 -5
- package/dist/paths-AoHWqjCM.d.ts +1 -0
- package/dist/plugin-config-observe-schema-BORZ9_7q.js +54 -0
- package/dist/{plugin-config-schema-CTLb-ChF.js → plugin-config-schema-CC-2N9Xw.js} +4 -49
- package/dist/{plugin-config-schema-CCwv_K7R.d.ts → plugin-config-schema-DmbzYMYe.d.ts} +1 -1
- package/dist/{profile-DbIDAmnc.d.ts → profile-C2e9mF3H.d.ts} +1 -1
- package/dist/{profile-section-writer-BV605K78.js → profile-section-writer-qPqgOH-a.js} +2 -2
- package/dist/{reflection-command-hooks-CTYkYqM3.js → reflection-command-hooks-Ctumt-yD.js} +3 -3
- package/dist/{reflection-command-hooks-h2CbYuXx.d.ts → reflection-command-hooks-xeMMA4Ik.d.ts} +4 -4
- package/dist/{reflection-deps-MWVeBZcs.d.ts → reflection-deps-Btr849dc.d.ts} +3 -3
- package/dist/{reflection-embedded-generator-D5OhLLVw.js → reflection-embedded-generator-B5K0O1sJ.js} +1 -1
- package/dist/{reflection-embedded-generator-Dk7sL8uy.d.ts → reflection-embedded-generator-W6MWE9e3.d.ts} +4 -4
- package/dist/{reflection-injection-hooks-B0fGkENX.d.ts → reflection-injection-hooks-BR-bvDmD.d.ts} +4 -4
- package/dist/{reflection-lifecycle-hooks-Bg_JBS6c.d.ts → reflection-lifecycle-hooks-DnQp5cvp.d.ts} +3 -3
- package/dist/rem-batch-executor-C7iItB5x.js +2 -0
- package/dist/{rem-batch-executor-Bdsf26tD.js → rem-trigger-BEIQodYk.js} +857 -138
- package/dist/{retriever-CXeB7-A2.d.ts → retriever-DgVRB1Zq.d.ts} +6 -6
- package/dist/{runtime-audit-log-CIYiNvwf.d.ts → runtime-audit-log-DNPzk6Wy.d.ts} +1 -1
- package/dist/{server-CDy9liZ-.js → server-DyWtDUHh.js} +11 -11
- package/dist/sidecar/main.js +2 -3
- package/dist/{sno-station-mem-runtime-dependencies-Bzfg4hGZ.js → sno-station-mem-runtime-dependencies-D47FAvbm.js} +6 -6
- package/dist/sno-station-mem-runtime-dependencies-EbtwfQh0.d.ts +31 -0
- package/dist/{store-D3n1P7Xn.js → store-ByUy8ukD.js} +1 -1
- package/dist/{store-Dbo7MIzt.d.ts → store-DmFEfRbK.d.ts} +1 -1
- package/dist/{strategy-hook-runner-C6qUCnzY.d.ts → strategy-hook-runner-BdG_ktbm.d.ts} +7 -7
- package/dist/{strategy-hook-runner-CHpG8vXD.js → strategy-hook-runner-CD5X8gs3.js} +1 -1
- package/dist/{types-DvQ6nTNC.d.ts → types-CCPXb_iJ.d.ts} +1 -1
- package/package.json +1 -1
- package/dist/memory-tool-dependencies-BGFjBpkG.d.ts +0 -14
- package/dist/paths-DHU342hf.d.ts +0 -1
- package/dist/rem-batch-executor-DdgZBL5K.js +0 -2
- package/dist/rem-trigger-6dmOjIU_.js +0 -730
- package/dist/routes-DLHT__Ay.js +0 -60
- package/dist/sno-station-mem-runtime-dependencies-cFPSMqDD.d.ts +0 -31
|
@@ -1,730 +0,0 @@
|
|
|
1
|
-
import { $ as appendAuditEntryStrict, r as enumerateRemCandidateScopes, tt as getAuditPath } from "./rem-batch-executor-Bdsf26tD.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 };
|