memkin 0.4.2 → 0.4.3
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/dist/apply/candidate-selection.d.ts +60 -0
- package/dist/apply/candidate-selection.d.ts.map +1 -0
- package/dist/apply/candidate-selection.js +159 -0
- package/dist/apply/candidate-selection.js.map +1 -0
- package/dist/apply/cutover.d.ts +45 -0
- package/dist/apply/cutover.d.ts.map +1 -0
- package/dist/apply/cutover.js +85 -0
- package/dist/apply/cutover.js.map +1 -0
- package/dist/apply/engine.d.ts +34 -0
- package/dist/apply/engine.d.ts.map +1 -0
- package/dist/apply/engine.js +255 -0
- package/dist/apply/engine.js.map +1 -0
- package/dist/apply/ids.d.ts +9 -0
- package/dist/apply/ids.d.ts.map +1 -0
- package/dist/apply/ids.js +0 -0
- package/dist/apply/ids.js.map +1 -0
- package/dist/apply/page-content.d.ts +14 -0
- package/dist/apply/page-content.d.ts.map +1 -0
- package/dist/apply/page-content.js +0 -0
- package/dist/apply/page-content.js.map +1 -0
- package/dist/apply/rematerialize.d.ts +13 -0
- package/dist/apply/rematerialize.d.ts.map +1 -0
- package/dist/apply/rematerialize.js +150 -0
- package/dist/apply/rematerialize.js.map +1 -0
- package/dist/apply/rollback.d.ts +20 -0
- package/dist/apply/rollback.d.ts.map +1 -0
- package/dist/apply/rollback.js +117 -0
- package/dist/apply/rollback.js.map +1 -0
- package/dist/apply/shadow-runner.d.ts +47 -0
- package/dist/apply/shadow-runner.d.ts.map +1 -0
- package/dist/apply/shadow-runner.js +84 -0
- package/dist/apply/shadow-runner.js.map +1 -0
- package/dist/apply/types.d.ts +51 -0
- package/dist/apply/types.d.ts.map +1 -0
- package/dist/apply/types.js +13 -0
- package/dist/apply/types.js.map +1 -0
- package/dist/cli.js +5 -0
- package/dist/cli.js.map +1 -1
- package/dist/consolidator/consolidator.d.ts +21 -2
- package/dist/consolidator/consolidator.d.ts.map +1 -1
- package/dist/consolidator/consolidator.js +16 -1
- package/dist/consolidator/consolidator.js.map +1 -1
- package/dist/core/config.d.ts +42 -0
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +25 -0
- package/dist/core/config.js.map +1 -1
- package/dist/core/pipeline.d.ts +26 -1
- package/dist/core/pipeline.d.ts.map +1 -1
- package/dist/core/pipeline.js +26 -0
- package/dist/core/pipeline.js.map +1 -1
- package/dist/core/schemas.d.ts +16 -16
- package/dist/daemon/serve-runtime.d.ts +1 -1
- package/dist/daemon/serve-runtime.d.ts.map +1 -1
- package/dist/daemon/serve-runtime.js +26 -1
- package/dist/daemon/serve-runtime.js.map +1 -1
- package/dist/distiller/authority-matrix.d.ts +22 -0
- package/dist/distiller/authority-matrix.d.ts.map +1 -0
- package/dist/distiller/authority-matrix.js +29 -0
- package/dist/distiller/authority-matrix.js.map +1 -0
- package/dist/distiller/contract.d.ts +1427 -0
- package/dist/distiller/contract.d.ts.map +1 -0
- package/dist/distiller/contract.js +166 -0
- package/dist/distiller/contract.js.map +1 -0
- package/dist/distiller/index.d.ts +83 -0
- package/dist/distiller/index.d.ts.map +1 -0
- package/dist/distiller/index.js +226 -0
- package/dist/distiller/index.js.map +1 -0
- package/dist/distiller/map-reduce.d.ts +81 -0
- package/dist/distiller/map-reduce.d.ts.map +1 -0
- package/dist/distiller/map-reduce.js +92 -0
- package/dist/distiller/map-reduce.js.map +1 -0
- package/dist/distiller/msg-id.d.ts +55 -0
- package/dist/distiller/msg-id.d.ts.map +1 -0
- package/dist/distiller/msg-id.js +91 -0
- package/dist/distiller/msg-id.js.map +1 -0
- package/dist/distiller/payload-privacy.d.ts +18 -0
- package/dist/distiller/payload-privacy.d.ts.map +1 -0
- package/dist/distiller/payload-privacy.js +51 -0
- package/dist/distiller/payload-privacy.js.map +1 -0
- package/dist/distiller/prompt.d.ts +28 -0
- package/dist/distiller/prompt.d.ts.map +1 -0
- package/dist/distiller/prompt.js +69 -0
- package/dist/distiller/prompt.js.map +1 -0
- package/dist/distiller/raw-privacy.d.ts +55 -0
- package/dist/distiller/raw-privacy.d.ts.map +1 -0
- package/dist/distiller/raw-privacy.js +123 -0
- package/dist/distiller/raw-privacy.js.map +1 -0
- package/dist/distiller/segmenter.d.ts +40 -0
- package/dist/distiller/segmenter.d.ts.map +1 -0
- package/dist/distiller/segmenter.js +75 -0
- package/dist/distiller/segmenter.js.map +1 -0
- package/dist/embedded-assets.generated.d.ts.map +1 -1
- package/dist/embedded-assets.generated.js +5 -4
- package/dist/embedded-assets.generated.js.map +1 -1
- package/dist/eval/golden.d.ts +12 -12
- package/dist/server/api.d.ts.map +1 -1
- package/dist/server/api.js +6 -1
- package/dist/server/api.js.map +1 -1
- package/dist/setup/generate-config.d.ts.map +1 -1
- package/dist/setup/generate-config.js +3 -0
- package/dist/setup/generate-config.js.map +1 -1
- package/dist/setup/validate-config.d.ts +2 -1
- package/dist/setup/validate-config.d.ts.map +1 -1
- package/dist/setup/validate-config.js.map +1 -1
- package/dist/store/agent-sessions.d.ts.map +1 -1
- package/dist/store/agent-sessions.js +3 -1
- package/dist/store/agent-sessions.js.map +1 -1
- package/dist/store/distilled-payload.d.ts +50 -0
- package/dist/store/distilled-payload.d.ts.map +1 -0
- package/dist/store/distilled-payload.js +117 -0
- package/dist/store/distilled-payload.js.map +1 -0
- package/dist/store/engine-factory.d.ts.map +1 -1
- package/dist/store/engine-factory.js +8 -1
- package/dist/store/engine-factory.js.map +1 -1
- package/dist/store/migrations/index.d.ts.map +1 -1
- package/dist/store/migrations/index.js +139 -0
- package/dist/store/migrations/index.js.map +1 -1
- package/dist/store/search.d.ts.map +1 -1
- package/dist/store/search.js +15 -8
- package/dist/store/search.js.map +1 -1
- package/dist/store/source-filter.d.ts +19 -0
- package/dist/store/source-filter.d.ts.map +1 -0
- package/dist/store/source-filter.js +44 -0
- package/dist/store/source-filter.js.map +1 -0
- package/dist/store/timeline.d.ts.map +1 -1
- package/dist/store/timeline.js +15 -8
- package/dist/store/timeline.js.map +1 -1
- package/dist/synth/scope.d.ts.map +1 -1
- package/dist/synth/scope.js +10 -4
- package/dist/synth/scope.js.map +1 -1
- package/package.json +2 -1
- package/web/dist/assets/index-Cie2dVF3.js +5167 -0
- package/web/dist/assets/index-diZtO98v.css +2 -0
- package/web/dist/assets/newsreader-T5L87s1n.woff2 +0 -0
- package/web/dist/index.html +19 -0
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
// src/apply/engine.ts
|
|
2
|
+
//
|
|
3
|
+
// The single, target-agnostic apply engine (spec §6.2). It consumes a frozen
|
|
4
|
+
// apply_plan and writes everything in ONE Postgres transaction:
|
|
5
|
+
// pages + content_chunks (via rematerialize's rechunk) + links + tags +
|
|
6
|
+
// timeline + handles + contributions + rematerialize + a mutation journal for
|
|
7
|
+
// the NON-derived writes → commit → record an apply_attempt.
|
|
8
|
+
//
|
|
9
|
+
// Target parameterization (staging | production) is a `SET LOCAL search_path`
|
|
10
|
+
// inside the transaction: data tables (pages, content_chunks, links, tags,
|
|
11
|
+
// timeline_entries, memory_contributions) are written UNQUALIFIED and resolve to
|
|
12
|
+
// the target schema; engine metadata (apply_attempt, apply_mutation_journal,
|
|
13
|
+
// distilled_payload) is always public-qualified.
|
|
14
|
+
//
|
|
15
|
+
// Replay is idempotent and never calls an LLM: the engine is pure plan
|
|
16
|
+
// consumption; contribution_id is the deterministic PK and pages upsert by slug,
|
|
17
|
+
// so re-running a plan that already succeeded is a no-op.
|
|
18
|
+
//
|
|
19
|
+
// CAS: UPDATE/SUPERSEDE/LINK_EXISTING lock the target row and compare its
|
|
20
|
+
// content_hash to the plan snapshot; a mismatch (concurrent write) aborts and
|
|
21
|
+
// the whole apply is retried once. A second conflict parks the attempt in
|
|
22
|
+
// dead_letter (spec §7).
|
|
23
|
+
import { rematerializeCanonicalPage } from "./rematerialize.js";
|
|
24
|
+
const TYPE_FOLDER = {
|
|
25
|
+
decision: "decisions",
|
|
26
|
+
task: "tasks",
|
|
27
|
+
reference: "references",
|
|
28
|
+
preference: "preferences",
|
|
29
|
+
knowledge: "knowledge",
|
|
30
|
+
discovery: "discoveries",
|
|
31
|
+
};
|
|
32
|
+
function deriveSlug(type, normalizedTopic) {
|
|
33
|
+
const folder = TYPE_FOLDER[type] ?? "notes";
|
|
34
|
+
return `${folder}/${normalizedTopic}`;
|
|
35
|
+
}
|
|
36
|
+
class CasConflictError extends Error {
|
|
37
|
+
slug;
|
|
38
|
+
constructor(slug) {
|
|
39
|
+
super(`CAS conflict on ${slug}`);
|
|
40
|
+
this.slug = slug;
|
|
41
|
+
this.name = "CasConflictError";
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
export class ApplyEngine {
|
|
45
|
+
ex;
|
|
46
|
+
constructor(ex) {
|
|
47
|
+
this.ex = ex;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Apply a frozen plan. Idempotent per plan; target-parameterized; single
|
|
51
|
+
* transaction; CAS with one retry then dead_letter.
|
|
52
|
+
*/
|
|
53
|
+
async apply(plan) {
|
|
54
|
+
// Idempotent replay: a plan that already applied is not re-run (no LLM ever).
|
|
55
|
+
const prior = await this.ex.query("SELECT id FROM public.apply_attempt WHERE plan_id = $1 AND status = 'applied' ORDER BY id DESC LIMIT 1", [plan.id]);
|
|
56
|
+
if (prior.rows.length > 0) {
|
|
57
|
+
return {
|
|
58
|
+
attemptId: prior.rows[0].id,
|
|
59
|
+
status: "applied",
|
|
60
|
+
target: plan.target,
|
|
61
|
+
applied: [],
|
|
62
|
+
retried: false,
|
|
63
|
+
replay: true,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
const meta = await this.loadPayloadMeta(plan.data.payload_id);
|
|
67
|
+
const attemptId = (await this.ex.query("INSERT INTO public.apply_attempt (plan_id, target, status) VALUES ($1, $2, 'pending') RETURNING id", [plan.id, plan.target])).rows[0].id;
|
|
68
|
+
let retried = false;
|
|
69
|
+
for (let pass = 0; pass < 2; pass++) {
|
|
70
|
+
try {
|
|
71
|
+
const applied = await this.runTransaction(plan, meta, attemptId);
|
|
72
|
+
await this.ex.query("UPDATE public.apply_attempt SET status = 'applied', detail = $2::jsonb, completed_at = NOW() WHERE id = $1", [attemptId, JSON.stringify({ retried, count: applied.length })]);
|
|
73
|
+
return {
|
|
74
|
+
attemptId,
|
|
75
|
+
status: "applied",
|
|
76
|
+
target: plan.target,
|
|
77
|
+
applied,
|
|
78
|
+
retried,
|
|
79
|
+
replay: false,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
catch (err) {
|
|
83
|
+
const isCas = err instanceof CasConflictError;
|
|
84
|
+
if (isCas && pass === 0) {
|
|
85
|
+
retried = true;
|
|
86
|
+
continue; // retry once
|
|
87
|
+
}
|
|
88
|
+
const status = isCas ? "dead_letter" : "failed";
|
|
89
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
90
|
+
await this.ex.query("UPDATE public.apply_attempt SET status = $2, detail = $3::jsonb, completed_at = NOW() WHERE id = $1", [attemptId, status, JSON.stringify({ retried, error: message })]);
|
|
91
|
+
return {
|
|
92
|
+
attemptId,
|
|
93
|
+
status,
|
|
94
|
+
target: plan.target,
|
|
95
|
+
applied: [],
|
|
96
|
+
retried,
|
|
97
|
+
replay: false,
|
|
98
|
+
error: message,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
// Unreachable, but satisfies the type checker.
|
|
103
|
+
return {
|
|
104
|
+
attemptId,
|
|
105
|
+
status: "failed",
|
|
106
|
+
target: plan.target,
|
|
107
|
+
applied: [],
|
|
108
|
+
retried,
|
|
109
|
+
replay: false,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
async loadPayloadMeta(payloadId) {
|
|
113
|
+
const r = await this.ex.query("SELECT source_instance, session_id, revision_id, created_at::text AS created_at FROM public.distilled_payload WHERE id = $1", [payloadId]);
|
|
114
|
+
if (r.rows.length === 0)
|
|
115
|
+
throw new Error(`payload ${payloadId} not found`);
|
|
116
|
+
const row = r.rows[0];
|
|
117
|
+
return {
|
|
118
|
+
sourceInstance: row.source_instance,
|
|
119
|
+
sessionId: row.session_id,
|
|
120
|
+
revisionId: row.revision_id,
|
|
121
|
+
createdAt: row.created_at,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
async runTransaction(plan, meta, attemptId) {
|
|
125
|
+
return this.ex.transaction(async (tx) => {
|
|
126
|
+
if (plan.target === "staging") {
|
|
127
|
+
await tx.query("SET LOCAL search_path TO staging, public");
|
|
128
|
+
}
|
|
129
|
+
const applied = [];
|
|
130
|
+
const journal = new Journal(tx, attemptId);
|
|
131
|
+
for (const action of plan.data.actions) {
|
|
132
|
+
const slug = await this.applyAction(tx, action, meta, attemptId, journal);
|
|
133
|
+
if (slug)
|
|
134
|
+
applied.push({ slug, action: action.action });
|
|
135
|
+
}
|
|
136
|
+
return applied;
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
async applyAction(tx, action, meta, attemptId, journal) {
|
|
140
|
+
if (action.action === "NOOP")
|
|
141
|
+
return null;
|
|
142
|
+
const source = {
|
|
143
|
+
platform: meta.sourceInstance,
|
|
144
|
+
channel: meta.sessionId,
|
|
145
|
+
timestamp: meta.createdAt,
|
|
146
|
+
raw_hash: "",
|
|
147
|
+
quote: "",
|
|
148
|
+
};
|
|
149
|
+
if (action.action === "NEW") {
|
|
150
|
+
const slug = deriveSlug(action.signal.type, action.normalized_topic);
|
|
151
|
+
const pageId = await this.ensurePage(tx, slug, action, journal);
|
|
152
|
+
await this.attachContribution(tx, action, pageId, meta, source, attemptId);
|
|
153
|
+
await rematerializeCanonicalPage(tx, pageId);
|
|
154
|
+
return slug;
|
|
155
|
+
}
|
|
156
|
+
if (action.action === "SUPERSEDE") {
|
|
157
|
+
const oldSlug = action.target_slug;
|
|
158
|
+
const old = await this.lockForCas(tx, oldSlug, action.target_content_hash);
|
|
159
|
+
// New page carries the superseding conclusion.
|
|
160
|
+
const newSlug = deriveSlug(action.signal.type, action.normalized_topic);
|
|
161
|
+
const newId = await this.ensurePage(tx, newSlug, action, journal);
|
|
162
|
+
await this.attachContribution(tx, action, newId, meta, source, attemptId);
|
|
163
|
+
await rematerializeCanonicalPage(tx, newId);
|
|
164
|
+
// Non-derived writes on the OLD page → journaled for rollback.
|
|
165
|
+
await this.markSuperseded(tx, old.id, oldSlug, newSlug, journal);
|
|
166
|
+
await tx.query(`INSERT INTO links (from_page_id, to_page_id, link_type, context, provenance)
|
|
167
|
+
VALUES ($1, $2, 'supersedes', '', $3::jsonb)
|
|
168
|
+
ON CONFLICT (from_page_id, to_page_id, link_type) DO NOTHING`, [newId, old.id, JSON.stringify({ auto: "supersede" })]);
|
|
169
|
+
return newSlug;
|
|
170
|
+
}
|
|
171
|
+
// UPDATE | LINK_EXISTING — attach to the existing target and rematerialize.
|
|
172
|
+
const targetSlug = action.target_slug;
|
|
173
|
+
const target = await this.lockForCas(tx, targetSlug, action.target_content_hash);
|
|
174
|
+
await this.attachContribution(tx, action, target.id, meta, source, attemptId);
|
|
175
|
+
await rematerializeCanonicalPage(tx, target.id);
|
|
176
|
+
return targetSlug;
|
|
177
|
+
}
|
|
178
|
+
/** Lock the target row and enforce the plan-snapshot CAS (spec §7). */
|
|
179
|
+
async lockForCas(tx, slug, snapshotHash) {
|
|
180
|
+
const r = await tx.query("SELECT id, content_hash FROM pages WHERE slug = $1 FOR UPDATE", [slug]);
|
|
181
|
+
if (r.rows.length === 0)
|
|
182
|
+
throw new Error(`apply target page missing: ${slug}`);
|
|
183
|
+
const row = r.rows[0];
|
|
184
|
+
if (snapshotHash !== null && row.content_hash !== snapshotHash) {
|
|
185
|
+
throw new CasConflictError(slug);
|
|
186
|
+
}
|
|
187
|
+
return row;
|
|
188
|
+
}
|
|
189
|
+
/** Insert (or reactivate) the page row; journal a brand-new page for rollback. */
|
|
190
|
+
async ensurePage(tx, slug, action, journal) {
|
|
191
|
+
const frontmatter = { pipeline: "v2", authority: action.signal.authority };
|
|
192
|
+
const inserted = await tx.query(`INSERT INTO pages (slug, type, title, compiled_truth, frontmatter, content_hash)
|
|
193
|
+
VALUES ($1, $2, $3, '', $4::jsonb, '')
|
|
194
|
+
ON CONFLICT (slug) DO NOTHING
|
|
195
|
+
RETURNING id`, [slug, action.signal.type, action.signal.topic, JSON.stringify(frontmatter)]);
|
|
196
|
+
if (inserted.rows.length > 0) {
|
|
197
|
+
await journal.record("page_created", { slug });
|
|
198
|
+
return inserted.rows[0].id;
|
|
199
|
+
}
|
|
200
|
+
const existing = await tx.query("SELECT id FROM pages WHERE slug = $1", [slug]);
|
|
201
|
+
return existing.rows[0].id;
|
|
202
|
+
}
|
|
203
|
+
async markSuperseded(tx, oldId, oldSlug, newSlug, journal) {
|
|
204
|
+
const prev = await tx.query("SELECT frontmatter->>'superseded_by' AS superseded_by FROM pages WHERE id = $1", [oldId]);
|
|
205
|
+
await journal.record("frontmatter_superseded_by", {
|
|
206
|
+
slug: oldSlug,
|
|
207
|
+
prev: prev.rows[0]?.superseded_by ?? null,
|
|
208
|
+
});
|
|
209
|
+
await tx.query(`UPDATE pages SET frontmatter = frontmatter || jsonb_build_object('superseded_by', $2::text),
|
|
210
|
+
updated_at = NOW() WHERE id = $1`, [oldId, newSlug]);
|
|
211
|
+
}
|
|
212
|
+
async attachContribution(tx, action, pageId, meta, source, attemptId) {
|
|
213
|
+
// Revision-update semantics (spec §6.1): a new revision of the same family
|
|
214
|
+
// deactivates prior revisions before this contribution goes active.
|
|
215
|
+
await tx.query(`UPDATE memory_contributions SET active = false
|
|
216
|
+
WHERE signal_family_key = $1 AND revision_id <> $2 AND active`, [action.signal_family_key, meta.revisionId]);
|
|
217
|
+
await tx.query(`INSERT INTO memory_contributions
|
|
218
|
+
(contribution_id, signal_family_key, canonical_page_id, session_ref, revision_id,
|
|
219
|
+
authority, signal_type, normalized_topic, signal, source_ref, evidence, active, apply_attempt_id)
|
|
220
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9::jsonb, $10::jsonb, $11::jsonb, true, $12)
|
|
221
|
+
ON CONFLICT (contribution_id) DO UPDATE SET
|
|
222
|
+
canonical_page_id = EXCLUDED.canonical_page_id,
|
|
223
|
+
active = true,
|
|
224
|
+
apply_attempt_id = EXCLUDED.apply_attempt_id`, [
|
|
225
|
+
action.contribution_id,
|
|
226
|
+
action.signal_family_key,
|
|
227
|
+
pageId,
|
|
228
|
+
`${meta.sourceInstance}:${meta.sessionId}`,
|
|
229
|
+
meta.revisionId,
|
|
230
|
+
action.signal.authority,
|
|
231
|
+
action.signal.type,
|
|
232
|
+
action.normalized_topic,
|
|
233
|
+
JSON.stringify(action.signal),
|
|
234
|
+
JSON.stringify(source),
|
|
235
|
+
JSON.stringify(action.signal.evidence ?? []),
|
|
236
|
+
attemptId,
|
|
237
|
+
]);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
/** Records inverse ops for NON-derived writes only (rollback safety net). */
|
|
241
|
+
class Journal {
|
|
242
|
+
tx;
|
|
243
|
+
attemptId;
|
|
244
|
+
seq = 0;
|
|
245
|
+
constructor(tx, attemptId) {
|
|
246
|
+
this.tx = tx;
|
|
247
|
+
this.attemptId = attemptId;
|
|
248
|
+
}
|
|
249
|
+
async record(kind, inverse) {
|
|
250
|
+
this.seq += 1;
|
|
251
|
+
await this.tx.query(`INSERT INTO public.apply_mutation_journal (apply_attempt_id, seq, kind, inverse)
|
|
252
|
+
VALUES ($1, $2, $3, $4::jsonb)`, [this.attemptId, this.seq, kind, JSON.stringify(inverse)]);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
//# sourceMappingURL=engine.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"engine.js","sourceRoot":"","sources":["../../src/apply/engine.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** Deterministic normalized topic (spec §6.1). */
|
|
2
|
+
export declare function normalizeTopic(topic: string): string;
|
|
3
|
+
/** contribution_id = hash(revision_id, type, normalized_topic). */
|
|
4
|
+
export declare function contributionId(revisionId: number, type: string, normalizedTopic: string): string;
|
|
5
|
+
/** signal_family_key = hash(source_instance, session_id, type, normalized_topic). */
|
|
6
|
+
export declare function signalFamilyKey(sourceInstance: string, sessionId: string, type: string, normalizedTopic: string): string;
|
|
7
|
+
/** Stable, human-readable session reference stored on each contribution. */
|
|
8
|
+
export declare function sessionRef(sourceInstance: string, sessionId: string): string;
|
|
9
|
+
//# sourceMappingURL=ids.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ids.d.ts","sourceRoot":"","sources":["../../src/apply/ids.ts"],"names":[],"mappings":"AA6BA,kDAAkD;AAClD,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEpD;AAED,mEAAmE;AACnE,wBAAgB,cAAc,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,GAAG,MAAM,CAEhG;AAED,qFAAqF;AACrF,wBAAgB,eAAe,CAC7B,cAAc,EAAE,MAAM,EACtB,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,MAAM,EACZ,eAAe,EAAE,MAAM,GACtB,MAAM,CAER;AAED,4EAA4E;AAC5E,wBAAgB,UAAU,CAAC,cAAc,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAE5E"}
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ids.js","sourceRoot":"","sources":["../../src/apply/ids.ts"],"names":[],"mappings":"AAAA,mBAAmB;AACnB,EAAE;AACF,uDAAuD;AACvD,EAAE;AACF,kEAAkE;AAClE,+EAA+E;AAC/E,8EAA8E;AAC9E,mFAAmF;AACnF,4DAA4D;AAC5D,EAAE;AACF,kFAAkF;AAClF,gFAAgF;AAChF,2EAA2E;AAC3E,0DAA0D;AAC1D,EAAE;AACF,+EAA+E;AAC/E,qDAAqD;AAErD,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAExD,MAAM,GAAG,GAAG,GAAG,CAAC;AAEhB,SAAS,MAAM,CAAC,KAA0B;IACxC,MAAM,CAAC,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC;IAC/B,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;IAChD,OAAO,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AACzB,CAAC;AAED,kDAAkD;AAClD,MAAM,UAAU,cAAc,CAAC,KAAa;IAC1C,OAAO,YAAY,CAAC,KAAK,CAAC,CAAC;AAC7B,CAAC;AAED,mEAAmE;AACnE,MAAM,UAAU,cAAc,CAAC,UAAkB,EAAE,IAAY,EAAE,eAAuB;IACtF,OAAO,MAAM,CAAC,CAAC,UAAU,EAAE,IAAI,EAAE,eAAe,CAAC,CAAC,CAAC;AACrD,CAAC;AAED,qFAAqF;AACrF,MAAM,UAAU,eAAe,CAC7B,cAAsB,EACtB,SAAiB,EACjB,IAAY,EACZ,eAAuB;IAEvB,OAAO,MAAM,CAAC,CAAC,cAAc,EAAE,SAAS,EAAE,IAAI,EAAE,eAAe,CAAC,CAAC,CAAC;AACpE,CAAC;AAED,4EAA4E;AAC5E,MAAM,UAAU,UAAU,CAAC,cAAsB,EAAE,SAAiB;IAClE,OAAO,GAAG,cAAc,IAAI,SAAS,EAAE,CAAC;AAC1C,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export declare const AUTO_BEGIN = "<!-- memkin:auto:begin -->";
|
|
2
|
+
export declare const AUTO_END = "<!-- memkin:auto:end -->";
|
|
3
|
+
/** Replace (or append) the system-managed region, preserving user content. */
|
|
4
|
+
export declare function replaceAutoSection(compiledTruth: string, autoBody: string): string;
|
|
5
|
+
/** Strip the system-managed region entirely (used when a page is orphaned). */
|
|
6
|
+
export declare function stripAutoSection(compiledTruth: string): string;
|
|
7
|
+
/**
|
|
8
|
+
* Content hash used by the CAS UPDATE guard (spec §7). Deterministic over the
|
|
9
|
+
* derived body + frontmatter so a concurrent writer's change is detected.
|
|
10
|
+
*/
|
|
11
|
+
export declare function computeContentHash(compiledTruth: string, frontmatter: Record<string, unknown>): string;
|
|
12
|
+
/** Order-independent JSON stringification so key order never perturbs the hash. */
|
|
13
|
+
export declare function stableStringify(value: unknown): string;
|
|
14
|
+
//# sourceMappingURL=page-content.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"page-content.d.ts","sourceRoot":"","sources":["../../src/apply/page-content.ts"],"names":[],"mappings":"AAgBA,eAAO,MAAM,UAAU,+BAA+B,CAAC;AACvD,eAAO,MAAM,QAAQ,6BAA6B,CAAC;AAInD,8EAA8E;AAC9E,wBAAgB,kBAAkB,CAAC,aAAa,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAOlF;AAED,+EAA+E;AAC/E,wBAAgB,gBAAgB,CAAC,aAAa,EAAE,MAAM,GAAG,MAAM,CAE9D;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,aAAa,EAAE,MAAM,EACrB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACnC,MAAM,CAMR;AAED,mFAAmF;AACnF,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAMtD"}
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"page-content.js","sourceRoot":"","sources":["../../src/apply/page-content.ts"],"names":[],"mappings":"AAAA,4BAA4B;AAC5B,EAAE;AACF,4EAA4E;AAC5E,0EAA0E;AAC1E,kCAAkC;AAClC,EAAE;AACF,6DAA6D;AAC7D,+BAA+B;AAC/B,uEAAuE;AACvE,6BAA6B;AAC7B,EAAE;AACF,gFAAgF;AAChF,sEAAsE;AAEtE,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC,MAAM,CAAC,MAAM,UAAU,GAAG,4BAA4B,CAAC;AACvD,MAAM,CAAC,MAAM,QAAQ,GAAG,0BAA0B,CAAC;AAEnD,MAAM,OAAO,GAAG,4DAA4D,CAAC;AAE7E,8EAA8E;AAC9E,MAAM,UAAU,kBAAkB,CAAC,aAAqB,EAAE,QAAgB;IACxE,MAAM,KAAK,GAAG,GAAG,UAAU,KAAK,QAAQ,CAAC,IAAI,EAAE,KAAK,QAAQ,EAAE,CAAC;IAC/D,IAAI,OAAO,CAAC,IAAI,CAAC,aAAa,CAAC,EAAE,CAAC;QAChC,OAAO,aAAa,CAAC,OAAO,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;IAC/C,CAAC;IACD,MAAM,IAAI,GAAG,aAAa,CAAC,IAAI,EAAE,CAAC;IAClC,OAAO,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,OAAO,KAAK,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC;AAC9C,CAAC;AAED,+EAA+E;AAC/E,MAAM,UAAU,gBAAgB,CAAC,aAAqB;IACpD,OAAO,aAAa,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;AACnD,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,kBAAkB,CAChC,aAAqB,EACrB,WAAoC;IAEpC,OAAO,UAAU,CAAC,QAAQ,CAAC;SACxB,MAAM,CAAC,aAAa,CAAC;SACrB,MAAM,CAAC,GAAG,CAAC;SACX,MAAM,CAAC,eAAe,CAAC,WAAW,CAAC,CAAC;SACpC,MAAM,CAAC,KAAK,CAAC,CAAC;AACnB,CAAC;AAED,mFAAmF;AACnF,MAAM,UAAU,eAAe,CAAC,KAAc;IAC5C,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;IAC9E,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,IAAI,KAAK,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;IAC7E,MAAM,GAAG,GAAG,KAAgC,CAAC;IAC7C,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;IACrC,OAAO,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,eAAe,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;AAC7F,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { SqlConn } from "../store/sql-executor.js";
|
|
2
|
+
export interface RematerializeResult {
|
|
3
|
+
pageId: number;
|
|
4
|
+
activeCount: number;
|
|
5
|
+
orphaned: boolean;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Regenerate all derivatives of a canonical page from its active contributions,
|
|
9
|
+
* inside the caller's transaction (spec §6.1). Returns orphaned=true when the
|
|
10
|
+
* page has no active contributions left.
|
|
11
|
+
*/
|
|
12
|
+
export declare function rematerializeCanonicalPage(tx: SqlConn, pageId: number): Promise<RematerializeResult>;
|
|
13
|
+
//# sourceMappingURL=rematerialize.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rematerialize.d.ts","sourceRoot":"","sources":["../../src/apply/rematerialize.ts"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,0BAA0B,CAAC;AA0BxD,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,OAAO,CAAC;CACnB;AAiID;;;;GAIG;AACH,wBAAsB,0BAA0B,CAC9C,EAAE,EAAE,OAAO,EACX,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,mBAAmB,CAAC,CAmD9B"}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
// src/apply/rematerialize.ts
|
|
2
|
+
//
|
|
3
|
+
// rematerializeCanonicalPage (spec §6.1) — the v4 core invariant: EVERY
|
|
4
|
+
// derivative of a canonical page (system-managed body, primary frontmatter
|
|
5
|
+
// source, derived links / tags / timeline, tracker state) is regenerated from
|
|
6
|
+
// the page's ACTIVE contributions, inside the caller's transaction. Any
|
|
7
|
+
// operation that changes the active set (a new revision, a rollback) finishes by
|
|
8
|
+
// calling this. Once a contribution is withdrawn (active=false), the page must
|
|
9
|
+
// no longer show its conclusion.
|
|
10
|
+
//
|
|
11
|
+
// A page that has lost ALL active contributions is marked `orphaned` (frontmatter
|
|
12
|
+
// flag) for the consolidator to review — never blind-deleted (spec §3.1 / §6.1),
|
|
13
|
+
// because it may already be referenced by other sessions, user edits, or pages.
|
|
14
|
+
//
|
|
15
|
+
// Table names are unqualified so the write lands in whichever schema the caller
|
|
16
|
+
// put first on search_path (production or staging) — this is what makes the
|
|
17
|
+
// engine target-agnostic.
|
|
18
|
+
import { rechunkTx } from "../store/chunks.js";
|
|
19
|
+
import { computeContentHash, replaceAutoSection, stripAutoSection } from "./page-content.js";
|
|
20
|
+
const AUTO_PROVENANCE = { auto: "contribution" };
|
|
21
|
+
function parseJson(v) {
|
|
22
|
+
if (v === null || v === undefined)
|
|
23
|
+
return null;
|
|
24
|
+
return typeof v === "string" ? JSON.parse(v) : v;
|
|
25
|
+
}
|
|
26
|
+
function toActive(row) {
|
|
27
|
+
const signal = (parseJson(row.signal) ?? {});
|
|
28
|
+
const entities = Array.isArray(signal.entities) ? signal.entities : [];
|
|
29
|
+
return {
|
|
30
|
+
contributionId: row.contribution_id,
|
|
31
|
+
type: row.signal_type,
|
|
32
|
+
authority: row.authority,
|
|
33
|
+
what: String(signal.what ?? ""),
|
|
34
|
+
why: signal.why ? String(signal.why) : null,
|
|
35
|
+
entities,
|
|
36
|
+
sourceRef: parseJson(row.source_ref),
|
|
37
|
+
createdAt: row.created_at,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
/** Render the system-managed body from the active contributions (spec §6.1, §7). */
|
|
41
|
+
function renderAutoBody(pageType, contribs) {
|
|
42
|
+
const line = (c) => {
|
|
43
|
+
const why = c.why ? ` — ${c.why}` : "";
|
|
44
|
+
const auth = c.authority === "user_confirmed" ? "" : ` _(${c.authority})_`;
|
|
45
|
+
return `- **[${c.type}]** ${c.what}${why}${auth}`;
|
|
46
|
+
};
|
|
47
|
+
if (pageType === "project") {
|
|
48
|
+
// Project tracker (spec §6.1): current state + recent changes, both derived.
|
|
49
|
+
const recent = [...contribs].sort((a, b) => b.createdAt.localeCompare(a.createdAt));
|
|
50
|
+
const state = contribs.map(line).join("\n");
|
|
51
|
+
const changes = recent.map((c) => `- ${c.createdAt.slice(0, 10)} · ${c.what}`).join("\n");
|
|
52
|
+
return `## 当前状态\n${state}\n\n## 最近变更\n${changes}`;
|
|
53
|
+
}
|
|
54
|
+
return contribs.map(line).join("\n");
|
|
55
|
+
}
|
|
56
|
+
/** Pick the primary source (spec §8): first user_confirmed, else earliest. */
|
|
57
|
+
function primarySource(contribs) {
|
|
58
|
+
const confirmed = contribs.find((c) => c.authority === "user_confirmed" && c.sourceRef);
|
|
59
|
+
if (confirmed)
|
|
60
|
+
return confirmed.sourceRef;
|
|
61
|
+
const withSource = contribs.find((c) => c.sourceRef);
|
|
62
|
+
return withSource ? withSource.sourceRef : null;
|
|
63
|
+
}
|
|
64
|
+
async function loadActive(tx, pageId) {
|
|
65
|
+
const rows = await tx.query(`SELECT contribution_id, signal_type, normalized_topic, authority, signal, source_ref, created_at::text AS created_at
|
|
66
|
+
FROM memory_contributions
|
|
67
|
+
WHERE canonical_page_id = $1 AND active
|
|
68
|
+
ORDER BY created_at ASC, contribution_id ASC`, [pageId]);
|
|
69
|
+
return rows.rows.map(toActive);
|
|
70
|
+
}
|
|
71
|
+
/** Clear all pipeline-derived links/timeline for this page (rebuilt after). */
|
|
72
|
+
async function clearDerived(tx, pageId) {
|
|
73
|
+
await tx.query(`DELETE FROM links WHERE from_page_id = $1 AND provenance->>'auto' = 'contribution'`, [pageId]);
|
|
74
|
+
await tx.query(`DELETE FROM timeline_entries WHERE page_id = $1 AND provenance->>'auto' = 'contribution'`, [pageId]);
|
|
75
|
+
}
|
|
76
|
+
async function rebuildTimeline(tx, pageId, contribs) {
|
|
77
|
+
for (const c of contribs) {
|
|
78
|
+
const date = c.sourceRef?.timestamp ?? c.createdAt;
|
|
79
|
+
const provenance = { ...(c.sourceRef ?? {}), ...AUTO_PROVENANCE };
|
|
80
|
+
await tx.query(`INSERT INTO timeline_entries (page_id, date, summary, detail, source, provenance)
|
|
81
|
+
VALUES ($1, $2, $3, $4, '', $5::jsonb)
|
|
82
|
+
ON CONFLICT (page_id, date, summary) DO UPDATE SET
|
|
83
|
+
detail = EXCLUDED.detail,
|
|
84
|
+
provenance = EXCLUDED.provenance`, [pageId, date, c.what.slice(0, 200), c.why ?? "", JSON.stringify(provenance)]);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
async function rebuildLinks(tx, pageId, contribs) {
|
|
88
|
+
const names = new Set();
|
|
89
|
+
for (const c of contribs)
|
|
90
|
+
for (const e of c.entities)
|
|
91
|
+
if (e.trim())
|
|
92
|
+
names.add(e.trim());
|
|
93
|
+
if (names.size === 0)
|
|
94
|
+
return;
|
|
95
|
+
// Best-effort: link to entity pages that already exist, matched by slug or title.
|
|
96
|
+
const targets = await tx.query(`SELECT DISTINCT id FROM pages
|
|
97
|
+
WHERE (slug = ANY($2::text[]) OR lower(title) = ANY($3::text[])) AND id <> $1`, [pageId, [...names], [...names].map((n) => n.toLowerCase())]);
|
|
98
|
+
for (const t of targets.rows) {
|
|
99
|
+
await tx.query(`INSERT INTO links (from_page_id, to_page_id, link_type, context, provenance)
|
|
100
|
+
VALUES ($1, $2, 'mentions', '', $3::jsonb)
|
|
101
|
+
ON CONFLICT (from_page_id, to_page_id, link_type) DO UPDATE SET provenance = EXCLUDED.provenance`, [pageId, t.id, JSON.stringify(AUTO_PROVENANCE)]);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
async function ensureTypeTags(tx, pageId, contribs) {
|
|
105
|
+
const types = new Set(contribs.map((c) => c.type));
|
|
106
|
+
for (const t of types) {
|
|
107
|
+
await tx.query(`INSERT INTO tags (page_id, tag) VALUES ($1, $2) ON CONFLICT (page_id, tag) DO NOTHING`, [pageId, t]);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Regenerate all derivatives of a canonical page from its active contributions,
|
|
112
|
+
* inside the caller's transaction (spec §6.1). Returns orphaned=true when the
|
|
113
|
+
* page has no active contributions left.
|
|
114
|
+
*/
|
|
115
|
+
export async function rematerializeCanonicalPage(tx, pageId) {
|
|
116
|
+
const pageRes = await tx.query("SELECT type, compiled_truth, frontmatter FROM pages WHERE id = $1 FOR UPDATE", [pageId]);
|
|
117
|
+
if (pageRes.rows.length === 0) {
|
|
118
|
+
return { pageId, activeCount: 0, orphaned: false };
|
|
119
|
+
}
|
|
120
|
+
const page = pageRes.rows[0];
|
|
121
|
+
const frontmatter = (parseJson(page.frontmatter) ?? {});
|
|
122
|
+
const contribs = await loadActive(tx, pageId);
|
|
123
|
+
await clearDerived(tx, pageId);
|
|
124
|
+
if (contribs.length === 0) {
|
|
125
|
+
// Orphaned: strip the auto section and flag for consolidator review.
|
|
126
|
+
const compiled = stripAutoSection(page.compiled_truth);
|
|
127
|
+
const fm = { ...frontmatter, orphaned: true };
|
|
128
|
+
const hash = computeContentHash(compiled, fm);
|
|
129
|
+
await tx.query(`UPDATE pages SET compiled_truth = $2, frontmatter = $3::jsonb, content_hash = $4, updated_at = NOW()
|
|
130
|
+
WHERE id = $1`, [pageId, compiled, JSON.stringify(fm), hash]);
|
|
131
|
+
await rechunkTx(tx, pageId, compiled);
|
|
132
|
+
return { pageId, activeCount: 0, orphaned: true };
|
|
133
|
+
}
|
|
134
|
+
const autoBody = renderAutoBody(page.type, contribs);
|
|
135
|
+
const compiled = replaceAutoSection(page.compiled_truth, autoBody);
|
|
136
|
+
const source = primarySource(contribs);
|
|
137
|
+
const fm = { ...frontmatter, pipeline: "v2" };
|
|
138
|
+
delete fm.orphaned;
|
|
139
|
+
if (source)
|
|
140
|
+
fm.source = source;
|
|
141
|
+
const hash = computeContentHash(compiled, fm);
|
|
142
|
+
await tx.query(`UPDATE pages SET compiled_truth = $2, frontmatter = $3::jsonb, content_hash = $4, updated_at = NOW()
|
|
143
|
+
WHERE id = $1`, [pageId, compiled, JSON.stringify(fm), hash]);
|
|
144
|
+
await rechunkTx(tx, pageId, compiled);
|
|
145
|
+
await rebuildTimeline(tx, pageId, contribs);
|
|
146
|
+
await rebuildLinks(tx, pageId, contribs);
|
|
147
|
+
await ensureTypeTags(tx, pageId, contribs);
|
|
148
|
+
return { pageId, activeCount: contribs.length, orphaned: false };
|
|
149
|
+
}
|
|
150
|
+
//# sourceMappingURL=rematerialize.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rematerialize.js","sourceRoot":"","sources":["../../src/apply/rematerialize.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { SqlExecutor } from "../store/sql-executor.js";
|
|
2
|
+
export declare class ReverseOrderError extends Error {
|
|
3
|
+
readonly attemptId: number;
|
|
4
|
+
constructor(attemptId: number);
|
|
5
|
+
}
|
|
6
|
+
export interface RollbackOutcome {
|
|
7
|
+
attemptId: number;
|
|
8
|
+
deactivated: number;
|
|
9
|
+
rematerialized: number;
|
|
10
|
+
orphaned: string[];
|
|
11
|
+
}
|
|
12
|
+
export declare class ApplyRollback {
|
|
13
|
+
private readonly ex;
|
|
14
|
+
constructor(ex: SqlExecutor);
|
|
15
|
+
rollback(attemptId: number): Promise<RollbackOutcome>;
|
|
16
|
+
private attemptTarget;
|
|
17
|
+
/** Undo one journaled NON-derived write. */
|
|
18
|
+
private applyInverse;
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=rollback.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rollback.d.ts","sourceRoot":"","sources":["../../src/apply/rollback.ts"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAAW,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAIrE,qBAAa,iBAAkB,SAAQ,KAAK;IAC9B,QAAQ,CAAC,SAAS,EAAE,MAAM;gBAAjB,SAAS,EAAE,MAAM;CAMvC;AAED,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,MAAM,CAAC;IACvB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAWD,qBAAa,aAAa;IACZ,OAAO,CAAC,QAAQ,CAAC,EAAE;gBAAF,EAAE,EAAE,WAAW;IAEtC,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC;YA0E7C,aAAa;IAS3B,4CAA4C;YAC9B,YAAY;CAgC3B"}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
// src/apply/rollback.ts
|
|
2
|
+
//
|
|
3
|
+
// Contributions-based rollback of one apply (spec §3.1, §6). The single source
|
|
4
|
+
// of truth for undo is the contribution set — NOT the page bodies:
|
|
5
|
+
// ① deactivate (active=false) every contribution the apply produced
|
|
6
|
+
// ② replay the NON-derived mutation journal in reverse (safety net)
|
|
7
|
+
// ③ rematerialize each affected page → derived links/tags/timeline/body
|
|
8
|
+
// rebuild from whatever contributions remain active.
|
|
9
|
+
//
|
|
10
|
+
// Invariants:
|
|
11
|
+
// - Reverse order: a later apply that still has active contributions on an
|
|
12
|
+
// affected page must be rolled back first (spec §6 "后发先回滚").
|
|
13
|
+
// - New-page rollback: a page left with zero active contributions is flagged
|
|
14
|
+
// `orphaned` (by rematerialize) for the consolidator to archive — never
|
|
15
|
+
// blind-deleted, because other sessions / user edits / links may rely on it.
|
|
16
|
+
// - Only NON-derived writes are journaled; derived writes are rebuilt by
|
|
17
|
+
// rematerialize, so the journal never double-touches them.
|
|
18
|
+
import { rematerializeCanonicalPage } from "./rematerialize.js";
|
|
19
|
+
export class ReverseOrderError extends Error {
|
|
20
|
+
attemptId;
|
|
21
|
+
constructor(attemptId) {
|
|
22
|
+
super(`apply_attempt ${attemptId} cannot be rolled back: a later apply still has active contributions on its pages (roll back the later apply first)`);
|
|
23
|
+
this.attemptId = attemptId;
|
|
24
|
+
this.name = "ReverseOrderError";
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function parseInverse(v) {
|
|
28
|
+
return typeof v === "string" ? JSON.parse(v) : v;
|
|
29
|
+
}
|
|
30
|
+
export class ApplyRollback {
|
|
31
|
+
ex;
|
|
32
|
+
constructor(ex) {
|
|
33
|
+
this.ex = ex;
|
|
34
|
+
}
|
|
35
|
+
async rollback(attemptId) {
|
|
36
|
+
const target = await this.attemptTarget(attemptId);
|
|
37
|
+
return this.ex.transaction(async (tx) => {
|
|
38
|
+
if (target === "staging") {
|
|
39
|
+
await tx.query("SET LOCAL search_path TO staging, public");
|
|
40
|
+
}
|
|
41
|
+
// Pages this apply contributed to (captured before deactivation).
|
|
42
|
+
const pageRows = await tx.query("SELECT DISTINCT canonical_page_id FROM memory_contributions WHERE apply_attempt_id = $1", [attemptId]);
|
|
43
|
+
const pageIds = pageRows.rows
|
|
44
|
+
.map((r) => r.canonical_page_id)
|
|
45
|
+
.filter((id) => id !== null);
|
|
46
|
+
// Reverse-order guard: no LATER apply may still be active on these pages.
|
|
47
|
+
if (pageIds.length > 0) {
|
|
48
|
+
const later = await tx.query(`SELECT 1 AS one FROM memory_contributions
|
|
49
|
+
WHERE active AND apply_attempt_id > $1 AND canonical_page_id = ANY($2::int[])
|
|
50
|
+
LIMIT 1`, [attemptId, pageIds]);
|
|
51
|
+
if (later.rows.length > 0)
|
|
52
|
+
throw new ReverseOrderError(attemptId);
|
|
53
|
+
}
|
|
54
|
+
// Lock affected pages for the duration of the rollback.
|
|
55
|
+
if (pageIds.length > 0) {
|
|
56
|
+
await tx.query("SELECT id FROM pages WHERE id = ANY($1::int[]) FOR UPDATE", [pageIds]);
|
|
57
|
+
}
|
|
58
|
+
// ① Deactivate this apply's contributions.
|
|
59
|
+
const deactivated = await tx.query("UPDATE memory_contributions SET active = false WHERE apply_attempt_id = $1 AND active RETURNING contribution_id", [attemptId]);
|
|
60
|
+
// ② Replay the non-derived mutation journal in reverse.
|
|
61
|
+
const journal = await tx.query("SELECT kind, inverse FROM public.apply_mutation_journal WHERE apply_attempt_id = $1 ORDER BY seq DESC", [attemptId]);
|
|
62
|
+
for (const entry of journal.rows) {
|
|
63
|
+
await this.applyInverse(tx, entry.kind, parseInverse(entry.inverse));
|
|
64
|
+
}
|
|
65
|
+
// ③ Rematerialize every affected page; collect the orphaned ones.
|
|
66
|
+
const orphaned = [];
|
|
67
|
+
for (const pageId of pageIds) {
|
|
68
|
+
const result = await rematerializeCanonicalPage(tx, pageId);
|
|
69
|
+
if (result.orphaned) {
|
|
70
|
+
const slug = await tx.query("SELECT slug FROM pages WHERE id = $1", [
|
|
71
|
+
pageId,
|
|
72
|
+
]);
|
|
73
|
+
if (slug.rows[0])
|
|
74
|
+
orphaned.push(slug.rows[0].slug);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
await tx.query("UPDATE public.apply_attempt SET detail = COALESCE(detail, '{}'::jsonb) || jsonb_build_object('rolled_back', true) WHERE id = $1", [attemptId]);
|
|
78
|
+
return {
|
|
79
|
+
attemptId,
|
|
80
|
+
deactivated: deactivated.rows.length,
|
|
81
|
+
rematerialized: pageIds.length,
|
|
82
|
+
orphaned,
|
|
83
|
+
};
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
async attemptTarget(attemptId) {
|
|
87
|
+
const r = await this.ex.query("SELECT target FROM public.apply_attempt WHERE id = $1", [attemptId]);
|
|
88
|
+
if (r.rows.length === 0)
|
|
89
|
+
throw new Error(`apply_attempt ${attemptId} not found`);
|
|
90
|
+
return r.rows[0].target;
|
|
91
|
+
}
|
|
92
|
+
/** Undo one journaled NON-derived write. */
|
|
93
|
+
async applyInverse(tx, kind, inverse) {
|
|
94
|
+
switch (kind) {
|
|
95
|
+
case "frontmatter_superseded_by": {
|
|
96
|
+
// Restore the previous supersede pointer (or remove it if there was none).
|
|
97
|
+
const slug = String(inverse.slug);
|
|
98
|
+
const prev = inverse.prev;
|
|
99
|
+
if (prev === null || prev === undefined) {
|
|
100
|
+
await tx.query("UPDATE pages SET frontmatter = frontmatter - 'superseded_by', updated_at = NOW() WHERE slug = $1", [slug]);
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
await tx.query("UPDATE pages SET frontmatter = frontmatter || jsonb_build_object('superseded_by', $2::text), updated_at = NOW() WHERE slug = $1", [slug, prev]);
|
|
104
|
+
}
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
case "page_created":
|
|
108
|
+
// The page becomes orphaned via rematerialize (step ③) rather than being
|
|
109
|
+
// deleted — the consolidator archives it. Nothing to undo destructively.
|
|
110
|
+
break;
|
|
111
|
+
default:
|
|
112
|
+
// Unknown kinds are ignored (forward-compatible safety net).
|
|
113
|
+
break;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
//# sourceMappingURL=rollback.js.map
|