@unson/brainbase-mcp 0.7.0 → 0.9.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.
Files changed (91) hide show
  1. package/README.md +1 -1
  2. package/contracts/foundation/graph-history.sql +634 -0
  3. package/contracts/judgment-dag/digest.json +2 -2
  4. package/contracts/judgment-value-proof/schema.json +43 -1
  5. package/dist/canonical-task-service.js +16 -5
  6. package/dist/cli.d.ts +2 -0
  7. package/dist/cli.js +107 -0
  8. package/dist/company-os-objectives.d.ts +11 -0
  9. package/dist/company-os-objectives.js +84 -47
  10. package/dist/foundation-authenticated-tools.d.ts +56 -0
  11. package/dist/foundation-authenticated-tools.js +347 -0
  12. package/dist/foundation-graph-http.d.ts +43 -0
  13. package/dist/foundation-graph-http.js +243 -0
  14. package/dist/foundation-graph-write.d.ts +90 -0
  15. package/dist/foundation-graph-write.js +447 -0
  16. package/dist/foundation-public-provider.d.ts +140 -0
  17. package/dist/foundation-public-provider.js +192 -0
  18. package/dist/graph-corrections.d.ts +157 -0
  19. package/dist/graph-corrections.js +620 -0
  20. package/dist/graph-foundation-reader.d.ts +60 -0
  21. package/dist/graph-foundation-reader.js +494 -0
  22. package/dist/graph-retrieval.d.ts +6 -1
  23. package/dist/graph-revision-reader.d.ts +80 -0
  24. package/dist/graph-revision-reader.js +246 -0
  25. package/dist/graph-web-http.d.ts +26 -0
  26. package/dist/graph-web-http.js +215 -0
  27. package/dist/graph-web.d.ts +364 -0
  28. package/dist/graph-web.js +639 -0
  29. package/dist/judgment-problem-snapshot.d.ts +30 -2
  30. package/dist/judgment-problem-snapshot.js +302 -6
  31. package/dist/judgment-value-proof-review.d.ts +166 -0
  32. package/dist/judgment-value-proof-review.js +437 -0
  33. package/dist/judgment-value-proof.d.ts +28 -1
  34. package/dist/judgment-value-proof.js +53 -8
  35. package/dist/knowledge-continuation.d.ts +127 -0
  36. package/dist/knowledge-continuation.js +446 -0
  37. package/dist/knowledge-lookup.d.ts +178 -0
  38. package/dist/knowledge-lookup.js +758 -0
  39. package/dist/local-web-fetch-bridge.d.ts +5 -0
  40. package/dist/local-web-fetch-bridge.js +37 -0
  41. package/dist/local-web-host.d.ts +109 -0
  42. package/dist/local-web-host.js +688 -0
  43. package/dist/local-web-security.d.ts +25 -0
  44. package/dist/local-web-security.js +95 -0
  45. package/dist/organization-graph.d.ts +15 -0
  46. package/dist/organization-graph.js +24 -4
  47. package/dist/personal-knowledge.d.ts +12 -0
  48. package/dist/personal-knowledge.js +29 -0
  49. package/dist/personal-memory-handover.d.ts +108 -0
  50. package/dist/personal-memory-handover.js +369 -0
  51. package/dist/philosophy-revision-reader.d.ts +85 -0
  52. package/dist/philosophy-revision-reader.js +241 -0
  53. package/dist/portable-graph.d.ts +24 -0
  54. package/dist/portable-graph.js +77 -0
  55. package/dist/server.d.ts +7 -2
  56. package/dist/server.js +9 -6
  57. package/dist/value-proof-review-http.d.ts +25 -0
  58. package/dist/value-proof-review-http.js +185 -0
  59. package/dist/world-model.d.ts +21 -3
  60. package/dist/world-model.js +94 -12
  61. package/package.json +94 -6
  62. package/ui/brainbase-tokens.css +56 -0
  63. package/ui/graph-own-share.js +367 -0
  64. package/ui/graph-projects-view.css +23 -0
  65. package/ui/graph-projects-view.js +730 -0
  66. package/ui/graph-registry-view.css +101 -0
  67. package/ui/graph-registry-view.js +804 -0
  68. package/ui/graph-view-shared.css +522 -0
  69. package/ui/graph-view-shared.js +1227 -0
  70. package/ui/judgment-view.css +119 -45
  71. package/ui/local-web-shell.css +396 -0
  72. package/ui/local-web-shell.js +378 -0
  73. package/ui/objective-editor-http-port.js +154 -0
  74. package/ui/objective-editor.css +180 -74
  75. package/ui/objective-editor.js +490 -43
  76. package/ui/outcome-knowledge.css +50 -14
  77. package/ui/outcome-knowledge.js +15 -1
  78. package/ui/value-proof-review.css +504 -0
  79. package/ui/value-proof-review.js +1104 -0
  80. package/ui/workspace-kit.css +485 -0
  81. package/ui/workspace-kit.js +229 -0
  82. package/ui/world-model-view.css +188 -0
  83. package/ui/world-model-view.js +486 -0
  84. package/ui/icons/mana/LICENSE.tabler.txt +0 -21
  85. package/ui/icons/mana/affiliate.svg +0 -20
  86. package/ui/icons/mana/brand-github.svg +0 -15
  87. package/ui/icons/mana/brand-google-drive.svg +0 -17
  88. package/ui/icons/mana/brand-slack.svg +0 -18
  89. package/ui/icons/mana/cpu.svg +0 -24
  90. package/ui/outcome-mana.css +0 -255
  91. package/ui/outcome-mana.js +0 -2325
