@kanadego/dsh-heartbeat 1.8.0 → 1.9.1
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 +50 -69
- package/assets/frontwin.ps1 +1 -0
- package/assets/idle.ps1 +1 -0
- package/assets/screenpulse.ps1 +4 -2
- package/config/policy.json +73 -72
- package/config/profile-schema.json +306 -306
- package/dist/{bindings-XPPSKILN.js → bindings-225SI7KC.js} +3 -2
- package/dist/{chunk-UCJR6TC7.js → chunk-2NB4QILF.js} +73 -21
- package/dist/chunk-2NB4QILF.js.map +1 -0
- package/dist/{chunk-IFTFDHZX.js → chunk-7TW6DD6Q.js} +11 -6
- package/dist/chunk-7TW6DD6Q.js.map +1 -0
- package/dist/{chunk-63FUJLIF.js → chunk-BQCXYE7N.js} +17 -2
- package/dist/chunk-BQCXYE7N.js.map +1 -0
- package/dist/chunk-HT57FOEF.js +2454 -0
- package/dist/chunk-HT57FOEF.js.map +1 -0
- package/dist/chunk-IV2ZWQA3.js +56 -0
- package/dist/chunk-IV2ZWQA3.js.map +1 -0
- package/dist/{chunk-MXJPX3FM.js → chunk-KOOQOMQY.js} +117 -62
- package/dist/chunk-KOOQOMQY.js.map +1 -0
- package/dist/{chunk-VIZNIQLK.js → chunk-OYVNWB5G.js} +82 -74
- package/dist/chunk-OYVNWB5G.js.map +1 -0
- package/dist/{chunk-RB3NLLGF.js → chunk-QOQ7EPZZ.js} +3 -3
- package/dist/{chunk-J6ZTRFFW.js → chunk-TY6UP74X.js} +6 -2
- package/dist/chunk-TY6UP74X.js.map +1 -0
- package/dist/chunk-WRJCINZ7.js +569 -0
- package/dist/chunk-WRJCINZ7.js.map +1 -0
- package/dist/chunk-WRUTATW4.js +47 -0
- package/dist/chunk-WRUTATW4.js.map +1 -0
- package/dist/chunk-ZLV4NMFX.js +333 -0
- package/dist/chunk-ZLV4NMFX.js.map +1 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.js +565 -491
- package/dist/cli/index.js.map +1 -1
- package/dist/{inbox-57NHQHY3.js → inbox-JLT5G7TL.js} +5 -3
- package/dist/index.d.ts +517 -0
- package/dist/index.js +482 -2418
- package/dist/index.js.map +1 -1
- package/dist/{migrate-43PUK25G.js → migrate-WDVFHUP7.js} +4 -3
- package/dist/orchestrator-2ZLU7RGE.js +60 -0
- package/dist/{pool-JQC5JWIZ.js → pool-B34NTH6D.js} +5 -3
- package/dist/{snapshot-ZBCSX67Y.js → snapshot-JQDJ346Z.js} +5 -4
- package/dist/snapshot-JQDJ346Z.js.map +1 -0
- package/package.json +10 -3
- package/config/watchlist.json +0 -9
- package/dist/chunk-63FUJLIF.js.map +0 -1
- package/dist/chunk-IFTFDHZX.js.map +0 -1
- package/dist/chunk-J6ZTRFFW.js.map +0 -1
- package/dist/chunk-MXJPX3FM.js.map +0 -1
- package/dist/chunk-SQJLKP7L.js +0 -729
- package/dist/chunk-SQJLKP7L.js.map +0 -1
- package/dist/chunk-UCJR6TC7.js.map +0 -1
- package/dist/chunk-VIZNIQLK.js.map +0 -1
- /package/dist/{bindings-XPPSKILN.js.map → bindings-225SI7KC.js.map} +0 -0
- /package/dist/{chunk-RB3NLLGF.js.map → chunk-QOQ7EPZZ.js.map} +0 -0
- /package/dist/{inbox-57NHQHY3.js.map → inbox-JLT5G7TL.js.map} +0 -0
- /package/dist/{migrate-43PUK25G.js.map → migrate-WDVFHUP7.js.map} +0 -0
- /package/dist/{pool-JQC5JWIZ.js.map → orchestrator-2ZLU7RGE.js.map} +0 -0
- /package/dist/{snapshot-ZBCSX67Y.js.map → pool-B34NTH6D.js.map} +0 -0
|
@@ -3,55 +3,28 @@ import {
|
|
|
3
3
|
readText,
|
|
4
4
|
saveJson,
|
|
5
5
|
writeText
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-7TW6DD6Q.js";
|
|
7
|
+
import {
|
|
8
|
+
atomicWriteFileSync
|
|
9
|
+
} from "./chunk-WRUTATW4.js";
|
|
7
10
|
|
|
8
11
|
// src/core/audit-log.ts
|
|
9
|
-
import fs2 from "fs";
|
|
10
|
-
import path2 from "path";
|
|
11
|
-
|
|
12
|
-
// src/core/atomic-fs.ts
|
|
13
12
|
import fs from "fs";
|
|
14
13
|
import path from "path";
|
|
15
|
-
|
|
16
|
-
function tmpSibling(target, tag = "w") {
|
|
17
|
-
return path.join(
|
|
18
|
-
path.dirname(target),
|
|
19
|
-
`.${path.basename(target)}.${tag}-${randomUUID().slice(0, 8)}.tmp`
|
|
20
|
-
);
|
|
21
|
-
}
|
|
22
|
-
function atomicWriteFileSync(target, data) {
|
|
23
|
-
const tmp = tmpSibling(target);
|
|
14
|
+
function appendAuditLine(file, event) {
|
|
24
15
|
try {
|
|
25
|
-
fs.
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
function atomicWriteJsonSync(target, value) {
|
|
32
|
-
atomicWriteFileSync(target, JSON.stringify(value, null, 2));
|
|
33
|
-
}
|
|
34
|
-
function shredFileSync(target, passes = 3) {
|
|
35
|
-
const stat = fs.statSync(target);
|
|
36
|
-
if (!stat.isFile()) throw new Error(`shred: not a file: ${target}`);
|
|
37
|
-
const buf = Buffer.alloc(Math.max(stat.size, 1));
|
|
38
|
-
for (let i = 0; i < passes; i++) {
|
|
39
|
-
randomFillSync(buf);
|
|
40
|
-
fs.writeFileSync(target, buf);
|
|
16
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
17
|
+
const line = JSON.stringify({ ts: event.ts ?? (/* @__PURE__ */ new Date()).toISOString(), ...event });
|
|
18
|
+
fs.appendFileSync(file, line + "\n", "utf8");
|
|
19
|
+
} catch (e) {
|
|
20
|
+
process.stderr.write(`[heartbeat] audit append failed: ${file}: ${String(e)}
|
|
21
|
+
`);
|
|
41
22
|
}
|
|
42
|
-
fs.rmSync(target, { force: true });
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// src/core/audit-log.ts
|
|
46
|
-
function appendAuditLine(file, event) {
|
|
47
|
-
fs2.mkdirSync(path2.dirname(file), { recursive: true });
|
|
48
|
-
const line = JSON.stringify({ ts: event.ts ?? (/* @__PURE__ */ new Date()).toISOString(), ...event });
|
|
49
|
-
fs2.appendFileSync(file, line + "\n", "utf8");
|
|
50
23
|
}
|
|
51
24
|
function readAuditLines(file) {
|
|
52
|
-
if (!
|
|
25
|
+
if (!fs.existsSync(file)) return [];
|
|
53
26
|
const out = [];
|
|
54
|
-
const raw =
|
|
27
|
+
const raw = fs.readFileSync(file, "utf8");
|
|
55
28
|
for (const line of raw.split("\n")) {
|
|
56
29
|
const trimmed = line.trim();
|
|
57
30
|
if (!trimmed) continue;
|
|
@@ -64,7 +37,7 @@ function readAuditLines(file) {
|
|
|
64
37
|
return out;
|
|
65
38
|
}
|
|
66
39
|
function pruneAuditFile(file, maxAgeMs, now = Date.now()) {
|
|
67
|
-
if (!
|
|
40
|
+
if (!fs.existsSync(file)) return 0;
|
|
68
41
|
const lines = readAuditLines(file);
|
|
69
42
|
const kept = lines.filter((e) => {
|
|
70
43
|
const ev = e;
|
|
@@ -80,9 +53,9 @@ function pruneAuditFile(file, maxAgeMs, now = Date.now()) {
|
|
|
80
53
|
}
|
|
81
54
|
|
|
82
55
|
// src/profile/store.ts
|
|
83
|
-
import
|
|
84
|
-
import { randomUUID
|
|
85
|
-
import
|
|
56
|
+
import path3 from "path";
|
|
57
|
+
import { randomUUID } from "crypto";
|
|
58
|
+
import fs3 from "fs";
|
|
86
59
|
|
|
87
60
|
// src/profile/types.ts
|
|
88
61
|
var PARTITIONS = ["interest", "projects", "comm", "psy"];
|
|
@@ -101,18 +74,34 @@ function emptyProfile() {
|
|
|
101
74
|
}
|
|
102
75
|
|
|
103
76
|
// src/profile/schema.ts
|
|
104
|
-
import
|
|
105
|
-
import
|
|
77
|
+
import fs2 from "fs";
|
|
78
|
+
import path2 from "path";
|
|
106
79
|
function loadProfileSchema(paths) {
|
|
107
|
-
const userPath =
|
|
108
|
-
const
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
80
|
+
const userPath = path2.join(paths.settingsDir, "profile-schema.json");
|
|
81
|
+
const factoryPath = path2.join(paths.configDir, "profile-schema.json");
|
|
82
|
+
const read = (file) => {
|
|
83
|
+
try {
|
|
84
|
+
const raw = JSON.parse(fs2.readFileSync(file, "utf8"));
|
|
85
|
+
if (!raw.partitions) return { error: "partitions missing" };
|
|
86
|
+
return { schema: raw, error: "" };
|
|
87
|
+
} catch (e) {
|
|
88
|
+
return { error: String(e) };
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
const user = fs2.existsSync(userPath) ? read(userPath) : null;
|
|
92
|
+
if (user?.schema) return { schema: user.schema, source: "user" };
|
|
93
|
+
const factory = read(factoryPath);
|
|
94
|
+
if (factory.schema) {
|
|
95
|
+
return user ? {
|
|
96
|
+
schema: factory.schema,
|
|
97
|
+
source: "factory",
|
|
98
|
+
fallbackReason: `user profile-schema.json unusable (${user.error}) \u2014 fell back to the factory copy`
|
|
99
|
+
} : { schema: factory.schema, source: "factory" };
|
|
115
100
|
}
|
|
101
|
+
return {
|
|
102
|
+
source: "none",
|
|
103
|
+
fallbackReason: `no usable profile-schema.json (user: ${user?.error ?? "absent"}; factory: ${factory.error})`
|
|
104
|
+
};
|
|
116
105
|
}
|
|
117
106
|
function checkAddAgainstSchema(schema, partition, topic, subTopic, nominated) {
|
|
118
107
|
const p = schema.partitions[partition];
|
|
@@ -137,15 +126,15 @@ function checkAddAgainstSchema(schema, partition, topic, subTopic, nominated) {
|
|
|
137
126
|
// src/profile/store.ts
|
|
138
127
|
var DAY_MS = 864e5;
|
|
139
128
|
function profileFilePath(dataDir) {
|
|
140
|
-
return
|
|
129
|
+
return path3.join(dataDir, "profile.json");
|
|
141
130
|
}
|
|
142
131
|
function journalFilePath(dataDir) {
|
|
143
|
-
return
|
|
132
|
+
return path3.join(dataDir, "profile_journal.jsonl");
|
|
144
133
|
}
|
|
145
134
|
var SNAPSHOT_FILE = "profile_snapshot.json";
|
|
146
135
|
var ARCHIVE_PREFIX = "profile_journal.archive-";
|
|
147
136
|
function snapshotFilePath(dataDir) {
|
|
148
|
-
return
|
|
137
|
+
return path3.join(dataDir, SNAPSHOT_FILE);
|
|
149
138
|
}
|
|
150
139
|
function loadSnapshot(guard, dataDir) {
|
|
151
140
|
const snap = loadJson(guard, snapshotFilePath(dataDir));
|
|
@@ -154,7 +143,7 @@ function loadSnapshot(guard, dataDir) {
|
|
|
154
143
|
}
|
|
155
144
|
function listArchiveFiles(guard, dataDir) {
|
|
156
145
|
try {
|
|
157
|
-
return
|
|
146
|
+
return fs3.readdirSync(guard.assert(dataDir)).filter((f) => f.startsWith(ARCHIVE_PREFIX) && f.endsWith(".jsonl")).sort();
|
|
158
147
|
} catch {
|
|
159
148
|
return [];
|
|
160
149
|
}
|
|
@@ -185,7 +174,7 @@ function replayJournal(guard, dataDir) {
|
|
|
185
174
|
const stats = { records: snap?.recordsFolded ?? 0, truncatedTail: 0, isLive: false };
|
|
186
175
|
for (const file of listArchiveFiles(guard, dataDir)) {
|
|
187
176
|
try {
|
|
188
|
-
replayText(doc,
|
|
177
|
+
replayText(doc, fs3.readFileSync(path3.join(dataDir, file), "utf8"), baselineTs, { records: 0, truncatedTail: 0, isLive: false });
|
|
189
178
|
} catch {
|
|
190
179
|
}
|
|
191
180
|
}
|
|
@@ -193,6 +182,17 @@ function replayJournal(guard, dataDir) {
|
|
|
193
182
|
replayText(doc, readText(guard, journalFilePath(dataDir), ""), baselineTs, stats);
|
|
194
183
|
return { doc, truncatedTail: stats.truncatedTail, records: stats.records };
|
|
195
184
|
}
|
|
185
|
+
function readJournalTexts(guard, dataDir) {
|
|
186
|
+
const texts = [];
|
|
187
|
+
for (const file of listArchiveFiles(guard, dataDir)) {
|
|
188
|
+
try {
|
|
189
|
+
texts.push(fs3.readFileSync(path3.join(dataDir, file), "utf8"));
|
|
190
|
+
} catch {
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
texts.push(readText(guard, journalFilePath(dataDir), ""));
|
|
194
|
+
return texts;
|
|
195
|
+
}
|
|
196
196
|
function loadProfile(guard, file) {
|
|
197
197
|
const doc = loadJson(guard, file);
|
|
198
198
|
if (!doc || !doc.partitions) return emptyProfile();
|
|
@@ -208,9 +208,9 @@ function parseIso(v) {
|
|
|
208
208
|
function refExists(guard, dataDir, ref) {
|
|
209
209
|
const base = ref.split("#")[0] ?? "";
|
|
210
210
|
if (!base) return false;
|
|
211
|
-
const target =
|
|
211
|
+
const target = path3.join(dataDir, base);
|
|
212
212
|
try {
|
|
213
|
-
return
|
|
213
|
+
return fs3.existsSync(guard.assert(target));
|
|
214
214
|
} catch {
|
|
215
215
|
return false;
|
|
216
216
|
}
|
|
@@ -240,10 +240,19 @@ function applyOpsToDoc(guard, dataDir, doc, ops, schema, policy, now) {
|
|
|
240
240
|
rejected.push({ op, reason: "psy partition is disabled" });
|
|
241
241
|
continue;
|
|
242
242
|
}
|
|
243
|
-
|
|
244
|
-
if (
|
|
245
|
-
|
|
243
|
+
let temporal = "stable";
|
|
244
|
+
if (schema) {
|
|
245
|
+
const check = checkAddAgainstSchema(schema, op.partition, op.topic, op.subTopic, op.temporal);
|
|
246
|
+
if (!check.ok) {
|
|
247
|
+
rejected.push({ op, reason: check.reason });
|
|
248
|
+
continue;
|
|
249
|
+
}
|
|
250
|
+
temporal = check.temporal;
|
|
251
|
+
} else if (!(op.partition in doc.partitions)) {
|
|
252
|
+
rejected.push({ op, reason: `unknown partition: ${op.partition}` });
|
|
246
253
|
continue;
|
|
254
|
+
} else {
|
|
255
|
+
temporal = op.temporal === "volatile" ? "volatile" : "stable";
|
|
247
256
|
}
|
|
248
257
|
if (!op.evidence || op.evidence.length === 0) {
|
|
249
258
|
rejected.push({ op, reason: "ADD without evidence (no provenance, axiom 1)" });
|
|
@@ -262,13 +271,13 @@ function applyOpsToDoc(guard, dataDir, doc, ops, schema, policy, now) {
|
|
|
262
271
|
}
|
|
263
272
|
dbSeq += 1;
|
|
264
273
|
const entry = {
|
|
265
|
-
id: `p${dbSeq.toString(36)}${
|
|
274
|
+
id: `p${dbSeq.toString(36)}${randomUUID().slice(0, 4)}`,
|
|
266
275
|
partition: op.partition,
|
|
267
276
|
topic: op.topic,
|
|
268
277
|
subTopic: op.subTopic,
|
|
269
278
|
content: op.content.trim(),
|
|
270
279
|
confidence: Math.min(op.confidence ?? cap, cap),
|
|
271
|
-
temporal
|
|
280
|
+
temporal,
|
|
272
281
|
validFrom: nowIso,
|
|
273
282
|
validTo: null,
|
|
274
283
|
supersededBy: null,
|
|
@@ -357,17 +366,17 @@ function persistWithJournal(guard, dataDir, doc, record) {
|
|
|
357
366
|
const line = JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), ...record });
|
|
358
367
|
const journal = journalFilePath(dataDir);
|
|
359
368
|
try {
|
|
360
|
-
|
|
369
|
+
fs3.appendFileSync(guard.assert(journal), line + "\n", "utf8");
|
|
361
370
|
} catch {
|
|
362
|
-
|
|
363
|
-
|
|
371
|
+
fs3.mkdirSync(dataDir, { recursive: true });
|
|
372
|
+
fs3.appendFileSync(guard.assert(journal), line + "\n", "utf8");
|
|
364
373
|
}
|
|
365
374
|
}
|
|
366
375
|
function applyOpPermissive(doc, op, ts) {
|
|
367
376
|
if (op.op === "ADD") {
|
|
368
377
|
dbSeq += 1;
|
|
369
378
|
doc.partitions[op.partition].entries.push({
|
|
370
|
-
id: op.assignedId ?? `r${dbSeq.toString(36)}${
|
|
379
|
+
id: op.assignedId ?? `r${dbSeq.toString(36)}${randomUUID().slice(0, 4)}`,
|
|
371
380
|
partition: op.partition,
|
|
372
381
|
topic: op.topic,
|
|
373
382
|
subTopic: op.subTopic,
|
|
@@ -442,7 +451,7 @@ function rebuildProfile(guard, dataDir, opts = {}) {
|
|
|
442
451
|
if (replayed.truncatedTail > 0) {
|
|
443
452
|
writeText(
|
|
444
453
|
guard,
|
|
445
|
-
|
|
454
|
+
path3.join(dataDir, "logs", "rebuild-report.txt"),
|
|
446
455
|
`rebuild truncated ${replayed.truncatedTail} torn line(s) at journal tail; ${replayed.records} records applied
|
|
447
456
|
`
|
|
448
457
|
);
|
|
@@ -451,8 +460,6 @@ function rebuildProfile(guard, dataDir, opts = {}) {
|
|
|
451
460
|
}
|
|
452
461
|
|
|
453
462
|
export {
|
|
454
|
-
atomicWriteJsonSync,
|
|
455
|
-
shredFileSync,
|
|
456
463
|
appendAuditLine,
|
|
457
464
|
readAuditLines,
|
|
458
465
|
pruneAuditFile,
|
|
@@ -462,6 +469,7 @@ export {
|
|
|
462
469
|
ARCHIVE_PREFIX,
|
|
463
470
|
snapshotFilePath,
|
|
464
471
|
replayJournal,
|
|
472
|
+
readJournalTexts,
|
|
465
473
|
loadProfile,
|
|
466
474
|
applyOpsToDoc,
|
|
467
475
|
runDeterministicAging,
|
|
@@ -469,4 +477,4 @@ export {
|
|
|
469
477
|
verifyProfile,
|
|
470
478
|
rebuildProfile
|
|
471
479
|
};
|
|
472
|
-
//# sourceMappingURL=chunk-
|
|
480
|
+
//# sourceMappingURL=chunk-OYVNWB5G.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core/audit-log.ts","../src/profile/store.ts","../src/profile/types.ts","../src/profile/schema.ts"],"sourcesContent":["// Append-only JSONL audit log with age-based retention pruning.\n// Audit files are plaintext by charter (transparency), and must never contain\n// sensitive raw observations (window titles, conversation text).\n\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport { atomicWriteFileSync } from './atomic-fs.js';\n\nexport interface AuditEvent {\n ts: string;\n [key: string]: unknown;\n}\n\n/**\n * H-15: audit is best-effort by contract — a full disk, a locked log file or an\n * unserialisable payload must never abort a beat (one caller inside a catch\n * block turning a handled failure into an unhandled one is the worst case).\n * Failures go to stderr so they stay visible instead of silently vanishing.\n */\nexport function appendAuditLine(file: string, event: Omit<AuditEvent, 'ts'> & { ts?: string }): void {\n try {\n fs.mkdirSync(path.dirname(file), { recursive: true });\n const line = JSON.stringify({ ts: event.ts ?? new Date().toISOString(), ...event });\n fs.appendFileSync(file, line + '\\n', 'utf8');\n } catch (e) {\n process.stderr.write(`[heartbeat] audit append failed: ${file}: ${String(e)}\\n`);\n }\n}\n\nexport function readAuditLines<T = AuditEvent>(file: string): T[] {\n if (!fs.existsSync(file)) return [];\n const out: T[] = [];\n const raw = fs.readFileSync(file, 'utf8');\n for (const line of raw.split('\\n')) {\n const trimmed = line.trim();\n if (!trimmed) continue;\n try {\n out.push(JSON.parse(trimmed) as T);\n } catch {\n // Skip corrupt lines; the log is diagnostic, not authoritative data.\n out.push({ ts: '', corrupt: true, raw: trimmed.slice(0, 200) } as unknown as T);\n }\n }\n return out;\n}\n\n/**\n * Drop entries older than maxAgeMs. Returns the number of removed lines.\n * Rewrites the file atomically; on rewrite failure the original is untouched.\n */\nexport function pruneAuditFile(file: string, maxAgeMs: number, now = Date.now()): number {\n if (!fs.existsSync(file)) return 0;\n const lines = readAuditLines(file);\n const kept = lines.filter((e) => {\n const ev = e as unknown as AuditEvent;\n const ts = Date.parse(ev.ts ?? '');\n if (!Number.isFinite(ts)) return true; // keep unparseable lines, never lose audit data silently\n return now - ts <= maxAgeMs;\n });\n const removed = lines.length - kept.length;\n if (removed === 0) return 0;\n const body = kept.map((e) => JSON.stringify(e)).join('\\n');\n atomicWriteFileSync(file, body ? body + '\\n' : '');\n return removed;\n}\n","// Profile store: materialized view + journal (the journal is the ONLY\n// authority; profile.json is a pure projection - r2 §14 verify/rebuild).\n// Every LLM-proposed op passes deterministic guards here (LLM nominates,\n// code decides - §3.3).\n\nimport path from 'node:path';\nimport { randomUUID } from 'node:crypto';\nimport fs from 'node:fs';\nimport type { PathGuard } from '../core/path-guard.js';\nimport { loadJson, saveJson, readText, writeText } from '../vault/vault.js';\nimport type { Policy } from '../config/schema.js';\nimport {\n CONFIDENCE_CAP,\n emptyProfile,\n PARTITIONS,\n type ApplyReport,\n type Evidence,\n type EvidenceKind,\n type InboxItem,\n type JournalRecord,\n type Partition,\n type ProfileDoc,\n type ProfileEntry,\n type ProfileOp,\n} from './types.js';\nimport { checkAddAgainstSchema, type ProfileSchema } from './schema.js';\n\nconst DAY_MS = 86_400_000;\n\nexport function profileFilePath(dataDir: string): string {\n return path.join(dataDir, 'profile.json');\n}\n\nexport function journalFilePath(dataDir: string): string {\n return path.join(dataDir, 'profile_journal.jsonl');\n}\n\n// ── snapshot basepoint (v1.8.0, design doc §14 ③) ─────────────────────────\n// The journal grows forever; the snapshot folds its history into one DPAPI\n// document and moves the folded records into a timestamped archive shard.\n// Replay order: snapshot → archives (name order) → live journal, skipping\n// records at or before the snapshot baseline — so verify/rebuild stay correct\n// without knowing whether a snapshot exists.\n\nexport const SNAPSHOT_FILE = 'profile_snapshot.json';\nexport const ARCHIVE_PREFIX = 'profile_journal.archive-';\n\nexport interface ProfileSnapshot {\n version: 1;\n /** ts of the LAST journal record folded into `doc`. */\n baselineTs: string;\n recordsFolded: number;\n doc: ProfileDoc;\n}\n\nexport function snapshotFilePath(dataDir: string): string {\n return path.join(dataDir, SNAPSHOT_FILE);\n}\n\nfunction loadSnapshot(guard: PathGuard, dataDir: string): ProfileSnapshot | null {\n const snap = loadJson<ProfileSnapshot>(guard, snapshotFilePath(dataDir));\n if (!snap || snap.version !== 1 || typeof snap.baselineTs !== 'string' || !snap.doc?.partitions) return null;\n return snap;\n}\n\nfunction listArchiveFiles(guard: PathGuard, dataDir: string): string[] {\n try {\n return fs.readdirSync(guard.assert(dataDir))\n .filter((f) => f.startsWith(ARCHIVE_PREFIX) && f.endsWith('.jsonl'))\n .sort(); // timestamped names sort chronologically\n } catch {\n return [];\n }\n}\n\n/** Replay one JSONL text into `doc`, skipping records at/before the baseline. */\nfunction replayText(doc: ProfileDoc, text: string, baselineTs: string, stats: { records: number; truncatedTail: number; isLive: boolean }): void {\n const lines = text.split('\\n');\n for (let i = 0; i < lines.length; i++) {\n const trimmed = lines[i]!.trim();\n if (!trimmed) continue;\n try {\n const rec = JSON.parse(trimmed) as JournalRecord;\n // Baseline skip: only records CARRYING a ts can be proven folded. A\n // ts-less record is always applied (applyOpPermissive tolerates it).\n if (baselineTs && rec.ts && String(rec.ts) <= baselineTs) continue;\n for (const op of rec.applied ?? []) applyOpPermissive(doc, op, rec.ts);\n stats.records += 1;\n } catch {\n // Torn tail only counts on the LIVE journal — archives are closed files.\n const isLast = lines.slice(i + 1).every((l) => !l.trim());\n if (stats.isLive && isLast) {\n stats.truncatedTail = lines.length - i;\n break;\n }\n // mid-file corrupt record: skip (journal stays append-only/immutable)\n }\n }\n}\n\n/** Full replay: snapshot basepoint + archive shards + live journal. */\nexport function replayJournal(guard: PathGuard, dataDir: string): ReplayResult {\n const snap = loadSnapshot(guard, dataDir);\n const doc = snap ? snap.doc : emptyProfile();\n const baselineTs = snap?.baselineTs ?? '';\n const stats = { records: snap?.recordsFolded ?? 0, truncatedTail: 0, isLive: false };\n for (const file of listArchiveFiles(guard, dataDir)) {\n try {\n // shard record counts are not reported (the snapshot already folded\n // them); only the live journal's torn tail is meaningful.\n replayText(doc, fs.readFileSync(path.join(dataDir, file), 'utf8'), baselineTs, { records: 0, truncatedTail: 0, isLive: false });\n } catch { /* unreadable shard: skip */ }\n }\n stats.isLive = true;\n replayText(doc, readText(guard, journalFilePath(dataDir), ''), baselineTs, stats);\n return { doc, truncatedTail: stats.truncatedTail, records: stats.records };\n}\n\n/** Every plaintext journal text in chronological order: archive shards first,\n * then the live journal — the RAW record timeline, for callers that need the\n * history itself rather than the accumulated doc (the weekly report counts\n * the week's ADD ops). A rotation must not hide history from them (H-01). */\nexport function readJournalTexts(guard: PathGuard, dataDir: string): string[] {\n const texts: string[] = [];\n for (const file of listArchiveFiles(guard, dataDir)) {\n try {\n texts.push(fs.readFileSync(path.join(dataDir, file), 'utf8'));\n } catch { /* unreadable shard: skip */ }\n }\n texts.push(readText(guard, journalFilePath(dataDir), ''));\n return texts;\n}\n\nexport function loadProfile(guard: PathGuard, file: string): ProfileDoc {\n const doc = loadJson<ProfileDoc>(guard, file);\n if (!doc || !doc.partitions) return emptyProfile();\n // ensure all partitions exist\n for (const p of PARTITIONS) {\n if (!doc.partitions[p]) doc.partitions[p] = { entries: [] };\n }\n return doc;\n}\n\nfunction parseIso(v: string): number {\n const t = Date.parse(v);\n return Number.isFinite(t) ? t : 0;\n}\n\n/** ref format \"<datafile>#<locator>\": the referenced file must exist under data/. */\nfunction refExists(guard: PathGuard, dataDir: string, ref: string): boolean {\n const base = ref.split('#')[0] ?? '';\n if (!base) return false;\n const target = path.join(dataDir, base);\n try {\n return fs.existsSync(guard.assert(target));\n } catch {\n return false;\n }\n}\n\nfunction capForKinds(kinds: EvidenceKind[]): number {\n if (kinds.length === 0) return 0.4;\n return Math.min(...kinds.map((k) => CONFIDENCE_CAP[k] ?? 0.4));\n}\n\nfunction findActive(doc: ProfileDoc, id: string): ProfileEntry | undefined {\n for (const p of PARTITIONS) {\n const hit = doc.partitions[p]!.entries.find((e) => e.id === id && e.validTo === null);\n if (hit) return hit;\n }\n return undefined;\n}\n\n/**\n * Apply validated ops to the doc (mutates). Returns applied/rejected.\n * Guards (§3.3): whitelist, evidence gate, per-run ops cap is enforced by the\n * caller, confidence caps by evidence kind, psy gating, INVALIDATE ownership:\n * volatile expiry is code-driven (LLM may not), stable invalidation needs a\n * contradicting observation attached.\n */\nexport function applyOpsToDoc(\n guard: PathGuard,\n dataDir: string,\n doc: ProfileDoc,\n ops: ProfileOp[],\n schema: ProfileSchema | undefined,\n policy: Policy,\n now: number,\n): ApplyReport {\n const applied: ProfileOp[] = [];\n const rejected: { op: ProfileOp; reason: string }[] = [];\n const nowIso = new Date(now).toISOString();\n\n for (const op of ops) {\n if (op.op === 'NOOP') {\n applied.push(op);\n continue;\n }\n if (op.op === 'ADD') {\n if (op.partition === 'psy' && !policy.profile.psyEnabled) {\n rejected.push({ op, reason: 'psy partition is disabled' });\n continue;\n }\n let temporal: 'stable' | 'volatile' = 'stable';\n if (schema) {\n const check = checkAddAgainstSchema(schema, op.partition, op.topic, op.subTopic, op.temporal);\n if (!check.ok) {\n rejected.push({ op, reason: check.reason! });\n continue;\n }\n temporal = check.temporal;\n } else if (!(op.partition in doc.partitions)) {\n // H-17: no usable schema — the partition set is the only boundary left.\n rejected.push({ op, reason: `unknown partition: ${op.partition}` });\n continue;\n } else {\n temporal = op.temporal === 'volatile' ? 'volatile' : 'stable';\n }\n if (!op.evidence || op.evidence.length === 0) {\n rejected.push({ op, reason: 'ADD without evidence (no provenance, axiom 1)' });\n continue;\n }\n const badRef = op.evidence.find((e) => !refExists(guard, dataDir, e.ref));\n if (badRef) {\n rejected.push({ op, reason: `evidence ref does not resolve: ${badRef.ref}` });\n continue;\n }\n const cap = capForKinds(op.evidence.map((e) => e.kind));\n const active = doc.partitions[op.partition]!.entries.filter((e) => e.validTo === null);\n if (active.length >= policy.profile.partitionCap) {\n rejected.push({ op, reason: `partition ${op.partition} at cap (${policy.profile.partitionCap}); converge first` });\n continue;\n }\n dbSeq += 1;\n const entry: ProfileEntry = {\n id: `p${dbSeq.toString(36)}${randomUUID().slice(0, 4)}`,\n partition: op.partition,\n topic: op.topic,\n subTopic: op.subTopic,\n content: op.content.trim(),\n confidence: Math.min(op.confidence ?? cap, cap),\n temporal,\n validFrom: nowIso,\n validTo: null,\n supersededBy: null,\n evidence: op.evidence,\n createdAt: nowIso,\n updatedAt: nowIso,\n updateCount: 0,\n };\n op.assignedId = entry.id; // journal replay must reproduce this id\n doc.partitions[op.partition]!.entries.push(entry);\n applied.push(op);\n continue;\n }\n if (op.op === 'UPDATE') {\n const entry = findActive(doc, op.id);\n if (!entry) {\n rejected.push({ op, reason: `unknown or inactive entry: ${op.id}` });\n continue;\n }\n if (op.changes.content !== undefined) entry.content = op.changes.content.trim();\n if (op.changes.confidence !== undefined) {\n const cap = capForKinds(entry.evidence.map((e) => e.kind));\n // confidence upgrades need a second confirming observation (§3.3):\n // only allowed up to cap, and only when the entry already has 2+ evidence\n if (op.changes.confidence > entry.confidence && entry.evidence.length < 2) {\n rejected.push({ op, reason: 'confidence upgrade requires a second confirming observation' });\n continue;\n }\n entry.confidence = Math.min(op.changes.confidence, cap);\n }\n entry.updatedAt = nowIso;\n entry.updateCount += 1;\n applied.push(op);\n continue;\n }\n if (op.op === 'INVALIDATE') {\n const entry = findActive(doc, op.id);\n if (!entry) {\n rejected.push({ op, reason: `unknown or inactive entry: ${op.id}` });\n continue;\n }\n if (entry.temporal === 'volatile') {\n rejected.push({ op, reason: 'volatile expiry is code-owned (time-driven), not LLM-nominated' });\n continue;\n }\n // stable: rebuttal-driven; requires a contradicting observation attached\n const hasNewObservation = (op.evidence ?? []).length > 0\n && (op.evidence ?? []).some((e) => parseIso(e.at) > parseIso(entry.evidence[entry.evidence.length - 1]?.at ?? ''));\n if (!hasNewObservation) {\n rejected.push({ op, reason: 'stable INVALIDATE requires a newer contradicting observation' });\n continue;\n }\n entry.validTo = nowIso;\n entry.supersededBy = null;\n entry.updatedAt = nowIso;\n entry.updateCount += 1;\n if (op.evidence) entry.evidence.push(...op.evidence);\n applied.push(op);\n continue;\n }\n }\n return { applied, rejected };\n}\n\nlet dbSeq = 0;\n\n/** Deterministic aging (D9): volatile expiry + stable low-activity marking. */\nexport function runDeterministicAging(\n doc: ProfileDoc,\n policy: Policy,\n now: number,\n): { volatileExpired: number; lowActivityMarked: number } {\n const nowIso = new Date(now).toISOString();\n let volatileExpired = 0;\n let lowActivityMarked = 0;\n for (const p of PARTITIONS) {\n for (const e of doc.partitions[p]!.entries) {\n if (e.validTo !== null) continue;\n const lastEvidence = Math.max(...e.evidence.map((x) => parseIso(x.at)), parseIso(e.updatedAt));\n if (e.temporal === 'volatile') {\n if (now - lastEvidence > policy.profile.volatileDays * DAY_MS) {\n e.validTo = nowIso;\n e.updatedAt = nowIso;\n e.updateCount += 1;\n volatileExpired += 1;\n }\n } else if (!e.lowActivity && now - lastEvidence > policy.profile.stableLowActivityDays * DAY_MS) {\n e.lowActivity = true;\n lowActivityMarked += 1;\n }\n }\n }\n return { volatileExpired, lowActivityMarked };\n}\n\n/** Persist the materialized view atomically + append the journal record. */\nexport function persistWithJournal(\n guard: PathGuard,\n dataDir: string,\n doc: ProfileDoc,\n record: Omit<JournalRecord, 'ts'>,\n): void {\n saveJson(guard, profileFilePath(dataDir), doc);\n const line = JSON.stringify({ ts: new Date().toISOString(), ...record });\n const journal = journalFilePath(dataDir);\n try {\n fs.appendFileSync(guard.assert(journal), line + '\\n', 'utf8');\n } catch {\n fs.mkdirSync(dataDir, { recursive: true });\n fs.appendFileSync(guard.assert(journal), line + '\\n', 'utf8');\n }\n}\n\n// ── journal replay (verify / rebuild, r2 §14) ───────────────────────────\n\nfunction applyOpPermissive(doc: ProfileDoc, op: ProfileOp, ts: string): void {\n // Journal is trusted history: replay applies without revalidation.\n if (op.op === 'ADD') {\n dbSeq += 1;\n doc.partitions[op.partition]!.entries.push({\n id: op.assignedId ?? `r${dbSeq.toString(36)}${randomUUID().slice(0, 4)}`,\n partition: op.partition,\n topic: op.topic,\n subTopic: op.subTopic,\n content: op.content,\n confidence: op.confidence ?? 0.5,\n temporal: op.temporal ?? 'stable',\n validFrom: ts,\n validTo: null,\n supersededBy: null,\n evidence: op.evidence,\n createdAt: ts,\n updatedAt: ts,\n updateCount: 0,\n });\n return;\n }\n if (op.op === 'UPDATE') {\n const e = [...PARTITIONS].flatMap((p) => doc.partitions[p]!.entries).find((x) => x.id === op.id);\n if (e) {\n if (op.changes.content !== undefined) e.content = op.changes.content;\n if (op.changes.confidence !== undefined) e.confidence = op.changes.confidence;\n e.updatedAt = ts;\n e.updateCount += 1;\n }\n return;\n }\n if (op.op === 'INVALIDATE') {\n const e = [...PARTITIONS].flatMap((p) => doc.partitions[p]!.entries).find((x) => x.id === op.id);\n if (e) {\n e.validTo = ts;\n e.updatedAt = ts;\n e.updateCount += 1;\n }\n }\n // NOOP: nothing\n}\n\nexport interface ReplayResult {\n doc: ProfileDoc;\n truncatedTail: number;\n records: number;\n}\n\nexport interface VerifyReport {\n ok: boolean;\n firstDivergence?: { id: string; expected: string; actual: string };\n truncatedTail: number;\n records: number;\n}\n\n/** profile verify: replay vs disk, report first divergence, never fix. */\nexport function verifyProfile(guard: PathGuard, dataDir: string): VerifyReport {\n const replayed = replayJournal(guard, dataDir);\n const onDisk = loadProfile(guard, profileFilePath(dataDir));\n // Timestamps legitimately differ (journal record ts >= op ts); identity\n // fields are also normalized. Compare semantic content only.\n const strip = (doc: ProfileDoc): string =>\n JSON.stringify(doc.partitions, (k, v) => (['id', 'supersededBy', 'validFrom', 'createdAt', 'updatedAt', 'retiredAt'].includes(k) ? '<norm>' : v));\n const ok = strip(replayed.doc) === strip(onDisk);\n if (ok) return { ok: true, truncatedTail: replayed.truncatedTail, records: replayed.records };\n // first divergence: first entry id present in only one view\n const diskIds = new Set([...PARTITIONS].flatMap((p) => onDisk.partitions[p]!.entries.map((e) => e.content))); const replayIds = new Set([...PARTITIONS].flatMap((p) => replayed.doc.partitions[p]!.entries.map((e) => e.content)));\n const onlyDisk = [...diskIds].find((c) => !replayIds.has(c));\n const onlyReplay = [...replayIds].find((c) => !diskIds.has(c));\n return {\n ok: false,\n firstDivergence: {\n id: onlyDisk ?? onlyReplay ?? '(content)',\n expected: onlyReplay ? 'absent in journal replay' : 'present in journal replay',\n actual: onlyDisk ? 'present on disk' : 'absent on disk',\n },\n truncatedTail: replayed.truncatedTail,\n records: replayed.records,\n };\n}\n\nexport interface RebuildReport {\n ok: boolean;\n truncatedTail: number;\n records: number;\n wrote: boolean;\n}\n\n/** profile rebuild: journal is authoritative; atomic replace; torn tail reported. */\nexport function rebuildProfile(\n guard: PathGuard,\n dataDir: string,\n opts: { check?: boolean } = {},\n): RebuildReport & { diffSummary?: string } {\n const replayed = replayJournal(guard, dataDir);\n const target = profileFilePath(dataDir);\n if (opts.check) {\n const onDisk = loadProfile(guard, target);\n const same = JSON.stringify(onDisk) === JSON.stringify(replayed.doc);\n return {\n ok: same,\n truncatedTail: replayed.truncatedTail,\n records: replayed.records,\n wrote: false,\n diffSummary: same ? 'no diff' : 'materialized view differs from journal replay',\n };\n }\n saveJson(guard, target, replayed.doc);\n if (replayed.truncatedTail > 0) {\n // explicit audit: never silently rebuild a view that lost its tail\n writeText(\n guard,\n path.join(dataDir, 'logs', 'rebuild-report.txt'),\n `rebuild truncated ${replayed.truncatedTail} torn line(s) at journal tail; ${replayed.records} records applied\\n`,\n );\n }\n return { ok: true, truncatedTail: replayed.truncatedTail, records: replayed.records, wrote: true };\n}\n","// User profile types (design doc §3). Fourth store: warm, structured,\n// slow-evolving model of the user. Not the material pool (hot cache), not\n// DSH long-term memory (conversation-validated layer).\n\nexport type Partition = 'interest' | 'projects' | 'comm' | 'psy';\nexport type Temporal = 'volatile' | 'stable';\nexport type EvidenceKind = 'chat' | 'screen' | 'browse' | 'hand' | 'ledger';\n\nexport interface Evidence {\n kind: EvidenceKind;\n at: string;\n /** Pointer to an existing audit/log location, e.g. \"heartbeat.jsonl#2026-09-06T12:00:00Z\". */\n ref: string;\n /** At most ONE short quote (<=1 sentence). Never raw conversation/screen text. */\n quote?: string;\n}\n\nexport interface ProfileEntry {\n id: string;\n partition: Partition;\n topic: string;\n subTopic: string;\n content: string;\n /** 0..1; capped per source kind (chat .6 / screen .4 / browse .4). */\n confidence: number;\n temporal: Temporal;\n validFrom: string;\n validTo: string | null;\n supersededBy: string | null;\n evidence: Evidence[];\n createdAt: string;\n updatedAt: string;\n updateCount: number;\n /** stable-tier audit flag (180d without observation): digest deprioritizes. */\n lowActivity?: boolean;\n}\n\nexport interface ProfileDoc {\n version: number;\n partitions: Record<Partition, { entries: ProfileEntry[] }>;\n}\n\nexport interface InboxItem {\n id?: string;\n kind: EvidenceKind;\n at: string;\n ref: string;\n /** <= 1 short sentence; never raw conversation/screen text (truncated by writer). */\n note: string;\n}\n\nexport type ProfileOp =\n | {\n op: 'ADD';\n partition: Partition;\n topic: string;\n subTopic: string;\n content: string;\n temporal?: Temporal;\n confidence?: number;\n why: string;\n evidence: Evidence[];\n /** Assigned by the store at apply time so journal replay preserves ids. */\n assignedId?: string;\n }\n | { op: 'UPDATE'; id: string; changes: { content?: string; confidence?: number }; why: string }\n | { op: 'INVALIDATE'; id: string; why: string; evidence?: Evidence[] }\n | { op: 'NOOP'; why: string };\n\nexport interface JournalRecord {\n ts: string;\n runId: string;\n applied: ProfileOp[];\n rejected: { op: ProfileOp; reason: string }[];\n}\n\nexport interface ApplyReport {\n applied: ProfileOp[];\n rejected: { op: ProfileOp; reason: string }[];\n}\n\nexport const PARTITIONS: readonly Partition[] = ['interest', 'projects', 'comm', 'psy'];\n\nexport const CONFIDENCE_CAP: Record<EvidenceKind, number> = {\n chat: 0.6,\n screen: 0.4,\n browse: 0.4,\n hand: 1.0,\n ledger: 0.6,\n};\n\nexport function emptyProfile(): ProfileDoc {\n return {\n version: 1,\n partitions: { interest: { entries: [] }, projects: { entries: [] }, comm: { entries: [] }, psy: { entries: [] } },\n };\n}\n","// profile-schema.json loading + ADD validation (r4 B10: temporal tier is an\n// ENTRY-level attribute; the schema declares, per sub_topic, the ALLOWED tier\n// set and the DEFAULT. Unlisted defaults to 'stable'.)\n\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport type { Partition, Temporal } from './types.js';\n\nexport interface SubTopicDecl {\n allowed?: Temporal[];\n default?: Temporal;\n}\n\nexport interface ProfileSchema {\n version: number;\n partitions: Partial<Record<Partition, {\n topics: Record<string, { subtopics: Record<string, SubTopicDecl> }>;\n }>>;\n}\n\nexport interface SchemaLoad {\n /** The schema to use, or undefined when even the factory copy is unusable. */\n schema?: ProfileSchema;\n /** Where the returned schema came from. */\n source: 'user' | 'factory' | 'none';\n /** Set when the chosen schema is not the first choice (H-17 fallback). */\n fallbackReason?: string;\n}\n\n/**\n * H-17: three-tier load. This file is user-editable by design, so a broken\n * comma used to throw on every beat — `runConsolidation` aborted before\n * `inboxClear`, the inbox grew forever and the profile froze with no way back.\n * Now: a bad user copy falls back to the factory copy (audited by the caller);\n * an unusable factory copy yields `undefined`, which callers read as \"no\n * whitelist\" instead of a fatal error.\n */\nexport function loadProfileSchema(paths: { configDir: string; settingsDir: string }): SchemaLoad {\n const userPath = path.join(paths.settingsDir, 'profile-schema.json');\n const factoryPath = path.join(paths.configDir, 'profile-schema.json');\n const read = (file: string): { schema?: ProfileSchema; error: string } => {\n try {\n const raw = JSON.parse(fs.readFileSync(file, 'utf8')) as ProfileSchema;\n if (!raw.partitions) return { error: 'partitions missing' };\n return { schema: raw, error: '' };\n } catch (e) {\n return { error: String(e) };\n }\n };\n const user = fs.existsSync(userPath) ? read(userPath) : null;\n if (user?.schema) return { schema: user.schema, source: 'user' };\n const factory = read(factoryPath);\n if (factory.schema) {\n return user\n ? {\n schema: factory.schema,\n source: 'factory',\n fallbackReason: `user profile-schema.json unusable (${user.error}) — fell back to the factory copy`,\n }\n : { schema: factory.schema, source: 'factory' };\n }\n return {\n source: 'none',\n fallbackReason: `no usable profile-schema.json (user: ${user?.error ?? 'absent'}; factory: ${factory.error})`,\n };\n}\n\nexport interface SchemaCheck {\n ok: boolean;\n reason?: string;\n temporal: Temporal;\n}\n\n/**\n * Validate an ADD against the whitelist and resolve the entry's temporal tier:\n * LLM nominates within the sub_topic's allowed set; missing nomination falls\n * back to the declared default; fully unlisted -> reject (charter boundary).\n */\nexport function checkAddAgainstSchema(\n schema: ProfileSchema,\n partition: Partition,\n topic: string,\n subTopic: string,\n nominated?: Temporal,\n): SchemaCheck {\n const p = schema.partitions[partition];\n if (!p) return { ok: false, reason: `partition not in schema: ${partition}`, temporal: 'stable' };\n const t = p.topics[topic];\n if (!t) return { ok: false, reason: `topic not in schema: ${partition}/${topic}`, temporal: 'stable' };\n const st = t.subtopics[subTopic];\n if (!st) return { ok: false, reason: `sub_topic not in schema: ${partition}/${topic}/${subTopic}`, temporal: 'stable' };\n const allowed: Temporal[] = st.allowed && st.allowed.length > 0 ? st.allowed : ['stable'];\n const def: Temporal = st.default && allowed.includes(st.default) ? st.default : allowed[0]!;\n if (!nominated) return { ok: true, temporal: def };\n if (!allowed.includes(nominated)) {\n return {\n ok: false,\n reason: `temporal \"${nominated}\" not allowed for ${partition}/${topic}/${subTopic} (allowed: ${allowed.join('|')})`,\n temporal: def,\n };\n }\n return { ok: true, temporal: nominated };\n}\n"],"mappings":"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","names":["path","fs","fs","path","path","fs"]}
|
|
@@ -4,10 +4,10 @@ import {
|
|
|
4
4
|
journalFilePath,
|
|
5
5
|
replayJournal,
|
|
6
6
|
snapshotFilePath
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-OYVNWB5G.js";
|
|
8
8
|
import {
|
|
9
9
|
saveJson
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-7TW6DD6Q.js";
|
|
11
11
|
|
|
12
12
|
// src/profile/snapshot.ts
|
|
13
13
|
import fs from "fs";
|
|
@@ -84,4 +84,4 @@ export {
|
|
|
84
84
|
snapshotProfile,
|
|
85
85
|
snapshotIfDue
|
|
86
86
|
};
|
|
87
|
-
//# sourceMappingURL=chunk-
|
|
87
|
+
//# sourceMappingURL=chunk-QOQ7EPZZ.js.map
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import {
|
|
2
|
+
atomicWriteFileSync
|
|
3
|
+
} from "./chunk-WRUTATW4.js";
|
|
4
|
+
|
|
1
5
|
// src/core/bindings.ts
|
|
2
6
|
import fs from "fs";
|
|
3
7
|
import path from "path";
|
|
@@ -16,7 +20,7 @@ function loadBindings(guard, settingsDir) {
|
|
|
16
20
|
function saveBindings(guard, settingsDir, data) {
|
|
17
21
|
const file = guard.assert(bindingsFilePath(settingsDir));
|
|
18
22
|
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
19
|
-
|
|
23
|
+
atomicWriteFileSync(file, JSON.stringify(data, null, 2));
|
|
20
24
|
}
|
|
21
25
|
function addBinding(guard, settingsDir, sessionId, opts = {}) {
|
|
22
26
|
const data = loadBindings(guard, settingsDir);
|
|
@@ -61,4 +65,4 @@ export {
|
|
|
61
65
|
deliverTargets,
|
|
62
66
|
observeTargets
|
|
63
67
|
};
|
|
64
|
-
//# sourceMappingURL=chunk-
|
|
68
|
+
//# sourceMappingURL=chunk-TY6UP74X.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core/bindings.ts"],"sourcesContent":["// Session bindings (design doc D13): binding = ①被心跳观察对话事件(inbox\n// 来源)+ ②主动消息投递目标。Canonical storage: data/settings/bindings.json\n// (user layer - survives uninstall/reinstall, D15). Edited via CLI or hand.\n\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport type { PathGuard } from './path-guard.js';\nimport { atomicWriteFileSync } from './atomic-fs.js';\n\nexport interface SessionBinding {\n sessionId: string;\n /** Deliver heartbeat expressions into this session's inbox (live only). */\n deliver: boolean;\n /** Observe this session's new user messages into the profile inbox. */\n observe: boolean;\n addedAt: string;\n}\n\nexport interface BindingsFile {\n bindings: SessionBinding[];\n}\n\nexport function bindingsFilePath(settingsDir: string): string {\n return path.join(settingsDir, 'bindings.json');\n}\n\nexport function loadBindings(guard: PathGuard, settingsDir: string): BindingsFile {\n const file = bindingsFilePath(settingsDir);\n try {\n const raw = JSON.parse(fs.readFileSync(guard.assert(file), 'utf8')) as BindingsFile;\n return { bindings: Array.isArray(raw.bindings) ? raw.bindings : [] };\n } catch {\n return { bindings: [] };\n }\n}\n\nexport function saveBindings(guard: PathGuard, settingsDir: string, data: BindingsFile): void {\n const file = guard.assert(bindingsFilePath(settingsDir));\n fs.mkdirSync(path.dirname(file), { recursive: true });\n atomicWriteFileSync(file, JSON.stringify(data, null, 2));\n}\n\nexport function addBinding(\n guard: PathGuard,\n settingsDir: string,\n sessionId: string,\n opts: { deliver?: boolean; observe?: boolean } = {},\n): SessionBinding {\n const data = loadBindings(guard, settingsDir);\n const existing = data.bindings.find((b) => b.sessionId === sessionId);\n if (existing) {\n existing.deliver = opts.deliver ?? existing.deliver;\n existing.observe = opts.observe ?? existing.observe;\n saveBindings(guard, settingsDir, data);\n return existing;\n }\n const binding: SessionBinding = {\n sessionId,\n deliver: opts.deliver ?? true,\n observe: opts.observe ?? false,\n addedAt: new Date().toISOString(),\n };\n data.bindings.push(binding);\n saveBindings(guard, settingsDir, data);\n return binding;\n}\n\nexport function removeBinding(guard: PathGuard, settingsDir: string, sessionId: string): boolean {\n const data = loadBindings(guard, settingsDir);\n const before = data.bindings.length;\n data.bindings = data.bindings.filter((b) => b.sessionId !== sessionId);\n if (data.bindings.length === before) return false;\n saveBindings(guard, settingsDir, data);\n return true;\n}\n\nexport function deliverTargets(data: BindingsFile): SessionBinding[] {\n return data.bindings.filter((b) => b.deliver);\n}\n\nexport function observeTargets(data: BindingsFile): SessionBinding[] {\n return data.bindings.filter((b) => b.observe);\n}\n"],"mappings":";;;;;AAIA,OAAO,QAAQ;AACf,OAAO,UAAU;AAiBV,SAAS,iBAAiB,aAA6B;AAC5D,SAAO,KAAK,KAAK,aAAa,eAAe;AAC/C;AAEO,SAAS,aAAa,OAAkB,aAAmC;AAChF,QAAM,OAAO,iBAAiB,WAAW;AACzC,MAAI;AACF,UAAM,MAAM,KAAK,MAAM,GAAG,aAAa,MAAM,OAAO,IAAI,GAAG,MAAM,CAAC;AAClE,WAAO,EAAE,UAAU,MAAM,QAAQ,IAAI,QAAQ,IAAI,IAAI,WAAW,CAAC,EAAE;AAAA,EACrE,QAAQ;AACN,WAAO,EAAE,UAAU,CAAC,EAAE;AAAA,EACxB;AACF;AAEO,SAAS,aAAa,OAAkB,aAAqB,MAA0B;AAC5F,QAAM,OAAO,MAAM,OAAO,iBAAiB,WAAW,CAAC;AACvD,KAAG,UAAU,KAAK,QAAQ,IAAI,GAAG,EAAE,WAAW,KAAK,CAAC;AACpD,sBAAoB,MAAM,KAAK,UAAU,MAAM,MAAM,CAAC,CAAC;AACzD;AAEO,SAAS,WACd,OACA,aACA,WACA,OAAiD,CAAC,GAClC;AAChB,QAAM,OAAO,aAAa,OAAO,WAAW;AAC5C,QAAM,WAAW,KAAK,SAAS,KAAK,CAAC,MAAM,EAAE,cAAc,SAAS;AACpE,MAAI,UAAU;AACZ,aAAS,UAAU,KAAK,WAAW,SAAS;AAC5C,aAAS,UAAU,KAAK,WAAW,SAAS;AAC5C,iBAAa,OAAO,aAAa,IAAI;AACrC,WAAO;AAAA,EACT;AACA,QAAM,UAA0B;AAAA,IAC9B;AAAA,IACA,SAAS,KAAK,WAAW;AAAA,IACzB,SAAS,KAAK,WAAW;AAAA,IACzB,UAAS,oBAAI,KAAK,GAAE,YAAY;AAAA,EAClC;AACA,OAAK,SAAS,KAAK,OAAO;AAC1B,eAAa,OAAO,aAAa,IAAI;AACrC,SAAO;AACT;AAEO,SAAS,cAAc,OAAkB,aAAqB,WAA4B;AAC/F,QAAM,OAAO,aAAa,OAAO,WAAW;AAC5C,QAAM,SAAS,KAAK,SAAS;AAC7B,OAAK,WAAW,KAAK,SAAS,OAAO,CAAC,MAAM,EAAE,cAAc,SAAS;AACrE,MAAI,KAAK,SAAS,WAAW,OAAQ,QAAO;AAC5C,eAAa,OAAO,aAAa,IAAI;AACrC,SAAO;AACT;AAEO,SAAS,eAAe,MAAsC;AACnE,SAAO,KAAK,SAAS,OAAO,CAAC,MAAM,EAAE,OAAO;AAC9C;AAEO,SAAS,eAAe,MAAsC;AACnE,SAAO,KAAK,SAAS,OAAO,CAAC,MAAM,EAAE,OAAO;AAC9C;","names":[]}
|