@shadowforge0/aquifer-memory 1.5.9 → 1.6.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/.env.example +23 -0
- package/README.md +96 -73
- package/README_CN.md +659 -0
- package/README_TW.md +680 -0
- package/aquifer.config.example.json +34 -0
- package/consumers/claude-code.js +11 -11
- package/consumers/cli.js +374 -39
- package/consumers/codex-handoff.js +152 -0
- package/consumers/codex.js +1549 -0
- package/consumers/default/daily-entries.js +23 -4
- package/consumers/default/index.js +2 -2
- package/consumers/default/prompts/summary.js +6 -6
- package/consumers/mcp.js +131 -7
- package/consumers/openclaw-ext/index.js +0 -1
- package/consumers/openclaw-plugin.js +44 -4
- package/consumers/shared/config.js +28 -0
- package/consumers/shared/factory.js +2 -0
- package/consumers/shared/ingest.js +1 -1
- package/consumers/shared/normalize.js +14 -3
- package/consumers/shared/recall-format.js +53 -0
- package/consumers/shared/summary-parser.js +151 -0
- package/core/aquifer.js +384 -18
- package/core/finalization-review.js +319 -0
- package/core/insights.js +210 -58
- package/core/mcp-manifest.js +69 -2
- package/core/memory-bootstrap.js +188 -0
- package/core/memory-consolidation.js +1236 -0
- package/core/memory-promotion.js +544 -0
- package/core/memory-recall.js +247 -0
- package/core/memory-records.js +581 -0
- package/core/memory-safety-gate.js +224 -0
- package/core/session-finalization.js +350 -0
- package/core/storage.js +456 -2
- package/docs/getting-started.md +99 -0
- package/docs/postprocess-contract.md +2 -2
- package/docs/setup.md +51 -2
- package/package.json +31 -9
- package/pipeline/normalize/adapters/codex.js +106 -0
- package/pipeline/normalize/detect.js +3 -2
- package/schema/001-base.sql +3 -0
- package/schema/007-v1-foundation.sql +273 -0
- package/schema/008-session-finalizations.sql +50 -0
- package/schema/009-v1-assertion-plane.sql +193 -0
- package/schema/010-v1-finalization-review.sql +160 -0
- package/schema/011-v1-compaction-claim.sql +46 -0
- package/schema/012-v1-compaction-lease.sql +39 -0
- package/schema/013-v1-compaction-lineage.sql +193 -0
- package/scripts/backfill-canonical-key.js +250 -0
- package/scripts/codex-recovery.js +532 -0
- package/consumers/miranda/context-inject.js +0 -119
- package/consumers/miranda/daily-entries.js +0 -224
- package/consumers/miranda/index.js +0 -364
- package/consumers/miranda/instance.js +0 -55
- package/consumers/miranda/llm.js +0 -99
- package/consumers/miranda/profile.json +0 -145
- package/consumers/miranda/prompts/summary.js +0 -303
- package/consumers/miranda/recall-format.js +0 -76
- package/consumers/miranda/render-daily-md.js +0 -186
- package/consumers/miranda/workspace-files.js +0 -91
- package/scripts/drop-entity-state-history.sql +0 -17
- package/scripts/drop-insights.sql +0 -12
- package/scripts/install-openclaw.sh +0 -59
- package/scripts/queries.json +0 -45
- package/scripts/retro-recall-bench.js +0 -409
- package/scripts/sample-bench-queries.sql +0 -75
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
-- Aquifer v1 structured assertion plane
|
|
2
|
+
-- Requires: 001-base.sql, 007-v1-foundation.sql, and 008-session-finalizations.sql
|
|
3
|
+
-- Usage: replace ${schema} with actual schema name
|
|
4
|
+
--
|
|
5
|
+
-- This migration is additive. It introduces a new v1 structured assertion
|
|
6
|
+
-- table and related integrity guards without repurposing legacy 004-facts rows.
|
|
7
|
+
|
|
8
|
+
-- =========================================================================
|
|
9
|
+
-- Scope tenant safety: prevent future cross-tenant ancestry leakage
|
|
10
|
+
-- =========================================================================
|
|
11
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_scopes_tenant_row
|
|
12
|
+
ON ${schema}.scopes (tenant_id, id);
|
|
13
|
+
|
|
14
|
+
CREATE OR REPLACE FUNCTION ${schema}.scope_parent_tenant_guard()
|
|
15
|
+
RETURNS trigger
|
|
16
|
+
LANGUAGE plpgsql
|
|
17
|
+
AS $$
|
|
18
|
+
DECLARE
|
|
19
|
+
parent_tenant TEXT;
|
|
20
|
+
BEGIN
|
|
21
|
+
IF NEW.parent_scope_id IS NULL THEN
|
|
22
|
+
RETURN NEW;
|
|
23
|
+
END IF;
|
|
24
|
+
|
|
25
|
+
SELECT tenant_id
|
|
26
|
+
INTO parent_tenant
|
|
27
|
+
FROM ${schema}.scopes
|
|
28
|
+
WHERE id = NEW.parent_scope_id;
|
|
29
|
+
|
|
30
|
+
IF parent_tenant IS NOT NULL AND parent_tenant <> NEW.tenant_id THEN
|
|
31
|
+
RAISE EXCEPTION
|
|
32
|
+
'scope parent tenant mismatch: child tenant=% parent scope id=% parent tenant=%',
|
|
33
|
+
NEW.tenant_id, NEW.parent_scope_id, parent_tenant
|
|
34
|
+
USING ERRCODE = '23514';
|
|
35
|
+
END IF;
|
|
36
|
+
|
|
37
|
+
RETURN NEW;
|
|
38
|
+
END;
|
|
39
|
+
$$;
|
|
40
|
+
|
|
41
|
+
DROP TRIGGER IF EXISTS trg_scopes_parent_tenant_guard
|
|
42
|
+
ON ${schema}.scopes;
|
|
43
|
+
|
|
44
|
+
CREATE TRIGGER trg_scopes_parent_tenant_guard
|
|
45
|
+
BEFORE INSERT OR UPDATE OF tenant_id, parent_scope_id
|
|
46
|
+
ON ${schema}.scopes
|
|
47
|
+
FOR EACH ROW
|
|
48
|
+
EXECUTE FUNCTION ${schema}.scope_parent_tenant_guard();
|
|
49
|
+
|
|
50
|
+
-- =========================================================================
|
|
51
|
+
-- fact_assertions_v1: structured assertion plane for curated memory
|
|
52
|
+
-- =========================================================================
|
|
53
|
+
CREATE TABLE IF NOT EXISTS ${schema}.fact_assertions_v1 (
|
|
54
|
+
id BIGSERIAL PRIMARY KEY,
|
|
55
|
+
tenant_id TEXT NOT NULL DEFAULT 'default',
|
|
56
|
+
canonical_key TEXT NOT NULL CHECK (btrim(canonical_key) <> ''),
|
|
57
|
+
scope_id BIGINT NOT NULL REFERENCES ${schema}.scopes(id) ON DELETE RESTRICT,
|
|
58
|
+
subject_entity_id BIGINT,
|
|
59
|
+
predicate TEXT NOT NULL CHECK (btrim(predicate) <> ''),
|
|
60
|
+
object_kind TEXT NOT NULL
|
|
61
|
+
CHECK (object_kind IN ('entity','value','none')),
|
|
62
|
+
object_entity_id BIGINT,
|
|
63
|
+
object_value_json JSONB,
|
|
64
|
+
qualifiers_json JSONB NOT NULL DEFAULT '{}'::jsonb,
|
|
65
|
+
valid_from TIMESTAMPTZ,
|
|
66
|
+
valid_to TIMESTAMPTZ,
|
|
67
|
+
observed_at TIMESTAMPTZ,
|
|
68
|
+
stale_after TIMESTAMPTZ,
|
|
69
|
+
accepted_at TIMESTAMPTZ,
|
|
70
|
+
revoked_at TIMESTAMPTZ,
|
|
71
|
+
superseded_at TIMESTAMPTZ,
|
|
72
|
+
status TEXT NOT NULL DEFAULT 'candidate'
|
|
73
|
+
CHECK (status IN (
|
|
74
|
+
'candidate','active','stale','superseded','revoked',
|
|
75
|
+
'tombstoned','quarantined','archived'
|
|
76
|
+
)),
|
|
77
|
+
authority TEXT NOT NULL DEFAULT 'llm_inference'
|
|
78
|
+
CHECK (authority IN (
|
|
79
|
+
'user_explicit','executable_evidence','verified_summary',
|
|
80
|
+
'llm_inference','raw_transcript','manual','system'
|
|
81
|
+
)),
|
|
82
|
+
assertion_hash TEXT NOT NULL CHECK (btrim(assertion_hash) <> ''),
|
|
83
|
+
superseded_by BIGINT REFERENCES ${schema}.fact_assertions_v1(id) ON DELETE SET NULL,
|
|
84
|
+
version_id BIGINT REFERENCES ${schema}.versions(id) ON DELETE SET NULL,
|
|
85
|
+
metadata JSONB NOT NULL DEFAULT '{}'::jsonb,
|
|
86
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
87
|
+
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
88
|
+
CHECK (valid_to IS NULL OR valid_from IS NULL OR valid_to > valid_from),
|
|
89
|
+
CHECK (revoked_at IS NULL OR accepted_at IS NULL OR revoked_at >= accepted_at),
|
|
90
|
+
CHECK (superseded_at IS NULL OR accepted_at IS NULL OR superseded_at >= accepted_at),
|
|
91
|
+
CHECK (NOT (revoked_at IS NOT NULL AND superseded_at IS NOT NULL)),
|
|
92
|
+
CHECK (
|
|
93
|
+
(object_kind = 'entity' AND object_entity_id IS NOT NULL AND object_value_json IS NULL)
|
|
94
|
+
OR (object_kind = 'value' AND object_entity_id IS NULL AND object_value_json IS NOT NULL)
|
|
95
|
+
OR (object_kind = 'none' AND object_entity_id IS NULL AND object_value_json IS NULL)
|
|
96
|
+
)
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_fact_assertions_v1_tenant_row
|
|
100
|
+
ON ${schema}.fact_assertions_v1 (tenant_id, id);
|
|
101
|
+
|
|
102
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_fact_assertions_v1_active_canonical
|
|
103
|
+
ON ${schema}.fact_assertions_v1 (tenant_id, canonical_key)
|
|
104
|
+
WHERE status = 'active';
|
|
105
|
+
|
|
106
|
+
CREATE INDEX IF NOT EXISTS idx_fact_assertions_v1_scope_status
|
|
107
|
+
ON ${schema}.fact_assertions_v1 (tenant_id, scope_id, status, observed_at DESC, id);
|
|
108
|
+
|
|
109
|
+
CREATE INDEX IF NOT EXISTS idx_fact_assertions_v1_hash
|
|
110
|
+
ON ${schema}.fact_assertions_v1 (tenant_id, assertion_hash);
|
|
111
|
+
|
|
112
|
+
CREATE INDEX IF NOT EXISTS idx_fact_assertions_v1_superseded_by
|
|
113
|
+
ON ${schema}.fact_assertions_v1 (superseded_by)
|
|
114
|
+
WHERE superseded_by IS NOT NULL;
|
|
115
|
+
|
|
116
|
+
DO $$
|
|
117
|
+
BEGIN
|
|
118
|
+
IF NOT EXISTS (
|
|
119
|
+
SELECT 1
|
|
120
|
+
FROM pg_constraint
|
|
121
|
+
WHERE conrelid = '${schema}.fact_assertions_v1'::regclass
|
|
122
|
+
AND conname = 'fact_assertions_v1_scope_tenant_fk'
|
|
123
|
+
) THEN
|
|
124
|
+
ALTER TABLE ${schema}.fact_assertions_v1
|
|
125
|
+
ADD CONSTRAINT fact_assertions_v1_scope_tenant_fk
|
|
126
|
+
FOREIGN KEY (tenant_id, scope_id)
|
|
127
|
+
REFERENCES ${schema}.scopes (tenant_id, id)
|
|
128
|
+
ON DELETE RESTRICT
|
|
129
|
+
NOT VALID;
|
|
130
|
+
END IF;
|
|
131
|
+
END;
|
|
132
|
+
$$;
|
|
133
|
+
|
|
134
|
+
COMMENT ON TABLE ${schema}.fact_assertions_v1 IS
|
|
135
|
+
'v1 structured assertion plane. New table; does not reuse legacy 004-facts rows.';
|
|
136
|
+
|
|
137
|
+
-- =========================================================================
|
|
138
|
+
-- memory_records: structured-assertion linkage + system-time lifecycle fields
|
|
139
|
+
-- =========================================================================
|
|
140
|
+
ALTER TABLE ${schema}.memory_records
|
|
141
|
+
ADD COLUMN IF NOT EXISTS backing_fact_id BIGINT,
|
|
142
|
+
ADD COLUMN IF NOT EXISTS observed_at TIMESTAMPTZ,
|
|
143
|
+
ADD COLUMN IF NOT EXISTS revoked_at TIMESTAMPTZ,
|
|
144
|
+
ADD COLUMN IF NOT EXISTS superseded_at TIMESTAMPTZ;
|
|
145
|
+
|
|
146
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_memory_records_tenant_row
|
|
147
|
+
ON ${schema}.memory_records (tenant_id, id);
|
|
148
|
+
|
|
149
|
+
CREATE INDEX IF NOT EXISTS idx_memory_records_backing_fact
|
|
150
|
+
ON ${schema}.memory_records (tenant_id, backing_fact_id)
|
|
151
|
+
WHERE backing_fact_id IS NOT NULL;
|
|
152
|
+
|
|
153
|
+
DO $$
|
|
154
|
+
BEGIN
|
|
155
|
+
IF NOT EXISTS (
|
|
156
|
+
SELECT 1
|
|
157
|
+
FROM pg_constraint
|
|
158
|
+
WHERE conrelid = '${schema}.memory_records'::regclass
|
|
159
|
+
AND conname = 'memory_records_scope_tenant_fk'
|
|
160
|
+
) THEN
|
|
161
|
+
ALTER TABLE ${schema}.memory_records
|
|
162
|
+
ADD CONSTRAINT memory_records_scope_tenant_fk
|
|
163
|
+
FOREIGN KEY (tenant_id, scope_id)
|
|
164
|
+
REFERENCES ${schema}.scopes (tenant_id, id)
|
|
165
|
+
ON DELETE RESTRICT
|
|
166
|
+
NOT VALID;
|
|
167
|
+
END IF;
|
|
168
|
+
END;
|
|
169
|
+
$$;
|
|
170
|
+
|
|
171
|
+
DO $$
|
|
172
|
+
BEGIN
|
|
173
|
+
IF NOT EXISTS (
|
|
174
|
+
SELECT 1
|
|
175
|
+
FROM pg_constraint
|
|
176
|
+
WHERE conrelid = '${schema}.memory_records'::regclass
|
|
177
|
+
AND conname = 'memory_records_backing_fact_tenant_fk'
|
|
178
|
+
) THEN
|
|
179
|
+
ALTER TABLE ${schema}.memory_records
|
|
180
|
+
ADD CONSTRAINT memory_records_backing_fact_tenant_fk
|
|
181
|
+
FOREIGN KEY (tenant_id, backing_fact_id)
|
|
182
|
+
REFERENCES ${schema}.fact_assertions_v1 (tenant_id, id)
|
|
183
|
+
NOT VALID;
|
|
184
|
+
END IF;
|
|
185
|
+
END;
|
|
186
|
+
$$;
|
|
187
|
+
|
|
188
|
+
-- =========================================================================
|
|
189
|
+
-- compaction_runs: source/output coverage fields for auditability
|
|
190
|
+
-- =========================================================================
|
|
191
|
+
ALTER TABLE ${schema}.compaction_runs
|
|
192
|
+
ADD COLUMN IF NOT EXISTS source_coverage JSONB NOT NULL DEFAULT '{}'::jsonb,
|
|
193
|
+
ADD COLUMN IF NOT EXISTS output_coverage JSONB NOT NULL DEFAULT '{}'::jsonb;
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
-- Aquifer v1 finalization review and lineage
|
|
2
|
+
-- Requires: 001-base.sql, 007-v1-foundation.sql, 008-session-finalizations.sql, 009-v1-assertion-plane.sql
|
|
3
|
+
-- Usage: replace ${schema} with actual schema name
|
|
4
|
+
--
|
|
5
|
+
-- Finalization must leave a human-reviewable artifact and row-level lineage.
|
|
6
|
+
-- DB storage alone is not treated as usable runtime memory.
|
|
7
|
+
|
|
8
|
+
-- =========================================================================
|
|
9
|
+
-- Finalization ledger: explicit human review and minimal SessionStart text
|
|
10
|
+
-- =========================================================================
|
|
11
|
+
ALTER TABLE ${schema}.session_finalizations
|
|
12
|
+
ADD COLUMN IF NOT EXISTS summary_text TEXT,
|
|
13
|
+
ADD COLUMN IF NOT EXISTS structured_summary JSONB NOT NULL DEFAULT '{}'::jsonb,
|
|
14
|
+
ADD COLUMN IF NOT EXISTS human_review_text TEXT,
|
|
15
|
+
ADD COLUMN IF NOT EXISTS session_start_text TEXT;
|
|
16
|
+
|
|
17
|
+
COMMENT ON COLUMN ${schema}.session_finalizations.human_review_text IS
|
|
18
|
+
'Human-facing finalization review: concise summary of what was made active, left open, quarantined, and excluded.';
|
|
19
|
+
|
|
20
|
+
COMMENT ON COLUMN ${schema}.session_finalizations.session_start_text IS
|
|
21
|
+
'Minimal active curated context eligible for SessionStart injection; excludes raw transcript/debug/audit fields.';
|
|
22
|
+
|
|
23
|
+
-- =========================================================================
|
|
24
|
+
-- Lifecycle semantics: incorrect is an explicit non-serving state
|
|
25
|
+
-- =========================================================================
|
|
26
|
+
ALTER TABLE ${schema}.memory_records
|
|
27
|
+
DROP CONSTRAINT IF EXISTS memory_records_status_check;
|
|
28
|
+
|
|
29
|
+
ALTER TABLE ${schema}.memory_records
|
|
30
|
+
ADD CONSTRAINT memory_records_status_check
|
|
31
|
+
CHECK (status IN (
|
|
32
|
+
'candidate','active','stale','superseded','revoked',
|
|
33
|
+
'tombstoned','quarantined','archived','incorrect'
|
|
34
|
+
));
|
|
35
|
+
|
|
36
|
+
ALTER TABLE ${schema}.fact_assertions_v1
|
|
37
|
+
DROP CONSTRAINT IF EXISTS fact_assertions_v1_status_check;
|
|
38
|
+
|
|
39
|
+
ALTER TABLE ${schema}.fact_assertions_v1
|
|
40
|
+
ADD CONSTRAINT fact_assertions_v1_status_check
|
|
41
|
+
CHECK (status IN (
|
|
42
|
+
'candidate','active','stale','superseded','revoked',
|
|
43
|
+
'tombstoned','quarantined','archived','incorrect'
|
|
44
|
+
));
|
|
45
|
+
|
|
46
|
+
-- =========================================================================
|
|
47
|
+
-- Row-level finalization lineage
|
|
48
|
+
-- =========================================================================
|
|
49
|
+
ALTER TABLE ${schema}.memory_records
|
|
50
|
+
ADD COLUMN IF NOT EXISTS created_by_finalization_id BIGINT;
|
|
51
|
+
|
|
52
|
+
ALTER TABLE ${schema}.fact_assertions_v1
|
|
53
|
+
ADD COLUMN IF NOT EXISTS created_by_finalization_id BIGINT;
|
|
54
|
+
|
|
55
|
+
ALTER TABLE ${schema}.evidence_refs
|
|
56
|
+
ADD COLUMN IF NOT EXISTS created_by_finalization_id BIGINT;
|
|
57
|
+
|
|
58
|
+
CREATE INDEX IF NOT EXISTS idx_memory_records_created_by_finalization
|
|
59
|
+
ON ${schema}.memory_records (tenant_id, created_by_finalization_id)
|
|
60
|
+
WHERE created_by_finalization_id IS NOT NULL;
|
|
61
|
+
|
|
62
|
+
CREATE INDEX IF NOT EXISTS idx_fact_assertions_created_by_finalization
|
|
63
|
+
ON ${schema}.fact_assertions_v1 (tenant_id, created_by_finalization_id)
|
|
64
|
+
WHERE created_by_finalization_id IS NOT NULL;
|
|
65
|
+
|
|
66
|
+
CREATE INDEX IF NOT EXISTS idx_evidence_refs_created_by_finalization
|
|
67
|
+
ON ${schema}.evidence_refs (tenant_id, created_by_finalization_id)
|
|
68
|
+
WHERE created_by_finalization_id IS NOT NULL;
|
|
69
|
+
|
|
70
|
+
DO $$
|
|
71
|
+
BEGIN
|
|
72
|
+
IF NOT EXISTS (
|
|
73
|
+
SELECT 1
|
|
74
|
+
FROM pg_constraint
|
|
75
|
+
WHERE conrelid = '${schema}.memory_records'::regclass
|
|
76
|
+
AND conname = 'memory_records_created_by_finalization_fk'
|
|
77
|
+
) THEN
|
|
78
|
+
ALTER TABLE ${schema}.memory_records
|
|
79
|
+
ADD CONSTRAINT memory_records_created_by_finalization_fk
|
|
80
|
+
FOREIGN KEY (created_by_finalization_id)
|
|
81
|
+
REFERENCES ${schema}.session_finalizations (id)
|
|
82
|
+
ON DELETE SET NULL
|
|
83
|
+
NOT VALID;
|
|
84
|
+
END IF;
|
|
85
|
+
END;
|
|
86
|
+
$$;
|
|
87
|
+
|
|
88
|
+
DO $$
|
|
89
|
+
BEGIN
|
|
90
|
+
IF NOT EXISTS (
|
|
91
|
+
SELECT 1
|
|
92
|
+
FROM pg_constraint
|
|
93
|
+
WHERE conrelid = '${schema}.fact_assertions_v1'::regclass
|
|
94
|
+
AND conname = 'fact_assertions_created_by_finalization_fk'
|
|
95
|
+
) THEN
|
|
96
|
+
ALTER TABLE ${schema}.fact_assertions_v1
|
|
97
|
+
ADD CONSTRAINT fact_assertions_created_by_finalization_fk
|
|
98
|
+
FOREIGN KEY (created_by_finalization_id)
|
|
99
|
+
REFERENCES ${schema}.session_finalizations (id)
|
|
100
|
+
ON DELETE SET NULL
|
|
101
|
+
NOT VALID;
|
|
102
|
+
END IF;
|
|
103
|
+
END;
|
|
104
|
+
$$;
|
|
105
|
+
|
|
106
|
+
DO $$
|
|
107
|
+
BEGIN
|
|
108
|
+
IF NOT EXISTS (
|
|
109
|
+
SELECT 1
|
|
110
|
+
FROM pg_constraint
|
|
111
|
+
WHERE conrelid = '${schema}.evidence_refs'::regclass
|
|
112
|
+
AND conname = 'evidence_refs_created_by_finalization_fk'
|
|
113
|
+
) THEN
|
|
114
|
+
ALTER TABLE ${schema}.evidence_refs
|
|
115
|
+
ADD CONSTRAINT evidence_refs_created_by_finalization_fk
|
|
116
|
+
FOREIGN KEY (created_by_finalization_id)
|
|
117
|
+
REFERENCES ${schema}.session_finalizations (id)
|
|
118
|
+
ON DELETE SET NULL
|
|
119
|
+
NOT VALID;
|
|
120
|
+
END IF;
|
|
121
|
+
END;
|
|
122
|
+
$$;
|
|
123
|
+
|
|
124
|
+
-- =========================================================================
|
|
125
|
+
-- Candidate ledger: every extracted item has an action and reason
|
|
126
|
+
-- =========================================================================
|
|
127
|
+
CREATE TABLE IF NOT EXISTS ${schema}.finalization_candidates (
|
|
128
|
+
id BIGSERIAL PRIMARY KEY,
|
|
129
|
+
tenant_id TEXT NOT NULL DEFAULT 'default',
|
|
130
|
+
finalization_id BIGINT NOT NULL REFERENCES ${schema}.session_finalizations(id) ON DELETE CASCADE,
|
|
131
|
+
session_id TEXT,
|
|
132
|
+
candidate_index INTEGER NOT NULL,
|
|
133
|
+
action TEXT NOT NULL
|
|
134
|
+
CHECK (action IN (
|
|
135
|
+
'promote','quarantine','skip','skipped','supersede','error'
|
|
136
|
+
)),
|
|
137
|
+
reason TEXT,
|
|
138
|
+
memory_type TEXT,
|
|
139
|
+
canonical_key TEXT,
|
|
140
|
+
summary TEXT,
|
|
141
|
+
payload JSONB NOT NULL DEFAULT '{}'::jsonb,
|
|
142
|
+
provenance JSONB NOT NULL DEFAULT '{}'::jsonb,
|
|
143
|
+
memory_record_id BIGINT,
|
|
144
|
+
fact_assertion_id BIGINT,
|
|
145
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
146
|
+
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_finalization_candidates_position
|
|
150
|
+
ON ${schema}.finalization_candidates (tenant_id, finalization_id, candidate_index);
|
|
151
|
+
|
|
152
|
+
CREATE INDEX IF NOT EXISTS idx_finalization_candidates_action
|
|
153
|
+
ON ${schema}.finalization_candidates (tenant_id, action, created_at DESC);
|
|
154
|
+
|
|
155
|
+
CREATE INDEX IF NOT EXISTS idx_finalization_candidates_memory
|
|
156
|
+
ON ${schema}.finalization_candidates (tenant_id, memory_record_id)
|
|
157
|
+
WHERE memory_record_id IS NOT NULL;
|
|
158
|
+
|
|
159
|
+
COMMENT ON TABLE ${schema}.finalization_candidates IS
|
|
160
|
+
'Per-finalization candidate ledger. Promoted, skipped, quarantined, and incorrect candidates remain auditable without becoming runtime memory.';
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
-- Aquifer v1 compaction claim/apply guard
|
|
2
|
+
-- Requires: 007-v1-foundation.sql and 009-v1-assertion-plane.sql
|
|
3
|
+
-- Usage: replace ${schema} with actual schema name
|
|
4
|
+
--
|
|
5
|
+
-- Adds the minimum DB contract needed before compaction_runs can coordinate
|
|
6
|
+
-- daily/weekly/monthly apply workers. This remains a ledger/claim guard; it
|
|
7
|
+
-- does not create or promote aggregate memory.
|
|
8
|
+
|
|
9
|
+
ALTER TABLE ${schema}.compaction_runs
|
|
10
|
+
ADD COLUMN IF NOT EXISTS claimed_at TIMESTAMPTZ,
|
|
11
|
+
ADD COLUMN IF NOT EXISTS worker_id TEXT,
|
|
12
|
+
ADD COLUMN IF NOT EXISTS apply_token TEXT;
|
|
13
|
+
|
|
14
|
+
ALTER TABLE ${schema}.compaction_runs
|
|
15
|
+
DROP CONSTRAINT IF EXISTS compaction_runs_status_check;
|
|
16
|
+
|
|
17
|
+
ALTER TABLE ${schema}.compaction_runs
|
|
18
|
+
ADD CONSTRAINT compaction_runs_status_check
|
|
19
|
+
CHECK (status IN ('planned','applying','applied','failed','skipped'));
|
|
20
|
+
|
|
21
|
+
ALTER TABLE ${schema}.compaction_runs
|
|
22
|
+
DROP CONSTRAINT IF EXISTS compaction_runs_applying_claim_check;
|
|
23
|
+
|
|
24
|
+
ALTER TABLE ${schema}.compaction_runs
|
|
25
|
+
ADD CONSTRAINT compaction_runs_applying_claim_check
|
|
26
|
+
CHECK (
|
|
27
|
+
status <> 'applying'
|
|
28
|
+
OR (
|
|
29
|
+
claimed_at IS NOT NULL
|
|
30
|
+
AND worker_id IS NOT NULL
|
|
31
|
+
AND btrim(worker_id) <> ''
|
|
32
|
+
AND apply_token IS NOT NULL
|
|
33
|
+
AND btrim(apply_token) <> ''
|
|
34
|
+
)
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_compaction_runs_one_live_apply
|
|
38
|
+
ON ${schema}.compaction_runs (tenant_id, cadence, period_start, period_end, policy_version)
|
|
39
|
+
WHERE status = 'applying';
|
|
40
|
+
|
|
41
|
+
CREATE INDEX IF NOT EXISTS idx_compaction_runs_claims
|
|
42
|
+
ON ${schema}.compaction_runs (tenant_id, cadence, status, claimed_at DESC)
|
|
43
|
+
WHERE status = 'applying';
|
|
44
|
+
|
|
45
|
+
COMMENT ON COLUMN ${schema}.compaction_runs.apply_token IS
|
|
46
|
+
'Opaque per-claim token. Apply/finalize code must present the claimed token before mutating memory.';
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
-- Aquifer v1 compaction claim lease guard
|
|
2
|
+
-- Requires: 011-v1-compaction-claim.sql
|
|
3
|
+
-- Usage: replace ${schema} with actual schema name
|
|
4
|
+
--
|
|
5
|
+
-- Adds row-level lease expiry for compaction claims. Lease expiry is a DB
|
|
6
|
+
-- timestamp fact, not a caller-clock interpretation of claimed_at.
|
|
7
|
+
|
|
8
|
+
ALTER TABLE ${schema}.compaction_runs
|
|
9
|
+
ADD COLUMN IF NOT EXISTS lease_expires_at TIMESTAMPTZ,
|
|
10
|
+
ADD COLUMN IF NOT EXISTS reclaimed_at TIMESTAMPTZ,
|
|
11
|
+
ADD COLUMN IF NOT EXISTS reclaimed_by_worker_id TEXT;
|
|
12
|
+
|
|
13
|
+
UPDATE ${schema}.compaction_runs
|
|
14
|
+
SET lease_expires_at = claimed_at + interval '600 seconds'
|
|
15
|
+
WHERE status = 'applying'
|
|
16
|
+
AND claimed_at IS NOT NULL
|
|
17
|
+
AND lease_expires_at IS NULL;
|
|
18
|
+
|
|
19
|
+
ALTER TABLE ${schema}.compaction_runs
|
|
20
|
+
DROP CONSTRAINT IF EXISTS compaction_runs_applying_lease_check;
|
|
21
|
+
|
|
22
|
+
ALTER TABLE ${schema}.compaction_runs
|
|
23
|
+
ADD CONSTRAINT compaction_runs_applying_lease_check
|
|
24
|
+
CHECK (
|
|
25
|
+
status <> 'applying'
|
|
26
|
+
OR (
|
|
27
|
+
lease_expires_at IS NOT NULL
|
|
28
|
+
AND lease_expires_at > claimed_at
|
|
29
|
+
)
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
CREATE INDEX IF NOT EXISTS idx_compaction_runs_claim_lease
|
|
33
|
+
ON ${schema}.compaction_runs (
|
|
34
|
+
tenant_id, cadence, period_start, period_end, policy_version, lease_expires_at
|
|
35
|
+
)
|
|
36
|
+
WHERE status = 'applying';
|
|
37
|
+
|
|
38
|
+
COMMENT ON COLUMN ${schema}.compaction_runs.lease_expires_at IS
|
|
39
|
+
'DB-time expiry for the current apply claim. Competing workers may reclaim only after this timestamp.';
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
-- Aquifer v1 compaction lineage and candidate ledger
|
|
2
|
+
-- Requires: 007-v1-foundation.sql, 009-v1-assertion-plane.sql, and 012-v1-compaction-lease.sql
|
|
3
|
+
-- Usage: replace ${schema} with actual schema name
|
|
4
|
+
--
|
|
5
|
+
-- Aggregate compaction candidates must remain auditable before promotion and
|
|
6
|
+
-- every promoted row must point back to the compaction run that created it.
|
|
7
|
+
|
|
8
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_compaction_runs_tenant_row
|
|
9
|
+
ON ${schema}.compaction_runs (tenant_id, id);
|
|
10
|
+
|
|
11
|
+
ALTER TABLE ${schema}.memory_records
|
|
12
|
+
ADD COLUMN IF NOT EXISTS created_by_compaction_run_id BIGINT;
|
|
13
|
+
|
|
14
|
+
ALTER TABLE ${schema}.fact_assertions_v1
|
|
15
|
+
ADD COLUMN IF NOT EXISTS created_by_compaction_run_id BIGINT;
|
|
16
|
+
|
|
17
|
+
ALTER TABLE ${schema}.evidence_refs
|
|
18
|
+
ADD COLUMN IF NOT EXISTS created_by_compaction_run_id BIGINT;
|
|
19
|
+
|
|
20
|
+
CREATE INDEX IF NOT EXISTS idx_memory_records_created_by_compaction_run
|
|
21
|
+
ON ${schema}.memory_records (tenant_id, created_by_compaction_run_id)
|
|
22
|
+
WHERE created_by_compaction_run_id IS NOT NULL;
|
|
23
|
+
|
|
24
|
+
CREATE INDEX IF NOT EXISTS idx_fact_assertions_created_by_compaction_run
|
|
25
|
+
ON ${schema}.fact_assertions_v1 (tenant_id, created_by_compaction_run_id)
|
|
26
|
+
WHERE created_by_compaction_run_id IS NOT NULL;
|
|
27
|
+
|
|
28
|
+
CREATE INDEX IF NOT EXISTS idx_evidence_refs_created_by_compaction_run
|
|
29
|
+
ON ${schema}.evidence_refs (tenant_id, created_by_compaction_run_id)
|
|
30
|
+
WHERE created_by_compaction_run_id IS NOT NULL;
|
|
31
|
+
|
|
32
|
+
DO $$
|
|
33
|
+
BEGIN
|
|
34
|
+
IF NOT EXISTS (
|
|
35
|
+
SELECT 1
|
|
36
|
+
FROM pg_constraint
|
|
37
|
+
WHERE conrelid = '${schema}.memory_records'::regclass
|
|
38
|
+
AND conname = 'memory_records_created_by_compaction_run_fk'
|
|
39
|
+
) THEN
|
|
40
|
+
ALTER TABLE ${schema}.memory_records
|
|
41
|
+
ADD CONSTRAINT memory_records_created_by_compaction_run_fk
|
|
42
|
+
FOREIGN KEY (tenant_id, created_by_compaction_run_id)
|
|
43
|
+
REFERENCES ${schema}.compaction_runs (tenant_id, id)
|
|
44
|
+
ON DELETE SET NULL
|
|
45
|
+
NOT VALID;
|
|
46
|
+
END IF;
|
|
47
|
+
END;
|
|
48
|
+
$$;
|
|
49
|
+
|
|
50
|
+
DO $$
|
|
51
|
+
BEGIN
|
|
52
|
+
IF NOT EXISTS (
|
|
53
|
+
SELECT 1
|
|
54
|
+
FROM pg_constraint
|
|
55
|
+
WHERE conrelid = '${schema}.fact_assertions_v1'::regclass
|
|
56
|
+
AND conname = 'fact_assertions_created_by_compaction_run_fk'
|
|
57
|
+
) THEN
|
|
58
|
+
ALTER TABLE ${schema}.fact_assertions_v1
|
|
59
|
+
ADD CONSTRAINT fact_assertions_created_by_compaction_run_fk
|
|
60
|
+
FOREIGN KEY (tenant_id, created_by_compaction_run_id)
|
|
61
|
+
REFERENCES ${schema}.compaction_runs (tenant_id, id)
|
|
62
|
+
ON DELETE SET NULL
|
|
63
|
+
NOT VALID;
|
|
64
|
+
END IF;
|
|
65
|
+
END;
|
|
66
|
+
$$;
|
|
67
|
+
|
|
68
|
+
DO $$
|
|
69
|
+
BEGIN
|
|
70
|
+
IF NOT EXISTS (
|
|
71
|
+
SELECT 1
|
|
72
|
+
FROM pg_constraint
|
|
73
|
+
WHERE conrelid = '${schema}.evidence_refs'::regclass
|
|
74
|
+
AND conname = 'evidence_refs_created_by_compaction_run_fk'
|
|
75
|
+
) THEN
|
|
76
|
+
ALTER TABLE ${schema}.evidence_refs
|
|
77
|
+
ADD CONSTRAINT evidence_refs_created_by_compaction_run_fk
|
|
78
|
+
FOREIGN KEY (tenant_id, created_by_compaction_run_id)
|
|
79
|
+
REFERENCES ${schema}.compaction_runs (tenant_id, id)
|
|
80
|
+
ON DELETE SET NULL
|
|
81
|
+
NOT VALID;
|
|
82
|
+
END IF;
|
|
83
|
+
END;
|
|
84
|
+
$$;
|
|
85
|
+
|
|
86
|
+
CREATE TABLE IF NOT EXISTS ${schema}.compaction_candidates (
|
|
87
|
+
id BIGSERIAL PRIMARY KEY,
|
|
88
|
+
tenant_id TEXT NOT NULL DEFAULT 'default',
|
|
89
|
+
compaction_run_id BIGINT NOT NULL,
|
|
90
|
+
candidate_index INTEGER NOT NULL,
|
|
91
|
+
candidate_hash TEXT NOT NULL CHECK (btrim(candidate_hash) <> ''),
|
|
92
|
+
action TEXT NOT NULL DEFAULT 'planned'
|
|
93
|
+
CHECK (action IN (
|
|
94
|
+
'planned','promote','quarantine','skip','skipped','error'
|
|
95
|
+
)),
|
|
96
|
+
reason TEXT,
|
|
97
|
+
memory_type TEXT,
|
|
98
|
+
canonical_key TEXT,
|
|
99
|
+
scope_kind TEXT,
|
|
100
|
+
scope_key TEXT,
|
|
101
|
+
context_key TEXT,
|
|
102
|
+
topic_key TEXT,
|
|
103
|
+
summary TEXT,
|
|
104
|
+
payload JSONB NOT NULL DEFAULT '{}'::jsonb,
|
|
105
|
+
source_memory_ids BIGINT[] NOT NULL DEFAULT ARRAY[]::BIGINT[],
|
|
106
|
+
source_canonical_keys JSONB NOT NULL DEFAULT '[]'::jsonb,
|
|
107
|
+
memory_record_id BIGINT,
|
|
108
|
+
fact_assertion_id BIGINT,
|
|
109
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
110
|
+
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
111
|
+
CHECK (jsonb_typeof(source_canonical_keys) = 'array'),
|
|
112
|
+
CHECK (jsonb_array_length(source_canonical_keys) = cardinality(source_memory_ids))
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
DO $$
|
|
116
|
+
BEGIN
|
|
117
|
+
IF NOT EXISTS (
|
|
118
|
+
SELECT 1
|
|
119
|
+
FROM pg_constraint
|
|
120
|
+
WHERE conrelid = '${schema}.compaction_candidates'::regclass
|
|
121
|
+
AND conname = 'compaction_candidates_run_fk'
|
|
122
|
+
) THEN
|
|
123
|
+
ALTER TABLE ${schema}.compaction_candidates
|
|
124
|
+
ADD CONSTRAINT compaction_candidates_run_fk
|
|
125
|
+
FOREIGN KEY (tenant_id, compaction_run_id)
|
|
126
|
+
REFERENCES ${schema}.compaction_runs (tenant_id, id)
|
|
127
|
+
ON DELETE CASCADE
|
|
128
|
+
NOT VALID;
|
|
129
|
+
END IF;
|
|
130
|
+
END;
|
|
131
|
+
$$;
|
|
132
|
+
|
|
133
|
+
DO $$
|
|
134
|
+
BEGIN
|
|
135
|
+
IF NOT EXISTS (
|
|
136
|
+
SELECT 1
|
|
137
|
+
FROM pg_constraint
|
|
138
|
+
WHERE conrelid = '${schema}.compaction_candidates'::regclass
|
|
139
|
+
AND conname = 'compaction_candidates_memory_fk'
|
|
140
|
+
) THEN
|
|
141
|
+
ALTER TABLE ${schema}.compaction_candidates
|
|
142
|
+
ADD CONSTRAINT compaction_candidates_memory_fk
|
|
143
|
+
FOREIGN KEY (tenant_id, memory_record_id)
|
|
144
|
+
REFERENCES ${schema}.memory_records (tenant_id, id)
|
|
145
|
+
ON DELETE SET NULL
|
|
146
|
+
NOT VALID;
|
|
147
|
+
END IF;
|
|
148
|
+
END;
|
|
149
|
+
$$;
|
|
150
|
+
|
|
151
|
+
DO $$
|
|
152
|
+
BEGIN
|
|
153
|
+
IF NOT EXISTS (
|
|
154
|
+
SELECT 1
|
|
155
|
+
FROM pg_constraint
|
|
156
|
+
WHERE conrelid = '${schema}.compaction_candidates'::regclass
|
|
157
|
+
AND conname = 'compaction_candidates_fact_fk'
|
|
158
|
+
) THEN
|
|
159
|
+
ALTER TABLE ${schema}.compaction_candidates
|
|
160
|
+
ADD CONSTRAINT compaction_candidates_fact_fk
|
|
161
|
+
FOREIGN KEY (tenant_id, fact_assertion_id)
|
|
162
|
+
REFERENCES ${schema}.fact_assertions_v1 (tenant_id, id)
|
|
163
|
+
ON DELETE SET NULL
|
|
164
|
+
NOT VALID;
|
|
165
|
+
END IF;
|
|
166
|
+
END;
|
|
167
|
+
$$;
|
|
168
|
+
|
|
169
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_compaction_candidates_position
|
|
170
|
+
ON ${schema}.compaction_candidates (tenant_id, compaction_run_id, candidate_index);
|
|
171
|
+
|
|
172
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_compaction_candidates_hash
|
|
173
|
+
ON ${schema}.compaction_candidates (tenant_id, compaction_run_id, candidate_hash);
|
|
174
|
+
|
|
175
|
+
CREATE INDEX IF NOT EXISTS idx_compaction_candidates_action
|
|
176
|
+
ON ${schema}.compaction_candidates (tenant_id, action, created_at DESC);
|
|
177
|
+
|
|
178
|
+
CREATE INDEX IF NOT EXISTS idx_compaction_candidates_memory
|
|
179
|
+
ON ${schema}.compaction_candidates (tenant_id, memory_record_id)
|
|
180
|
+
WHERE memory_record_id IS NOT NULL;
|
|
181
|
+
|
|
182
|
+
CREATE INDEX IF NOT EXISTS idx_compaction_candidates_fact
|
|
183
|
+
ON ${schema}.compaction_candidates (tenant_id, fact_assertion_id)
|
|
184
|
+
WHERE fact_assertion_id IS NOT NULL;
|
|
185
|
+
|
|
186
|
+
CREATE INDEX IF NOT EXISTS idx_compaction_candidates_sources
|
|
187
|
+
ON ${schema}.compaction_candidates USING GIN (source_memory_ids);
|
|
188
|
+
|
|
189
|
+
COMMENT ON TABLE ${schema}.compaction_candidates IS
|
|
190
|
+
'Per-compaction-run aggregate candidate ledger. Planned, promoted, skipped, quarantined, and errored candidates remain auditable without bypassing promotion.';
|
|
191
|
+
|
|
192
|
+
COMMENT ON COLUMN ${schema}.memory_records.created_by_compaction_run_id IS
|
|
193
|
+
'Compaction run that formally promoted this memory, if any. Planner-only candidates do not set this column.';
|