package/README.md CHANGED
@@ -55,7 +55,7 @@ Brainbaseの価値は、情報を多く保存することではありません
55
55
  - 組織版はOSS UIを依存として組み込み、組織・メンバー・権限・承認・監査・tenant管理だけを追加します。
56
56
  - 顧客固有のbranding、feature flag、接続先設定は顧客設定リポジトリが所有し、共通UIを複製しません。
57
57
 
58
- 共通UIの最初の公開単位として、知識UIとMana委任UIを `@unson/brainbase-mcp/ui/*` から利用できます。
58
+ 共通UIの最初の公開単位として、知識UIを `@unson/brainbase-mcp/ui/*` から利用できます。Mana委任UIはUnsonの実行基盤(Mana)に結びつくため組織版の画面とし、0.8.0の後に共通UIから外しました。
59
59
 
60
60
  実装済み・develop・計画中の境界は、[現在の状態](https://brainbase.pages.dev/guide/status)を参照してください。
61
61
 
@@ -0,0 +1,634 @@
1
+ /*
2
+ * Canonical Graph Foundation history
3
+ *
4
+ * This migration is deliberately limited to public.graph_entities and the
5
+ * history objects created below. The application role must not receive
6
+ * INSERT/UPDATE/DELETE on graph_foundation_revisions. The host migration
7
+ * should grant SELECT on that table to the same runtime roles that can read
8
+ * graph_entities; the policy below still requires the current row to be
9
+ * visible through graph_entities RLS.
10
+ *
11
+ * The host applies this file inside its existing single transaction. Do not
12
+ * add BEGIN/COMMIT here: the host applies schema, RLS, this migration, and
13
+ * its readback checks atomically.
14
+ */
15
+
16
+ LOCK TABLE public.graph_entities IN SHARE ROW EXCLUSIVE MODE;
17
+
18
+ DO $graph_history_required_columns$
19
+ DECLARE
20
+ missing_columns text[];
21
+ BEGIN
22
+ IF to_regclass('public.graph_entities') IS NULL THEN
23
+ RAISE EXCEPTION 'GRAPH_FOUNDATION_HISTORY_MISSING_GRAPH_ENTITIES'
24
+ USING ERRCODE = 'undefined_table';
25
+ END IF;
26
+
27
+ SELECT array_agg(required_column ORDER BY required_column)
28
+ INTO missing_columns
29
+ FROM (VALUES
30
+ ('id'),
31
+ ('entity_type'),
32
+ ('project_id'),
33
+ ('payload'),
34
+ ('role_min'),
35
+ ('sensitivity'),
36
+ ('lifecycle_status'),
37
+ ('version')
38
+ ) AS required(required_column)
39
+ WHERE NOT EXISTS (
40
+ SELECT 1
41
+ FROM information_schema.columns AS c
42
+ WHERE c.table_schema = 'public'
43
+ AND c.table_name = 'graph_entities'
44
+ AND c.column_name = required.required_column
45
+ );
46
+
47
+ IF missing_columns IS NOT NULL THEN
48
+ RAISE EXCEPTION 'GRAPH_FOUNDATION_HISTORY_GRAPH_ENTITIES_COLUMNS_MISSING: %', missing_columns
49
+ USING ERRCODE = 'undefined_column';
50
+ END IF;
51
+ END;
52
+ $graph_history_required_columns$;
53
+
54
+ /* PostgreSQL 18/PGlite exposes sha256(bytea) in the core catalog. */
55
+ CREATE OR REPLACE FUNCTION public.graph_foundation_storage_digest(p_payload jsonb)
56
+ RETURNS text
57
+ LANGUAGE sql
58
+ IMMUTABLE
59
+ STRICT
60
+ SET search_path = pg_catalog, public
61
+ AS $graph_foundation_storage_digest$
62
+ SELECT 'sha256:' || encode(sha256(convert_to(p_payload::text, 'UTF8')), 'hex');
63
+ $graph_foundation_storage_digest$;
64
+
65
+ CREATE TABLE IF NOT EXISTS public.graph_foundation_revisions (
66
+ entity_id text NOT NULL,
67
+ entity_type text NOT NULL
68
+ CONSTRAINT graph_foundation_revisions_entity_type_check
69
+ CHECK (entity_type IN ('objective', 'variable', 'model', 'constraint', 'philosophy')),
70
+ revision text NOT NULL
71
+ CONSTRAINT graph_foundation_revisions_revision_check
72
+ CHECK (revision ~ '^[1-9][0-9]*$'),
73
+ payload jsonb NOT NULL,
74
+ project_id text,
75
+ role_min text,
76
+ sensitivity text,
77
+ lifecycle_status text,
78
+ storage_digest text NOT NULL
79
+ CONSTRAINT graph_foundation_revisions_storage_digest_format_check
80
+ CHECK (storage_digest ~ '^sha256:[0-9a-f]{64}$'),
81
+ captured_at timestamptz NOT NULL DEFAULT clock_timestamp(),
82
+ CONSTRAINT graph_foundation_revisions_storage_digest_check
83
+ CHECK (storage_digest = public.graph_foundation_storage_digest(payload)),
84
+ CONSTRAINT graph_foundation_revisions_pkey
85
+ PRIMARY KEY (entity_id, entity_type, revision)
86
+ );
87
+
88
+ /* Make a pre-existing table from an interrupted/older attempt converge. */
89
+ DO $graph_history_constraints$
90
+ BEGIN
91
+ IF NOT EXISTS (
92
+ SELECT 1 FROM pg_constraint
93
+ WHERE conrelid = 'public.graph_foundation_revisions'::regclass
94
+ AND conname = 'graph_foundation_revisions_entity_type_check'
95
+ ) THEN
96
+ ALTER TABLE public.graph_foundation_revisions
97
+ ADD CONSTRAINT graph_foundation_revisions_entity_type_check
98
+ CHECK (entity_type IN ('objective', 'variable', 'model', 'constraint', 'philosophy'));
99
+ END IF;
100
+
101
+ IF NOT EXISTS (
102
+ SELECT 1 FROM pg_constraint
103
+ WHERE conrelid = 'public.graph_foundation_revisions'::regclass
104
+ AND conname = 'graph_foundation_revisions_revision_check'
105
+ ) THEN
106
+ ALTER TABLE public.graph_foundation_revisions
107
+ ADD CONSTRAINT graph_foundation_revisions_revision_check
108
+ CHECK (revision ~ '^[1-9][0-9]*$');
109
+ END IF;
110
+
111
+ IF NOT EXISTS (
112
+ SELECT 1 FROM pg_constraint
113
+ WHERE conrelid = 'public.graph_foundation_revisions'::regclass
114
+ AND conname = 'graph_foundation_revisions_storage_digest_format_check'
115
+ ) THEN
116
+ ALTER TABLE public.graph_foundation_revisions
117
+ ADD CONSTRAINT graph_foundation_revisions_storage_digest_format_check
118
+ CHECK (storage_digest ~ '^sha256:[0-9a-f]{64}$');
119
+ END IF;
120
+
121
+ IF NOT EXISTS (
122
+ SELECT 1 FROM pg_constraint
123
+ WHERE conrelid = 'public.graph_foundation_revisions'::regclass
124
+ AND conname = 'graph_foundation_revisions_storage_digest_check'
125
+ ) THEN
126
+ ALTER TABLE public.graph_foundation_revisions
127
+ ADD CONSTRAINT graph_foundation_revisions_storage_digest_check
128
+ CHECK (storage_digest = public.graph_foundation_storage_digest(payload));
129
+ END IF;
130
+
131
+ IF NOT EXISTS (
132
+ SELECT 1 FROM pg_constraint
133
+ WHERE conrelid = 'public.graph_foundation_revisions'::regclass
134
+ AND conname = 'graph_foundation_revisions_pkey'
135
+ ) THEN
136
+ ALTER TABLE public.graph_foundation_revisions
137
+ ADD CONSTRAINT graph_foundation_revisions_pkey
138
+ PRIMARY KEY (entity_id, entity_type, revision);
139
+ END IF;
140
+ END;
141
+ $graph_history_constraints$;
142
+
143
+ /*
144
+ * This private table supplies a per-trigger nonce. A caller cannot make a
145
+ * direct history INSERT look like the SECURITY DEFINER append helper merely
146
+ * by setting a custom GUC: the nonce must have been created by that helper in
147
+ * the same transaction and backend.
148
+ */
149
+ CREATE TABLE IF NOT EXISTS public.graph_foundation_capture_nonces (
150
+ nonce text PRIMARY KEY,
151
+ transaction_id bigint NOT NULL,
152
+ backend_pid integer NOT NULL,
153
+ created_at timestamptz NOT NULL DEFAULT clock_timestamp()
154
+ );
155
+ REVOKE ALL ON TABLE public.graph_foundation_capture_nonces FROM PUBLIC;
156
+ REVOKE ALL ON TABLE public.graph_foundation_revisions FROM PUBLIC;
157
+
158
+ CREATE OR REPLACE FUNCTION public.assert_graph_foundation_payload(
159
+ p_entity_id text,
160
+ p_entity_type text,
161
+ p_payload jsonb
162
+ )
163
+ RETURNS void
164
+ LANGUAGE plpgsql
165
+ IMMUTABLE
166
+ SET search_path = pg_catalog, public
167
+ AS $assert_graph_foundation_payload$
168
+ DECLARE
169
+ foundation jsonb;
170
+ draft_candidate boolean;
171
+ required_key text;
172
+ required_keys text[];
173
+ BEGIN
174
+ IF p_entity_type NOT IN ('objective', 'variable', 'model', 'constraint') THEN
175
+ IF p_entity_type = 'philosophy' AND jsonb_typeof(p_payload) = 'object' THEN
176
+ RETURN;
177
+ END IF;
178
+ RAISE EXCEPTION 'GRAPH_FOUNDATION_HISTORY_INVALID_ENTITY_TYPE: %', p_entity_type
179
+ USING ERRCODE = 'check_violation';
180
+ END IF;
181
+
182
+ IF p_payload IS NULL OR jsonb_typeof(p_payload) <> 'object' THEN
183
+ RAISE EXCEPTION 'GRAPH_FOUNDATION_HISTORY_FOUNDATION_PAYLOAD_MUST_BE_OBJECT: %', p_entity_id
184
+ USING ERRCODE = 'check_violation';
185
+ END IF;
186
+
187
+ foundation := p_payload -> 'foundation';
188
+ IF foundation IS NULL OR jsonb_typeof(foundation) <> 'object' THEN
189
+ RAISE EXCEPTION 'GRAPH_FOUNDATION_HISTORY_FOUNDATION_DEFINITION_REQUIRED: %/%', p_entity_type, p_entity_id
190
+ USING ERRCODE = 'check_violation';
191
+ END IF;
192
+
193
+ IF foundation ->> 'id' IS DISTINCT FROM p_entity_id THEN
194
+ RAISE EXCEPTION 'GRAPH_FOUNDATION_HISTORY_FOUNDATION_ID_MISMATCH: %/%', p_entity_type, p_entity_id
195
+ USING ERRCODE = 'check_violation';
196
+ END IF;
197
+ IF foundation ->> 'type' IS DISTINCT FROM p_entity_type THEN
198
+ RAISE EXCEPTION 'GRAPH_FOUNDATION_HISTORY_FOUNDATION_TYPE_MISMATCH: %/%', p_entity_type, p_entity_id
199
+ USING ERRCODE = 'check_violation';
200
+ END IF;
201
+ IF foundation ->> 'revision' IS NULL
202
+ OR foundation ->> 'revision' !~ '^[1-9][0-9]*$' THEN
203
+ RAISE EXCEPTION 'GRAPH_FOUNDATION_HISTORY_FOUNDATION_REVISION_INVALID: %/%', p_entity_type, p_entity_id
204
+ USING ERRCODE = 'check_violation';
205
+ END IF;
206
+
207
+ required_keys := ARRAY[
208
+ 'id', 'type', 'revision', 'meaning', 'adoptionState', 'authorizedUses',
209
+ 'acl', 'storage', 'provenance', 'scope'
210
+ ];
211
+ FOREACH required_key IN ARRAY required_keys LOOP
212
+ IF NOT (foundation ? required_key)
213
+ OR jsonb_typeof(foundation -> required_key) = 'null' THEN
214
+ RAISE EXCEPTION 'GRAPH_FOUNDATION_HISTORY_FOUNDATION_FIELD_REQUIRED: %/% %',
215
+ p_entity_type, p_entity_id, required_key
216
+ USING ERRCODE = 'check_violation';
217
+ END IF;
218
+ END LOOP;
219
+
220
+ IF jsonb_typeof(foundation -> 'meaning') <> 'string'
221
+ OR jsonb_typeof(foundation -> 'adoptionState') <> 'string'
222
+ OR jsonb_typeof(foundation -> 'authorizedUses') <> 'array'
223
+ OR jsonb_typeof(foundation -> 'provenance') <> 'array'
224
+ OR jsonb_typeof(foundation -> 'acl') <> 'object'
225
+ OR jsonb_typeof(foundation -> 'scope') <> 'object' THEN
226
+ RAISE EXCEPTION 'GRAPH_FOUNDATION_HISTORY_FOUNDATION_BASE_INVALID: %/%', p_entity_type, p_entity_id
227
+ USING ERRCODE = 'check_violation';
228
+ END IF;
229
+
230
+ IF NOT (foundation -> 'acl' ? 'ownerId')
231
+ OR NOT (foundation -> 'acl' ? 'visibility')
232
+ OR NOT (foundation -> 'acl' ? 'readerIds')
233
+ OR NOT (foundation -> 'acl' ? 'writerIds')
234
+ OR jsonb_typeof(foundation -> 'acl' -> 'ownerId') <> 'string'
235
+ OR jsonb_typeof(foundation -> 'acl' -> 'visibility') <> 'string'
236
+ OR jsonb_typeof(foundation -> 'acl' -> 'readerIds') <> 'array'
237
+ OR jsonb_typeof(foundation -> 'acl' -> 'writerIds') <> 'array' THEN
238
+ RAISE EXCEPTION 'GRAPH_FOUNDATION_HISTORY_FOUNDATION_ACL_INVALID: %/%', p_entity_type, p_entity_id
239
+ USING ERRCODE = 'check_violation';
240
+ END IF;
241
+
242
+ IF NOT (foundation -> 'scope' ? 'subjectIds')
243
+ OR NOT (foundation -> 'scope' ? 'validFrom')
244
+ OR jsonb_typeof(foundation -> 'scope' -> 'subjectIds') <> 'array'
245
+ OR jsonb_typeof(foundation -> 'scope' -> 'validFrom') <> 'string'
246
+ OR ((foundation -> 'scope') ? 'validUntil'
247
+ AND jsonb_typeof(foundation -> 'scope' -> 'validUntil') <> 'string') THEN
248
+ RAISE EXCEPTION 'GRAPH_FOUNDATION_HISTORY_FOUNDATION_SCOPE_INVALID: %/%', p_entity_type, p_entity_id
249
+ USING ERRCODE = 'check_violation';
250
+ END IF;
251
+
252
+ /*
253
+ * Keep history capture aligned with the canonical draft writer. A draft
254
+ * is relaxed only for the exact candidate contract; an approved/proposed
255
+ * row must continue through the strict type-specific checks below.
256
+ */
257
+ draft_candidate := foundation ->> 'adoptionState' = 'draft'
258
+ AND foundation ->> 'storage' = 'candidate'
259
+ AND (foundation -> 'authorizedUses') = '["draft"]'::jsonb;
260
+
261
+ IF p_entity_type = 'objective' THEN
262
+ IF draft_candidate THEN
263
+ IF ((foundation ? 'beneficiaryIds') AND jsonb_typeof(foundation -> 'beneficiaryIds') IS DISTINCT FROM 'array')
264
+ OR ((foundation ? 'desiredState') AND jsonb_typeof(foundation -> 'desiredState') IS DISTINCT FROM 'string')
265
+ OR ((foundation ? 'criteria') AND jsonb_typeof(foundation -> 'criteria') IS DISTINCT FROM 'array')
266
+ OR ((foundation ? 'evaluationPeriod') AND jsonb_typeof(foundation -> 'evaluationPeriod') IS DISTINCT FROM 'object')
267
+ OR ((foundation ? 'accountableId') AND jsonb_typeof(foundation -> 'accountableId') IS DISTINCT FROM 'string') THEN
268
+ RAISE EXCEPTION 'GRAPH_FOUNDATION_HISTORY_OBJECTIVE_FIELDS_INVALID: %/%', p_entity_type, p_entity_id
269
+ USING ERRCODE = 'check_violation';
270
+ END IF;
271
+ ELSIF NOT (foundation ? 'beneficiaryIds')
272
+ OR NOT (foundation ? 'desiredState')
273
+ OR NOT (foundation ? 'criteria')
274
+ OR NOT (foundation ? 'evaluationPeriod')
275
+ OR jsonb_typeof(foundation -> 'beneficiaryIds') <> 'array'
276
+ OR jsonb_typeof(foundation -> 'desiredState') <> 'string'
277
+ OR jsonb_typeof(foundation -> 'criteria') <> 'array'
278
+ OR jsonb_typeof(foundation -> 'evaluationPeriod') <> 'object' THEN
279
+ RAISE EXCEPTION 'GRAPH_FOUNDATION_HISTORY_OBJECTIVE_FIELDS_INVALID: %/%', p_entity_type, p_entity_id
280
+ USING ERRCODE = 'check_violation';
281
+ END IF;
282
+ ELSIF p_entity_type = 'variable' THEN
283
+ IF draft_candidate THEN
284
+ IF ((foundation ? 'subject') AND jsonb_typeof(foundation -> 'subject') IS DISTINCT FROM 'string')
285
+ OR ((foundation ? 'valueKind') AND jsonb_typeof(foundation -> 'valueKind') IS DISTINCT FROM 'string')
286
+ OR ((foundation ? 'unit') AND jsonb_typeof(foundation -> 'unit') IS DISTINCT FROM 'string')
287
+ OR ((foundation ? 'aggregation') AND jsonb_typeof(foundation -> 'aggregation') IS DISTINCT FROM 'string')
288
+ OR ((foundation ? 'granularity') AND jsonb_typeof(foundation -> 'granularity') IS DISTINCT FROM 'string')
289
+ OR ((foundation ? 'measurementMethod') AND jsonb_typeof(foundation -> 'measurementMethod') IS DISTINCT FROM 'string') THEN
290
+ RAISE EXCEPTION 'GRAPH_FOUNDATION_HISTORY_VARIABLE_FIELDS_INVALID: %/%', p_entity_type, p_entity_id
291
+ USING ERRCODE = 'check_violation';
292
+ END IF;
293
+ ELSIF NOT (foundation ? 'subject')
294
+ OR NOT (foundation ? 'valueKind')
295
+ OR NOT (foundation ? 'aggregation')
296
+ OR NOT (foundation ? 'granularity')
297
+ OR NOT (foundation ? 'measurementMethod')
298
+ OR jsonb_typeof(foundation -> 'subject') <> 'string'
299
+ OR jsonb_typeof(foundation -> 'valueKind') <> 'string'
300
+ OR jsonb_typeof(foundation -> 'aggregation') <> 'string'
301
+ OR jsonb_typeof(foundation -> 'granularity') <> 'string'
302
+ OR jsonb_typeof(foundation -> 'measurementMethod') <> 'string' THEN
303
+ RAISE EXCEPTION 'GRAPH_FOUNDATION_HISTORY_VARIABLE_FIELDS_INVALID: %/%', p_entity_type, p_entity_id
304
+ USING ERRCODE = 'check_violation';
305
+ END IF;
306
+ ELSIF p_entity_type = 'model' THEN
307
+ IF draft_candidate THEN
308
+ IF NOT (foundation ? 'epistemicState')
309
+ OR jsonb_typeof(foundation -> 'epistemicState') IS DISTINCT FROM 'string'
310
+ OR ((foundation ? 'inputVariableRefs') AND jsonb_typeof(foundation -> 'inputVariableRefs') IS DISTINCT FROM 'array')
311
+ OR ((foundation ? 'outputVariableRefs') AND jsonb_typeof(foundation -> 'outputVariableRefs') IS DISTINCT FROM 'array')
312
+ OR ((foundation ? 'applicability') AND jsonb_typeof(foundation -> 'applicability') IS DISTINCT FROM 'object')
313
+ OR ((foundation ? 'relationship') AND jsonb_typeof(foundation -> 'relationship') IS DISTINCT FROM 'string')
314
+ OR ((foundation ? 'uncertainty') AND jsonb_typeof(foundation -> 'uncertainty') IS DISTINCT FROM 'string')
315
+ OR ((foundation ? 'validationState') AND jsonb_typeof(foundation -> 'validationState') IS DISTINCT FROM 'string') THEN
316
+ RAISE EXCEPTION 'GRAPH_FOUNDATION_HISTORY_MODEL_FIELDS_INVALID: %/%', p_entity_type, p_entity_id
317
+ USING ERRCODE = 'check_violation';
318
+ END IF;
319
+ ELSIF NOT (foundation ? 'epistemicState')
320
+ OR NOT (foundation ? 'inputVariableRefs')
321
+ OR NOT (foundation ? 'outputVariableRefs')
322
+ OR NOT (foundation ? 'applicability')
323
+ OR NOT (foundation ? 'relationship')
324
+ OR NOT (foundation ? 'uncertainty')
325
+ OR NOT (foundation ? 'validationState')
326
+ OR jsonb_typeof(foundation -> 'epistemicState') <> 'string'
327
+ OR jsonb_typeof(foundation -> 'inputVariableRefs') <> 'array'
328
+ OR jsonb_typeof(foundation -> 'outputVariableRefs') <> 'array'
329
+ OR jsonb_typeof(foundation -> 'applicability') <> 'object'
330
+ OR jsonb_typeof(foundation -> 'relationship') <> 'string'
331
+ OR jsonb_typeof(foundation -> 'uncertainty') <> 'string'
332
+ OR jsonb_typeof(foundation -> 'validationState') <> 'string' THEN
333
+ RAISE EXCEPTION 'GRAPH_FOUNDATION_HISTORY_MODEL_FIELDS_INVALID: %/%', p_entity_type, p_entity_id
334
+ USING ERRCODE = 'check_violation';
335
+ END IF;
336
+ ELSIF p_entity_type = 'constraint' THEN
337
+ IF draft_candidate THEN
338
+ IF ((foundation ? 'condition') AND jsonb_typeof(foundation -> 'condition') IS DISTINCT FROM 'string')
339
+ OR ((foundation ? 'appliesTo') AND jsonb_typeof(foundation -> 'appliesTo') IS DISTINCT FROM 'array')
340
+ OR ((foundation ? 'exceptions') AND jsonb_typeof(foundation -> 'exceptions') IS DISTINCT FROM 'array')
341
+ OR ((foundation ? 'adoptionBasis') AND jsonb_typeof(foundation -> 'adoptionBasis') IS DISTINCT FROM 'array') THEN
342
+ RAISE EXCEPTION 'GRAPH_FOUNDATION_HISTORY_CONSTRAINT_FIELDS_INVALID: %/%', p_entity_type, p_entity_id
343
+ USING ERRCODE = 'check_violation';
344
+ END IF;
345
+ ELSIF NOT (foundation ? 'condition')
346
+ OR NOT (foundation ? 'appliesTo')
347
+ OR NOT (foundation ? 'exceptions')
348
+ OR NOT (foundation ? 'adoptionBasis')
349
+ OR jsonb_typeof(foundation -> 'condition') <> 'string'
350
+ OR jsonb_typeof(foundation -> 'appliesTo') <> 'array'
351
+ OR jsonb_typeof(foundation -> 'exceptions') <> 'array'
352
+ OR jsonb_typeof(foundation -> 'adoptionBasis') <> 'array' THEN
353
+ RAISE EXCEPTION 'GRAPH_FOUNDATION_HISTORY_CONSTRAINT_FIELDS_INVALID: %/%', p_entity_type, p_entity_id
354
+ USING ERRCODE = 'check_violation';
355
+ END IF;
356
+ END IF;
357
+ END;
358
+ $assert_graph_foundation_payload$;
359
+
360
+ CREATE OR REPLACE FUNCTION public.guard_graph_foundation_revision_mutation()
361
+ RETURNS trigger
362
+ LANGUAGE plpgsql
363
+ SECURITY DEFINER
364
+ SET search_path = pg_catalog, public
365
+ AS $guard_graph_foundation_revision_mutation$
366
+ DECLARE
367
+ nonce_value text;
368
+ BEGIN
369
+ IF TG_OP <> 'INSERT' THEN
370
+ RAISE EXCEPTION 'GRAPH_FOUNDATION_HISTORY_APPEND_ONLY: % is forbidden', TG_OP
371
+ USING ERRCODE = 'insufficient_privilege';
372
+ END IF;
373
+
374
+ nonce_value := current_setting('graph.foundation_capture_nonce', true);
375
+ IF nonce_value IS NULL OR nonce_value = ''
376
+ OR NOT EXISTS (
377
+ SELECT 1
378
+ FROM public.graph_foundation_capture_nonces AS nonce
379
+ WHERE nonce.nonce = nonce_value
380
+ AND nonce.transaction_id = txid_current()
381
+ AND nonce.backend_pid = pg_backend_pid()
382
+ ) THEN
383
+ RAISE EXCEPTION 'GRAPH_FOUNDATION_HISTORY_DIRECT_INSERT_FORBIDDEN'
384
+ USING ERRCODE = 'insufficient_privilege';
385
+ END IF;
386
+
387
+ RETURN NEW;
388
+ END;
389
+ $guard_graph_foundation_revision_mutation$;
390
+
391
+ CREATE OR REPLACE FUNCTION public.append_graph_foundation_revision(
392
+ p_entity_id text,
393
+ p_entity_type text,
394
+ p_revision text,
395
+ p_payload jsonb,
396
+ p_project_id text,
397
+ p_role_min text,
398
+ p_sensitivity text,
399
+ p_lifecycle_status text,
400
+ p_allow_migration_initial boolean DEFAULT false
401
+ )
402
+ RETURNS void
403
+ LANGUAGE plpgsql
404
+ SECURITY DEFINER
405
+ SET search_path = pg_catalog, public
406
+ AS $append_graph_foundation_revision$
407
+ DECLARE
408
+ nonce_value text;
409
+ previous_revision numeric;
410
+ expected_revision numeric;
411
+ saved_digest text;
412
+ BEGIN
413
+ IF p_entity_type NOT IN ('objective', 'variable', 'model', 'constraint', 'philosophy') THEN
414
+ RAISE EXCEPTION 'GRAPH_FOUNDATION_HISTORY_INVALID_ENTITY_TYPE: %', p_entity_type
415
+ USING ERRCODE = 'check_violation';
416
+ END IF;
417
+
418
+ IF p_entity_type = 'philosophy' THEN
419
+ PERFORM public.assert_graph_foundation_payload(p_entity_id, p_entity_type, p_payload);
420
+ ELSE
421
+ PERFORM public.assert_graph_foundation_payload(p_entity_id, p_entity_type, p_payload);
422
+ IF p_revision IS NULL OR p_revision !~ '^[1-9][0-9]*$' THEN
423
+ RAISE EXCEPTION 'GRAPH_FOUNDATION_HISTORY_FOUNDATION_REVISION_INVALID: %/%', p_entity_type, p_entity_id
424
+ USING ERRCODE = 'check_violation';
425
+ END IF;
426
+ END IF;
427
+
428
+ saved_digest := public.graph_foundation_storage_digest(p_payload);
429
+
430
+ SELECT max(history.revision::numeric)
431
+ INTO previous_revision
432
+ FROM public.graph_foundation_revisions AS history
433
+ WHERE history.entity_id = p_entity_id
434
+ AND history.entity_type = p_entity_type;
435
+
436
+ IF p_entity_type = 'philosophy' THEN
437
+ expected_revision := coalesce(previous_revision, 0) + 1;
438
+ p_revision := expected_revision::text;
439
+ ELSE
440
+ expected_revision := coalesce(previous_revision, 0) + 1;
441
+ IF previous_revision IS NULL AND p_allow_migration_initial THEN
442
+ expected_revision := p_revision::numeric;
443
+ ELSIF p_revision::numeric <> expected_revision THEN
444
+ RAISE EXCEPTION
445
+ 'GRAPH_FOUNDATION_HISTORY_REVISION_SEQUENCE: %/% expected %, got %',
446
+ p_entity_type, p_entity_id, expected_revision::text, p_revision
447
+ USING ERRCODE = 'check_violation';
448
+ END IF;
449
+ END IF;
450
+
451
+ nonce_value := md5(
452
+ clock_timestamp()::text || ':' || txid_current()::text || ':' ||
453
+ pg_backend_pid()::text || ':' || random()::text
454
+ );
455
+ INSERT INTO public.graph_foundation_capture_nonces (nonce, transaction_id, backend_pid)
456
+ VALUES (nonce_value, txid_current(), pg_backend_pid());
457
+ PERFORM set_config('graph.foundation_capture_nonce', nonce_value, true);
458
+
459
+ BEGIN
460
+ INSERT INTO public.graph_foundation_revisions (
461
+ entity_id,
462
+ entity_type,
463
+ revision,
464
+ payload,
465
+ project_id,
466
+ role_min,
467
+ sensitivity,
468
+ lifecycle_status,
469
+ storage_digest
470
+ ) VALUES (
471
+ p_entity_id,
472
+ p_entity_type,
473
+ p_revision,
474
+ p_payload,
475
+ p_project_id,
476
+ p_role_min,
477
+ p_sensitivity,
478
+ p_lifecycle_status,
479
+ saved_digest
480
+ );
481
+ EXCEPTION WHEN OTHERS THEN
482
+ PERFORM set_config('graph.foundation_capture_nonce', '', true);
483
+ DELETE FROM public.graph_foundation_capture_nonces WHERE nonce = nonce_value;
484
+ RAISE;
485
+ END;
486
+
487
+ PERFORM set_config('graph.foundation_capture_nonce', '', true);
488
+ DELETE FROM public.graph_foundation_capture_nonces WHERE nonce = nonce_value;
489
+ END;
490
+ $append_graph_foundation_revision$;
491
+
492
+ REVOKE ALL ON FUNCTION public.append_graph_foundation_revision(
493
+ text, text, text, jsonb, text, text, text, text, boolean
494
+ ) FROM PUBLIC;
495
+
496
+ CREATE OR REPLACE FUNCTION public.capture_graph_foundation_revision()
497
+ RETURNS trigger
498
+ LANGUAGE plpgsql
499
+ SECURITY DEFINER
500
+ SET search_path = pg_catalog, public
501
+ AS $capture_graph_foundation_revision$
502
+ BEGIN
503
+ IF TG_TABLE_SCHEMA <> 'public' OR TG_TABLE_NAME <> 'graph_entities'
504
+ OR TG_OP NOT IN ('INSERT', 'UPDATE') THEN
505
+ RAISE EXCEPTION 'GRAPH_FOUNDATION_HISTORY_TRIGGER_ONLY'
506
+ USING ERRCODE = 'insufficient_privilege';
507
+ END IF;
508
+
509
+ IF NEW.entity_type IN ('objective', 'variable', 'model', 'constraint', 'philosophy')
510
+ AND (
511
+ TG_OP = 'INSERT'
512
+ OR OLD.id IS DISTINCT FROM NEW.id
513
+ OR OLD.entity_type IS DISTINCT FROM NEW.entity_type
514
+ OR OLD.payload IS DISTINCT FROM NEW.payload
515
+ OR OLD.project_id IS DISTINCT FROM NEW.project_id
516
+ OR OLD.role_min IS DISTINCT FROM NEW.role_min
517
+ OR OLD.sensitivity IS DISTINCT FROM NEW.sensitivity
518
+ OR OLD.lifecycle_status IS DISTINCT FROM NEW.lifecycle_status
519
+ ) THEN
520
+ IF NEW.entity_type = 'philosophy' THEN
521
+ PERFORM public.append_graph_foundation_revision(
522
+ NEW.id, NEW.entity_type, NULL, NEW.payload, NEW.project_id,
523
+ NEW.role_min, NEW.sensitivity, NEW.lifecycle_status, false
524
+ );
525
+ ELSE
526
+ PERFORM public.append_graph_foundation_revision(
527
+ NEW.id, NEW.entity_type, NEW.payload -> 'foundation' ->> 'revision', NEW.payload,
528
+ NEW.project_id, NEW.role_min, NEW.sensitivity, NEW.lifecycle_status, false
529
+ );
530
+ END IF;
531
+ END IF;
532
+
533
+ RETURN NEW;
534
+ END;
535
+ $capture_graph_foundation_revision$;
536
+
537
+ /*
538
+ * The SECURITY DEFINER append helper must see every prior revision while it
539
+ * calculates max+1, including rows whose current Graph entity is inactive or
540
+ * deleted. Normalize this before the migration backfill below.
541
+ */
542
+ ALTER TABLE public.graph_foundation_revisions ENABLE ROW LEVEL SECURITY;
543
+ ALTER TABLE public.graph_foundation_revisions NO FORCE ROW LEVEL SECURITY;
544
+
545
+ /* Recreate both guards so a partially applied prior migration cannot leave a gap. */
546
+ DROP TRIGGER IF EXISTS graph_foundation_revisions_guard
547
+ ON public.graph_foundation_revisions;
548
+ DROP TRIGGER IF EXISTS graph_foundation_revisions_truncate_guard
549
+ ON public.graph_foundation_revisions;
550
+ CREATE TRIGGER graph_foundation_revisions_guard
551
+ BEFORE INSERT OR UPDATE OR DELETE ON public.graph_foundation_revisions
552
+ FOR EACH ROW EXECUTE FUNCTION public.guard_graph_foundation_revision_mutation();
553
+ CREATE TRIGGER graph_foundation_revisions_truncate_guard
554
+ BEFORE TRUNCATE ON public.graph_foundation_revisions
555
+ FOR EACH STATEMENT EXECUTE FUNCTION public.guard_graph_foundation_revision_mutation();
556
+
557
+ /* Existing graph rows are captured once. No pre-migration history is inferred. */
558
+ SELECT public.append_graph_foundation_revision(
559
+ graph.id,
560
+ graph.entity_type,
561
+ CASE WHEN graph.entity_type = 'philosophy'
562
+ THEN NULL
563
+ ELSE graph.payload -> 'foundation' ->> 'revision'
564
+ END,
565
+ graph.payload,
566
+ graph.project_id,
567
+ graph.role_min,
568
+ graph.sensitivity,
569
+ graph.lifecycle_status,
570
+ true
571
+ )
572
+ FROM public.graph_entities AS graph
573
+ WHERE graph.entity_type IN ('objective', 'variable', 'model', 'constraint', 'philosophy')
574
+ AND NOT EXISTS (
575
+ SELECT 1
576
+ FROM public.graph_foundation_revisions AS history
577
+ WHERE history.entity_id = graph.id
578
+ AND history.entity_type = graph.entity_type
579
+ AND history.payload IS NOT DISTINCT FROM graph.payload
580
+ AND history.project_id IS NOT DISTINCT FROM graph.project_id
581
+ AND history.role_min IS NOT DISTINCT FROM graph.role_min
582
+ AND history.sensitivity IS NOT DISTINCT FROM graph.sensitivity
583
+ AND history.lifecycle_status IS NOT DISTINCT FROM graph.lifecycle_status
584
+ AND history.storage_digest = public.graph_foundation_storage_digest(graph.payload)
585
+ );
586
+
587
+ DROP TRIGGER IF EXISTS graph_entities_foundation_history_capture
588
+ ON public.graph_entities;
589
+ CREATE TRIGGER graph_entities_foundation_history_capture
590
+ AFTER INSERT OR UPDATE ON public.graph_entities
591
+ FOR EACH ROW EXECUTE FUNCTION public.capture_graph_foundation_revision();
592
+
593
+ DROP POLICY IF EXISTS graph_foundation_revisions_current_graph_read
594
+ ON public.graph_foundation_revisions;
595
+ CREATE POLICY graph_foundation_revisions_current_graph_read
596
+ ON public.graph_foundation_revisions
597
+ FOR SELECT
598
+ USING (
599
+ storage_digest = public.graph_foundation_storage_digest(payload)
600
+ AND EXISTS (
601
+ SELECT 1
602
+ FROM public.graph_entities AS current_graph
603
+ WHERE current_graph.id = graph_foundation_revisions.entity_id
604
+ AND current_graph.entity_type = graph_foundation_revisions.entity_type
605
+ AND current_graph.lifecycle_status = 'active'
606
+ )
607
+ );
608
+
609
+ /*
610
+ * brainbase_app is the canonical host runtime role. Keep this conditional so
611
+ * local/OSS installs that use another role do not fail during migration; those
612
+ * deployments must grant SELECT explicitly alongside their graph_entities
613
+ * reader grant. This is intentionally SELECT-only: capture runs through the
614
+ * SECURITY DEFINER trigger and runtime roles never receive history writes.
615
+ */
616
+ DO $graph_history_runtime_grant$
617
+ BEGIN
618
+ IF EXISTS (SELECT 1 FROM pg_catalog.pg_roles WHERE rolname = 'brainbase_app') THEN
619
+ REVOKE ALL ON TABLE public.graph_foundation_revisions FROM brainbase_app;
620
+ GRANT SELECT ON TABLE public.graph_foundation_revisions TO brainbase_app;
621
+ REVOKE ALL ON FUNCTION public.append_graph_foundation_revision(
622
+ text, text, text, jsonb, text, text, text, text, boolean
623
+ ) FROM brainbase_app;
624
+ END IF;
625
+ END;
626
+ $graph_history_runtime_grant$;
627
+
628
+ /*
629
+ * Stable host readback bindings:
630
+ * graph_entities_foundation_history_capture (AFTER INSERT OR UPDATE)
631
+ * graph_foundation_revisions_guard (BEFORE INSERT OR UPDATE OR DELETE)
632
+ * graph_foundation_revisions_truncate_guard (BEFORE TRUNCATE)
633
+ * graph_foundation_storage_digest(jsonb)
634
+ */
@@ -25,8 +25,8 @@
25
25
  },
26
26
  {
27
27
  "path": "README.md",
28
- "sha256": "eec54fc479eee1deba5c0abbfb3d733596229c87a2c4bacfc446b52e3c0140ed"
28
+ "sha256": "bb90217590722381e3df17950e69e27e72aad25372f54a9a0767cc5c6d14c7d5"
29
29
  }
30
30
  ],
31
- "digest": "f4b1b5eb55f71eaa481666c5f9503959a6c3ccf6fa8e7d28536653e8e1ab2f38"
31
+ "digest": "8c7b66383e9bb30525c3a9a8e7f37c120209c17b1673ebe2a2a6769b076e94be"
32
32
  }