hippo-memory 1.30.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/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 +233 -13
- package/dist/cli.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 +428 -290
- 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 +34 -4
- package/dist/mcp/server.js.map +1 -1
- 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 +4 -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 +233 -13
- package/dist/src/cli.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 +428 -290
- 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 +34 -4
- package/dist/src/mcp/server.js.map +1 -1
- 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 +4 -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,100 @@
|
|
|
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 type { DatabaseSyncLike } from './db.js';
|
|
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 declare function normalizeValueForRejection(content: string): string;
|
|
22
|
+
/**
|
|
23
|
+
* Full sha256 hex (64 chars) of the normalized content. Reuses the strong-
|
|
24
|
+
* identity convention (importers.ts:814 content-hash tag), NOT the privacy-
|
|
25
|
+
* lossy 16-char convention (recall-trace query hashing) — a tombstone lookup
|
|
26
|
+
* key needs collision resistance, not redaction (plan §1).
|
|
27
|
+
*/
|
|
28
|
+
export declare function rejectionDigest(content: string): string;
|
|
29
|
+
/**
|
|
30
|
+
* Thrown by the write-path guard (checkRejectionGuard, called from
|
|
31
|
+
* upsertEntryRow) when an incoming write would introduce a value matching a
|
|
32
|
+
* tombstoned digest. Carries enough context for the transaction-owner catch
|
|
33
|
+
* blocks (writeEntry, api.supersede) to write a post-rollback
|
|
34
|
+
* `reject_refusal` audit row via `auditRejectionRefusal` (plan §3).
|
|
35
|
+
*/
|
|
36
|
+
export declare class RejectedValueError extends Error {
|
|
37
|
+
readonly digest: string;
|
|
38
|
+
readonly tenantId: string;
|
|
39
|
+
readonly entryId: string;
|
|
40
|
+
readonly reason: string | null;
|
|
41
|
+
readonly rejectedAt: string;
|
|
42
|
+
constructor(opts: {
|
|
43
|
+
digest: string;
|
|
44
|
+
tenantId: string;
|
|
45
|
+
entryId: string;
|
|
46
|
+
reason: string | null;
|
|
47
|
+
rejectedAt: string;
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
export interface RejectedValueRow {
|
|
51
|
+
tenantId: string;
|
|
52
|
+
digest: string;
|
|
53
|
+
reason: string | null;
|
|
54
|
+
rejectedBy: string | null;
|
|
55
|
+
rejectedAt: string;
|
|
56
|
+
sourceMemoryId: string | null;
|
|
57
|
+
normalizedChars: number | null;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Look up a tombstone by tenant + digest. One indexed point query — the
|
|
61
|
+
* guard's common-case cost, a miss ends the guard (plan §3).
|
|
62
|
+
*/
|
|
63
|
+
export declare function findRejectedValue(db: DatabaseSyncLike, tenantId: string, digest: string): RejectedValueRow | null;
|
|
64
|
+
/**
|
|
65
|
+
* Insert (or refresh) a tombstone row. Caller owns the transaction — used by
|
|
66
|
+
* the T2 `reject` verb and `resolveConflict`'s `rejectLoserValue` path.
|
|
67
|
+
*/
|
|
68
|
+
export declare function insertRejectedValue(db: DatabaseSyncLike, opts: {
|
|
69
|
+
tenantId: string;
|
|
70
|
+
digest: string;
|
|
71
|
+
reason: string;
|
|
72
|
+
rejectedBy: string;
|
|
73
|
+
rejectedAt: string;
|
|
74
|
+
sourceMemoryId?: string | null;
|
|
75
|
+
normalizedChars: number;
|
|
76
|
+
}): void;
|
|
77
|
+
/**
|
|
78
|
+
* Delete a tombstone by tenant + exact digest — the T2 `unreject` verb, the
|
|
79
|
+
* only v1 escape hatch (plan §4).
|
|
80
|
+
*/
|
|
81
|
+
export declare function deleteRejectedValue(db: DatabaseSyncLike, tenantId: string, digest: string): boolean;
|
|
82
|
+
/** List tombstones for a tenant, newest first — the T2 `rejections` verb. */
|
|
83
|
+
export declare function listRejectedValues(db: DatabaseSyncLike, tenantId: string): RejectedValueRow[];
|
|
84
|
+
/**
|
|
85
|
+
* The write-path guard's check helper, called from `upsertEntryRow`
|
|
86
|
+
* (store.ts). Fires when the incoming content's digest matches a tombstone
|
|
87
|
+
* AND the write *introduces* that content: the row is new, OR the stored
|
|
88
|
+
* row's content digest differs from the incoming one — an UPSERT editing a
|
|
89
|
+
* same-id row TO a rejected value is a content introduction and must be
|
|
90
|
+
* refused. Unchanged same-id re-persists (recall boost, decay, star toggle)
|
|
91
|
+
* are exempt by construction (plan §3).
|
|
92
|
+
*
|
|
93
|
+
* Ordering minimizes queries on the common (miss) path: (a) caller has
|
|
94
|
+
* already computed nothing yet — this does the digest + point lookup first;
|
|
95
|
+
* (b) a miss returns immediately (ONE indexed point query); (c) only on a
|
|
96
|
+
* tombstone hit does it SELECT the stored row's content to classify
|
|
97
|
+
* new-row vs content-introduction (+1 query, rare path).
|
|
98
|
+
*/
|
|
99
|
+
export declare function checkRejectionGuard(db: DatabaseSyncLike, tenantId: string, entryId: string, content: string): void;
|
|
100
|
+
//# sourceMappingURL=rejection.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rejection.d.ts","sourceRoot":"","sources":["../src/rejection.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAGH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAEhD;;;;;GAKG;AACH,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAElE;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEvD;AAED;;;;;;GAMG;AACH,qBAAa,kBAAmB,SAAQ,KAAK;IAC3C,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;gBAEhB,IAAI,EAAE;QAChB,MAAM,EAAE,MAAM,CAAC;QACf,QAAQ,EAAE,MAAM,CAAC;QACjB,OAAO,EAAE,MAAM,CAAC;QAChB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;QACtB,UAAU,EAAE,MAAM,CAAC;KACpB;CAYF;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;CAChC;AAwBD;;;GAGG;AACH,wBAAgB,iBAAiB,CAC/B,EAAE,EAAE,gBAAgB,EACpB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,GACb,gBAAgB,GAAG,IAAI,CAQzB;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CACjC,EAAE,EAAE,gBAAgB,EACpB,IAAI,EAAE;IACJ,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,eAAe,EAAE,MAAM,CAAC;CACzB,GACA,IAAI,CAmBN;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,EAAE,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAGnG;AAED,6EAA6E;AAC7E,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,GAAG,gBAAgB,EAAE,CAQ7F;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,mBAAmB,CACjC,EAAE,EAAE,gBAAgB,EACpB,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,GACd,IAAI,CAmCN"}
|
|
@@ -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/server.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAyBA,kFAAkF;AAClF,wBAAgB,+BAA+B,IAAI,IAAI,CAEtD;
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAyBA,kFAAkF;AAClF,wBAAgB,+BAA+B,IAAI,IAAI,CAEtD;AA+QD,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1B;;;8BAG0B;IAC1B,MAAM,CAAC,EAAE,OAAO,WAAW,EAAE,MAAM,CAAC;CACrC;AAED,MAAM,WAAW,SAAS;IACxB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AA0HD;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,aAAa,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAMrE;AAmrFD;;;;;;;;;;GAUG;AACH,wBAAsB,KAAK,CAAC,IAAI,EAAE,SAAS,GAAG,OAAO,CAAC,YAAY,CAAC,CA0JlE"}
|
package/dist/server.js
CHANGED
|
@@ -109,6 +109,10 @@ const VALID_AUDIT_OPS = new Set([
|
|
|
109
109
|
'customer_note_supersede', // E2 — emitted by saveCustomerNote on a supersession
|
|
110
110
|
'customer_note_close', // E2 — emitted by closeCustomerNote
|
|
111
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
|
|
112
116
|
]);
|
|
113
117
|
// Cap on GET /v1/audit?limit=. Matches docs/api.md (when written) and is large
|
|
114
118
|
// enough to dump a small deployment's full audit log without paginating, but
|