thinkwork-cli 0.13.0 → 0.13.1
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-client-JUBCQQDF.js → api-client-4DZ266J5.js} +1 -1
- package/dist/{chunk-STY44FQO.js → chunk-Y2WHJECR.js} +44 -12
- package/dist/cli.js +36715 -12965
- package/dist/drizzle/0227_analyst_reader_role.sql +100 -38
- package/dist/drizzle/0230_analyst_rls.sql +183 -66
- package/dist/drizzle/0232_memory_source_thin_ledger.sql +176 -0
- package/dist/drizzle/0232_message_source_event_id.sql +9 -0
- package/dist/drizzle/0233_msteams_install_and_links.sql +151 -0
- package/dist/drizzle/0233_workflow_memory_stage_tokens.sql +20 -0
- package/dist/drizzle/0234_memory_claim_lifecycle.sql +160 -0
- package/dist/drizzle/0235_workflow_token_execution_claim.sql +34 -0
- package/dist/drizzle/0236_memory_retraction_fencing.sql +50 -0
- package/dist/drizzle/0237_memory_claims_active_value_uidx.sql +172 -0
- package/dist/drizzle/0238_memory_erase_generation_fence.sql +97 -0
- package/dist/drizzle/0239_canonical_entity_identity.sql +257 -0
- package/dist/drizzle/0240_memory_personal_scope.sql +45 -0
- package/dist/drizzle/0241_knowledge_base_document_manifest.sql +67 -0
- package/dist/drizzle/0242_search_queries.sql +28 -0
- package/dist/drizzle/0243_section_sources_thread_refs.sql +12 -0
- package/dist/drizzle/0244_system_automations_and_memory_stage_overrides.sql +45 -0
- package/dist/drizzle/0245_wiki_page_render.sql +21 -0
- package/dist/drizzle/0246_automation_workflow_ownership.sql +30 -0
- package/dist/drizzle/0247_capability_runtime.sql +402 -0
- package/dist/drizzle/0248_capability_private_interpreter.sql +13 -0
- package/dist/drizzle/0249_capability_headless_execution_evidence.sql +40 -0
- package/dist/drizzle/0250_kg_schema_extraction.sql +549 -0
- package/dist/drizzle/0251_agents_json_render_ui.sql +25 -0
- package/dist/drizzle/0251_brain_stragglers.sql +242 -0
- package/dist/drizzle/0252_drop_kg_and_straggler_compat_views.sql +100 -0
- package/dist/drizzle/0253_drop_brain_substrate.sql +91 -0
- package/dist/drizzle/0254_capability_autonomous_admission.sql +29 -0
- package/dist/drizzle/0255_capability_autonomous_approval_mode.sql +16 -0
- package/dist/drizzle/0256_agent_profile_manifest_authority.sql +18 -0
- package/dist/drizzle/0257_capability_approvals.sql +53 -0
- package/dist/drizzle/0258_pending_tool_approvals.sql +84 -0
- package/dist/drizzle/0259_tenant_capability_registry_trust.sql +19 -0
- package/dist/drizzle/0260_scheduled_jobs_run_as_user.sql +20 -0
- package/dist/drizzle/0261_thread_turns_origin_turn_index.sql +11 -0
- package/dist/terraform/examples/greenfield/main.tf +40 -0
- package/dist/terraform/modules/app/agentcore-admin/main.tf +341 -0
- package/dist/terraform/modules/app/agentcore-admin/outputs.tf +20 -0
- package/dist/terraform/modules/app/agentcore-code-interpreter/main.tf +61 -0
- package/dist/terraform/modules/app/agentcore-harness/main.tf +101 -0
- package/dist/terraform/modules/app/agentcore-harness/outputs.tf +9 -0
- package/dist/terraform/modules/app/agentcore-harness/variables.tf +25 -0
- package/dist/terraform/modules/app/agentcore-pi/main.tf +7 -0
- package/dist/terraform/modules/app/capability-broker/main.tf +393 -0
- package/dist/terraform/modules/app/capability-broker/outputs.tf +54 -0
- package/dist/terraform/modules/app/capability-broker/variables.tf +98 -0
- package/dist/terraform/modules/app/deployment-control-plane/runner.py +46 -5
- package/dist/terraform/modules/app/deployment-control-plane/test_runner_bundle.py +94 -0
- package/dist/terraform/modules/app/lambda-api/capability-search-alarms.tf +60 -0
- package/dist/terraform/modules/app/lambda-api/handlers.tf +361 -16
- package/dist/terraform/modules/app/lambda-api/iam-grouped.tf +131 -5
- package/dist/terraform/modules/app/lambda-api/main.tf +11 -0
- package/dist/terraform/modules/app/lambda-api/memory-alarms.tf +92 -0
- package/dist/terraform/modules/app/lambda-api/msteams-app-secrets.tf +46 -0
- package/dist/terraform/modules/app/lambda-api/variables.tf +92 -0
- package/dist/terraform/modules/app/workflow-interpreter-stepfunctions/alarms.tf +47 -0
- package/dist/terraform/modules/app/workflow-interpreter-stepfunctions/main.tf +43 -2
- package/dist/terraform/modules/foundation/cognito/main.tf +32 -8
- package/dist/terraform/modules/foundation/cognito/variables.tf +12 -0
- package/dist/terraform/modules/foundation/vpc/outputs.tf +5 -0
- package/dist/terraform/modules/thinkwork/main.tf +152 -5
- package/dist/terraform/modules/thinkwork/outputs.tf +55 -0
- package/dist/terraform/modules/thinkwork/variables.tf +42 -0
- package/dist/workspace-defaults/files/TOOLS.md +39 -0
- package/dist/workspace-defaults/files/agents/analyst/INSTRUCTIONS.md +18 -0
- package/dist/workspace-defaults/files/agents/coding/INSTRUCTIONS.md +11 -0
- package/dist/workspace-defaults/files/agents/research/INSTRUCTIONS.md +12 -0
- package/dist/workspace-defaults/files/agents/reviewer/INSTRUCTIONS.md +15 -0
- package/dist/workspace-defaults/files/skills/document-composer/SKILL.md +6 -2
- package/package.json +4 -2
- /package/dist/workspace-defaults/files/{AGENTS.md → INSTRUCTIONS.md} +0 -0
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
-- THINK-193 U2 (Codex concurrency finding): structural backstop against
|
|
2
|
+
-- duplicate ACTIVE same-value claims. upsertClaimsForEvidence serializes
|
|
3
|
+
-- writers with a per-subject transaction-scoped advisory lock; this partial
|
|
4
|
+
-- unique index guarantees the invariant even if a future caller bypasses
|
|
5
|
+
-- the lock. Superseded/retracted rows keep full temporal history (a value
|
|
6
|
+
-- may recur later as a NEW active edition — only one can be active).
|
|
7
|
+
--
|
|
8
|
+
-- Codex round-5/6 P1: a bare CREATE UNIQUE INDEX cannot upgrade a database
|
|
9
|
+
-- that already contains the duplicates the index forbids, and repair +
|
|
10
|
+
-- index creation must be ATOMIC against concurrent writers. The whole file
|
|
11
|
+
-- is ONE explicit transaction that write-locks memory_claims and
|
|
12
|
+
-- memory_claim_evidence before scanning:
|
|
13
|
+
-- 1. ABORT loudly if any active duplicate group has the same value_hash
|
|
14
|
+
-- but DIFFERENT full JSON values (sha256 collision — human decision).
|
|
15
|
+
-- 2. Per duplicate group, keep the EARLIEST row (created_at, id). Edge
|
|
16
|
+
-- merge is PAIR-SAFE: first materialize the DISTINCT
|
|
17
|
+
-- (keeper, evidence) pairs implied by all loser ACTIVE edges, then
|
|
18
|
+
-- ensure exactly ONE ACTIVE keeper edge per pair (reactivating a
|
|
19
|
+
-- pre-existing retracted keeper edge, inserting otherwise — never
|
|
20
|
+
-- violating the (claim_id, evidence_item_id) pair unique), then
|
|
21
|
+
-- retract all loser edges. Finally close the losers as 'superseded'
|
|
22
|
+
-- with the duplicate-edition interval policy: zero-length at their own
|
|
23
|
+
-- effective_from when the keeper is earlier-or-equal (NULL keeper
|
|
24
|
+
-- counts as earliest), else closed at the keeper's effective_from; a
|
|
25
|
+
-- NULL result falls back to now() so every ended interval is closed.
|
|
26
|
+
-- 3. Assert zero remaining active duplicates (RAISE rolls the whole
|
|
27
|
+
-- transaction back).
|
|
28
|
+
-- Re-running is idempotent: with no duplicates the repair is a no-op and
|
|
29
|
+
-- the index CREATE is IF NOT EXISTS.
|
|
30
|
+
--
|
|
31
|
+
-- Hand-rolled (partial index + repair; drizzle-kit cannot express it).
|
|
32
|
+
-- Apply with:
|
|
33
|
+
-- psql "$DATABASE_URL" -f drizzle/0237_memory_claims_active_value_uidx.sql
|
|
34
|
+
-- creates: public.memory_claims_active_value_uidx
|
|
35
|
+
|
|
36
|
+
BEGIN;
|
|
37
|
+
|
|
38
|
+
LOCK TABLE public.memory_claims IN SHARE ROW EXCLUSIVE MODE;
|
|
39
|
+
LOCK TABLE public.memory_claim_evidence IN SHARE ROW EXCLUSIVE MODE;
|
|
40
|
+
|
|
41
|
+
DO $$
|
|
42
|
+
DECLARE
|
|
43
|
+
collision_groups integer;
|
|
44
|
+
remaining_duplicates integer;
|
|
45
|
+
BEGIN
|
|
46
|
+
-- 1. Same hash, different value: refuse to merge automatically.
|
|
47
|
+
SELECT count(*) INTO collision_groups FROM (
|
|
48
|
+
SELECT 1
|
|
49
|
+
FROM memory_claims
|
|
50
|
+
WHERE status = 'active'
|
|
51
|
+
GROUP BY tenant_id, target_scope, target_id, subject_key,
|
|
52
|
+
ontology_predicate, value_hash
|
|
53
|
+
HAVING count(*) > 1 AND count(DISTINCT value::text) > 1
|
|
54
|
+
) t;
|
|
55
|
+
IF collision_groups > 0 THEN
|
|
56
|
+
RAISE EXCEPTION
|
|
57
|
+
'memory_claims: % active duplicate group(s) share value_hash with DIFFERING values (hash collision) — resolve manually before applying 0237',
|
|
58
|
+
collision_groups;
|
|
59
|
+
END IF;
|
|
60
|
+
|
|
61
|
+
-- 2. Deterministic repair of identical-value active duplicates.
|
|
62
|
+
CREATE TEMP TABLE _mc_active_dupe_losers ON COMMIT DROP AS
|
|
63
|
+
SELECT id,
|
|
64
|
+
keeper_id,
|
|
65
|
+
keeper_effective_from,
|
|
66
|
+
effective_from AS own_effective_from
|
|
67
|
+
FROM (
|
|
68
|
+
SELECT id,
|
|
69
|
+
effective_from,
|
|
70
|
+
first_value(id) OVER w AS keeper_id,
|
|
71
|
+
first_value(effective_from) OVER w AS keeper_effective_from,
|
|
72
|
+
row_number() OVER w AS rn
|
|
73
|
+
FROM memory_claims
|
|
74
|
+
WHERE status = 'active'
|
|
75
|
+
WINDOW w AS (
|
|
76
|
+
PARTITION BY tenant_id, target_scope, target_id, subject_key,
|
|
77
|
+
ontology_predicate, value_hash
|
|
78
|
+
ORDER BY created_at, id
|
|
79
|
+
)
|
|
80
|
+
) ranked
|
|
81
|
+
WHERE rn > 1;
|
|
82
|
+
|
|
83
|
+
-- 2a. Materialize the DISTINCT (keeper, evidence) pairs implied by loser
|
|
84
|
+
-- ACTIVE edges — several losers sharing one evidence item collapse to
|
|
85
|
+
-- one target pair, so the merge can never violate the
|
|
86
|
+
-- (claim_id, evidence_item_id) pair unique.
|
|
87
|
+
CREATE TEMP TABLE _mc_keeper_pairs ON COMMIT DROP AS
|
|
88
|
+
SELECT DISTINCT ON (l.keeper_id, e.evidence_item_id)
|
|
89
|
+
l.keeper_id,
|
|
90
|
+
e.evidence_item_id,
|
|
91
|
+
e.tenant_id,
|
|
92
|
+
e.source_config_id
|
|
93
|
+
FROM memory_claim_evidence e
|
|
94
|
+
JOIN _mc_active_dupe_losers l ON l.id = e.claim_id
|
|
95
|
+
WHERE e.status = 'active';
|
|
96
|
+
|
|
97
|
+
-- 2b. Reactivate a pre-existing (possibly retracted) keeper edge for each
|
|
98
|
+
-- pair — the keeper must end with ACTIVE support wherever a loser had
|
|
99
|
+
-- it.
|
|
100
|
+
UPDATE memory_claim_evidence k
|
|
101
|
+
SET status = 'active', retracted_at = NULL
|
|
102
|
+
FROM _mc_keeper_pairs p
|
|
103
|
+
WHERE k.claim_id = p.keeper_id
|
|
104
|
+
AND k.evidence_item_id = p.evidence_item_id
|
|
105
|
+
AND k.status <> 'active';
|
|
106
|
+
|
|
107
|
+
-- 2c. Insert the keeper edges that do not exist at all.
|
|
108
|
+
INSERT INTO memory_claim_evidence
|
|
109
|
+
(tenant_id, claim_id, evidence_item_id, source_config_id, status)
|
|
110
|
+
SELECT p.tenant_id, p.keeper_id, p.evidence_item_id, p.source_config_id,
|
|
111
|
+
'active'
|
|
112
|
+
FROM _mc_keeper_pairs p
|
|
113
|
+
WHERE NOT EXISTS (
|
|
114
|
+
SELECT 1 FROM memory_claim_evidence k
|
|
115
|
+
WHERE k.claim_id = p.keeper_id
|
|
116
|
+
AND k.evidence_item_id = p.evidence_item_id
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
-- 2d. Retract ALL loser active edges (their support now lives on the
|
|
120
|
+
-- keeper).
|
|
121
|
+
UPDATE memory_claim_evidence e
|
|
122
|
+
SET status = 'retracted', retracted_at = now()
|
|
123
|
+
FROM _mc_active_dupe_losers l
|
|
124
|
+
WHERE e.claim_id = l.id
|
|
125
|
+
AND e.status = 'active';
|
|
126
|
+
|
|
127
|
+
-- 2e. Close and supersede the losers (duplicate-edition interval policy).
|
|
128
|
+
UPDATE memory_claims c
|
|
129
|
+
SET status = 'superseded',
|
|
130
|
+
effective_to = COALESCE(
|
|
131
|
+
c.effective_to,
|
|
132
|
+
CASE
|
|
133
|
+
WHEN l.keeper_effective_from IS NULL
|
|
134
|
+
OR (l.own_effective_from IS NOT NULL
|
|
135
|
+
AND l.keeper_effective_from <= l.own_effective_from)
|
|
136
|
+
THEN l.own_effective_from
|
|
137
|
+
ELSE l.keeper_effective_from
|
|
138
|
+
END,
|
|
139
|
+
now()
|
|
140
|
+
),
|
|
141
|
+
updated_at = now()
|
|
142
|
+
FROM _mc_active_dupe_losers l
|
|
143
|
+
WHERE c.id = l.id;
|
|
144
|
+
|
|
145
|
+
-- 3. The invariant must hold before the index lands.
|
|
146
|
+
SELECT count(*) INTO remaining_duplicates FROM (
|
|
147
|
+
SELECT 1
|
|
148
|
+
FROM memory_claims
|
|
149
|
+
WHERE status = 'active'
|
|
150
|
+
GROUP BY tenant_id, target_scope, target_id, subject_key,
|
|
151
|
+
ontology_predicate, value_hash
|
|
152
|
+
HAVING count(*) > 1
|
|
153
|
+
) t;
|
|
154
|
+
IF remaining_duplicates > 0 THEN
|
|
155
|
+
RAISE EXCEPTION
|
|
156
|
+
'memory_claims duplicate repair left % active duplicate group(s) — aborting index creation',
|
|
157
|
+
remaining_duplicates;
|
|
158
|
+
END IF;
|
|
159
|
+
END $$;
|
|
160
|
+
|
|
161
|
+
CREATE UNIQUE INDEX IF NOT EXISTS memory_claims_active_value_uidx
|
|
162
|
+
ON public.memory_claims (
|
|
163
|
+
tenant_id,
|
|
164
|
+
target_scope,
|
|
165
|
+
target_id,
|
|
166
|
+
subject_key,
|
|
167
|
+
ontology_predicate,
|
|
168
|
+
value_hash
|
|
169
|
+
)
|
|
170
|
+
WHERE status = 'active';
|
|
171
|
+
|
|
172
|
+
COMMIT;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
-- THINK-193 U2 (Codex rounds 3-6): erase write-fence, per-generation child
|
|
2
|
+
-- scoping, durable bounded-cleanup progress, and DB-level integrity for the
|
|
3
|
+
-- retraction saga.
|
|
4
|
+
-- Contents:
|
|
5
|
+
-- * memory_source_configs.erase_generation — the erase write-fence. Bumped
|
|
6
|
+
-- in the SAME transaction that disables the source and persists the
|
|
7
|
+
-- durable 'erase' marker (beginSourceErase). Stage writers capture it
|
|
8
|
+
-- with the source row; internal writes CAS on it inside their own
|
|
9
|
+
-- transaction and external writes (Hindsight upsert, S3 put) check it
|
|
10
|
+
-- immediately before AND after the call (with compensation on movement),
|
|
11
|
+
-- so an in-flight acquire/project/retain cannot resurrect
|
|
12
|
+
-- claims/snapshots/documents after an erase starts.
|
|
13
|
+
-- * memory_retraction_attempts.erase_generation — tags 'erase' markers and
|
|
14
|
+
-- their 'source'-scoped children with the generation they belong to, so
|
|
15
|
+
-- aggregate child accounting never counts dead-lettered children from a
|
|
16
|
+
-- PREVIOUS (remediated) erase. Constrained to 0 for derivation-scoped
|
|
17
|
+
-- rows.
|
|
18
|
+
-- * memory_retraction_attempts.cleanup_phase / cleanup_cursor — durable
|
|
19
|
+
-- bounded-cleanup progress on the 'erase' marker (NULL →
|
|
20
|
+
-- 'snapshots_deleted' → 'evidence_purged'; checkpoints last, then
|
|
21
|
+
-- terminal). Closed CHECK domain, valid only on scope='erase'.
|
|
22
|
+
-- * Composite accounting index for the per-generation child queries.
|
|
23
|
+
--
|
|
24
|
+
-- Hand-rolled (drizzle meta journal is not in use for this change);
|
|
25
|
+
-- apply with: psql "$DATABASE_URL" -f drizzle/0238_memory_erase_generation_fence.sql
|
|
26
|
+
-- creates-column: public.memory_source_configs.erase_generation
|
|
27
|
+
-- creates-column: public.memory_retraction_attempts.erase_generation
|
|
28
|
+
-- creates-column: public.memory_retraction_attempts.cleanup_phase
|
|
29
|
+
-- creates-column: public.memory_retraction_attempts.cleanup_cursor
|
|
30
|
+
-- creates-constraint: public.memory_source_configs.memory_source_configs_erase_generation_nonnegative
|
|
31
|
+
-- creates-constraint: public.memory_retraction_attempts.memory_retraction_attempts_erase_generation_nonnegative
|
|
32
|
+
-- creates-constraint: public.memory_retraction_attempts.memory_retraction_attempts_derivation_generation_check
|
|
33
|
+
-- creates-constraint: public.memory_retraction_attempts.memory_retraction_attempts_cleanup_phase_check
|
|
34
|
+
-- creates: public.memory_retraction_attempts_erase_accounting_idx
|
|
35
|
+
|
|
36
|
+
ALTER TABLE public.memory_source_configs
|
|
37
|
+
ADD COLUMN IF NOT EXISTS erase_generation integer NOT NULL DEFAULT 0;
|
|
38
|
+
|
|
39
|
+
ALTER TABLE public.memory_retraction_attempts
|
|
40
|
+
ADD COLUMN IF NOT EXISTS erase_generation integer NOT NULL DEFAULT 0;
|
|
41
|
+
|
|
42
|
+
ALTER TABLE public.memory_retraction_attempts
|
|
43
|
+
ADD COLUMN IF NOT EXISTS cleanup_phase text;
|
|
44
|
+
|
|
45
|
+
ALTER TABLE public.memory_retraction_attempts
|
|
46
|
+
ADD COLUMN IF NOT EXISTS cleanup_cursor text;
|
|
47
|
+
|
|
48
|
+
DO $$
|
|
49
|
+
BEGIN
|
|
50
|
+
IF NOT EXISTS (
|
|
51
|
+
SELECT 1 FROM pg_constraint
|
|
52
|
+
WHERE conname = 'memory_source_configs_erase_generation_nonnegative'
|
|
53
|
+
AND conrelid = 'public.memory_source_configs'::regclass
|
|
54
|
+
) THEN
|
|
55
|
+
ALTER TABLE public.memory_source_configs
|
|
56
|
+
ADD CONSTRAINT memory_source_configs_erase_generation_nonnegative
|
|
57
|
+
CHECK (erase_generation >= 0);
|
|
58
|
+
END IF;
|
|
59
|
+
|
|
60
|
+
IF NOT EXISTS (
|
|
61
|
+
SELECT 1 FROM pg_constraint
|
|
62
|
+
WHERE conname = 'memory_retraction_attempts_erase_generation_nonnegative'
|
|
63
|
+
AND conrelid = 'public.memory_retraction_attempts'::regclass
|
|
64
|
+
) THEN
|
|
65
|
+
ALTER TABLE public.memory_retraction_attempts
|
|
66
|
+
ADD CONSTRAINT memory_retraction_attempts_erase_generation_nonnegative
|
|
67
|
+
CHECK (erase_generation >= 0);
|
|
68
|
+
END IF;
|
|
69
|
+
|
|
70
|
+
IF NOT EXISTS (
|
|
71
|
+
SELECT 1 FROM pg_constraint
|
|
72
|
+
WHERE conname = 'memory_retraction_attempts_derivation_generation_check'
|
|
73
|
+
AND conrelid = 'public.memory_retraction_attempts'::regclass
|
|
74
|
+
) THEN
|
|
75
|
+
ALTER TABLE public.memory_retraction_attempts
|
|
76
|
+
ADD CONSTRAINT memory_retraction_attempts_derivation_generation_check
|
|
77
|
+
CHECK (scope <> 'derivation' OR erase_generation = 0);
|
|
78
|
+
END IF;
|
|
79
|
+
|
|
80
|
+
IF NOT EXISTS (
|
|
81
|
+
SELECT 1 FROM pg_constraint
|
|
82
|
+
WHERE conname = 'memory_retraction_attempts_cleanup_phase_check'
|
|
83
|
+
AND conrelid = 'public.memory_retraction_attempts'::regclass
|
|
84
|
+
) THEN
|
|
85
|
+
ALTER TABLE public.memory_retraction_attempts
|
|
86
|
+
ADD CONSTRAINT memory_retraction_attempts_cleanup_phase_check
|
|
87
|
+
CHECK (
|
|
88
|
+
cleanup_phase IS NULL
|
|
89
|
+
OR (scope = 'erase'
|
|
90
|
+
AND cleanup_phase IN ('snapshots_deleted', 'evidence_purged'))
|
|
91
|
+
);
|
|
92
|
+
END IF;
|
|
93
|
+
END $$;
|
|
94
|
+
|
|
95
|
+
CREATE INDEX IF NOT EXISTS memory_retraction_attempts_erase_accounting_idx
|
|
96
|
+
ON public.memory_retraction_attempts
|
|
97
|
+
(source_config_id, erase_generation, scope, status);
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
-- THINK-193 U4: canonical entity identity.
|
|
2
|
+
-- Contents:
|
|
3
|
+
-- * identity.* schema (greenfield pgSchema per the feature-schema
|
|
4
|
+
-- extraction pattern — no compat views needed for new tables):
|
|
5
|
+
-- - identity.canonical_entities — stable instance registry with
|
|
6
|
+
-- merged-redirect invariant CHECK.
|
|
7
|
+
-- - identity.entity_source_mappings — exact mapping wins; unique per
|
|
8
|
+
-- (tenant, source_system, namespace, external_id).
|
|
9
|
+
-- - identity.entity_identity_claims — natural-key evidence; plain
|
|
10
|
+
-- lookup index (per-rule uniqueness enforced in the matcher).
|
|
11
|
+
-- - identity.entity_resolution_cases — operator ambiguity queue;
|
|
12
|
+
-- partial unique coalesces open cases by signature.
|
|
13
|
+
-- - identity.entity_resolution_events — append-only audit (no split).
|
|
14
|
+
-- * knowledge_graph_entities.canonical_entity_id + resolution_state.
|
|
15
|
+
-- * wiki.pages.canonical_entity_id + partial unique for tenant Entity pages.
|
|
16
|
+
-- * ontology.entity_types.identity_rules + identity_rules_version.
|
|
17
|
+
-- * memory_claims.canonical_subject_id FK (column landed in 0234).
|
|
18
|
+
--
|
|
19
|
+
-- Hand-rolled (drizzle meta journal is not in use for this change);
|
|
20
|
+
-- apply with: psql "$DATABASE_URL" -f drizzle/0239_canonical_entity_identity.sql
|
|
21
|
+
-- creates: identity.canonical_entities
|
|
22
|
+
-- creates: identity.entity_source_mappings
|
|
23
|
+
-- creates: identity.entity_identity_claims
|
|
24
|
+
-- creates: identity.entity_resolution_cases
|
|
25
|
+
-- creates: identity.entity_resolution_events
|
|
26
|
+
-- creates: identity.uq_entity_source_mappings_source_identity
|
|
27
|
+
-- creates: identity.uq_entity_resolution_cases_open_signature
|
|
28
|
+
-- creates-column: public.knowledge_graph_entities.canonical_entity_id
|
|
29
|
+
-- creates-column: public.knowledge_graph_entities.resolution_state
|
|
30
|
+
-- creates-column: wiki.pages.canonical_entity_id
|
|
31
|
+
-- creates-column: ontology.entity_types.identity_rules
|
|
32
|
+
-- creates-column: ontology.entity_types.identity_rules_version
|
|
33
|
+
-- creates: public.idx_kg_entities_tenant_canonical
|
|
34
|
+
-- creates: wiki.uq_pages_tenant_canonical_entity
|
|
35
|
+
-- creates-constraint: public.knowledge_graph_entities.knowledge_graph_entities_resolution_state_allowed
|
|
36
|
+
-- creates-constraint: public.memory_claims.memory_claims_canonical_subject_id_fk
|
|
37
|
+
|
|
38
|
+
\set ON_ERROR_STOP on
|
|
39
|
+
|
|
40
|
+
BEGIN;
|
|
41
|
+
|
|
42
|
+
SELECT pg_advisory_xact_lock(hashtext('canonical_entity_identity_0239'));
|
|
43
|
+
|
|
44
|
+
SET LOCAL lock_timeout = '5s';
|
|
45
|
+
SET LOCAL statement_timeout = '60s';
|
|
46
|
+
|
|
47
|
+
CREATE SCHEMA IF NOT EXISTS identity;
|
|
48
|
+
COMMENT ON SCHEMA identity IS
|
|
49
|
+
'Canonical entity instance registry (THINK-193 U4): mappings, natural-key claims, and operator resolution queue.';
|
|
50
|
+
|
|
51
|
+
-- ---------------------------------------------------------------------------
|
|
52
|
+
-- identity.canonical_entities
|
|
53
|
+
-- ---------------------------------------------------------------------------
|
|
54
|
+
|
|
55
|
+
CREATE TABLE IF NOT EXISTS identity.canonical_entities (
|
|
56
|
+
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
57
|
+
tenant_id uuid NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
|
|
58
|
+
entity_type_slug text NOT NULL,
|
|
59
|
+
display_name text NOT NULL,
|
|
60
|
+
normalized_name text NOT NULL,
|
|
61
|
+
status text NOT NULL DEFAULT 'active',
|
|
62
|
+
merged_into_id uuid REFERENCES identity.canonical_entities(id) ON DELETE SET NULL,
|
|
63
|
+
version integer NOT NULL DEFAULT 1,
|
|
64
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
65
|
+
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
66
|
+
CONSTRAINT canonical_entities_status_allowed
|
|
67
|
+
CHECK (status IN ('active','merged','archived')),
|
|
68
|
+
CONSTRAINT canonical_entities_merged_redirect_required
|
|
69
|
+
CHECK ((status = 'merged') = (merged_into_id IS NOT NULL))
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
CREATE INDEX IF NOT EXISTS idx_canonical_entities_tenant_status
|
|
73
|
+
ON identity.canonical_entities (tenant_id, status);
|
|
74
|
+
|
|
75
|
+
CREATE INDEX IF NOT EXISTS idx_canonical_entities_tenant_type_name
|
|
76
|
+
ON identity.canonical_entities (tenant_id, entity_type_slug, normalized_name);
|
|
77
|
+
|
|
78
|
+
-- ---------------------------------------------------------------------------
|
|
79
|
+
-- identity.entity_source_mappings
|
|
80
|
+
-- ---------------------------------------------------------------------------
|
|
81
|
+
|
|
82
|
+
CREATE TABLE IF NOT EXISTS identity.entity_source_mappings (
|
|
83
|
+
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
84
|
+
tenant_id uuid NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
|
|
85
|
+
canonical_entity_id uuid NOT NULL
|
|
86
|
+
REFERENCES identity.canonical_entities(id) ON DELETE CASCADE,
|
|
87
|
+
source_system text NOT NULL,
|
|
88
|
+
namespace text NOT NULL DEFAULT '',
|
|
89
|
+
external_id text NOT NULL,
|
|
90
|
+
visibility text NOT NULL DEFAULT 'tenant',
|
|
91
|
+
created_by text NOT NULL,
|
|
92
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
93
|
+
CONSTRAINT entity_source_mappings_visibility_allowed
|
|
94
|
+
CHECK (visibility IN ('tenant','private')),
|
|
95
|
+
CONSTRAINT entity_source_mappings_created_by_allowed
|
|
96
|
+
CHECK (created_by IN ('rule','operator','backfill'))
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
CREATE UNIQUE INDEX IF NOT EXISTS uq_entity_source_mappings_source_identity
|
|
100
|
+
ON identity.entity_source_mappings (tenant_id, source_system, namespace, external_id);
|
|
101
|
+
|
|
102
|
+
CREATE INDEX IF NOT EXISTS idx_entity_source_mappings_canonical
|
|
103
|
+
ON identity.entity_source_mappings (canonical_entity_id);
|
|
104
|
+
|
|
105
|
+
-- ---------------------------------------------------------------------------
|
|
106
|
+
-- identity.entity_identity_claims
|
|
107
|
+
-- ---------------------------------------------------------------------------
|
|
108
|
+
|
|
109
|
+
CREATE TABLE IF NOT EXISTS identity.entity_identity_claims (
|
|
110
|
+
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
111
|
+
tenant_id uuid NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
|
|
112
|
+
canonical_entity_id uuid NOT NULL
|
|
113
|
+
REFERENCES identity.canonical_entities(id) ON DELETE CASCADE,
|
|
114
|
+
rule_slug text NOT NULL,
|
|
115
|
+
rule_version integer NOT NULL DEFAULT 1,
|
|
116
|
+
key_kind text NOT NULL,
|
|
117
|
+
normalized_value text NOT NULL,
|
|
118
|
+
value_hash text NOT NULL,
|
|
119
|
+
confidence numeric(5,4),
|
|
120
|
+
precedence integer NOT NULL DEFAULT 0,
|
|
121
|
+
visibility text NOT NULL DEFAULT 'tenant',
|
|
122
|
+
state text NOT NULL DEFAULT 'active',
|
|
123
|
+
evidence jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
124
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
125
|
+
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
126
|
+
CONSTRAINT entity_identity_claims_visibility_allowed
|
|
127
|
+
CHECK (visibility IN ('tenant','private')),
|
|
128
|
+
CONSTRAINT entity_identity_claims_state_allowed
|
|
129
|
+
CHECK (state IN ('active','superseded','rejected'))
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
-- Plain lookup index: per-rule uniqueness scope is enforced by the matcher
|
|
133
|
+
-- against the approved rule's `unique` flag, not one global unique index.
|
|
134
|
+
CREATE INDEX IF NOT EXISTS idx_entity_identity_claims_lookup
|
|
135
|
+
ON identity.entity_identity_claims (tenant_id, key_kind, value_hash);
|
|
136
|
+
|
|
137
|
+
CREATE INDEX IF NOT EXISTS idx_entity_identity_claims_canonical
|
|
138
|
+
ON identity.entity_identity_claims (canonical_entity_id);
|
|
139
|
+
|
|
140
|
+
-- ---------------------------------------------------------------------------
|
|
141
|
+
-- identity.entity_resolution_cases
|
|
142
|
+
-- ---------------------------------------------------------------------------
|
|
143
|
+
|
|
144
|
+
CREATE TABLE IF NOT EXISTS identity.entity_resolution_cases (
|
|
145
|
+
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
146
|
+
tenant_id uuid NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
|
|
147
|
+
signature_hash text NOT NULL,
|
|
148
|
+
entity_type_slug text NOT NULL,
|
|
149
|
+
display_hint text,
|
|
150
|
+
candidates jsonb NOT NULL DEFAULT '[]'::jsonb,
|
|
151
|
+
conflicting_claims jsonb NOT NULL DEFAULT '[]'::jsonb,
|
|
152
|
+
impact_summary jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
153
|
+
item_count integer NOT NULL DEFAULT 1,
|
|
154
|
+
status text NOT NULL DEFAULT 'open',
|
|
155
|
+
decision text,
|
|
156
|
+
decided_by_user_id uuid REFERENCES public.users(id) ON DELETE SET NULL,
|
|
157
|
+
resolved_canonical_entity_id uuid
|
|
158
|
+
REFERENCES identity.canonical_entities(id) ON DELETE SET NULL,
|
|
159
|
+
decided_at timestamptz,
|
|
160
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
161
|
+
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
162
|
+
CONSTRAINT entity_resolution_cases_status_allowed
|
|
163
|
+
CHECK (status IN ('open','resolved','expired')),
|
|
164
|
+
CONSTRAINT entity_resolution_cases_decision_allowed
|
|
165
|
+
CHECK (decision IS NULL OR decision IN ('link','create','defer','reject','merge'))
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
CREATE UNIQUE INDEX IF NOT EXISTS uq_entity_resolution_cases_open_signature
|
|
169
|
+
ON identity.entity_resolution_cases (tenant_id, signature_hash)
|
|
170
|
+
WHERE status = 'open';
|
|
171
|
+
|
|
172
|
+
CREATE INDEX IF NOT EXISTS idx_entity_resolution_cases_tenant_status_created
|
|
173
|
+
ON identity.entity_resolution_cases (tenant_id, status, created_at);
|
|
174
|
+
|
|
175
|
+
-- ---------------------------------------------------------------------------
|
|
176
|
+
-- identity.entity_resolution_events — append-only audit
|
|
177
|
+
-- ---------------------------------------------------------------------------
|
|
178
|
+
|
|
179
|
+
CREATE TABLE IF NOT EXISTS identity.entity_resolution_events (
|
|
180
|
+
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
181
|
+
tenant_id uuid NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
|
|
182
|
+
case_id uuid REFERENCES identity.entity_resolution_cases(id) ON DELETE SET NULL,
|
|
183
|
+
canonical_entity_id uuid
|
|
184
|
+
REFERENCES identity.canonical_entities(id) ON DELETE SET NULL,
|
|
185
|
+
event_type text NOT NULL,
|
|
186
|
+
actor_user_id uuid REFERENCES public.users(id) ON DELETE SET NULL,
|
|
187
|
+
payload jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
188
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
189
|
+
CONSTRAINT entity_resolution_events_type_allowed
|
|
190
|
+
CHECK (event_type IN ('create','link','defer','reject','merge'))
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
CREATE INDEX IF NOT EXISTS idx_entity_resolution_events_tenant_created
|
|
194
|
+
ON identity.entity_resolution_events (tenant_id, created_at);
|
|
195
|
+
|
|
196
|
+
CREATE INDEX IF NOT EXISTS idx_entity_resolution_events_case
|
|
197
|
+
ON identity.entity_resolution_events (case_id);
|
|
198
|
+
|
|
199
|
+
-- ---------------------------------------------------------------------------
|
|
200
|
+
-- Projection columns
|
|
201
|
+
-- ---------------------------------------------------------------------------
|
|
202
|
+
|
|
203
|
+
ALTER TABLE public.knowledge_graph_entities
|
|
204
|
+
ADD COLUMN IF NOT EXISTS canonical_entity_id uuid
|
|
205
|
+
REFERENCES identity.canonical_entities(id) ON DELETE SET NULL;
|
|
206
|
+
|
|
207
|
+
ALTER TABLE public.knowledge_graph_entities
|
|
208
|
+
ADD COLUMN IF NOT EXISTS resolution_state text NOT NULL DEFAULT 'legacy';
|
|
209
|
+
|
|
210
|
+
DO $$
|
|
211
|
+
BEGIN
|
|
212
|
+
IF NOT EXISTS (
|
|
213
|
+
SELECT 1 FROM pg_constraint
|
|
214
|
+
WHERE conname = 'knowledge_graph_entities_resolution_state_allowed'
|
|
215
|
+
AND conrelid = 'public.knowledge_graph_entities'::regclass
|
|
216
|
+
) THEN
|
|
217
|
+
ALTER TABLE public.knowledge_graph_entities
|
|
218
|
+
ADD CONSTRAINT knowledge_graph_entities_resolution_state_allowed
|
|
219
|
+
CHECK (resolution_state IN ('resolved','deferred','private','legacy'));
|
|
220
|
+
END IF;
|
|
221
|
+
END $$;
|
|
222
|
+
|
|
223
|
+
CREATE INDEX IF NOT EXISTS idx_kg_entities_tenant_canonical
|
|
224
|
+
ON public.knowledge_graph_entities (tenant_id, canonical_entity_id)
|
|
225
|
+
WHERE canonical_entity_id IS NOT NULL;
|
|
226
|
+
|
|
227
|
+
ALTER TABLE wiki.pages
|
|
228
|
+
ADD COLUMN IF NOT EXISTS canonical_entity_id uuid
|
|
229
|
+
REFERENCES identity.canonical_entities(id) ON DELETE SET NULL;
|
|
230
|
+
|
|
231
|
+
CREATE UNIQUE INDEX IF NOT EXISTS uq_pages_tenant_canonical_entity
|
|
232
|
+
ON wiki.pages (tenant_id, canonical_entity_id)
|
|
233
|
+
WHERE owner_id IS NULL AND canonical_entity_id IS NOT NULL AND type = 'entity';
|
|
234
|
+
|
|
235
|
+
ALTER TABLE ontology.entity_types
|
|
236
|
+
ADD COLUMN IF NOT EXISTS identity_rules jsonb NOT NULL DEFAULT '[]'::jsonb;
|
|
237
|
+
|
|
238
|
+
ALTER TABLE ontology.entity_types
|
|
239
|
+
ADD COLUMN IF NOT EXISTS identity_rules_version integer NOT NULL DEFAULT 0;
|
|
240
|
+
|
|
241
|
+
-- memory_claims.canonical_subject_id landed as a bare uuid in 0234; the
|
|
242
|
+
-- identity registry now exists, so wire the FK.
|
|
243
|
+
DO $$
|
|
244
|
+
BEGIN
|
|
245
|
+
IF NOT EXISTS (
|
|
246
|
+
SELECT 1 FROM pg_constraint
|
|
247
|
+
WHERE conname = 'memory_claims_canonical_subject_id_fk'
|
|
248
|
+
AND conrelid = 'public.memory_claims'::regclass
|
|
249
|
+
) THEN
|
|
250
|
+
ALTER TABLE public.memory_claims
|
|
251
|
+
ADD CONSTRAINT memory_claims_canonical_subject_id_fk
|
|
252
|
+
FOREIGN KEY (canonical_subject_id)
|
|
253
|
+
REFERENCES identity.canonical_entities(id) ON DELETE SET NULL;
|
|
254
|
+
END IF;
|
|
255
|
+
END $$;
|
|
256
|
+
|
|
257
|
+
COMMIT;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
-- THINK-193 U6: personal (user-scoped) external-memory evidence + claims.
|
|
2
|
+
-- The Gmail email tracer writes evidence and claims into the owner's User
|
|
3
|
+
-- Bank (target_scope 'user'); the U1/U2 CHECK constraints only allowed the
|
|
4
|
+
-- shared scopes. Replace both constraints with _v2 versions that include
|
|
5
|
+
-- 'user'. Shared-only surfaces stay guarded in code (SharedTargetScope,
|
|
6
|
+
-- graph/wiki hard-reject user_* banks).
|
|
7
|
+
--
|
|
8
|
+
-- Hand-rolled (drizzle meta journal is not in use for this change);
|
|
9
|
+
-- apply with: psql "$DATABASE_URL" -f drizzle/0240_memory_personal_scope.sql
|
|
10
|
+
-- creates-constraint: public.memory_evidence_items.memory_evidence_items_target_scope_check_v2
|
|
11
|
+
-- drops-constraint: public.memory_evidence_items.memory_evidence_items_target_scope_check
|
|
12
|
+
-- creates-constraint: public.memory_claims.memory_claims_target_scope_check_v2
|
|
13
|
+
-- drops-constraint: public.memory_claims.memory_claims_target_scope_check
|
|
14
|
+
|
|
15
|
+
ALTER TABLE public.memory_evidence_items
|
|
16
|
+
DROP CONSTRAINT IF EXISTS memory_evidence_items_target_scope_check;
|
|
17
|
+
|
|
18
|
+
DO $$
|
|
19
|
+
BEGIN
|
|
20
|
+
IF NOT EXISTS (
|
|
21
|
+
SELECT 1 FROM pg_constraint
|
|
22
|
+
WHERE conname = 'memory_evidence_items_target_scope_check_v2'
|
|
23
|
+
AND conrelid = 'public.memory_evidence_items'::regclass
|
|
24
|
+
) THEN
|
|
25
|
+
ALTER TABLE public.memory_evidence_items
|
|
26
|
+
ADD CONSTRAINT memory_evidence_items_target_scope_check_v2
|
|
27
|
+
CHECK (target_scope IN ('user', 'space', 'tenant'));
|
|
28
|
+
END IF;
|
|
29
|
+
END $$;
|
|
30
|
+
|
|
31
|
+
ALTER TABLE public.memory_claims
|
|
32
|
+
DROP CONSTRAINT IF EXISTS memory_claims_target_scope_check;
|
|
33
|
+
|
|
34
|
+
DO $$
|
|
35
|
+
BEGIN
|
|
36
|
+
IF NOT EXISTS (
|
|
37
|
+
SELECT 1 FROM pg_constraint
|
|
38
|
+
WHERE conname = 'memory_claims_target_scope_check_v2'
|
|
39
|
+
AND conrelid = 'public.memory_claims'::regclass
|
|
40
|
+
) THEN
|
|
41
|
+
ALTER TABLE public.memory_claims
|
|
42
|
+
ADD CONSTRAINT memory_claims_target_scope_check_v2
|
|
43
|
+
CHECK (target_scope IN ('user', 'space', 'tenant'));
|
|
44
|
+
END IF;
|
|
45
|
+
END $$;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
-- THINK-193 U7: Bedrock Knowledge Base document-projection manifest.
|
|
2
|
+
-- Contents:
|
|
3
|
+
-- * public.knowledge_base_documents — ONE row per (knowledge_base_id,
|
|
4
|
+
-- data_source_id, document_key). The row id is the document's stable
|
|
5
|
+
-- manifest identity across editions; `edition` increments on etag/content
|
|
6
|
+
-- change; effective_to closes only at verified deletion
|
|
7
|
+
-- (ingest_status 'absent_verified'). Edition history lives in
|
|
8
|
+
-- memory_evidence_items.source_version (edition + content hash).
|
|
9
|
+
-- * uq_kb_documents_identity — the manifest key.
|
|
10
|
+
-- * idx_kb_documents_kb_updated — the bedrock_kb adapter's acquisition
|
|
11
|
+
-- cursor (changed editions since (updated_at, id)).
|
|
12
|
+
-- * idx_kb_documents_kb_ingest_status — settlement scan for 'deleting'.
|
|
13
|
+
--
|
|
14
|
+
-- Hand-rolled (drizzle meta journal is not in use for this change);
|
|
15
|
+
-- apply with: psql "$DATABASE_URL" -f drizzle/0241_knowledge_base_document_manifest.sql
|
|
16
|
+
-- creates: public.knowledge_base_documents
|
|
17
|
+
-- creates: public.uq_kb_documents_identity
|
|
18
|
+
-- creates: public.idx_kb_documents_tenant
|
|
19
|
+
-- creates: public.idx_kb_documents_kb_updated
|
|
20
|
+
-- creates: public.idx_kb_documents_kb_ingest_status
|
|
21
|
+
-- creates-constraint: public.knowledge_base_documents.knowledge_base_documents_ingest_status_check
|
|
22
|
+
-- creates-constraint: public.knowledge_base_documents.knowledge_base_documents_projection_status_check
|
|
23
|
+
-- creates-constraint: public.knowledge_base_documents.knowledge_base_documents_edition_positive
|
|
24
|
+
|
|
25
|
+
\set ON_ERROR_STOP on
|
|
26
|
+
|
|
27
|
+
BEGIN;
|
|
28
|
+
|
|
29
|
+
CREATE TABLE IF NOT EXISTS public.knowledge_base_documents (
|
|
30
|
+
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
31
|
+
tenant_id uuid NOT NULL REFERENCES public.tenants(id),
|
|
32
|
+
knowledge_base_id uuid NOT NULL REFERENCES public.knowledge_bases(id) ON DELETE CASCADE,
|
|
33
|
+
data_source_id text NOT NULL,
|
|
34
|
+
document_key text NOT NULL,
|
|
35
|
+
s3_version_id text,
|
|
36
|
+
etag text,
|
|
37
|
+
content_hash text,
|
|
38
|
+
edition integer NOT NULL DEFAULT 1,
|
|
39
|
+
effective_from timestamptz,
|
|
40
|
+
effective_to timestamptz,
|
|
41
|
+
ingest_status text NOT NULL DEFAULT 'pending',
|
|
42
|
+
projection_status text NOT NULL DEFAULT 'pending',
|
|
43
|
+
last_error text,
|
|
44
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
45
|
+
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
46
|
+
CONSTRAINT knowledge_base_documents_ingest_status_check CHECK (
|
|
47
|
+
ingest_status IN ('pending', 'ingesting', 'indexed', 'failed', 'deleting', 'absent_verified')
|
|
48
|
+
),
|
|
49
|
+
CONSTRAINT knowledge_base_documents_projection_status_check CHECK (
|
|
50
|
+
projection_status IN ('pending', 'projected', 'skipped', 'failed', 'retracting', 'retracted')
|
|
51
|
+
),
|
|
52
|
+
CONSTRAINT knowledge_base_documents_edition_positive CHECK (edition >= 1)
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
CREATE UNIQUE INDEX IF NOT EXISTS uq_kb_documents_identity
|
|
56
|
+
ON public.knowledge_base_documents (knowledge_base_id, data_source_id, document_key);
|
|
57
|
+
|
|
58
|
+
CREATE INDEX IF NOT EXISTS idx_kb_documents_tenant
|
|
59
|
+
ON public.knowledge_base_documents (tenant_id);
|
|
60
|
+
|
|
61
|
+
CREATE INDEX IF NOT EXISTS idx_kb_documents_kb_updated
|
|
62
|
+
ON public.knowledge_base_documents (knowledge_base_id, updated_at, id);
|
|
63
|
+
|
|
64
|
+
CREATE INDEX IF NOT EXISTS idx_kb_documents_kb_ingest_status
|
|
65
|
+
ON public.knowledge_base_documents (knowledge_base_id, ingest_status);
|
|
66
|
+
|
|
67
|
+
COMMIT;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
-- THINK-263 R15/KTD-8: search-query telemetry (the flywheel sensor).
|
|
2
|
+
-- One append-only row per broker query with per-leg hit counts and statuses.
|
|
3
|
+
-- Raw query text is retained as the future wiki-compile demand-queue input;
|
|
4
|
+
-- retention policy: rows older than 180 days are eligible for deletion
|
|
5
|
+
-- (operational sweep, not enforced in-schema). Operator read paths must be
|
|
6
|
+
-- tenant-scoped. Additive — safe to apply ahead of code deploy.
|
|
7
|
+
--
|
|
8
|
+
-- Hand-rolled (drizzle meta journal is not in use for this change);
|
|
9
|
+
-- apply with: psql "$DATABASE_URL" -f drizzle/0242_search_queries.sql
|
|
10
|
+
-- creates: public.search_queries
|
|
11
|
+
|
|
12
|
+
CREATE TABLE IF NOT EXISTS public.search_queries (
|
|
13
|
+
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
14
|
+
tenant_id uuid NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
|
|
15
|
+
user_id uuid,
|
|
16
|
+
query_id uuid,
|
|
17
|
+
query_text text NOT NULL,
|
|
18
|
+
sources jsonb NOT NULL,
|
|
19
|
+
leg_hit_counts jsonb NOT NULL,
|
|
20
|
+
leg_statuses jsonb NOT NULL,
|
|
21
|
+
total_hits integer NOT NULL DEFAULT 0,
|
|
22
|
+
escalated boolean NOT NULL DEFAULT false,
|
|
23
|
+
duration_ms integer,
|
|
24
|
+
created_at timestamptz NOT NULL DEFAULT now()
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
CREATE INDEX IF NOT EXISTS idx_search_queries_tenant_created
|
|
28
|
+
ON public.search_queries (tenant_id, created_at DESC);
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
-- THINK-263 U2/R14: thread backpointers on wiki section provenance.
|
|
2
|
+
-- Nullable text[] stamped at compile time from cited memory records'
|
|
3
|
+
-- provenance (U1 write-time stamps); backfilled for historical rows by
|
|
4
|
+
-- packages/api/scripts/backfill-section-source-thread-refs.mts. Additive — safe to apply
|
|
5
|
+
-- ahead of code deploy.
|
|
6
|
+
--
|
|
7
|
+
-- Hand-rolled (drizzle meta journal is not in use for this change);
|
|
8
|
+
-- apply with: psql "$DATABASE_URL" -f drizzle/0243_section_sources_thread_refs.sql
|
|
9
|
+
-- creates-column: wiki.section_sources.source_thread_ids
|
|
10
|
+
|
|
11
|
+
ALTER TABLE wiki.section_sources
|
|
12
|
+
ADD COLUMN IF NOT EXISTS source_thread_ids text[];
|