mover-os 4.7.8 → 4.7.10
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 +34 -24
- package/install.js +2229 -204
- package/package.json +3 -2
- package/src/dashboard/build.js +41 -3
- package/src/dashboard/lib/active-context-parser.js +16 -4
- package/src/dashboard/lib/agent-session.js +70 -49
- package/src/dashboard/lib/approval-registry.js +170 -0
- package/src/dashboard/lib/daily-note-resolver.js +20 -3
- package/src/dashboard/lib/date-utils.js +9 -1
- package/src/dashboard/lib/distribution-parser.js +69 -10
- package/src/dashboard/lib/drift-history.js +6 -1
- package/src/dashboard/lib/engine-default-fingerprints.json +53 -0
- package/src/dashboard/lib/engine-health.js +4 -1
- package/src/dashboard/lib/engine-writer.js +157 -11
- package/src/dashboard/lib/goal-forecast.js +154 -31
- package/src/dashboard/lib/library-indexer-v2.js +14 -0
- package/src/dashboard/lib/library-search.js +290 -0
- package/src/dashboard/lib/log-activation.sh +0 -0
- package/src/dashboard/lib/memory-index.js +61 -12
- package/src/dashboard/lib/pid-markers.js +80 -0
- package/src/dashboard/lib/regenerate-manifest.js +0 -0
- package/src/dashboard/lib/run-registry.js +75 -15
- package/src/dashboard/lib/state-core/backfill.js +298 -0
- package/src/dashboard/lib/state-core/dryrun.js +188 -0
- package/src/dashboard/lib/state-core/event-log.js +615 -0
- package/src/dashboard/lib/state-core/events.js +265 -0
- package/src/dashboard/lib/state-core/projections.js +376 -0
- package/src/dashboard/lib/state-core/start-close.js +162 -0
- package/src/dashboard/lib/state-core/trial.js +96 -0
- package/src/dashboard/lib/strategy-parser.js +3 -0
- package/src/dashboard/lib/suggested-now.js +2 -2
- package/src/dashboard/lib/transcript-parser.js +48 -8
- package/src/dashboard/server.js +422 -44
- package/src/dashboard/shortcut.js +0 -0
- package/src/dashboard/ui/dist/assets/index-BoxTW_kj.js +161 -0
- package/src/dashboard/ui/dist/assets/index-CZWNQDt5.css +1 -0
- package/src/dashboard/ui/dist/assets/index-wnamvqxp.js +34 -0
- package/src/dashboard/ui/dist/index.html +2 -2
- package/src/dashboard/ui/dist/sw.js +1 -1
- package/src/system/counts.json +7 -0
- package/src/dashboard/ui/dist/assets/index-598CSGOZ.js +0 -157
- package/src/dashboard/ui/dist/assets/index-BP--M69H.css +0 -1
- package/src/dashboard/ui/dist/assets/index-CidzmwSW.js +0 -34
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
// dryrun.js — CORE-LOOP PHASE 2 (SCHEMA-SPEC v0.2 section 4, steps 2-3).
|
|
4
|
+
// The migration contract the owner inspects BEFORE anything touches a live
|
|
5
|
+
// vault: predictState computes the exact bytes a migration would write
|
|
6
|
+
// (events.jsonl + the four projection files) WITHOUT writing; applyState
|
|
7
|
+
// writes them to a pristine copy, re-reads every file from disk, and throws
|
|
8
|
+
// unless predicted and actual sha256 match byte-for-byte. The manifest is
|
|
9
|
+
// the lab's dry-run.js pattern promoted to the product path.
|
|
10
|
+
//
|
|
11
|
+
// Determinism: the caller captures ONE `now` and passes it to both predict
|
|
12
|
+
// and apply (rebuiltAt is operational metadata outside the hashed projection
|
|
13
|
+
// body, but it is inside the FILE bytes, so byte-identity needs the same
|
|
14
|
+
// clock reading). Event ids are fresh UUIDs per backfill run, so the
|
|
15
|
+
// manifest binds one run's events to one run's files — rerunning backfill
|
|
16
|
+
// produces the same dedupeKeys and facts, not the same ids.
|
|
17
|
+
|
|
18
|
+
const fs = require("fs");
|
|
19
|
+
const path = require("path");
|
|
20
|
+
const { serializeEnvelope, sha256Hex, stableStringify, isValidEnvelope, acquireLock } = require("./event-log");
|
|
21
|
+
const { resolveEffectiveEvents, computeMetrics, computeStreak, computeBet, computeDrift, rebuildProjections, PROJECTION_VERSION } = require("./projections");
|
|
22
|
+
|
|
23
|
+
const STATE_DIR = path.join("02_Areas", "Engine", "State");
|
|
24
|
+
const LOG_NAME = "events.jsonl";
|
|
25
|
+
const PROJECTION_NAMES = {
|
|
26
|
+
metrics: "projection-metrics.json",
|
|
27
|
+
streak: "projection-streak.json",
|
|
28
|
+
bet: "projection-bet.json",
|
|
29
|
+
drift: "projection-drift.json",
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
// One projection file's content. The hashed body carries the deterministic
|
|
33
|
+
// watermark (v0.2 amendment 3: throughEventId/throughEventTs inside, never
|
|
34
|
+
// rebuiltAt); `hash` must reproduce across rebuilds of the same log.
|
|
35
|
+
function projectionFileBody(kind, data, watermark, eventCount) {
|
|
36
|
+
return {
|
|
37
|
+
v: PROJECTION_VERSION,
|
|
38
|
+
kind,
|
|
39
|
+
throughEventId: watermark.id,
|
|
40
|
+
throughEventTs: watermark.ts,
|
|
41
|
+
eventCount,
|
|
42
|
+
data,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function projectionFileText(kind, data, watermark, eventCount, rebuiltAt) {
|
|
47
|
+
const body = projectionFileBody(kind, data, watermark, eventCount);
|
|
48
|
+
const hash = sha256Hex(stableStringify(body));
|
|
49
|
+
// rebuiltAt and hash sit OUTSIDE the hashed body; stableStringify keeps
|
|
50
|
+
// the bytes reproducible for identical inputs.
|
|
51
|
+
return stableStringify({ rebuiltAt, hash, body }) + "\n";
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Predicts every byte a migration write would produce. Pure of the target
|
|
55
|
+
// filesystem: takes the events, returns { files: [{file, bytes, sha256}] }.
|
|
56
|
+
function predictState(events, { now } = {}) {
|
|
57
|
+
if (!Array.isArray(events) || !events.every((e) => isValidEnvelope(e))) {
|
|
58
|
+
throw new Error("dryrun.predictState: events must all be valid v0.2 envelopes");
|
|
59
|
+
}
|
|
60
|
+
if (typeof now !== "number") {
|
|
61
|
+
throw new Error("dryrun.predictState: opts.now (epoch ms) is required: predict and apply must share one clock reading for byte identity");
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const logText = events.map((e) => serializeEnvelope(e) + "\n").join("");
|
|
65
|
+
|
|
66
|
+
const effective = resolveEffectiveEvents(events);
|
|
67
|
+
const last = events.length ? events[events.length - 1] : null;
|
|
68
|
+
const watermark = { id: last ? last.id : null, ts: last ? last.ts : null };
|
|
69
|
+
const eventCount = effective.length;
|
|
70
|
+
|
|
71
|
+
const data = {
|
|
72
|
+
metrics: computeMetrics(effective),
|
|
73
|
+
streak: computeStreak(effective),
|
|
74
|
+
bet: computeBet(effective),
|
|
75
|
+
drift: computeDrift(effective, {}),
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const files = [{ file: path.join(STATE_DIR, LOG_NAME), text: logText }];
|
|
79
|
+
for (const [kind, name] of Object.entries(PROJECTION_NAMES)) {
|
|
80
|
+
files.push({ file: path.join(STATE_DIR, name), text: projectionFileText(kind, data[kind], watermark, eventCount, now) });
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
files: files.map((f) => ({ file: f.file, bytes: Buffer.byteLength(f.text, "utf8"), sha256: sha256Hex(f.text), text: f.text })),
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Writes the predicted state onto a vault COPY and verifies byte identity by
|
|
89
|
+
// re-reading RAW BUFFERS from disk (a utf8-decode round trip can mask an
|
|
90
|
+
// invalid byte, terra attack 3). Refuses a non-pristine target; the whole
|
|
91
|
+
// check-write-verify sequence runs under the log's own advisory lock so two
|
|
92
|
+
// concurrent migrations cannot both pass the pristine check (terra attack 4),
|
|
93
|
+
// and an `.applying` marker makes a crash mid-write recoverable: a rerun
|
|
94
|
+
// that finds the marker knows the previous apply never completed and clears
|
|
95
|
+
// the partial State files instead of refusing forever.
|
|
96
|
+
//
|
|
97
|
+
// SCOPE (terra re-verdict #3, acknowledged not patched): verification is
|
|
98
|
+
// point-in-time. The lock binds every COOPERATIVE state-core writer; a
|
|
99
|
+
// hostile direct filesystem writer can always mutate a file between the last
|
|
100
|
+
// check and the caller's next read - no userspace check-then-return closes
|
|
101
|
+
// that. The durable integrity token is IN each file: every projection
|
|
102
|
+
// carries its own body hash, and the log's lines are self-validating
|
|
103
|
+
// envelopes, so a READER can re-verify at read time. verified:true means
|
|
104
|
+
// "the bytes I wrote were the bytes on disk when I looked", nothing more.
|
|
105
|
+
function applyState(vaultRoot, events, { now } = {}) {
|
|
106
|
+
const predicted = predictState(events, { now });
|
|
107
|
+
const logAbs = path.join(vaultRoot, STATE_DIR, LOG_NAME);
|
|
108
|
+
const applyingMarker = path.join(vaultRoot, STATE_DIR, ".applying");
|
|
109
|
+
|
|
110
|
+
fs.mkdirSync(path.dirname(logAbs), { recursive: true });
|
|
111
|
+
const lock = acquireLock(logAbs);
|
|
112
|
+
try {
|
|
113
|
+
if (fs.existsSync(applyingMarker)) {
|
|
114
|
+
// A previous apply crashed mid-write: everything under State/ from
|
|
115
|
+
// that run is unpublished garbage. Clear exactly the files we own.
|
|
116
|
+
for (const f of predicted.files) {
|
|
117
|
+
try { fs.rmSync(path.join(vaultRoot, f.file), { force: true }); } catch (_) {}
|
|
118
|
+
}
|
|
119
|
+
fs.rmSync(applyingMarker, { force: true });
|
|
120
|
+
}
|
|
121
|
+
if (fs.existsSync(logAbs)) {
|
|
122
|
+
throw new Error(`dryrun.applyState: ${logAbs} already exists. applyState only writes to a pristine copy, never over an existing log`);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
fs.writeFileSync(applyingMarker, JSON.stringify({ pid: process.pid, startedAt: now }), "utf8");
|
|
126
|
+
for (const f of predicted.files) {
|
|
127
|
+
fs.writeFileSync(path.join(vaultRoot, f.file), f.text, "utf8");
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const mismatches = [];
|
|
131
|
+
const manifest = predicted.files.map((f) => {
|
|
132
|
+
const actualBuf = fs.readFileSync(path.join(vaultRoot, f.file)); // raw Buffer, no decode
|
|
133
|
+
const actual = cryptoSha(actualBuf);
|
|
134
|
+
if (actual !== f.sha256 || actualBuf.length !== f.bytes) {
|
|
135
|
+
mismatches.push({ file: f.file, predicted: f.sha256, actual, predictedBytes: f.bytes, actualBytes: actualBuf.length });
|
|
136
|
+
}
|
|
137
|
+
return { file: f.file, bytes: f.bytes, sha256: f.sha256, actual };
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
if (mismatches.length) {
|
|
141
|
+
throw new Error(`dryrun.applyState: predicted vs actual bytes diverge for ${mismatches.map((m) => m.file).join(", ")}. Do not proceed to a live vault`);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Cross-check the file assembly against the product reducer itself: the
|
|
145
|
+
// rebuilt projection HASH (the full reduced body, not just the watermark
|
|
146
|
+
// id — terra attack 3b showed a mid-window event mutation slipping past
|
|
147
|
+
// an id-only check) must be reproducible from the bytes on disk.
|
|
148
|
+
const rebuilt = rebuildProjections(logAbs, { now: () => now });
|
|
149
|
+
const last = events.length ? events[events.length - 1] : null;
|
|
150
|
+
if ((rebuilt.body.throughEventId || null) !== (last ? last.id : null)) {
|
|
151
|
+
throw new Error("dryrun.applyState: rebuildProjections watermark disagrees with the written log: dryrun assembly has drifted from the reducer");
|
|
152
|
+
}
|
|
153
|
+
const effective = resolveEffectiveEvents(events);
|
|
154
|
+
const expectedBody = {
|
|
155
|
+
v: PROJECTION_VERSION,
|
|
156
|
+
throughEventId: last ? last.id : null,
|
|
157
|
+
throughEventTs: last ? last.ts : null,
|
|
158
|
+
eventCount: effective.length,
|
|
159
|
+
metrics: computeMetrics(effective),
|
|
160
|
+
streak: computeStreak(effective),
|
|
161
|
+
bet: computeBet(effective),
|
|
162
|
+
drift: computeDrift(effective, {}),
|
|
163
|
+
};
|
|
164
|
+
if (rebuilt.hash !== sha256Hex(stableStringify(expectedBody))) {
|
|
165
|
+
throw new Error("dryrun.applyState: the reducer's rebuilt hash does not match the predicted reduction. Do not proceed to a live vault");
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
fs.rmSync(applyingMarker, { force: true });
|
|
169
|
+
return { manifest: manifest.map(({ file, bytes, sha256 }) => ({ file, bytes, sha256 })), verified: true, rebuiltHash: rebuilt.hash };
|
|
170
|
+
} finally {
|
|
171
|
+
lock.release();
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Raw-buffer sha256 (predictState hashes the utf8 TEXT, which encodes to the
|
|
176
|
+
// same bytes it writes; verification must hash what is actually on disk).
|
|
177
|
+
function cryptoSha(buf) {
|
|
178
|
+
return require("crypto").createHash("sha256").update(buf).digest("hex");
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
module.exports = {
|
|
182
|
+
STATE_DIR,
|
|
183
|
+
LOG_NAME,
|
|
184
|
+
PROJECTION_NAMES,
|
|
185
|
+
projectionFileText,
|
|
186
|
+
predictState,
|
|
187
|
+
applyState,
|
|
188
|
+
};
|