hippo-memory 1.29.0 → 1.31.0
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 +13 -0
- package/dist/api.d.ts +49 -0
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js +78 -2
- package/dist/api.js.map +1 -1
- package/dist/audit.d.ts +1 -1
- package/dist/audit.d.ts.map +1 -1
- package/dist/audit.js.map +1 -1
- package/dist/capture.d.ts.map +1 -1
- package/dist/capture.js +79 -23
- package/dist/capture.js.map +1 -1
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +234 -13
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +8 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +21 -0
- package/dist/config.js.map +1 -1
- package/dist/connectors/github/ingest.d.ts.map +1 -1
- package/dist/connectors/github/ingest.js +17 -0
- package/dist/connectors/github/ingest.js.map +1 -1
- package/dist/connectors/slack/ingest.d.ts.map +1 -1
- package/dist/connectors/slack/ingest.js +15 -0
- package/dist/connectors/slack/ingest.js.map +1 -1
- package/dist/consolidate.d.ts.map +1 -1
- package/dist/consolidate.js +623 -312
- package/dist/consolidate.js.map +1 -1
- package/dist/dag.d.ts +10 -0
- package/dist/dag.d.ts.map +1 -1
- package/dist/dag.js +32 -3
- package/dist/dag.js.map +1 -1
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +43 -1
- package/dist/db.js.map +1 -1
- package/dist/extract.d.ts.map +1 -1
- package/dist/extract.js +18 -1
- package/dist/extract.js.map +1 -1
- package/dist/importers.d.ts +10 -0
- package/dist/importers.d.ts.map +1 -1
- package/dist/importers.js +242 -159
- package/dist/importers.js.map +1 -1
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +41 -5
- package/dist/mcp/server.js.map +1 -1
- package/dist/memory-value-weights.d.ts +22 -0
- package/dist/memory-value-weights.d.ts.map +1 -0
- package/dist/memory-value-weights.js +31 -0
- package/dist/memory-value-weights.js.map +1 -0
- package/dist/memory-value.d.ts +123 -0
- package/dist/memory-value.d.ts.map +1 -0
- package/dist/memory-value.js +255 -0
- package/dist/memory-value.js.map +1 -0
- package/dist/reject-flow.d.ts +66 -0
- package/dist/reject-flow.d.ts.map +1 -0
- package/dist/reject-flow.js +207 -0
- package/dist/reject-flow.js.map +1 -0
- package/dist/rejection.d.ts +100 -0
- package/dist/rejection.d.ts.map +1 -0
- package/dist/rejection.js +157 -0
- package/dist/rejection.js.map +1 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +5 -0
- package/dist/server.js.map +1 -1
- package/dist/shared.d.ts +9 -0
- package/dist/shared.d.ts.map +1 -1
- package/dist/shared.js +57 -6
- package/dist/shared.js.map +1 -1
- package/dist/sleep-redact.d.ts +2 -0
- package/dist/sleep-redact.d.ts.map +1 -1
- package/dist/sleep-redact.js +2 -0
- package/dist/sleep-redact.js.map +1 -1
- package/dist/src/api.js +78 -2
- package/dist/src/api.js.map +1 -1
- package/dist/src/audit.js.map +1 -1
- package/dist/src/capture.js +79 -23
- package/dist/src/capture.js.map +1 -1
- package/dist/src/cli.js +234 -13
- package/dist/src/cli.js.map +1 -1
- package/dist/src/config.js +21 -0
- package/dist/src/config.js.map +1 -1
- package/dist/src/connectors/github/ingest.js +17 -0
- package/dist/src/connectors/github/ingest.js.map +1 -1
- package/dist/src/connectors/slack/ingest.js +15 -0
- package/dist/src/connectors/slack/ingest.js.map +1 -1
- package/dist/src/consolidate.js +623 -312
- package/dist/src/consolidate.js.map +1 -1
- package/dist/src/dag.js +32 -3
- package/dist/src/dag.js.map +1 -1
- package/dist/src/db.js +43 -1
- package/dist/src/db.js.map +1 -1
- package/dist/src/extract.js +18 -1
- package/dist/src/extract.js.map +1 -1
- package/dist/src/importers.js +242 -159
- package/dist/src/importers.js.map +1 -1
- package/dist/src/mcp/server.js +41 -5
- package/dist/src/mcp/server.js.map +1 -1
- package/dist/src/memory-value-weights.js +31 -0
- package/dist/src/memory-value-weights.js.map +1 -0
- package/dist/src/memory-value.js +255 -0
- package/dist/src/memory-value.js.map +1 -0
- package/dist/src/reject-flow.js +207 -0
- package/dist/src/reject-flow.js.map +1 -0
- package/dist/src/rejection.js +157 -0
- package/dist/src/rejection.js.map +1 -0
- package/dist/src/server.js +5 -0
- package/dist/src/server.js.map +1 -1
- package/dist/src/shared.js +57 -6
- package/dist/src/shared.js.map +1 -1
- package/dist/src/sleep-redact.js +2 -0
- package/dist/src/sleep-redact.js.map +1 -1
- package/dist/src/store.js +498 -35
- package/dist/src/store.js.map +1 -1
- package/dist/src/version.js +1 -1
- package/dist/store.d.ts +107 -3
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +498 -35
- package/dist/store.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/extensions/openclaw-plugin/openclaw.plugin.json +1 -1
- package/extensions/openclaw-plugin/package.json +1 -1
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AT1 rejected-value tombstone — shared reject/unreject/list flow.
|
|
3
|
+
* docs/plans/2026-08-15-at1-rejected-value-tombstone.md (T2, plan §4).
|
|
4
|
+
*
|
|
5
|
+
* The CLI (`hippo reject`/`rejections`/`unreject`) and the Context-based
|
|
6
|
+
* `api.reject`/`api.unreject`/`api.listRejections` surfaces both need the
|
|
7
|
+
* SAME multi-step transaction + post-commit mirror-purge flow. Extracted
|
|
8
|
+
* here (leaf module) so neither duplicates it.
|
|
9
|
+
*
|
|
10
|
+
* Module direction: this file imports from store.ts, rejection.ts, and
|
|
11
|
+
* raw-archive.ts. Nothing imports FROM this file except cli.ts and api.ts,
|
|
12
|
+
* so it introduces no cycle.
|
|
13
|
+
*/
|
|
14
|
+
import { openHippoDb, closeHippoDb } from './db.js';
|
|
15
|
+
import { appendAuditEvent } from './audit.js';
|
|
16
|
+
import { archiveRawMemory } from './raw-archive.js';
|
|
17
|
+
import { initStore, deleteEntryCore, purgeMirrorBestEffort, writeIndexMirror, buildIndexFromDb, } from './store.js';
|
|
18
|
+
import { rejectionDigest, normalizeValueForRejection, insertRejectedValue, deleteRejectedValue, listRejectedValues, } from './rejection.js';
|
|
19
|
+
/**
|
|
20
|
+
* `hippo reject` / `api.reject` core flow. ONE connection, one transaction:
|
|
21
|
+
* insert the tombstone, enumerate + remove every live tenant row whose
|
|
22
|
+
* normalized digest matches (kind-aware), one aggregate `reject_value`
|
|
23
|
+
* audit, COMMIT. Then post-commit (mirrors the existing purge+reaper
|
|
24
|
+
* pattern verbatim from api.archiveRaw, api.ts:1913-1938): best-effort
|
|
25
|
+
* mirror purge per removed id, `mirror_cleaned_at` stamps for raw ids, one
|
|
26
|
+
* index mirror rewrite.
|
|
27
|
+
*/
|
|
28
|
+
export function rejectValue(opts) {
|
|
29
|
+
if (!opts.reason.trim()) {
|
|
30
|
+
throw new Error('reject requires a non-empty --reason (the tombstone stores no content; reason is its only identity).');
|
|
31
|
+
}
|
|
32
|
+
if (opts.memoryId === undefined && opts.value === undefined) {
|
|
33
|
+
throw new Error('reject requires either a memory id or --value.');
|
|
34
|
+
}
|
|
35
|
+
if (opts.memoryId !== undefined && opts.value !== undefined) {
|
|
36
|
+
// P2 fix: the CLI's flag parser already refuses both forms together;
|
|
37
|
+
// the shared flow itself didn't enforce it, so a direct api caller
|
|
38
|
+
// passing both silently got the memoryId path with `value` ignored —
|
|
39
|
+
// surprising for a caller who thought they were rejecting `value`.
|
|
40
|
+
throw new Error('reject accepts either a memory id or --value, not both.');
|
|
41
|
+
}
|
|
42
|
+
if (opts.value !== undefined && normalizeValueForRejection(opts.value).length === 0) {
|
|
43
|
+
// Direct api callers can pass strings the CLI flag parser would have
|
|
44
|
+
// refused; an empty-normalized tombstone would refuse nothing meaningful
|
|
45
|
+
// and pollute the listing.
|
|
46
|
+
throw new Error('reject --value requires non-empty content.');
|
|
47
|
+
}
|
|
48
|
+
initStore(opts.hippoRoot);
|
|
49
|
+
const db = openHippoDb(opts.hippoRoot);
|
|
50
|
+
try {
|
|
51
|
+
let content;
|
|
52
|
+
if (opts.memoryId !== undefined) {
|
|
53
|
+
const row = db
|
|
54
|
+
.prepare(`SELECT content, tenant_id FROM memories WHERE id = ?`)
|
|
55
|
+
.get(opts.memoryId);
|
|
56
|
+
if (!row || row.tenant_id !== opts.tenantId) {
|
|
57
|
+
throw new Error(`memory not found: ${opts.memoryId}`);
|
|
58
|
+
}
|
|
59
|
+
content = row.content;
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
content = opts.value;
|
|
63
|
+
}
|
|
64
|
+
const digest = rejectionDigest(content);
|
|
65
|
+
const now = new Date().toISOString();
|
|
66
|
+
const removedIds = [];
|
|
67
|
+
const removedRawIds = [];
|
|
68
|
+
db.exec('BEGIN');
|
|
69
|
+
try {
|
|
70
|
+
insertRejectedValue(db, {
|
|
71
|
+
tenantId: opts.tenantId,
|
|
72
|
+
digest,
|
|
73
|
+
reason: opts.reason,
|
|
74
|
+
rejectedBy: opts.actor,
|
|
75
|
+
rejectedAt: now,
|
|
76
|
+
sourceMemoryId: opts.memoryId ?? null,
|
|
77
|
+
normalizedChars: normalizeValueForRejection(content).length,
|
|
78
|
+
});
|
|
79
|
+
// O(N) scan over the tenant's rows (plan §4): human-triggered command
|
|
80
|
+
// on ~1-5k-row stores — acceptable, documented. A digest column on
|
|
81
|
+
// memories is the escape if stores grow 100x; not needed now.
|
|
82
|
+
const rows = db
|
|
83
|
+
.prepare(`SELECT id, kind, content FROM memories WHERE tenant_id = ?`)
|
|
84
|
+
.all(opts.tenantId);
|
|
85
|
+
for (const row of rows) {
|
|
86
|
+
if (rejectionDigest(row.content) !== digest)
|
|
87
|
+
continue;
|
|
88
|
+
if (row.kind === 'raw') {
|
|
89
|
+
// Append-only trigger respected — archiveRawMemory is the only
|
|
90
|
+
// legitimate removal path for kind='raw', and its inner SAVEPOINT
|
|
91
|
+
// composes safely inside this BEGIN/COMMIT.
|
|
92
|
+
archiveRawMemory(db, row.id, { reason: opts.reason, who: opts.actor });
|
|
93
|
+
removedRawIds.push(row.id);
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
// suppressForgetAudit: the aggregate reject_value row below is the
|
|
97
|
+
// trail for these removals, not N individual forget rows (plan
|
|
98
|
+
// §4, round-3 advisory 2 — mirrors api.ts:1873-1877).
|
|
99
|
+
deleteEntryCore(db, row.id, { actor: opts.actor, suppressForgetAudit: true });
|
|
100
|
+
}
|
|
101
|
+
removedIds.push(row.id);
|
|
102
|
+
}
|
|
103
|
+
try {
|
|
104
|
+
appendAuditEvent(db, {
|
|
105
|
+
tenantId: opts.tenantId,
|
|
106
|
+
actor: opts.actor,
|
|
107
|
+
op: 'reject_value',
|
|
108
|
+
targetId: opts.memoryId,
|
|
109
|
+
metadata: { digest, removedIds, count: removedIds.length },
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
// Best-effort — mirrors store.ts's private audit() semantics. This
|
|
114
|
+
// runs INSIDE the still-open transaction (COMMIT is the next
|
|
115
|
+
// statement): a swallowed audit failure lets the tombstone +
|
|
116
|
+
// removals commit without the trail row, rather than rolling the
|
|
117
|
+
// whole reject back over bookkeeping.
|
|
118
|
+
}
|
|
119
|
+
db.exec('COMMIT');
|
|
120
|
+
}
|
|
121
|
+
catch (err) {
|
|
122
|
+
try {
|
|
123
|
+
db.exec('ROLLBACK');
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
// already rolled back
|
|
127
|
+
}
|
|
128
|
+
throw err;
|
|
129
|
+
}
|
|
130
|
+
// Post-commit, db handle still open (same pattern as api.archiveRaw):
|
|
131
|
+
// best-effort mirror purge per removed id, reaper-backstop stamp for
|
|
132
|
+
// raw ids, one index mirror rewrite.
|
|
133
|
+
for (const id of removedIds) {
|
|
134
|
+
// AT1 fix: purgeMirrorBestEffort retries once, then — for non-raw ids,
|
|
135
|
+
// which cleanupArchivedMirrors' reaper never scans — reports the
|
|
136
|
+
// EXPLICIT leftover path(s) instead of the false "will retry via
|
|
137
|
+
// reaper" claim. See its own doc comment (store.ts, near
|
|
138
|
+
// removeEntryMirrors) for the full rationale.
|
|
139
|
+
const mirrorOk = purgeMirrorBestEffort(opts.hippoRoot, id, removedRawIds.includes(id), 'hippo reject');
|
|
140
|
+
if (mirrorOk && removedRawIds.includes(id)) {
|
|
141
|
+
db.prepare(`UPDATE raw_archive SET mirror_cleaned_at = ? WHERE memory_id = ?`).run(new Date().toISOString(), id);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
if (removedIds.length > 0) {
|
|
145
|
+
writeIndexMirror(opts.hippoRoot, buildIndexFromDb(db));
|
|
146
|
+
}
|
|
147
|
+
return { digest, content, removedIds, removedRawIds };
|
|
148
|
+
}
|
|
149
|
+
finally {
|
|
150
|
+
closeHippoDb(db);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* `hippo unreject` / `api.unreject` — resolve a unique tombstone by digest
|
|
155
|
+
* (or prefix), delete it, audit `unreject_value`. The only v1 escape hatch
|
|
156
|
+
* (plan §4): no per-write force flag.
|
|
157
|
+
*/
|
|
158
|
+
export function unrejectValue(hippoRoot, tenantId, digestOrPrefix, actor) {
|
|
159
|
+
// P2 fix: an empty/blank prefix startsWith-matches EVERY digest (every
|
|
160
|
+
// string starts with ''), which would previously fall through to the
|
|
161
|
+
// ambiguous-candidates branch and list the whole tombstone set instead of
|
|
162
|
+
// failing loud on the actually-invalid input. Reject before the DB round
|
|
163
|
+
// trip.
|
|
164
|
+
if (digestOrPrefix.trim().length === 0) {
|
|
165
|
+
return { status: 'not_found' };
|
|
166
|
+
}
|
|
167
|
+
initStore(hippoRoot);
|
|
168
|
+
const db = openHippoDb(hippoRoot);
|
|
169
|
+
try {
|
|
170
|
+
const all = listRejectedValues(db, tenantId);
|
|
171
|
+
const matches = all.filter((r) => r.digest.startsWith(digestOrPrefix));
|
|
172
|
+
if (matches.length === 0)
|
|
173
|
+
return { status: 'not_found' };
|
|
174
|
+
if (matches.length > 1)
|
|
175
|
+
return { status: 'ambiguous', candidates: matches };
|
|
176
|
+
const target = matches[0];
|
|
177
|
+
deleteRejectedValue(db, tenantId, target.digest);
|
|
178
|
+
try {
|
|
179
|
+
appendAuditEvent(db, {
|
|
180
|
+
tenantId,
|
|
181
|
+
actor,
|
|
182
|
+
op: 'unreject_value',
|
|
183
|
+
targetId: target.sourceMemoryId ?? undefined,
|
|
184
|
+
metadata: { digest: target.digest, reason: target.reason },
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
catch {
|
|
188
|
+
// Best-effort, same as reject's audit call above.
|
|
189
|
+
}
|
|
190
|
+
return { status: 'ok', digest: target.digest, reason: target.reason };
|
|
191
|
+
}
|
|
192
|
+
finally {
|
|
193
|
+
closeHippoDb(db);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
/** `hippo rejections` / `api.listRejections` — list tombstones for a tenant. */
|
|
197
|
+
export function listRejectionsForTenant(hippoRoot, tenantId) {
|
|
198
|
+
initStore(hippoRoot);
|
|
199
|
+
const db = openHippoDb(hippoRoot);
|
|
200
|
+
try {
|
|
201
|
+
return listRejectedValues(db, tenantId);
|
|
202
|
+
}
|
|
203
|
+
finally {
|
|
204
|
+
closeHippoDb(db);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
//# sourceMappingURL=reject-flow.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reject-flow.js","sourceRoot":"","sources":["../../src/reject-flow.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AT1 rejected-value tombstone — core invariant.
|
|
3
|
+
* docs/plans/2026-08-15-at1-rejected-value-tombstone.md
|
|
4
|
+
*
|
|
5
|
+
* Exact-normalized-value semantics: a human who rejects a fact can refuse
|
|
6
|
+
* byte-stable re-ingestion of the same value across remember/capture/import/
|
|
7
|
+
* sync surfaces. Paraphrase/semantic matching is explicitly out of scope
|
|
8
|
+
* (documented limitation, plan "Design").
|
|
9
|
+
*
|
|
10
|
+
* Kept db-agnostic (helpers take a `DatabaseSyncLike` handle) and does NOT
|
|
11
|
+
* import from store.ts — store.ts imports from here, and the reverse would
|
|
12
|
+
* be a cycle.
|
|
13
|
+
*/
|
|
14
|
+
import { createHash } from 'node:crypto';
|
|
15
|
+
/**
|
|
16
|
+
* Normalize content for rejection-digest comparisons: Unicode NFC →
|
|
17
|
+
* lowercase → collapse whitespace runs to a single space → trim. No
|
|
18
|
+
* punctuation stripping — over-normalization creates false refusals, which
|
|
19
|
+
* are worse than misses (plan §1).
|
|
20
|
+
*/
|
|
21
|
+
export function normalizeValueForRejection(content) {
|
|
22
|
+
return content.normalize('NFC').toLowerCase().replace(/\s+/g, ' ').trim();
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Full sha256 hex (64 chars) of the normalized content. Reuses the strong-
|
|
26
|
+
* identity convention (importers.ts:814 content-hash tag), NOT the privacy-
|
|
27
|
+
* lossy 16-char convention (recall-trace query hashing) — a tombstone lookup
|
|
28
|
+
* key needs collision resistance, not redaction (plan §1).
|
|
29
|
+
*/
|
|
30
|
+
export function rejectionDigest(content) {
|
|
31
|
+
return createHash('sha256').update(normalizeValueForRejection(content)).digest('hex');
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Thrown by the write-path guard (checkRejectionGuard, called from
|
|
35
|
+
* upsertEntryRow) when an incoming write would introduce a value matching a
|
|
36
|
+
* tombstoned digest. Carries enough context for the transaction-owner catch
|
|
37
|
+
* blocks (writeEntry, api.supersede) to write a post-rollback
|
|
38
|
+
* `reject_refusal` audit row via `auditRejectionRefusal` (plan §3).
|
|
39
|
+
*/
|
|
40
|
+
export class RejectedValueError extends Error {
|
|
41
|
+
digest;
|
|
42
|
+
tenantId;
|
|
43
|
+
entryId;
|
|
44
|
+
reason;
|
|
45
|
+
rejectedAt;
|
|
46
|
+
constructor(opts) {
|
|
47
|
+
super(`Memory value refused: matches a rejected value (digest ${opts.digest.slice(0, 12)}..., ` +
|
|
48
|
+
`reason: ${opts.reason ?? 'none given'}). Run "hippo unreject" to allow it again.`);
|
|
49
|
+
this.name = 'RejectedValueError';
|
|
50
|
+
this.digest = opts.digest;
|
|
51
|
+
this.tenantId = opts.tenantId;
|
|
52
|
+
this.entryId = opts.entryId;
|
|
53
|
+
this.reason = opts.reason;
|
|
54
|
+
this.rejectedAt = opts.rejectedAt;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function rowToRejectedValue(row) {
|
|
58
|
+
return {
|
|
59
|
+
tenantId: row.tenant_id,
|
|
60
|
+
digest: row.digest,
|
|
61
|
+
reason: row.reason,
|
|
62
|
+
rejectedBy: row.rejected_by,
|
|
63
|
+
rejectedAt: row.rejected_at,
|
|
64
|
+
sourceMemoryId: row.source_memory_id,
|
|
65
|
+
normalizedChars: row.normalized_chars,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Look up a tombstone by tenant + digest. One indexed point query — the
|
|
70
|
+
* guard's common-case cost, a miss ends the guard (plan §3).
|
|
71
|
+
*/
|
|
72
|
+
export function findRejectedValue(db, tenantId, digest) {
|
|
73
|
+
const row = db
|
|
74
|
+
.prepare(`SELECT tenant_id, digest, reason, rejected_by, rejected_at, source_memory_id, normalized_chars
|
|
75
|
+
FROM rejected_values WHERE tenant_id = ? AND digest = ?`)
|
|
76
|
+
.get(tenantId, digest);
|
|
77
|
+
return row ? rowToRejectedValue(row) : null;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Insert (or refresh) a tombstone row. Caller owns the transaction — used by
|
|
81
|
+
* the T2 `reject` verb and `resolveConflict`'s `rejectLoserValue` path.
|
|
82
|
+
*/
|
|
83
|
+
export function insertRejectedValue(db, opts) {
|
|
84
|
+
db.prepare(`INSERT INTO rejected_values(tenant_id, digest, reason, rejected_by, rejected_at, source_memory_id, normalized_chars)
|
|
85
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
86
|
+
ON CONFLICT(tenant_id, digest) DO UPDATE SET
|
|
87
|
+
reason = excluded.reason,
|
|
88
|
+
rejected_by = excluded.rejected_by,
|
|
89
|
+
rejected_at = excluded.rejected_at,
|
|
90
|
+
source_memory_id = excluded.source_memory_id,
|
|
91
|
+
normalized_chars = excluded.normalized_chars`).run(opts.tenantId, opts.digest, opts.reason, opts.rejectedBy, opts.rejectedAt, opts.sourceMemoryId ?? null, opts.normalizedChars);
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Delete a tombstone by tenant + exact digest — the T2 `unreject` verb, the
|
|
95
|
+
* only v1 escape hatch (plan §4).
|
|
96
|
+
*/
|
|
97
|
+
export function deleteRejectedValue(db, tenantId, digest) {
|
|
98
|
+
const result = db.prepare(`DELETE FROM rejected_values WHERE tenant_id = ? AND digest = ?`).run(tenantId, digest);
|
|
99
|
+
return (result.changes ?? 0) > 0;
|
|
100
|
+
}
|
|
101
|
+
/** List tombstones for a tenant, newest first — the T2 `rejections` verb. */
|
|
102
|
+
export function listRejectedValues(db, tenantId) {
|
|
103
|
+
const rows = db
|
|
104
|
+
.prepare(`SELECT tenant_id, digest, reason, rejected_by, rejected_at, source_memory_id, normalized_chars
|
|
105
|
+
FROM rejected_values WHERE tenant_id = ? ORDER BY rejected_at DESC, digest ASC`)
|
|
106
|
+
.all(tenantId);
|
|
107
|
+
return rows.map(rowToRejectedValue);
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* The write-path guard's check helper, called from `upsertEntryRow`
|
|
111
|
+
* (store.ts). Fires when the incoming content's digest matches a tombstone
|
|
112
|
+
* AND the write *introduces* that content: the row is new, OR the stored
|
|
113
|
+
* row's content digest differs from the incoming one — an UPSERT editing a
|
|
114
|
+
* same-id row TO a rejected value is a content introduction and must be
|
|
115
|
+
* refused. Unchanged same-id re-persists (recall boost, decay, star toggle)
|
|
116
|
+
* are exempt by construction (plan §3).
|
|
117
|
+
*
|
|
118
|
+
* Ordering minimizes queries on the common (miss) path: (a) caller has
|
|
119
|
+
* already computed nothing yet — this does the digest + point lookup first;
|
|
120
|
+
* (b) a miss returns immediately (ONE indexed point query); (c) only on a
|
|
121
|
+
* tombstone hit does it SELECT the stored row's content to classify
|
|
122
|
+
* new-row vs content-introduction (+1 query, rare path).
|
|
123
|
+
*/
|
|
124
|
+
export function checkRejectionGuard(db, tenantId, entryId, content) {
|
|
125
|
+
const incomingDigest = rejectionDigest(content);
|
|
126
|
+
const tombstone = findRejectedValue(db, tenantId, incomingDigest);
|
|
127
|
+
if (!tombstone)
|
|
128
|
+
return; // miss ends the guard — the overwhelmingly common case
|
|
129
|
+
// Deliberately id-only (no tenant filter): memory ids are globally unique
|
|
130
|
+
// ULIDs, this lookup only classifies new-row vs same-id re-persist for the
|
|
131
|
+
// id the caller is already writing, and the tombstone lookup above is the
|
|
132
|
+
// tenant-scoped decision. Matches deleteEntry's own by-id SELECT.
|
|
133
|
+
//
|
|
134
|
+
// P2 fix: also read tenant_id. Content-digest-only comparison let a
|
|
135
|
+
// same-id upsert that ONLY changes tenantId slip through as an "unchanged
|
|
136
|
+
// re-persist" — content C sitting quietly (never rejected) in tenant A
|
|
137
|
+
// could be re-tagged into tenant B, and since C's digest already matched
|
|
138
|
+
// this row's stored digest, the guard exempted it even though B is the
|
|
139
|
+
// tenant that rejected C (that is WHY `tombstone` above is non-null: the
|
|
140
|
+
// lookup already ran under the INCOMING/destination tenantId). A tenant
|
|
141
|
+
// change on the SAME id is therefore always a content introduction into
|
|
142
|
+
// the destination tenant, exactly as if the row were new there.
|
|
143
|
+
const storedRow = db.prepare(`SELECT content, tenant_id FROM memories WHERE id = ?`).get(entryId);
|
|
144
|
+
const isNewRow = storedRow === undefined;
|
|
145
|
+
const tenantChanged = !isNewRow && storedRow.tenant_id !== tenantId;
|
|
146
|
+
const isContentIntroduction = isNewRow || tenantChanged || rejectionDigest(storedRow.content) !== incomingDigest;
|
|
147
|
+
if (!isContentIntroduction)
|
|
148
|
+
return; // unchanged same-id re-persist — exempt by construction
|
|
149
|
+
throw new RejectedValueError({
|
|
150
|
+
digest: incomingDigest,
|
|
151
|
+
tenantId,
|
|
152
|
+
entryId,
|
|
153
|
+
reason: tombstone.reason,
|
|
154
|
+
rejectedAt: tombstone.rejectedAt,
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
//# sourceMappingURL=rejection.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rejection.js","sourceRoot":"","sources":["../../src/rejection.ts"],"names":[],"mappings":"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"}
|
package/dist/src/server.js
CHANGED
|
@@ -108,6 +108,11 @@ const VALID_AUDIT_OPS = new Set([
|
|
|
108
108
|
'customer_note_create', // E2 customer_note first-class object — emitted by saveCustomerNote
|
|
109
109
|
'customer_note_supersede', // E2 — emitted by saveCustomerNote on a supersession
|
|
110
110
|
'customer_note_close', // E2 — emitted by closeCustomerNote
|
|
111
|
+
'mv_rescue', // LC2-E3 — emitted by consolidate() per rescue; lockstep with AuditOp union + cli.ts VALID_AUDIT_OPS
|
|
112
|
+
'reject_value', // AT1 — emitted by `hippo reject`; lockstep with AuditOp union + cli.ts VALID_AUDIT_OPS
|
|
113
|
+
'reject_refusal', // AT1 — emitted when the rejection guard refuses a write; lockstep
|
|
114
|
+
'unreject_value', // AT1 — emitted by `hippo unreject`; lockstep
|
|
115
|
+
'conflict_resolve', // AT1 — emitted by resolveConflict on every resolution path; lockstep
|
|
111
116
|
]);
|
|
112
117
|
// Cap on GET /v1/audit?limit=. Matches docs/api.md (when written) and is large
|
|
113
118
|
// enough to dump a small deployment's full audit log without paginating, but
|