@jungjaehoon/mama-core 1.4.2 → 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/README.md +48 -10
- package/db/migrations/026-create-canonical-entity-tables.sql +99 -0
- package/db/migrations/027-create-entity-review-support.sql +44 -0
- package/db/migrations/028-create-entity-audit-tables.sql +45 -0
- package/db/migrations/029-extend-entity-observations-observation-type.sql +60 -0
- package/db/migrations/030-case-first-memory-substrate.sql +901 -0
- package/db/migrations/031-wiki-page-index-provenance-columns.sql +2 -0
- package/db/migrations/032-add-memory-provenance-columns.sql +21 -0
- package/db/migrations/033-create-model-runs-and-tool-traces.sql +53 -0
- package/db/migrations/034-add-connector-event-scope-columns.sql +14 -0
- package/db/migrations/035-create-twin-edges.sql +63 -0
- package/db/migrations/036-create-agent-situation-packets.sql +61 -0
- package/dist/agent-graph/alias-write.d.ts +3 -0
- package/dist/agent-graph/alias-write.js +231 -0
- package/dist/agent-graph/entity-resolve.d.ts +3 -0
- package/dist/agent-graph/entity-resolve.js +275 -0
- package/dist/agent-graph/errors.d.ts +4 -0
- package/dist/agent-graph/errors.js +11 -0
- package/dist/agent-graph/graph-query.d.ts +5 -0
- package/dist/agent-graph/graph-query.js +359 -0
- package/dist/agent-graph/index.d.ts +6 -0
- package/dist/agent-graph/index.js +22 -0
- package/dist/agent-graph/types.d.ts +162 -0
- package/dist/agent-graph/types.js +3 -0
- package/dist/agent-situation/builder.d.ts +8 -0
- package/dist/agent-situation/builder.js +239 -0
- package/dist/agent-situation/cache-key.d.ts +4 -0
- package/dist/agent-situation/cache-key.js +64 -0
- package/dist/agent-situation/index.d.ts +7 -0
- package/dist/agent-situation/index.js +28 -0
- package/dist/agent-situation/packet-store.d.ts +27 -0
- package/dist/agent-situation/packet-store.js +228 -0
- package/dist/agent-situation/ranking-policy.d.ts +18 -0
- package/dist/agent-situation/ranking-policy.js +75 -0
- package/dist/agent-situation/source-readers.d.ts +52 -0
- package/dist/agent-situation/source-readers.js +300 -0
- package/dist/agent-situation/types.d.ts +195 -0
- package/dist/agent-situation/types.js +3 -0
- package/dist/canonicalize.d.ts +38 -0
- package/dist/canonicalize.js +106 -0
- package/dist/cases/case-links.d.ts +116 -0
- package/dist/cases/case-links.js +794 -0
- package/dist/cases/composition-overrides.d.ts +90 -0
- package/dist/cases/composition-overrides.js +724 -0
- package/dist/cases/corrections.d.ts +156 -0
- package/dist/cases/corrections.js +1045 -0
- package/dist/cases/freshness.d.ts +57 -0
- package/dist/cases/freshness.js +283 -0
- package/dist/cases/live-state.d.ts +48 -0
- package/dist/cases/live-state.js +256 -0
- package/dist/cases/membership-explain.d.ts +56 -0
- package/dist/cases/membership-explain.js +140 -0
- package/dist/cases/membership-matcher.d.ts +23 -0
- package/dist/cases/membership-matcher.js +193 -0
- package/dist/cases/merge-split.d.ts +61 -0
- package/dist/cases/merge-split.js +368 -0
- package/dist/cases/role-inference.d.ts +10 -0
- package/dist/cases/role-inference.js +85 -0
- package/dist/cases/search-rollup.d.ts +27 -0
- package/dist/cases/search-rollup.js +309 -0
- package/dist/cases/sqlite-transaction.d.ts +10 -0
- package/dist/cases/sqlite-transaction.js +62 -0
- package/dist/cases/store.d.ts +74 -0
- package/dist/cases/store.js +816 -0
- package/dist/cases/target-ref.d.ts +25 -0
- package/dist/cases/target-ref.js +22 -0
- package/dist/cases/timeline-range.d.ts +6 -0
- package/dist/cases/timeline-range.js +322 -0
- package/dist/cases/tombstone-sweeper.d.ts +13 -0
- package/dist/cases/tombstone-sweeper.js +128 -0
- package/dist/cases/types.d.ts +244 -0
- package/dist/cases/types.js +47 -0
- package/dist/cases/wiki-page-index.d.ts +50 -0
- package/dist/cases/wiki-page-index.js +350 -0
- package/dist/connectors/event-index.d.ts +45 -0
- package/dist/connectors/event-index.js +381 -0
- package/dist/connectors/raw-query.d.ts +18 -0
- package/dist/connectors/raw-query.js +305 -0
- package/dist/connectors/types.d.ts +109 -0
- package/dist/connectors/types.js +3 -0
- package/dist/db-adapter/node-sqlite-adapter.d.ts +6 -0
- package/dist/db-adapter/node-sqlite-adapter.js +131 -0
- package/dist/db-manager.d.ts +12 -1
- package/dist/db-manager.js +60 -5
- package/dist/decision-formatter.d.ts +2 -0
- package/dist/decision-formatter.js +11 -8
- package/dist/edges/ref-validation.d.ts +8 -0
- package/dist/edges/ref-validation.js +262 -0
- package/dist/edges/store.d.ts +10 -0
- package/dist/edges/store.js +343 -0
- package/dist/edges/types.d.ts +86 -0
- package/dist/edges/types.js +18 -0
- package/dist/embedding-server/mobile/daemon.d.ts +1 -0
- package/dist/embedding-server/mobile/daemon.js +11 -1
- package/dist/embeddings.js +13 -0
- package/dist/entities/audit-metrics.d.ts +73 -0
- package/dist/entities/audit-metrics.js +128 -0
- package/dist/entities/candidate-generator.d.ts +9 -0
- package/dist/entities/candidate-generator.js +436 -0
- package/dist/entities/entity-impact.d.ts +41 -0
- package/dist/entities/entity-impact.js +140 -0
- package/dist/entities/entity-linked-decision-counts.d.ts +2 -0
- package/dist/entities/entity-linked-decision-counts.js +29 -0
- package/dist/entities/entity-list.d.ts +23 -0
- package/dist/entities/entity-list.js +99 -0
- package/dist/entities/entity-orphan-list.d.ts +26 -0
- package/dist/entities/entity-orphan-list.js +91 -0
- package/dist/entities/entity-search.d.ts +21 -0
- package/dist/entities/entity-search.js +150 -0
- package/dist/entities/errors.d.ts +65 -0
- package/dist/entities/errors.js +124 -0
- package/dist/entities/exact-merge-backfill.d.ts +11 -0
- package/dist/entities/exact-merge-backfill.js +161 -0
- package/dist/entities/lineage-backfill.d.ts +12 -0
- package/dist/entities/lineage-backfill.js +101 -0
- package/dist/entities/lineage-store.d.ts +51 -0
- package/dist/entities/lineage-store.js +308 -0
- package/dist/entities/normalization.d.ts +14 -0
- package/dist/entities/normalization.js +63 -0
- package/dist/entities/policy-store.d.ts +17 -0
- package/dist/entities/policy-store.js +263 -0
- package/dist/entities/policy-types.d.ts +72 -0
- package/dist/entities/policy-types.js +59 -0
- package/dist/entities/projection.d.ts +8 -0
- package/dist/entities/projection.js +82 -0
- package/dist/entities/provenance-query.d.ts +33 -0
- package/dist/entities/provenance-query.js +167 -0
- package/dist/entities/read-identity.d.ts +31 -0
- package/dist/entities/read-identity.js +264 -0
- package/dist/entities/recall-bridge.d.ts +5 -0
- package/dist/entities/recall-bridge.js +171 -0
- package/dist/entities/resolution-engine.d.ts +15 -0
- package/dist/entities/resolution-engine.js +69 -0
- package/dist/entities/rollback-preview.d.ts +39 -0
- package/dist/entities/rollback-preview.js +235 -0
- package/dist/entities/source-locator.d.ts +3 -0
- package/dist/entities/source-locator.js +32 -0
- package/dist/entities/store.d.ts +75 -0
- package/dist/entities/store.js +465 -0
- package/dist/entities/types.d.ts +162 -0
- package/dist/entities/types.js +30 -0
- package/dist/index.d.ts +62 -3
- package/dist/index.js +116 -26
- package/dist/mama-api.d.ts +53 -35
- package/dist/mama-api.js +437 -78
- package/dist/memory/api.d.ts +26 -38
- package/dist/memory/api.js +772 -68
- package/dist/memory/event-store.d.ts +3 -0
- package/dist/memory/event-store.js +21 -2
- package/dist/memory/provenance-audit.d.ts +22 -0
- package/dist/memory/provenance-audit.js +112 -0
- package/dist/memory/provenance-query.d.ts +11 -0
- package/dist/memory/provenance-query.js +193 -0
- package/dist/memory/provenance.d.ts +29 -0
- package/dist/memory/provenance.js +136 -0
- package/dist/memory/scope-backfill.d.ts +17 -0
- package/dist/memory/scope-backfill.js +77 -0
- package/dist/memory/types.d.ts +97 -16
- package/dist/model-runs/store.d.ts +13 -0
- package/dist/model-runs/store.js +457 -0
- package/dist/model-runs/tool-trace-store.d.ts +4 -0
- package/dist/model-runs/tool-trace-store.js +114 -0
- package/dist/model-runs/types.d.ts +69 -0
- package/dist/model-runs/types.js +5 -0
- package/dist/runtime/trusted-provenance.d.ts +3 -0
- package/dist/runtime/trusted-provenance.js +6 -0
- package/dist/search/feedback-store.d.ts +59 -0
- package/dist/search/feedback-store.js +211 -0
- package/dist/search/question-type.d.ts +5 -0
- package/dist/search/question-type.js +41 -0
- package/dist/search/ranker-features.d.ts +23 -0
- package/dist/search/ranker-features.js +104 -0
- package/dist/search/ranker-rescore.d.ts +31 -0
- package/dist/search/ranker-rescore.js +226 -0
- package/dist/search/ranker-trainer.d.ts +94 -0
- package/dist/search/ranker-trainer.js +860 -0
- package/dist/search/search-quality.d.ts +36 -0
- package/dist/search/search-quality.js +29 -0
- package/dist/test-utils.d.ts +14 -0
- package/dist/test-utils.js +52 -0
- package/package.json +28 -3
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseEntityIngestRunRow = parseEntityIngestRunRow;
|
|
4
|
+
exports.createEntityIngestRun = createEntityIngestRun;
|
|
5
|
+
exports.completeEntityIngestRun = completeEntityIngestRun;
|
|
6
|
+
exports.failEntityIngestRun = failEntityIngestRun;
|
|
7
|
+
exports.appendEntityLineageLink = appendEntityLineageLink;
|
|
8
|
+
exports.supersedeEntityLineageForEntity = supersedeEntityLineageForEntity;
|
|
9
|
+
exports.seedLineageForEntityMaterialization = seedLineageForEntityMaterialization;
|
|
10
|
+
exports.adoptLineageAfterMerge = adoptLineageAfterMerge;
|
|
11
|
+
exports.adoptLineageAfterMergeSync = adoptLineageAfterMergeSync;
|
|
12
|
+
exports.listActiveEntityLineage = listActiveEntityLineage;
|
|
13
|
+
exports.listEntityLineageHistory = listEntityLineageHistory;
|
|
14
|
+
const node_crypto_1 = require("node:crypto");
|
|
15
|
+
const db_manager_js_1 = require("../db-manager.js");
|
|
16
|
+
function now() {
|
|
17
|
+
return Date.now();
|
|
18
|
+
}
|
|
19
|
+
function parseEntityIngestRunRow(row) {
|
|
20
|
+
return {
|
|
21
|
+
id: String(row.id),
|
|
22
|
+
connector: String(row.connector),
|
|
23
|
+
run_kind: row.run_kind,
|
|
24
|
+
status: row.status,
|
|
25
|
+
scope_key: String(row.scope_key),
|
|
26
|
+
source_window_start: typeof row.source_window_start === 'number' ? row.source_window_start : null,
|
|
27
|
+
source_window_end: typeof row.source_window_end === 'number' ? row.source_window_end : null,
|
|
28
|
+
raw_count: Number(row.raw_count ?? 0),
|
|
29
|
+
observation_count: Number(row.observation_count ?? 0),
|
|
30
|
+
candidate_count: Number(row.candidate_count ?? 0),
|
|
31
|
+
reviewable_count: Number(row.reviewable_count ?? 0),
|
|
32
|
+
audit_run_id: typeof row.audit_run_id === 'string' ? row.audit_run_id : null,
|
|
33
|
+
audit_classification: typeof row.audit_classification === 'string'
|
|
34
|
+
? row.audit_classification
|
|
35
|
+
: null,
|
|
36
|
+
error_reason: typeof row.error_reason === 'string' ? row.error_reason : null,
|
|
37
|
+
created_at: Number(row.created_at),
|
|
38
|
+
completed_at: typeof row.completed_at === 'number' ? row.completed_at : null,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
function parseEntityLineageLinkRow(row) {
|
|
42
|
+
return {
|
|
43
|
+
id: String(row.id),
|
|
44
|
+
canonical_entity_id: String(row.canonical_entity_id),
|
|
45
|
+
entity_observation_id: String(row.entity_observation_id),
|
|
46
|
+
source_entity_id: typeof row.source_entity_id === 'string' ? row.source_entity_id : null,
|
|
47
|
+
contribution_kind: row.contribution_kind,
|
|
48
|
+
run_id: typeof row.run_id === 'string' ? row.run_id : null,
|
|
49
|
+
candidate_id: typeof row.candidate_id === 'string' ? row.candidate_id : null,
|
|
50
|
+
review_action_id: typeof row.review_action_id === 'string' ? row.review_action_id : null,
|
|
51
|
+
status: row.status,
|
|
52
|
+
capture_mode: row.capture_mode,
|
|
53
|
+
confidence: Number(row.confidence),
|
|
54
|
+
created_at: Number(row.created_at),
|
|
55
|
+
superseded_at: typeof row.superseded_at === 'number' ? row.superseded_at : null,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
async function createEntityIngestRun(input) {
|
|
59
|
+
await (0, db_manager_js_1.initDB)();
|
|
60
|
+
const adapter = (0, db_manager_js_1.getAdapter)();
|
|
61
|
+
const createdAt = now();
|
|
62
|
+
const id = input.id || `eir_${(0, node_crypto_1.randomUUID)()}`;
|
|
63
|
+
adapter
|
|
64
|
+
.prepare(`
|
|
65
|
+
INSERT INTO entity_ingest_runs (
|
|
66
|
+
id, connector, run_kind, status, scope_key, source_window_start, source_window_end,
|
|
67
|
+
raw_count, observation_count, candidate_count, reviewable_count, audit_run_id,
|
|
68
|
+
audit_classification, error_reason, created_at, completed_at
|
|
69
|
+
) VALUES (?, ?, ?, 'running', ?, ?, ?, 0, 0, 0, 0, NULL, NULL, NULL, ?, NULL)
|
|
70
|
+
`)
|
|
71
|
+
.run(id, input.connector, input.run_kind, input.scope_key, input.source_window_start, input.source_window_end, createdAt);
|
|
72
|
+
const row = adapter.prepare(`SELECT * FROM entity_ingest_runs WHERE id = ?`).get(id);
|
|
73
|
+
return parseEntityIngestRunRow(row);
|
|
74
|
+
}
|
|
75
|
+
async function completeEntityIngestRun(id, input) {
|
|
76
|
+
await (0, db_manager_js_1.initDB)();
|
|
77
|
+
const adapter = (0, db_manager_js_1.getAdapter)();
|
|
78
|
+
const completedAt = now();
|
|
79
|
+
const completion = adapter
|
|
80
|
+
.prepare(`
|
|
81
|
+
UPDATE entity_ingest_runs
|
|
82
|
+
SET status = 'complete',
|
|
83
|
+
raw_count = ?,
|
|
84
|
+
observation_count = ?,
|
|
85
|
+
candidate_count = ?,
|
|
86
|
+
reviewable_count = ?,
|
|
87
|
+
audit_run_id = ?,
|
|
88
|
+
audit_classification = ?,
|
|
89
|
+
error_reason = NULL,
|
|
90
|
+
completed_at = ?
|
|
91
|
+
WHERE id = ?
|
|
92
|
+
`)
|
|
93
|
+
.run(input.raw_count, input.observation_count, input.candidate_count, input.reviewable_count, input.audit_run_id ?? null, input.audit_classification ?? null, completedAt, id);
|
|
94
|
+
if (completion.changes !== 1) {
|
|
95
|
+
throw new Error(`Entity ingest run not found: ${id}`);
|
|
96
|
+
}
|
|
97
|
+
const row = adapter.prepare(`SELECT * FROM entity_ingest_runs WHERE id = ?`).get(id);
|
|
98
|
+
return parseEntityIngestRunRow(row);
|
|
99
|
+
}
|
|
100
|
+
async function failEntityIngestRun(id, errorReason) {
|
|
101
|
+
await (0, db_manager_js_1.initDB)();
|
|
102
|
+
const adapter = (0, db_manager_js_1.getAdapter)();
|
|
103
|
+
const completedAt = now();
|
|
104
|
+
const completion = adapter
|
|
105
|
+
.prepare(`
|
|
106
|
+
UPDATE entity_ingest_runs
|
|
107
|
+
SET status = 'failed',
|
|
108
|
+
error_reason = ?,
|
|
109
|
+
completed_at = ?
|
|
110
|
+
WHERE id = ?
|
|
111
|
+
`)
|
|
112
|
+
.run(errorReason, completedAt, id);
|
|
113
|
+
if (completion.changes !== 1) {
|
|
114
|
+
throw new Error(`Entity ingest run not found: ${id}`);
|
|
115
|
+
}
|
|
116
|
+
const row = adapter.prepare(`SELECT * FROM entity_ingest_runs WHERE id = ?`).get(id);
|
|
117
|
+
return parseEntityIngestRunRow(row);
|
|
118
|
+
}
|
|
119
|
+
async function appendEntityLineageLink(input) {
|
|
120
|
+
await (0, db_manager_js_1.initDB)();
|
|
121
|
+
const adapter = (0, db_manager_js_1.getAdapter)();
|
|
122
|
+
const existing = adapter
|
|
123
|
+
.prepare(`
|
|
124
|
+
SELECT *
|
|
125
|
+
FROM entity_lineage_links
|
|
126
|
+
WHERE canonical_entity_id = ?
|
|
127
|
+
AND entity_observation_id = ?
|
|
128
|
+
AND status = 'active'
|
|
129
|
+
LIMIT 1
|
|
130
|
+
`)
|
|
131
|
+
.get(input.canonical_entity_id, input.entity_observation_id);
|
|
132
|
+
// Idempotent by (canonical_entity_id, entity_observation_id, status='active').
|
|
133
|
+
// If an active row already exists for the pair, the existing row is returned
|
|
134
|
+
// and the caller-provided metadata is ignored.
|
|
135
|
+
if (existing) {
|
|
136
|
+
return {
|
|
137
|
+
link: parseEntityLineageLinkRow(existing),
|
|
138
|
+
created: false,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
const id = `elin_${(0, node_crypto_1.randomUUID)()}`;
|
|
142
|
+
const createdAt = now();
|
|
143
|
+
adapter
|
|
144
|
+
.prepare(`
|
|
145
|
+
INSERT INTO entity_lineage_links (
|
|
146
|
+
id, canonical_entity_id, entity_observation_id, source_entity_id,
|
|
147
|
+
contribution_kind, run_id, candidate_id, review_action_id,
|
|
148
|
+
status, capture_mode, confidence, created_at, superseded_at
|
|
149
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, 'active', ?, ?, ?, NULL)
|
|
150
|
+
`)
|
|
151
|
+
.run(id, input.canonical_entity_id, input.entity_observation_id, input.source_entity_id, input.contribution_kind, input.run_id, input.candidate_id, input.review_action_id, input.capture_mode, input.confidence, createdAt);
|
|
152
|
+
const row = adapter.prepare(`SELECT * FROM entity_lineage_links WHERE id = ?`).get(id);
|
|
153
|
+
return {
|
|
154
|
+
link: parseEntityLineageLinkRow(row),
|
|
155
|
+
created: true,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
async function supersedeEntityLineageForEntity(entityId) {
|
|
159
|
+
await (0, db_manager_js_1.initDB)();
|
|
160
|
+
const adapter = (0, db_manager_js_1.getAdapter)();
|
|
161
|
+
const supersededAt = now();
|
|
162
|
+
const result = adapter
|
|
163
|
+
.prepare(`
|
|
164
|
+
UPDATE entity_lineage_links
|
|
165
|
+
SET status = 'superseded',
|
|
166
|
+
superseded_at = ?
|
|
167
|
+
WHERE canonical_entity_id = ?
|
|
168
|
+
AND status = 'active'
|
|
169
|
+
`)
|
|
170
|
+
.run(supersededAt, entityId);
|
|
171
|
+
return Number(result.changes ?? 0);
|
|
172
|
+
}
|
|
173
|
+
async function seedLineageForEntityMaterialization(input) {
|
|
174
|
+
const result = await appendEntityLineageLink({
|
|
175
|
+
canonical_entity_id: input.canonical_entity_id,
|
|
176
|
+
entity_observation_id: input.entity_observation_id,
|
|
177
|
+
source_entity_id: null,
|
|
178
|
+
contribution_kind: 'seed',
|
|
179
|
+
run_id: input.run_id ?? null,
|
|
180
|
+
candidate_id: null,
|
|
181
|
+
review_action_id: null,
|
|
182
|
+
capture_mode: input.capture_mode ?? 'direct',
|
|
183
|
+
confidence: input.confidence ?? 1,
|
|
184
|
+
});
|
|
185
|
+
return result.link;
|
|
186
|
+
}
|
|
187
|
+
function runLineageTransaction(adapter, fn) {
|
|
188
|
+
const savepoint = `adopt_lineage_${(0, node_crypto_1.randomUUID)().replace(/-/g, '')}`;
|
|
189
|
+
adapter.prepare(`SAVEPOINT ${savepoint}`).run();
|
|
190
|
+
try {
|
|
191
|
+
const result = fn();
|
|
192
|
+
adapter.prepare(`RELEASE SAVEPOINT ${savepoint}`).run();
|
|
193
|
+
return result;
|
|
194
|
+
}
|
|
195
|
+
catch (error) {
|
|
196
|
+
try {
|
|
197
|
+
adapter.prepare(`ROLLBACK TO SAVEPOINT ${savepoint}`).run();
|
|
198
|
+
}
|
|
199
|
+
catch {
|
|
200
|
+
// Ignore rollback errors when the connection already unwound the savepoint.
|
|
201
|
+
}
|
|
202
|
+
try {
|
|
203
|
+
adapter.prepare(`RELEASE SAVEPOINT ${savepoint}`).run();
|
|
204
|
+
}
|
|
205
|
+
catch {
|
|
206
|
+
// Ignore cleanup failures after rollback.
|
|
207
|
+
}
|
|
208
|
+
throw error;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
async function adoptLineageAfterMerge(input) {
|
|
212
|
+
if (!input.adapter) {
|
|
213
|
+
await (0, db_manager_js_1.initDB)();
|
|
214
|
+
const adapter = (0, db_manager_js_1.getAdapter)();
|
|
215
|
+
return adoptLineageAfterMergeSync({ ...input, adapter });
|
|
216
|
+
}
|
|
217
|
+
return adoptLineageAfterMergeSync(input);
|
|
218
|
+
}
|
|
219
|
+
function adoptLineageAfterMergeSync(input) {
|
|
220
|
+
return runLineageTransaction(input.adapter, () => adoptLineageAfterMergeWithAdapter(input.adapter, input));
|
|
221
|
+
}
|
|
222
|
+
function adoptLineageAfterMergeWithAdapter(adapter, input) {
|
|
223
|
+
const createdAt = now();
|
|
224
|
+
const supersededAt = now();
|
|
225
|
+
const sourceRows = adapter
|
|
226
|
+
.prepare(`
|
|
227
|
+
SELECT *
|
|
228
|
+
FROM entity_lineage_links
|
|
229
|
+
WHERE canonical_entity_id = ?
|
|
230
|
+
AND status = 'active'
|
|
231
|
+
ORDER BY created_at ASC
|
|
232
|
+
`)
|
|
233
|
+
.all(input.source_entity_id);
|
|
234
|
+
adapter
|
|
235
|
+
.prepare(`
|
|
236
|
+
UPDATE entity_lineage_links
|
|
237
|
+
SET status = 'superseded',
|
|
238
|
+
superseded_at = ?
|
|
239
|
+
WHERE canonical_entity_id = ?
|
|
240
|
+
AND status = 'active'
|
|
241
|
+
`)
|
|
242
|
+
.run(supersededAt, input.source_entity_id);
|
|
243
|
+
const adopted = [];
|
|
244
|
+
for (const row of sourceRows) {
|
|
245
|
+
const observationId = String(row.entity_observation_id);
|
|
246
|
+
const existing = adapter
|
|
247
|
+
.prepare(`
|
|
248
|
+
SELECT *
|
|
249
|
+
FROM entity_lineage_links
|
|
250
|
+
WHERE canonical_entity_id = ?
|
|
251
|
+
AND entity_observation_id = ?
|
|
252
|
+
AND status = 'active'
|
|
253
|
+
LIMIT 1
|
|
254
|
+
`)
|
|
255
|
+
.get(input.target_entity_id, observationId);
|
|
256
|
+
if (existing) {
|
|
257
|
+
adopted.push(parseEntityLineageLinkRow(existing));
|
|
258
|
+
continue;
|
|
259
|
+
}
|
|
260
|
+
const id = `elin_${(0, node_crypto_1.randomUUID)()}`;
|
|
261
|
+
adapter
|
|
262
|
+
.prepare(`
|
|
263
|
+
INSERT INTO entity_lineage_links (
|
|
264
|
+
id, canonical_entity_id, entity_observation_id, source_entity_id,
|
|
265
|
+
contribution_kind, run_id, candidate_id, review_action_id,
|
|
266
|
+
status, capture_mode, confidence, created_at, superseded_at
|
|
267
|
+
) VALUES (?, ?, ?, ?, 'merge_adopt', ?, ?, ?, 'active', ?, ?, ?, NULL)
|
|
268
|
+
`)
|
|
269
|
+
.run(id, input.target_entity_id, observationId, input.source_entity_id, typeof row.run_id === 'string' ? row.run_id : null, input.candidate_id ?? null, input.review_action_id ?? null, input.capture_mode ?? 'direct', input.confidence ?? Number(row.confidence ?? 1), createdAt);
|
|
270
|
+
const created = adapter
|
|
271
|
+
.prepare(`SELECT * FROM entity_lineage_links WHERE id = ?`)
|
|
272
|
+
.get(id);
|
|
273
|
+
adopted.push(parseEntityLineageLinkRow(created));
|
|
274
|
+
}
|
|
275
|
+
return adopted;
|
|
276
|
+
}
|
|
277
|
+
async function listActiveEntityLineage(entityId, adapter) {
|
|
278
|
+
if (!adapter) {
|
|
279
|
+
await (0, db_manager_js_1.initDB)();
|
|
280
|
+
}
|
|
281
|
+
const effectiveAdapter = adapter ?? (0, db_manager_js_1.getAdapter)();
|
|
282
|
+
const rows = effectiveAdapter
|
|
283
|
+
.prepare(`
|
|
284
|
+
SELECT *
|
|
285
|
+
FROM entity_lineage_links
|
|
286
|
+
WHERE canonical_entity_id = ?
|
|
287
|
+
AND status = 'active'
|
|
288
|
+
ORDER BY created_at ASC
|
|
289
|
+
`)
|
|
290
|
+
.all(entityId);
|
|
291
|
+
return rows.map(parseEntityLineageLinkRow);
|
|
292
|
+
}
|
|
293
|
+
async function listEntityLineageHistory(entityId, adapter) {
|
|
294
|
+
if (!adapter) {
|
|
295
|
+
await (0, db_manager_js_1.initDB)();
|
|
296
|
+
}
|
|
297
|
+
const effectiveAdapter = adapter ?? (0, db_manager_js_1.getAdapter)();
|
|
298
|
+
const rows = effectiveAdapter
|
|
299
|
+
.prepare(`
|
|
300
|
+
SELECT *
|
|
301
|
+
FROM entity_lineage_links
|
|
302
|
+
WHERE canonical_entity_id = ?
|
|
303
|
+
ORDER BY created_at ASC
|
|
304
|
+
`)
|
|
305
|
+
.all(entityId);
|
|
306
|
+
return rows.map(parseEntityLineageLinkRow);
|
|
307
|
+
}
|
|
308
|
+
//# sourceMappingURL=lineage-store.js.map
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export type EntityScript = 'Latn' | 'Hang' | 'Jpan' | 'Mixed' | 'Unknown';
|
|
2
|
+
export interface NormalizedEntityLabel {
|
|
3
|
+
original: string;
|
|
4
|
+
normalized: string;
|
|
5
|
+
script: EntityScript;
|
|
6
|
+
}
|
|
7
|
+
export declare function detectScript(input: string): EntityScript;
|
|
8
|
+
export declare function normalizeEntityLabel(input: unknown): NormalizedEntityLabel;
|
|
9
|
+
export declare function extractStructuredIdentifiers(input: string): {
|
|
10
|
+
emails: string[];
|
|
11
|
+
handles: string[];
|
|
12
|
+
domains: string[];
|
|
13
|
+
};
|
|
14
|
+
//# sourceMappingURL=normalization.d.ts.map
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.detectScript = detectScript;
|
|
4
|
+
exports.normalizeEntityLabel = normalizeEntityLabel;
|
|
5
|
+
exports.extractStructuredIdentifiers = extractStructuredIdentifiers;
|
|
6
|
+
const errors_js_1 = require("./errors.js");
|
|
7
|
+
const LATIN_RE = /\p{Script=Latin}/u;
|
|
8
|
+
const HANGUL_RE = /\p{Script=Hangul}/u;
|
|
9
|
+
const HIRAGANA_RE = /\p{Script=Hiragana}/u;
|
|
10
|
+
const KATAKANA_RE = /\p{Script=Katakana}/u;
|
|
11
|
+
const HAN_RE = /\p{Script=Han}/u;
|
|
12
|
+
function collapseWhitespace(input) {
|
|
13
|
+
return input.normalize('NFKC').trim().replace(/\s+/gu, ' ');
|
|
14
|
+
}
|
|
15
|
+
function detectScript(input) {
|
|
16
|
+
const hasLatin = LATIN_RE.test(input);
|
|
17
|
+
const hasHangul = HANGUL_RE.test(input);
|
|
18
|
+
const hasJapanese = HIRAGANA_RE.test(input) || KATAKANA_RE.test(input) || HAN_RE.test(input);
|
|
19
|
+
const activeScripts = [hasLatin, hasHangul, hasJapanese].filter(Boolean).length;
|
|
20
|
+
if (activeScripts > 1) {
|
|
21
|
+
return 'Mixed';
|
|
22
|
+
}
|
|
23
|
+
if (hasHangul) {
|
|
24
|
+
return 'Hang';
|
|
25
|
+
}
|
|
26
|
+
if (hasJapanese) {
|
|
27
|
+
return 'Jpan';
|
|
28
|
+
}
|
|
29
|
+
if (hasLatin) {
|
|
30
|
+
return 'Latn';
|
|
31
|
+
}
|
|
32
|
+
return 'Unknown';
|
|
33
|
+
}
|
|
34
|
+
function normalizeEntityLabel(input) {
|
|
35
|
+
if (typeof input !== 'string' || input.trim().length === 0) {
|
|
36
|
+
throw new errors_js_1.InvalidEntityLabelError({
|
|
37
|
+
input,
|
|
38
|
+
reason: typeof input === 'string' ? 'empty_string' : 'non_string_input',
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
const original = input;
|
|
42
|
+
const collapsed = collapseWhitespace(input);
|
|
43
|
+
const script = detectScript(collapsed);
|
|
44
|
+
const normalized = script === 'Latn' ? collapsed.toLowerCase() : collapsed;
|
|
45
|
+
return {
|
|
46
|
+
original,
|
|
47
|
+
normalized,
|
|
48
|
+
script,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
function extractStructuredIdentifiers(input) {
|
|
52
|
+
const emails = Array.from(new Set((input.match(/\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/giu) ?? []).map((v) => v.toLowerCase())));
|
|
53
|
+
const handles = Array.from(new Set((input.match(/(?<!\w)@([a-z0-9_][a-z0-9_.-]*)/giu) ?? []).map((v) => v.slice(1).toLowerCase())));
|
|
54
|
+
const domains = Array.from(new Set((input.match(/\b(?:[a-z0-9-]+\.)+[a-z]{2,}\b/giu) ?? [])
|
|
55
|
+
.map((v) => v.toLowerCase())
|
|
56
|
+
.filter((v) => !emails.some((email) => email.endsWith(`@${v}`)))));
|
|
57
|
+
return {
|
|
58
|
+
emails,
|
|
59
|
+
handles,
|
|
60
|
+
domains,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=normalization.js.map
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { DatabaseAdapter } from '../db-manager.js';
|
|
2
|
+
import { createDefaultEntityPolicyBootstrap, type EntityPolicyApprovalInput, type EntityPolicyBootstrapDocument, type EntityPolicyKind, type EntityPolicyProposalInput, type EntityPolicyProposalRow, type EntityPolicyRow, type EntityRole, type EntityRoleBinding, type EntityRoleBindingInput } from './policy-types.js';
|
|
3
|
+
type PolicyStoreAdapter = Pick<DatabaseAdapter, 'prepare' | 'transaction'>;
|
|
4
|
+
export declare function ensureEntityPolicyBootstrap(args: {
|
|
5
|
+
bootstrapPath: string;
|
|
6
|
+
now?: () => number;
|
|
7
|
+
}): Promise<void>;
|
|
8
|
+
export declare function listEntityPolicies(adapter?: PolicyStoreAdapter): EntityPolicyRow[];
|
|
9
|
+
export declare function getEntityPolicy(policyKey: string, adapter?: PolicyStoreAdapter): EntityPolicyRow | null;
|
|
10
|
+
export declare function listEntityRoleBindings(adapter?: PolicyStoreAdapter): EntityRoleBinding[];
|
|
11
|
+
export declare function upsertEntityRoleBinding(input: EntityRoleBindingInput, adapter?: PolicyStoreAdapter): void;
|
|
12
|
+
export declare function resolveEntityRoleForActor(actorId: string, adapter?: PolicyStoreAdapter): EntityRole;
|
|
13
|
+
export declare function createEntityPolicyProposal(input: EntityPolicyProposalInput, adapter?: PolicyStoreAdapter): string;
|
|
14
|
+
export declare function getEntityPolicyProposal(proposalId: string, adapter?: PolicyStoreAdapter): EntityPolicyProposalRow | null;
|
|
15
|
+
export declare function approveEntityPolicyProposal(input: EntityPolicyApprovalInput, adapter?: PolicyStoreAdapter): EntityPolicyProposalRow;
|
|
16
|
+
export { createDefaultEntityPolicyBootstrap, type EntityPolicyApprovalInput, type EntityPolicyBootstrapDocument, type EntityPolicyKind, type EntityPolicyProposalInput, type EntityPolicyProposalRow, type EntityPolicyRow, type EntityRole, type EntityRoleBinding, type EntityRoleBindingInput, };
|
|
17
|
+
//# sourceMappingURL=policy-store.d.ts.map
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.createDefaultEntityPolicyBootstrap = void 0;
|
|
7
|
+
exports.ensureEntityPolicyBootstrap = ensureEntityPolicyBootstrap;
|
|
8
|
+
exports.listEntityPolicies = listEntityPolicies;
|
|
9
|
+
exports.getEntityPolicy = getEntityPolicy;
|
|
10
|
+
exports.listEntityRoleBindings = listEntityRoleBindings;
|
|
11
|
+
exports.upsertEntityRoleBinding = upsertEntityRoleBinding;
|
|
12
|
+
exports.resolveEntityRoleForActor = resolveEntityRoleForActor;
|
|
13
|
+
exports.createEntityPolicyProposal = createEntityPolicyProposal;
|
|
14
|
+
exports.getEntityPolicyProposal = getEntityPolicyProposal;
|
|
15
|
+
exports.approveEntityPolicyProposal = approveEntityPolicyProposal;
|
|
16
|
+
const node_crypto_1 = require("node:crypto");
|
|
17
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
18
|
+
const db_manager_js_1 = require("../db-manager.js");
|
|
19
|
+
const policy_types_js_1 = require("./policy-types.js");
|
|
20
|
+
Object.defineProperty(exports, "createDefaultEntityPolicyBootstrap", { enumerable: true, get: function () { return policy_types_js_1.createDefaultEntityPolicyBootstrap; } });
|
|
21
|
+
function now() {
|
|
22
|
+
return Date.now();
|
|
23
|
+
}
|
|
24
|
+
function parseJsonObject(raw, label) {
|
|
25
|
+
const parsed = JSON.parse(raw);
|
|
26
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
27
|
+
throw new Error(`${label} must be a JSON object`);
|
|
28
|
+
}
|
|
29
|
+
return parsed;
|
|
30
|
+
}
|
|
31
|
+
function parsePolicyRow(row) {
|
|
32
|
+
const policyKind = String(row.policy_kind);
|
|
33
|
+
if (!(0, policy_types_js_1.isEntityPolicyKind)(policyKind)) {
|
|
34
|
+
throw new Error(`Invalid entity policy row: policy_kind=${policyKind}`);
|
|
35
|
+
}
|
|
36
|
+
const valueJson = String(row.value_json);
|
|
37
|
+
return {
|
|
38
|
+
policy_key: String(row.policy_key),
|
|
39
|
+
policy_kind: policyKind,
|
|
40
|
+
value_json: valueJson,
|
|
41
|
+
value: parseJsonObject(valueJson, 'entity_policy.value_json'),
|
|
42
|
+
version: Number(row.version),
|
|
43
|
+
created_at: Number(row.created_at),
|
|
44
|
+
updated_at: Number(row.updated_at),
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
function parseRoleBindingRow(row) {
|
|
48
|
+
const role = String(row.role);
|
|
49
|
+
if (!(0, policy_types_js_1.isEntityRole)(role)) {
|
|
50
|
+
throw new Error(`Invalid entity role binding row: role=${role}`);
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
actor_id: String(row.actor_id),
|
|
54
|
+
role,
|
|
55
|
+
created_at: Number(row.created_at),
|
|
56
|
+
updated_at: Number(row.updated_at),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
function parseProposalRow(row) {
|
|
60
|
+
const policyKind = String(row.policy_kind);
|
|
61
|
+
if (!(0, policy_types_js_1.isEntityPolicyKind)(policyKind)) {
|
|
62
|
+
throw new Error(`Invalid entity policy proposal row: policy_kind=${policyKind}`);
|
|
63
|
+
}
|
|
64
|
+
const status = String(row.status);
|
|
65
|
+
if (!(0, policy_types_js_1.isEntityPolicyProposalStatus)(status)) {
|
|
66
|
+
throw new Error(`Invalid entity policy proposal row: status=${status}`);
|
|
67
|
+
}
|
|
68
|
+
const proposedValueJson = String(row.proposed_value_json);
|
|
69
|
+
return {
|
|
70
|
+
proposal_id: String(row.proposal_id),
|
|
71
|
+
policy_key: String(row.policy_key),
|
|
72
|
+
policy_kind: policyKind,
|
|
73
|
+
proposed_value_json: proposedValueJson,
|
|
74
|
+
proposed_value: parseJsonObject(proposedValueJson, 'entity_policy_proposals.proposed_value_json'),
|
|
75
|
+
proposer_actor: String(row.proposer_actor),
|
|
76
|
+
approver_actor: typeof row.approver_actor === 'string' ? row.approver_actor : null,
|
|
77
|
+
reason: String(row.reason),
|
|
78
|
+
status,
|
|
79
|
+
created_at: Number(row.created_at),
|
|
80
|
+
approved_at: typeof row.approved_at === 'number' ? row.approved_at : null,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
function readBootstrapDocument(bootstrapPath) {
|
|
84
|
+
const raw = node_fs_1.default.readFileSync(bootstrapPath, 'utf-8');
|
|
85
|
+
const parsed = JSON.parse(raw);
|
|
86
|
+
if (!parsed || typeof parsed !== 'object') {
|
|
87
|
+
throw new Error('Entity policy bootstrap must be a JSON object');
|
|
88
|
+
}
|
|
89
|
+
if (typeof parsed.version !== 'number') {
|
|
90
|
+
throw new Error('Entity policy bootstrap must include numeric version');
|
|
91
|
+
}
|
|
92
|
+
if (!Array.isArray(parsed.policies)) {
|
|
93
|
+
throw new Error('Entity policy bootstrap must include policies[]');
|
|
94
|
+
}
|
|
95
|
+
if (!Array.isArray(parsed.role_bindings)) {
|
|
96
|
+
throw new Error('Entity policy bootstrap must include role_bindings[]');
|
|
97
|
+
}
|
|
98
|
+
for (const policy of parsed.policies) {
|
|
99
|
+
if (!policy || typeof policy !== 'object' || Array.isArray(policy)) {
|
|
100
|
+
throw new Error('Entity policy bootstrap policies must be objects');
|
|
101
|
+
}
|
|
102
|
+
if (typeof policy.policy_key !== 'string' ||
|
|
103
|
+
typeof policy.policy_kind !== 'string' ||
|
|
104
|
+
!(0, policy_types_js_1.isEntityPolicyKind)(policy.policy_kind) ||
|
|
105
|
+
!policy.value ||
|
|
106
|
+
typeof policy.value !== 'object' ||
|
|
107
|
+
Array.isArray(policy.value)) {
|
|
108
|
+
throw new Error('Entity policy bootstrap policy rows are invalid');
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
for (const binding of parsed.role_bindings) {
|
|
112
|
+
if (!binding || typeof binding !== 'object' || Array.isArray(binding)) {
|
|
113
|
+
throw new Error('Entity policy bootstrap role_bindings must be objects');
|
|
114
|
+
}
|
|
115
|
+
if (typeof binding.actor_id !== 'string' ||
|
|
116
|
+
typeof binding.role !== 'string' ||
|
|
117
|
+
!(0, policy_types_js_1.isEntityRole)(binding.role)) {
|
|
118
|
+
throw new Error('Entity policy bootstrap role binding rows are invalid');
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return parsed;
|
|
122
|
+
}
|
|
123
|
+
function countRows(tableName, adapter) {
|
|
124
|
+
const row = adapter.prepare(`SELECT COUNT(*) AS total FROM ${tableName}`).get();
|
|
125
|
+
return Number(row.total ?? 0);
|
|
126
|
+
}
|
|
127
|
+
async function ensureEntityPolicyBootstrap(args) {
|
|
128
|
+
await (0, db_manager_js_1.initDB)();
|
|
129
|
+
const adapter = (0, db_manager_js_1.getAdapter)();
|
|
130
|
+
const document = readBootstrapDocument(args.bootstrapPath);
|
|
131
|
+
const timestamp = args.now?.() ?? now();
|
|
132
|
+
adapter.transaction(() => {
|
|
133
|
+
const policyCount = countRows('entity_policy', adapter);
|
|
134
|
+
const roleBindingCount = countRows('entity_role_bindings', adapter);
|
|
135
|
+
if ((policyCount === 0) !== (roleBindingCount === 0)) {
|
|
136
|
+
throw new Error('Entity policy bootstrap tables are out of sync: entity_policy and entity_role_bindings must be seeded together.');
|
|
137
|
+
}
|
|
138
|
+
if (policyCount === 0 && roleBindingCount === 0) {
|
|
139
|
+
for (const policy of document.policies) {
|
|
140
|
+
adapter
|
|
141
|
+
.prepare(`
|
|
142
|
+
INSERT INTO entity_policy (
|
|
143
|
+
policy_key, policy_kind, value_json, version, created_at, updated_at
|
|
144
|
+
) VALUES (?, ?, ?, 1, ?, ?)
|
|
145
|
+
`)
|
|
146
|
+
.run(policy.policy_key, policy.policy_kind, JSON.stringify(policy.value), timestamp, timestamp);
|
|
147
|
+
}
|
|
148
|
+
for (const binding of document.role_bindings) {
|
|
149
|
+
adapter
|
|
150
|
+
.prepare(`
|
|
151
|
+
INSERT INTO entity_role_bindings (
|
|
152
|
+
actor_id, role, created_at, updated_at
|
|
153
|
+
) VALUES (?, ?, ?, ?)
|
|
154
|
+
`)
|
|
155
|
+
.run(binding.actor_id, binding.role, timestamp, timestamp);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
function listEntityPolicies(adapter = (0, db_manager_js_1.getAdapter)()) {
|
|
161
|
+
const rows = adapter
|
|
162
|
+
.prepare('SELECT * FROM entity_policy ORDER BY policy_key ASC')
|
|
163
|
+
.all();
|
|
164
|
+
return rows.map(parsePolicyRow);
|
|
165
|
+
}
|
|
166
|
+
function getEntityPolicy(policyKey, adapter = (0, db_manager_js_1.getAdapter)()) {
|
|
167
|
+
const row = adapter
|
|
168
|
+
.prepare('SELECT * FROM entity_policy WHERE policy_key = ? LIMIT 1')
|
|
169
|
+
.get(policyKey);
|
|
170
|
+
return row ? parsePolicyRow(row) : null;
|
|
171
|
+
}
|
|
172
|
+
function listEntityRoleBindings(adapter = (0, db_manager_js_1.getAdapter)()) {
|
|
173
|
+
const rows = adapter
|
|
174
|
+
.prepare('SELECT * FROM entity_role_bindings ORDER BY actor_id ASC')
|
|
175
|
+
.all();
|
|
176
|
+
return rows.map(parseRoleBindingRow);
|
|
177
|
+
}
|
|
178
|
+
function upsertEntityRoleBinding(input, adapter = (0, db_manager_js_1.getAdapter)()) {
|
|
179
|
+
const timestamp = now();
|
|
180
|
+
adapter
|
|
181
|
+
.prepare(`
|
|
182
|
+
INSERT INTO entity_role_bindings (
|
|
183
|
+
actor_id, role, created_at, updated_at
|
|
184
|
+
) VALUES (?, ?, ?, ?)
|
|
185
|
+
ON CONFLICT(actor_id) DO UPDATE SET
|
|
186
|
+
role = excluded.role,
|
|
187
|
+
updated_at = excluded.updated_at
|
|
188
|
+
`)
|
|
189
|
+
.run(input.actor_id, input.role, timestamp, timestamp);
|
|
190
|
+
}
|
|
191
|
+
function resolveEntityRoleForActor(actorId, adapter = (0, db_manager_js_1.getAdapter)()) {
|
|
192
|
+
const row = adapter
|
|
193
|
+
.prepare('SELECT role FROM entity_role_bindings WHERE actor_id = ? LIMIT 1')
|
|
194
|
+
.get(actorId);
|
|
195
|
+
return row?.role && (0, policy_types_js_1.isEntityRole)(row.role) ? row.role : 'viewer';
|
|
196
|
+
}
|
|
197
|
+
function createEntityPolicyProposal(input, adapter = (0, db_manager_js_1.getAdapter)()) {
|
|
198
|
+
const proposalId = input.proposal_id ?? `epol_${(0, node_crypto_1.randomUUID)()}`;
|
|
199
|
+
const createdAt = input.created_at ?? now();
|
|
200
|
+
adapter
|
|
201
|
+
.prepare(`
|
|
202
|
+
INSERT INTO entity_policy_proposals (
|
|
203
|
+
proposal_id, policy_key, policy_kind, proposed_value_json, proposer_actor,
|
|
204
|
+
approver_actor, reason, status, created_at, approved_at
|
|
205
|
+
) VALUES (?, ?, ?, ?, ?, NULL, ?, 'pending', ?, NULL)
|
|
206
|
+
`)
|
|
207
|
+
.run(proposalId, input.policy_key, input.policy_kind, JSON.stringify(input.proposed_value), input.proposer_actor, input.reason, createdAt);
|
|
208
|
+
return proposalId;
|
|
209
|
+
}
|
|
210
|
+
function getEntityPolicyProposal(proposalId, adapter = (0, db_manager_js_1.getAdapter)()) {
|
|
211
|
+
const row = adapter
|
|
212
|
+
.prepare('SELECT * FROM entity_policy_proposals WHERE proposal_id = ? LIMIT 1')
|
|
213
|
+
.get(proposalId);
|
|
214
|
+
return row ? parseProposalRow(row) : null;
|
|
215
|
+
}
|
|
216
|
+
function approveEntityPolicyProposal(input, adapter = (0, db_manager_js_1.getAdapter)()) {
|
|
217
|
+
const approvedAt = input.approved_at ?? now();
|
|
218
|
+
return adapter.transaction(() => {
|
|
219
|
+
const proposal = getEntityPolicyProposal(input.proposal_id, adapter);
|
|
220
|
+
if (!proposal) {
|
|
221
|
+
throw new Error(`Entity policy proposal not found: ${input.proposal_id}`);
|
|
222
|
+
}
|
|
223
|
+
if (proposal.status !== 'pending') {
|
|
224
|
+
throw new Error(`Entity policy proposal is not pending: ${input.proposal_id}`);
|
|
225
|
+
}
|
|
226
|
+
if (proposal.proposer_actor === input.approver_actor) {
|
|
227
|
+
throw new Error('Entity policy proposal requires a different approver');
|
|
228
|
+
}
|
|
229
|
+
const approverRole = resolveEntityRoleForActor(input.approver_actor, adapter);
|
|
230
|
+
if (approverRole !== 'admin') {
|
|
231
|
+
throw new Error('Entity policy proposal approval requires an admin approver');
|
|
232
|
+
}
|
|
233
|
+
const existingPolicy = getEntityPolicy(proposal.policy_key, adapter);
|
|
234
|
+
const version = existingPolicy ? existingPolicy.version + 1 : 1;
|
|
235
|
+
adapter
|
|
236
|
+
.prepare(`
|
|
237
|
+
INSERT INTO entity_policy (
|
|
238
|
+
policy_key, policy_kind, value_json, version, created_at, updated_at
|
|
239
|
+
) VALUES (?, ?, ?, ?, ?, ?)
|
|
240
|
+
ON CONFLICT(policy_key) DO UPDATE SET
|
|
241
|
+
policy_kind = excluded.policy_kind,
|
|
242
|
+
value_json = excluded.value_json,
|
|
243
|
+
version = excluded.version,
|
|
244
|
+
updated_at = excluded.updated_at
|
|
245
|
+
`)
|
|
246
|
+
.run(proposal.policy_key, proposal.policy_kind, proposal.proposed_value_json, version, approvedAt, approvedAt);
|
|
247
|
+
adapter
|
|
248
|
+
.prepare(`
|
|
249
|
+
UPDATE entity_policy_proposals
|
|
250
|
+
SET approver_actor = ?,
|
|
251
|
+
status = 'approved',
|
|
252
|
+
approved_at = ?
|
|
253
|
+
WHERE proposal_id = ?
|
|
254
|
+
`)
|
|
255
|
+
.run(input.approver_actor, approvedAt, input.proposal_id);
|
|
256
|
+
const approved = getEntityPolicyProposal(input.proposal_id, adapter);
|
|
257
|
+
if (!approved) {
|
|
258
|
+
throw new Error(`Failed to reload approved entity policy proposal: ${input.proposal_id}`);
|
|
259
|
+
}
|
|
260
|
+
return approved;
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
//# sourceMappingURL=policy-store.js.map
|