2293 lines
89 KiB
PL/PgSQL
2293 lines
89 KiB
PL/PgSQL
-- Minimum additive LivingIP V3 epistemic contract for the canonical KB tables.
|
|
--
|
|
-- This is a compatibility migration, not a production rollout. Existing rows
|
|
-- remain in the visible legacy tier. Only rows explicitly linked to an accepted
|
|
-- proposal are required to satisfy the modern provenance contract.
|
|
--
|
|
-- CUTOVER ORDER: deploy the cutover-aware writer, stop the default V2 worker,
|
|
-- ALTER ROLE kb_apply NOLOGIN, and end every kb_apply session before running this
|
|
-- migration. Approved historical V2 proposals remain review-visible, but the V3
|
|
-- contract blocks their execution until each packet is migrated or rejected.
|
|
|
|
\set ON_ERROR_STOP on
|
|
set timezone = 'UTC';
|
|
|
|
begin;
|
|
|
|
-- Stable bigint key: controlled V3 function/trigger DDL is exclusive while
|
|
-- authorized applies hold the shared transaction form through commit/rollback.
|
|
select pg_catalog.pg_advisory_xact_lock(6072343533146485505);
|
|
|
|
-- The worker mirrors this declaration, and tests require byte-equivalent
|
|
-- catalog predicates in preflight/postflight. SHA-256 pins every guard body.
|
|
do $trigger_contract_declaration$
|
|
begin
|
|
perform pg_catalog.set_config(
|
|
'teleo.v3_expected_trigger_contract',
|
|
$trigger_contract$
|
|
[
|
|
{
|
|
"argument_count": 0,
|
|
"argument_hex": "",
|
|
"function_config": [
|
|
"search_path=pg_catalog, pg_temp"
|
|
],
|
|
"function_identity_arguments": "",
|
|
"function_name": "teleo_v3_enforce_apply_contract_cutover",
|
|
"function_owner": "kb_gate_owner",
|
|
"function_owner_only_execute": true,
|
|
"function_schema": "kb_stage",
|
|
"function_security_definer": true,
|
|
"function_source_sha256": "3ab2d5e1d5d1d18b13579fe2c0cae487036c7b147cf7f1b6b9a3633274756c18",
|
|
"schema_name": "kb_stage",
|
|
"table_name": "kb_proposals",
|
|
"trigger_name": "teleo_v3_00_enforce_apply_contract_cutover",
|
|
"trigger_type": 23,
|
|
"update_columns": [
|
|
"payload",
|
|
"proposal_type",
|
|
"status"
|
|
],
|
|
"when_expression": null
|
|
},
|
|
{
|
|
"argument_count": 2,
|
|
"argument_hex": "61636365707465645f62795f70726f706f73616c5f69640061646d697373696f6e5f737461746500",
|
|
"function_config": [
|
|
"search_path=pg_catalog, pg_temp"
|
|
],
|
|
"function_identity_arguments": "",
|
|
"function_name": "teleo_v3_require_linked_insert",
|
|
"function_owner": "kb_gate_owner",
|
|
"function_owner_only_execute": null,
|
|
"function_schema": "kb_stage",
|
|
"function_security_definer": false,
|
|
"function_source_sha256": "d8c190bd2fc90159b9a4824015c10a73a701a263bba90b67f8ccce75e454e6f8",
|
|
"schema_name": "public",
|
|
"table_name": "claims",
|
|
"trigger_name": "teleo_v3_00_claim_require_linked_insert",
|
|
"trigger_type": 7,
|
|
"update_columns": [],
|
|
"when_expression": null
|
|
},
|
|
{
|
|
"argument_count": 1,
|
|
"argument_hex": "61636365707465645f62795f70726f706f73616c5f696400",
|
|
"function_config": [
|
|
"search_path=pg_catalog, pg_temp"
|
|
],
|
|
"function_identity_arguments": "",
|
|
"function_name": "teleo_v3_require_accepted_proposal",
|
|
"function_owner": "kb_gate_owner",
|
|
"function_owner_only_execute": true,
|
|
"function_schema": "kb_stage",
|
|
"function_security_definer": true,
|
|
"function_source_sha256": "ca398ef2f185488c0712bd5ebc22402ab9e41e5dfd2f5823b367e6a382e8a64f",
|
|
"schema_name": "public",
|
|
"table_name": "claims",
|
|
"trigger_name": "teleo_v3_claim_require_accepted_proposal",
|
|
"trigger_type": 23,
|
|
"update_columns": [
|
|
"accepted_by_proposal_id"
|
|
],
|
|
"when_expression": null
|
|
},
|
|
{
|
|
"argument_count": 1,
|
|
"argument_hex": "61636365707465645f62795f70726f706f73616c5f696400",
|
|
"function_config": [
|
|
"search_path=pg_catalog, pg_temp"
|
|
],
|
|
"function_identity_arguments": "",
|
|
"function_name": "teleo_v3_require_linked_insert",
|
|
"function_owner": "kb_gate_owner",
|
|
"function_owner_only_execute": null,
|
|
"function_schema": "kb_stage",
|
|
"function_security_definer": false,
|
|
"function_source_sha256": "d8c190bd2fc90159b9a4824015c10a73a701a263bba90b67f8ccce75e454e6f8",
|
|
"schema_name": "public",
|
|
"table_name": "sources",
|
|
"trigger_name": "teleo_v3_00_source_require_linked_insert",
|
|
"trigger_type": 7,
|
|
"update_columns": [],
|
|
"when_expression": null
|
|
},
|
|
{
|
|
"argument_count": 20,
|
|
"argument_hex": "6964006f776e65725f6167656e745f6964007479706500746578740070726f706f736974696f6e00626f64795f6d61726b646f776e0073636f7065006163636573735f73636f7065007374617475730061646d697373696f6e5f7374617465007265766973696f6e5f6372697465726961007265766973696f6e5f6b696e6400737570657273656465735f696400737570657273656465645f627900636f6e666964656e6365007461677300637265617465645f627900637265617465645f617400757064617465645f61740061636365707465645f62795f70726f706f73616c5f696400",
|
|
"function_config": [
|
|
"search_path=pg_catalog, pg_temp"
|
|
],
|
|
"function_identity_arguments": "",
|
|
"function_name": "teleo_v3_guard_immutable_fields",
|
|
"function_owner": "kb_gate_owner",
|
|
"function_owner_only_execute": null,
|
|
"function_schema": "kb_stage",
|
|
"function_security_definer": false,
|
|
"function_source_sha256": "d243741e552fe8c232440ffa5f853cf261f4b86b6f32d76f375734a2235c2d13",
|
|
"schema_name": "public",
|
|
"table_name": "claims",
|
|
"trigger_name": "teleo_v3_claim_immutable",
|
|
"trigger_type": 19,
|
|
"update_columns": [],
|
|
"when_expression": null
|
|
},
|
|
{
|
|
"argument_count": 0,
|
|
"argument_hex": "",
|
|
"function_config": [
|
|
"search_path=pg_catalog, pg_temp"
|
|
],
|
|
"function_identity_arguments": "",
|
|
"function_name": "teleo_v3_reject_delete",
|
|
"function_owner": "kb_gate_owner",
|
|
"function_owner_only_execute": null,
|
|
"function_schema": "kb_stage",
|
|
"function_security_definer": false,
|
|
"function_source_sha256": "46190723ab4520954858dbd4bb6c9230e68e41c68b5aee3b8c2719e1416de43c",
|
|
"schema_name": "public",
|
|
"table_name": "claims",
|
|
"trigger_name": "teleo_v3_claim_reject_delete",
|
|
"trigger_type": 11,
|
|
"update_columns": [],
|
|
"when_expression": null
|
|
},
|
|
{
|
|
"argument_count": 1,
|
|
"argument_hex": "61636365707465645f66726f6d5f70726f706f73616c5f696400",
|
|
"function_config": [
|
|
"search_path=pg_catalog, pg_temp"
|
|
],
|
|
"function_identity_arguments": "",
|
|
"function_name": "teleo_v3_require_linked_insert",
|
|
"function_owner": "kb_gate_owner",
|
|
"function_owner_only_execute": null,
|
|
"function_schema": "kb_stage",
|
|
"function_security_definer": false,
|
|
"function_source_sha256": "d8c190bd2fc90159b9a4824015c10a73a701a263bba90b67f8ccce75e454e6f8",
|
|
"schema_name": "public",
|
|
"table_name": "claim_evidence",
|
|
"trigger_name": "teleo_v3_00_evidence_require_linked_insert",
|
|
"trigger_type": 7,
|
|
"update_columns": [],
|
|
"when_expression": null
|
|
},
|
|
{
|
|
"argument_count": 1,
|
|
"argument_hex": "61636365707465645f62795f70726f706f73616c5f696400",
|
|
"function_config": [
|
|
"search_path=pg_catalog, pg_temp"
|
|
],
|
|
"function_identity_arguments": "",
|
|
"function_name": "teleo_v3_require_accepted_proposal",
|
|
"function_owner": "kb_gate_owner",
|
|
"function_owner_only_execute": true,
|
|
"function_schema": "kb_stage",
|
|
"function_security_definer": true,
|
|
"function_source_sha256": "ca398ef2f185488c0712bd5ebc22402ab9e41e5dfd2f5823b367e6a382e8a64f",
|
|
"schema_name": "public",
|
|
"table_name": "sources",
|
|
"trigger_name": "teleo_v3_source_require_accepted_proposal",
|
|
"trigger_type": 23,
|
|
"update_columns": [
|
|
"accepted_by_proposal_id"
|
|
],
|
|
"when_expression": null
|
|
},
|
|
{
|
|
"argument_count": 1,
|
|
"argument_hex": "61636365707465645f62795f70726f706f73616c5f696400",
|
|
"function_config": [
|
|
"search_path=pg_catalog, pg_temp"
|
|
],
|
|
"function_identity_arguments": "",
|
|
"function_name": "teleo_v3_require_linked_insert",
|
|
"function_owner": "kb_gate_owner",
|
|
"function_owner_only_execute": null,
|
|
"function_schema": "kb_stage",
|
|
"function_security_definer": false,
|
|
"function_source_sha256": "d8c190bd2fc90159b9a4824015c10a73a701a263bba90b67f8ccce75e454e6f8",
|
|
"schema_name": "public",
|
|
"table_name": "claim_edges",
|
|
"trigger_name": "teleo_v3_00_edge_require_linked_insert",
|
|
"trigger_type": 7,
|
|
"update_columns": [],
|
|
"when_expression": null
|
|
},
|
|
{
|
|
"argument_count": 18,
|
|
"argument_hex": "696400736f757263655f747970650075726c0073746f726167655f70617468006578636572707400686173680063616e6f6e6963616c5f75726c0073746f726167655f75726900636f6e74656e745f68617368006163636573735f73636f706500696e67657374696f6e5f6f726967696e0070726f76656e616e63655f73746174757300736f757263655f636c617373006175746f5f70726f6d6f74696f6e5f706f6c69637900637265617465645f62790063617074757265645f617400637265617465645f61740061636365707465645f62795f70726f706f73616c5f696400",
|
|
"function_config": [
|
|
"search_path=pg_catalog, pg_temp"
|
|
],
|
|
"function_identity_arguments": "",
|
|
"function_name": "teleo_v3_guard_immutable_fields",
|
|
"function_owner": "kb_gate_owner",
|
|
"function_owner_only_execute": null,
|
|
"function_schema": "kb_stage",
|
|
"function_security_definer": false,
|
|
"function_source_sha256": "d243741e552fe8c232440ffa5f853cf261f4b86b6f32d76f375734a2235c2d13",
|
|
"schema_name": "public",
|
|
"table_name": "sources",
|
|
"trigger_name": "teleo_v3_source_immutable",
|
|
"trigger_type": 19,
|
|
"update_columns": [],
|
|
"when_expression": null
|
|
},
|
|
{
|
|
"argument_count": 0,
|
|
"argument_hex": "",
|
|
"function_config": [
|
|
"search_path=pg_catalog, pg_temp"
|
|
],
|
|
"function_identity_arguments": "",
|
|
"function_name": "teleo_v3_reject_delete",
|
|
"function_owner": "kb_gate_owner",
|
|
"function_owner_only_execute": null,
|
|
"function_schema": "kb_stage",
|
|
"function_security_definer": false,
|
|
"function_source_sha256": "46190723ab4520954858dbd4bb6c9230e68e41c68b5aee3b8c2719e1416de43c",
|
|
"schema_name": "public",
|
|
"table_name": "sources",
|
|
"trigger_name": "teleo_v3_source_reject_delete",
|
|
"trigger_type": 11,
|
|
"update_columns": [],
|
|
"when_expression": null
|
|
},
|
|
{
|
|
"argument_count": 1,
|
|
"argument_hex": "61636365707465645f66726f6d5f70726f706f73616c5f696400",
|
|
"function_config": [
|
|
"search_path=pg_catalog, pg_temp"
|
|
],
|
|
"function_identity_arguments": "",
|
|
"function_name": "teleo_v3_require_accepted_proposal",
|
|
"function_owner": "kb_gate_owner",
|
|
"function_owner_only_execute": true,
|
|
"function_schema": "kb_stage",
|
|
"function_security_definer": true,
|
|
"function_source_sha256": "ca398ef2f185488c0712bd5ebc22402ab9e41e5dfd2f5823b367e6a382e8a64f",
|
|
"schema_name": "public",
|
|
"table_name": "claim_evidence",
|
|
"trigger_name": "teleo_v3_evidence_require_accepted_proposal",
|
|
"trigger_type": 23,
|
|
"update_columns": [
|
|
"accepted_from_proposal_id"
|
|
],
|
|
"when_expression": null
|
|
},
|
|
{
|
|
"argument_count": 0,
|
|
"argument_hex": "",
|
|
"function_config": [
|
|
"search_path=pg_catalog, pg_temp"
|
|
],
|
|
"function_identity_arguments": "",
|
|
"function_name": "teleo_v3_validate_claim_evidence",
|
|
"function_owner": "kb_gate_owner",
|
|
"function_owner_only_execute": null,
|
|
"function_schema": "kb_stage",
|
|
"function_security_definer": false,
|
|
"function_source_sha256": "4c697c497699760214aca99f255c215e393319fd4482d4b36efac839280c3733",
|
|
"schema_name": "public",
|
|
"table_name": "claim_evidence",
|
|
"trigger_name": "teleo_v3_evidence_validate_provenance",
|
|
"trigger_type": 23,
|
|
"update_columns": [],
|
|
"when_expression": null
|
|
},
|
|
{
|
|
"argument_count": 13,
|
|
"argument_hex": "696400636c61696d5f696400736f757263655f696400726f6c65007765696768740065786365727074006c6f6361746f725f6a736f6e00736f757263655f636f6e74656e745f6861736800726174696f6e616c6500706f6c617269747900637265617465645f627900637265617465645f61740061636365707465645f66726f6d5f70726f706f73616c5f696400",
|
|
"function_config": [
|
|
"search_path=pg_catalog, pg_temp"
|
|
],
|
|
"function_identity_arguments": "",
|
|
"function_name": "teleo_v3_guard_immutable_fields",
|
|
"function_owner": "kb_gate_owner",
|
|
"function_owner_only_execute": null,
|
|
"function_schema": "kb_stage",
|
|
"function_security_definer": false,
|
|
"function_source_sha256": "d243741e552fe8c232440ffa5f853cf261f4b86b6f32d76f375734a2235c2d13",
|
|
"schema_name": "public",
|
|
"table_name": "claim_evidence",
|
|
"trigger_name": "teleo_v3_evidence_immutable",
|
|
"trigger_type": 19,
|
|
"update_columns": [],
|
|
"when_expression": null
|
|
},
|
|
{
|
|
"argument_count": 0,
|
|
"argument_hex": "",
|
|
"function_config": [
|
|
"search_path=pg_catalog, pg_temp"
|
|
],
|
|
"function_identity_arguments": "",
|
|
"function_name": "teleo_v3_reject_delete",
|
|
"function_owner": "kb_gate_owner",
|
|
"function_owner_only_execute": null,
|
|
"function_schema": "kb_stage",
|
|
"function_security_definer": false,
|
|
"function_source_sha256": "46190723ab4520954858dbd4bb6c9230e68e41c68b5aee3b8c2719e1416de43c",
|
|
"schema_name": "public",
|
|
"table_name": "claim_evidence",
|
|
"trigger_name": "teleo_v3_evidence_reject_delete",
|
|
"trigger_type": 11,
|
|
"update_columns": [],
|
|
"when_expression": null
|
|
},
|
|
{
|
|
"argument_count": 1,
|
|
"argument_hex": "61636365707465645f62795f70726f706f73616c5f696400",
|
|
"function_config": [
|
|
"search_path=pg_catalog, pg_temp"
|
|
],
|
|
"function_identity_arguments": "",
|
|
"function_name": "teleo_v3_require_accepted_proposal",
|
|
"function_owner": "kb_gate_owner",
|
|
"function_owner_only_execute": true,
|
|
"function_schema": "kb_stage",
|
|
"function_security_definer": true,
|
|
"function_source_sha256": "ca398ef2f185488c0712bd5ebc22402ab9e41e5dfd2f5823b367e6a382e8a64f",
|
|
"schema_name": "public",
|
|
"table_name": "claim_edges",
|
|
"trigger_name": "teleo_v3_edge_require_accepted_proposal",
|
|
"trigger_type": 23,
|
|
"update_columns": [
|
|
"accepted_by_proposal_id"
|
|
],
|
|
"when_expression": null
|
|
},
|
|
{
|
|
"argument_count": 0,
|
|
"argument_hex": "",
|
|
"function_config": [
|
|
"search_path=pg_catalog, pg_temp"
|
|
],
|
|
"function_identity_arguments": "",
|
|
"function_name": "teleo_v3_validate_claim_edge",
|
|
"function_owner": "kb_gate_owner",
|
|
"function_owner_only_execute": null,
|
|
"function_schema": "kb_stage",
|
|
"function_security_definer": false,
|
|
"function_source_sha256": "6a7a3e9d1d5ac6e9490abc9b7b1f321bb2e0948aea2671415aad28101f4ee936",
|
|
"schema_name": "public",
|
|
"table_name": "claim_edges",
|
|
"trigger_name": "teleo_v3_edge_validate_endpoints",
|
|
"trigger_type": 23,
|
|
"update_columns": [],
|
|
"when_expression": null
|
|
},
|
|
{
|
|
"argument_count": 11,
|
|
"argument_hex": "69640066726f6d5f636c61696d00746f5f636c61696d00656467655f74797065007765696768740072656c6174696f6e5f7479706500726174696f6e616c650073636f70655f6f725f636f6e646974696f6e7300637265617465645f627900637265617465645f61740061636365707465645f62795f70726f706f73616c5f696400",
|
|
"function_config": [
|
|
"search_path=pg_catalog, pg_temp"
|
|
],
|
|
"function_identity_arguments": "",
|
|
"function_name": "teleo_v3_guard_immutable_fields",
|
|
"function_owner": "kb_gate_owner",
|
|
"function_owner_only_execute": null,
|
|
"function_schema": "kb_stage",
|
|
"function_security_definer": false,
|
|
"function_source_sha256": "d243741e552fe8c232440ffa5f853cf261f4b86b6f32d76f375734a2235c2d13",
|
|
"schema_name": "public",
|
|
"table_name": "claim_edges",
|
|
"trigger_name": "teleo_v3_edge_immutable",
|
|
"trigger_type": 19,
|
|
"update_columns": [],
|
|
"when_expression": null
|
|
},
|
|
{
|
|
"argument_count": 0,
|
|
"argument_hex": "",
|
|
"function_config": [
|
|
"search_path=pg_catalog, pg_temp"
|
|
],
|
|
"function_identity_arguments": "",
|
|
"function_name": "teleo_v3_reject_delete",
|
|
"function_owner": "kb_gate_owner",
|
|
"function_owner_only_execute": null,
|
|
"function_schema": "kb_stage",
|
|
"function_security_definer": false,
|
|
"function_source_sha256": "46190723ab4520954858dbd4bb6c9230e68e41c68b5aee3b8c2719e1416de43c",
|
|
"schema_name": "public",
|
|
"table_name": "claim_edges",
|
|
"trigger_name": "teleo_v3_edge_reject_delete",
|
|
"trigger_type": 11,
|
|
"update_columns": [],
|
|
"when_expression": null
|
|
},
|
|
{
|
|
"argument_count": 1,
|
|
"argument_hex": "61636365707465645f62795f70726f706f73616c5f696400",
|
|
"function_config": [
|
|
"search_path=pg_catalog, pg_temp"
|
|
],
|
|
"function_identity_arguments": "",
|
|
"function_name": "teleo_v3_require_accepted_proposal",
|
|
"function_owner": "kb_gate_owner",
|
|
"function_owner_only_execute": true,
|
|
"function_schema": "kb_stage",
|
|
"function_security_definer": true,
|
|
"function_source_sha256": "ca398ef2f185488c0712bd5ebc22402ab9e41e5dfd2f5823b367e6a382e8a64f",
|
|
"schema_name": "public",
|
|
"table_name": "claim_evidence_assessments",
|
|
"trigger_name": "teleo_v3_assessment_require_accepted_proposal",
|
|
"trigger_type": 23,
|
|
"update_columns": [
|
|
"accepted_by_proposal_id"
|
|
],
|
|
"when_expression": null
|
|
},
|
|
{
|
|
"argument_count": 0,
|
|
"argument_hex": "",
|
|
"function_config": [
|
|
"search_path=pg_catalog, pg_temp"
|
|
],
|
|
"function_identity_arguments": "",
|
|
"function_name": "teleo_v3_validate_evidence_assessment",
|
|
"function_owner": "kb_gate_owner",
|
|
"function_owner_only_execute": null,
|
|
"function_schema": "kb_stage",
|
|
"function_security_definer": false,
|
|
"function_source_sha256": "860f939b4878abe8e2e604e03be9eeb6c23b4878af4b19f9ec42de4b821fac61",
|
|
"schema_name": "public",
|
|
"table_name": "claim_evidence_assessments",
|
|
"trigger_name": "teleo_v3_assessment_validate",
|
|
"trigger_type": 23,
|
|
"update_columns": [],
|
|
"when_expression": null
|
|
},
|
|
{
|
|
"argument_count": 10,
|
|
"argument_hex": "696400636c61696d5f696400737570706f72745f74696572006368616c6c656e67655f74696572006f766572616c6c5f737461746500726174696f6e616c650065766964656e63655f7365745f686173680061636365707465645f62795f70726f706f73616c5f696400737570657273656465735f6173736573736d656e745f696400637265617465645f617400",
|
|
"function_config": [
|
|
"search_path=pg_catalog, pg_temp"
|
|
],
|
|
"function_identity_arguments": "",
|
|
"function_name": "teleo_v3_guard_immutable_fields",
|
|
"function_owner": "kb_gate_owner",
|
|
"function_owner_only_execute": null,
|
|
"function_schema": "kb_stage",
|
|
"function_security_definer": false,
|
|
"function_source_sha256": "d243741e552fe8c232440ffa5f853cf261f4b86b6f32d76f375734a2235c2d13",
|
|
"schema_name": "public",
|
|
"table_name": "claim_evidence_assessments",
|
|
"trigger_name": "teleo_v3_assessment_immutable",
|
|
"trigger_type": 19,
|
|
"update_columns": [],
|
|
"when_expression": null
|
|
},
|
|
{
|
|
"argument_count": 0,
|
|
"argument_hex": "",
|
|
"function_config": [
|
|
"search_path=pg_catalog, pg_temp"
|
|
],
|
|
"function_identity_arguments": "",
|
|
"function_name": "teleo_v3_reject_delete",
|
|
"function_owner": "kb_gate_owner",
|
|
"function_owner_only_execute": null,
|
|
"function_schema": "kb_stage",
|
|
"function_security_definer": false,
|
|
"function_source_sha256": "46190723ab4520954858dbd4bb6c9230e68e41c68b5aee3b8c2719e1416de43c",
|
|
"schema_name": "public",
|
|
"table_name": "claim_evidence_assessments",
|
|
"trigger_name": "teleo_v3_assessment_reject_delete",
|
|
"trigger_type": 11,
|
|
"update_columns": [],
|
|
"when_expression": null
|
|
},
|
|
{
|
|
"argument_count": 0,
|
|
"argument_hex": "",
|
|
"function_config": [
|
|
"search_path=pg_catalog, pg_temp"
|
|
],
|
|
"function_identity_arguments": "",
|
|
"function_name": "teleo_v3_guard_linked_proposal_decision",
|
|
"function_owner": "kb_gate_owner",
|
|
"function_owner_only_execute": null,
|
|
"function_schema": "kb_stage",
|
|
"function_security_definer": false,
|
|
"function_source_sha256": "602ee8f81f4515b0237f09a503ecbfe4fce2dc4a799e13f833554103b0e83e32",
|
|
"schema_name": "kb_stage",
|
|
"table_name": "kb_proposals",
|
|
"trigger_name": "teleo_v3_guard_linked_proposal_decision",
|
|
"trigger_type": 19,
|
|
"update_columns": [
|
|
"applied_at",
|
|
"applied_by_agent_id",
|
|
"applied_by_handle",
|
|
"payload",
|
|
"proposal_type",
|
|
"rationale",
|
|
"review_note",
|
|
"reviewed_at",
|
|
"reviewed_by_agent_id",
|
|
"reviewed_by_handle",
|
|
"status"
|
|
],
|
|
"when_expression": null
|
|
}
|
|
]
|
|
$trigger_contract$,
|
|
true
|
|
);
|
|
end
|
|
$trigger_contract_declaration$;
|
|
|
|
|
|
do $preflight$
|
|
declare
|
|
v_contract_state text;
|
|
v_apply_can_login boolean;
|
|
v_legacy_queue_count bigint;
|
|
v_preexisting_objects text;
|
|
v_ownership_valid boolean;
|
|
begin
|
|
if to_regclass('public.agents') is null then
|
|
raise exception 'teleo_v3_epistemic_contract: missing public.agents';
|
|
end if;
|
|
if to_regclass('public.claims') is null then
|
|
raise exception 'teleo_v3_epistemic_contract: missing public.claims';
|
|
end if;
|
|
if to_regclass('public.sources') is null then
|
|
raise exception 'teleo_v3_epistemic_contract: missing public.sources';
|
|
end if;
|
|
if to_regclass('public.claim_evidence') is null then
|
|
raise exception 'teleo_v3_epistemic_contract: missing public.claim_evidence';
|
|
end if;
|
|
if to_regclass('public.claim_edges') is null then
|
|
raise exception 'teleo_v3_epistemic_contract: missing public.claim_edges';
|
|
end if;
|
|
if to_regclass('kb_stage.kb_proposals') is null then
|
|
raise exception 'teleo_v3_epistemic_contract: missing kb_stage.kb_proposals';
|
|
end if;
|
|
if not exists (
|
|
select 1 from pg_catalog.pg_roles role
|
|
where role.rolname = 'kb_gate_owner'
|
|
and not role.rolcanlogin
|
|
and not role.rolinherit
|
|
and not role.rolsuper
|
|
and not role.rolcreatedb
|
|
and not role.rolcreaterole
|
|
and not role.rolreplication
|
|
and not role.rolbypassrls
|
|
) then
|
|
raise exception using
|
|
errcode = '55000',
|
|
message = 'teleo_v3_epistemic_contract: kb_gate_owner must be NOLOGIN NOINHERIT and unprivileged';
|
|
end if;
|
|
if not exists (select 1 from pg_catalog.pg_roles where rolname = 'kb_apply') then
|
|
raise exception 'teleo_v3_epistemic_contract: missing runtime role kb_apply';
|
|
end if;
|
|
if not exists (select 1 from pg_catalog.pg_roles where rolname = 'kb_review') then
|
|
raise exception 'teleo_v3_epistemic_contract: missing runtime role kb_review';
|
|
end if;
|
|
if exists (
|
|
select 1
|
|
from pg_catalog.pg_auth_members membership
|
|
join pg_catalog.pg_roles member_role on member_role.oid = membership.member
|
|
join pg_catalog.pg_roles granted_role on granted_role.oid = membership.roleid
|
|
where member_role.rolname in ('kb_gate_owner', 'kb_review', 'kb_apply')
|
|
or granted_role.rolname in ('kb_gate_owner', 'kb_review', 'kb_apply')
|
|
) then
|
|
raise exception using
|
|
errcode = '55000',
|
|
message = 'teleo_v3_epistemic_contract: protected role membership drift must be removed';
|
|
end if;
|
|
if not pg_catalog.has_schema_privilege('kb_gate_owner', 'kb_stage', 'USAGE')
|
|
or not pg_catalog.has_table_privilege('kb_gate_owner', 'kb_stage.kb_proposals', 'SELECT')
|
|
or not pg_catalog.has_table_privilege('kb_gate_owner', 'kb_stage.kb_proposals', 'UPDATE') then
|
|
raise exception 'teleo_v3_epistemic_contract: kb_gate_owner lacks narrow proposal-ledger lock access';
|
|
end if;
|
|
|
|
-- TELEO_V3_CONTRACT_STATE_QUERY_BEGIN
|
|
with expected_triggers as (
|
|
select expected.*
|
|
from pg_catalog.jsonb_to_recordset(pg_catalog.current_setting('teleo.v3_expected_trigger_contract')::jsonb) as expected(
|
|
schema_name text,
|
|
table_name text,
|
|
trigger_name text,
|
|
trigger_type integer,
|
|
update_columns jsonb,
|
|
when_expression text,
|
|
argument_count integer,
|
|
argument_hex text,
|
|
function_schema text,
|
|
function_name text,
|
|
function_identity_arguments text,
|
|
function_owner text,
|
|
function_security_definer boolean,
|
|
function_config jsonb,
|
|
function_source_sha256 text,
|
|
function_owner_only_execute boolean
|
|
)
|
|
), expected_functions as (
|
|
select distinct function_schema, function_name, function_identity_arguments,
|
|
function_owner, function_security_definer, function_config,
|
|
function_source_sha256, function_owner_only_execute
|
|
from expected_triggers
|
|
), actual_v3_functions as (
|
|
select procedure.oid, namespace.nspname as function_schema,
|
|
procedure.proname as function_name,
|
|
pg_catalog.pg_get_function_identity_arguments(procedure.oid) as function_identity_arguments,
|
|
owner_role.rolname as function_owner,
|
|
procedure.prosecdef as function_security_definer,
|
|
pg_catalog.to_jsonb(procedure.proconfig) as function_config,
|
|
pg_catalog.encode(
|
|
pg_catalog.sha256(pg_catalog.convert_to(procedure.prosrc, 'UTF8')),
|
|
'hex'
|
|
) as function_source_sha256,
|
|
not exists (
|
|
select 1
|
|
from pg_catalog.aclexplode(
|
|
coalesce(procedure.proacl, pg_catalog.acldefault('f', procedure.proowner))
|
|
) acl
|
|
where acl.privilege_type = 'EXECUTE'
|
|
and acl.grantee <> procedure.proowner
|
|
) as function_owner_only_execute,
|
|
language.lanname as function_language,
|
|
procedure.prokind as function_kind,
|
|
procedure.pronargs as function_argument_count,
|
|
procedure.prorettype
|
|
from pg_catalog.pg_proc procedure
|
|
join pg_catalog.pg_namespace namespace on namespace.oid = procedure.pronamespace
|
|
join pg_catalog.pg_roles owner_role on owner_role.oid = procedure.proowner
|
|
join pg_catalog.pg_language language on language.oid = procedure.prolang
|
|
where namespace.nspname = 'kb_stage'
|
|
and pg_catalog.left(procedure.proname, 9) = E'teleo_v3_'
|
|
and procedure.proname <> E'teleo_v3_acquire_protected_relation_locks'
|
|
), actual_v3_catalog_lock_functions as (
|
|
select procedure.oid,
|
|
owner_role.rolname as function_owner,
|
|
procedure.prosecdef as function_security_definer,
|
|
pg_catalog.to_jsonb(procedure.proconfig) as function_config,
|
|
pg_catalog.encode(
|
|
pg_catalog.sha256(pg_catalog.convert_to(procedure.prosrc, 'UTF8')),
|
|
'hex'
|
|
) as function_source_sha256,
|
|
language.lanname as function_language,
|
|
procedure.prokind as function_kind,
|
|
procedure.pronargs as function_argument_count,
|
|
pg_catalog.pg_get_function_identity_arguments(procedure.oid) as function_identity_arguments,
|
|
procedure.prorettype,
|
|
exists (
|
|
select 1
|
|
from pg_catalog.aclexplode(
|
|
coalesce(procedure.proacl, pg_catalog.acldefault('f', procedure.proowner))
|
|
) acl
|
|
join pg_catalog.pg_roles grantee on grantee.oid = acl.grantee
|
|
where acl.privilege_type = 'EXECUTE'
|
|
and grantee.rolname = 'kb_apply'
|
|
) as apply_execute,
|
|
not exists (
|
|
select 1
|
|
from pg_catalog.aclexplode(
|
|
coalesce(procedure.proacl, pg_catalog.acldefault('f', procedure.proowner))
|
|
) acl
|
|
where acl.privilege_type = 'EXECUTE'
|
|
and acl.grantee <> procedure.proowner
|
|
and acl.grantee <> (
|
|
select role.oid from pg_catalog.pg_roles role where role.rolname = 'kb_apply'
|
|
)
|
|
) as execute_acl_exact
|
|
from pg_catalog.pg_proc procedure
|
|
join pg_catalog.pg_namespace namespace on namespace.oid = procedure.pronamespace
|
|
join pg_catalog.pg_roles owner_role on owner_role.oid = procedure.proowner
|
|
join pg_catalog.pg_language language on language.oid = procedure.prolang
|
|
where namespace.nspname = 'kb_stage'
|
|
and procedure.proname = E'teleo_v3_acquire_protected_relation_locks'
|
|
), actual_v3_triggers as (
|
|
select trigger_row.*, table_namespace.nspname as schema_name,
|
|
relation.relname as table_name,
|
|
function_namespace.nspname as function_schema,
|
|
procedure.proname as function_name,
|
|
pg_catalog.pg_get_function_identity_arguments(procedure.oid) as function_identity_arguments,
|
|
coalesce((
|
|
select pg_catalog.jsonb_agg(attribute.attname order by attribute.attname)
|
|
from pg_catalog.unnest(trigger_row.tgattr::smallint[]) update_column(attnum)
|
|
join pg_catalog.pg_attribute attribute
|
|
on attribute.attrelid = trigger_row.tgrelid
|
|
and attribute.attnum = update_column.attnum
|
|
and not attribute.attisdropped
|
|
), '[]'::jsonb) as update_columns,
|
|
pg_catalog.pg_get_expr(trigger_row.tgqual, trigger_row.tgrelid, true) as when_expression,
|
|
pg_catalog.encode(trigger_row.tgargs, 'hex') as argument_hex
|
|
from pg_catalog.pg_trigger trigger_row
|
|
join pg_catalog.pg_class relation on relation.oid = trigger_row.tgrelid
|
|
join pg_catalog.pg_namespace table_namespace on table_namespace.oid = relation.relnamespace
|
|
join pg_catalog.pg_proc procedure on procedure.oid = trigger_row.tgfoid
|
|
join pg_catalog.pg_namespace function_namespace on function_namespace.oid = procedure.pronamespace
|
|
where pg_catalog.left(trigger_row.tgname, 9) = E'teleo_v3_'
|
|
), function_contract as (
|
|
select
|
|
(select pg_catalog.count(*) from actual_v3_functions) =
|
|
(select pg_catalog.count(*) from expected_functions)
|
|
and not exists (
|
|
select 1
|
|
from expected_functions expected
|
|
where not exists (
|
|
select 1
|
|
from actual_v3_functions actual
|
|
where actual.function_schema = expected.function_schema
|
|
and actual.function_name = expected.function_name
|
|
and actual.function_identity_arguments = expected.function_identity_arguments
|
|
and actual.function_owner = expected.function_owner
|
|
and actual.function_security_definer = expected.function_security_definer
|
|
and actual.function_config = expected.function_config
|
|
and actual.function_source_sha256 = expected.function_source_sha256
|
|
and (
|
|
expected.function_owner_only_execute is null
|
|
or actual.function_owner_only_execute = expected.function_owner_only_execute
|
|
)
|
|
and actual.function_language = 'plpgsql'
|
|
and actual.function_kind = 'f'
|
|
and actual.function_argument_count = 0
|
|
and actual.prorettype = 'pg_catalog.trigger'::pg_catalog.regtype
|
|
)
|
|
) as valid
|
|
), catalog_lock_function_contract as (
|
|
select pg_catalog.count(*) = 1
|
|
and pg_catalog.bool_and(
|
|
function_owner = E'kb_gate_owner'
|
|
and function_security_definer
|
|
and function_config = '["search_path=pg_catalog, pg_temp"]'::jsonb
|
|
and function_source_sha256 = E'897345944ba0f1b2a5c01b098fb17de6f881a122e906277086fa7889e76219e8'
|
|
and function_language = 'plpgsql'
|
|
and function_kind = 'f'
|
|
and function_argument_count = 0
|
|
and function_identity_arguments = ''
|
|
and prorettype = 'pg_catalog.void'::pg_catalog.regtype
|
|
and apply_execute
|
|
and execute_acl_exact
|
|
) as valid
|
|
from actual_v3_catalog_lock_functions
|
|
), trigger_contract as (
|
|
select
|
|
(select pg_catalog.count(*) from actual_v3_triggers) =
|
|
(select pg_catalog.count(*) from expected_triggers)
|
|
and not exists (
|
|
select 1
|
|
from expected_triggers expected
|
|
where not exists (
|
|
select 1
|
|
from actual_v3_triggers actual
|
|
where actual.schema_name = expected.schema_name
|
|
and actual.table_name = expected.table_name
|
|
and actual.tgname = expected.trigger_name
|
|
and not actual.tgisinternal
|
|
and actual.tgenabled = 'O'
|
|
and actual.tgtype = expected.trigger_type
|
|
and actual.update_columns = expected.update_columns
|
|
and actual.when_expression is not distinct from expected.when_expression
|
|
and actual.tgnargs = expected.argument_count
|
|
and actual.argument_hex = expected.argument_hex
|
|
and actual.tgconstraint = 0::pg_catalog.oid
|
|
and not actual.tgdeferrable
|
|
and not actual.tginitdeferred
|
|
and actual.tgconstrrelid = 0::pg_catalog.oid
|
|
and actual.tgparentid = 0::pg_catalog.oid
|
|
and actual.tgoldtable is null
|
|
and actual.tgnewtable is null
|
|
and actual.function_schema = expected.function_schema
|
|
and actual.function_name = expected.function_name
|
|
and actual.function_identity_arguments = expected.function_identity_arguments
|
|
and exists (
|
|
select 1
|
|
from actual_v3_functions function_row
|
|
where function_row.oid = actual.tgfoid
|
|
and function_row.function_schema = expected.function_schema
|
|
and function_row.function_name = expected.function_name
|
|
and function_row.function_identity_arguments = expected.function_identity_arguments
|
|
)
|
|
)
|
|
) as valid
|
|
), catalog_state as (
|
|
select
|
|
exists (select 1 from actual_v3_functions)
|
|
or exists (select 1 from actual_v3_catalog_lock_functions)
|
|
or exists (select 1 from actual_v3_triggers) as artifacts_present,
|
|
(select valid from function_contract) as functions_valid,
|
|
(select valid from catalog_lock_function_contract) as catalog_lock_function_valid,
|
|
(select valid from trigger_contract) as triggers_valid,
|
|
exists (
|
|
select 1
|
|
from pg_catalog.pg_roles owner_role
|
|
where owner_role.rolname = E'kb_gate_owner'
|
|
and not owner_role.rolcanlogin
|
|
and not owner_role.rolinherit
|
|
and not owner_role.rolsuper
|
|
and not owner_role.rolcreatedb
|
|
and not owner_role.rolcreaterole
|
|
and not owner_role.rolreplication
|
|
and not owner_role.rolbypassrls
|
|
) as owner_role_valid,
|
|
not exists (
|
|
select 1
|
|
from pg_catalog.pg_auth_members membership
|
|
join pg_catalog.pg_roles member_role on member_role.oid = membership.member
|
|
join pg_catalog.pg_roles granted_role on granted_role.oid = membership.roleid
|
|
where member_role.rolname in (E'kb_gate_owner', E'kb_review', E'kb_apply')
|
|
or granted_role.rolname in (E'kb_gate_owner', E'kb_review', E'kb_apply')
|
|
) as memberships_valid
|
|
)
|
|
select case
|
|
when not artifacts_present then 'V2'
|
|
when functions_valid and catalog_lock_function_valid and triggers_valid
|
|
and owner_role_valid and memberships_valid then 'V3'
|
|
else 'INVALID'
|
|
end
|
|
into v_contract_state
|
|
from catalog_state;
|
|
-- TELEO_V3_CONTRACT_STATE_QUERY_END
|
|
|
|
if v_contract_state <> 'V3' then
|
|
select role.rolcanlogin into v_apply_can_login
|
|
from pg_catalog.pg_roles role where role.rolname = 'kb_apply';
|
|
if v_apply_can_login then
|
|
raise exception using
|
|
errcode = '55000',
|
|
message = 'teleo_v3 cutover refused before DDL: stop the V2 worker and ALTER ROLE kb_apply NOLOGIN';
|
|
end if;
|
|
if exists (
|
|
select 1 from pg_catalog.pg_stat_activity activity
|
|
where activity.datid = (select oid from pg_catalog.pg_database where datname = current_database())
|
|
and activity.usename = 'kb_apply'
|
|
and activity.pid <> pg_catalog.pg_backend_pid()
|
|
) then
|
|
raise exception using
|
|
errcode = '55000',
|
|
message = 'teleo_v3 cutover refused before DDL: active kb_apply sessions must exit';
|
|
end if;
|
|
end if;
|
|
|
|
select pg_catalog.count(*) into v_legacy_queue_count
|
|
from kb_stage.kb_proposals proposal
|
|
where proposal.status::text in ('pending_review', 'approved')
|
|
and proposal.proposal_type in ('revise_strategy', 'add_edge', 'attach_evidence', 'approve_claim')
|
|
and not (
|
|
proposal.proposal_type = 'approve_claim'
|
|
and pg_catalog.jsonb_typeof(proposal.payload) = 'object'
|
|
and pg_catalog.jsonb_typeof(proposal.payload->'apply_payload') = 'object'
|
|
and pg_catalog.jsonb_typeof(proposal.payload->'apply_payload'->'contract_version') = 'number'
|
|
and proposal.payload->'apply_payload'->>'contract_version' = '3'
|
|
);
|
|
if v_legacy_queue_count > 0 then
|
|
raise notice 'teleo_v3 cutover retains % pending/approved V2 proposal(s) for explicit migration or rejection',
|
|
v_legacy_queue_count;
|
|
end if;
|
|
|
|
if not exists (
|
|
select 1 from information_schema.columns
|
|
where table_schema = 'public' and table_name = 'claims' and column_name = 'text'
|
|
) then
|
|
raise exception 'teleo_v3_epistemic_contract: public.claims.text compatibility column is required';
|
|
end if;
|
|
if not exists (
|
|
select 1 from information_schema.columns
|
|
where table_schema = 'public' and table_name = 'sources' and column_name = 'hash'
|
|
) then
|
|
raise exception 'teleo_v3_epistemic_contract: public.sources.hash compatibility column is required';
|
|
end if;
|
|
if not exists (
|
|
select 1 from information_schema.columns
|
|
where table_schema = 'public' and table_name = 'claim_edges' and column_name = 'edge_type'
|
|
) then
|
|
raise exception 'teleo_v3_epistemic_contract: public.claim_edges.edge_type compatibility column is required';
|
|
end if;
|
|
|
|
if v_contract_state = 'V2' then
|
|
with expected_columns(table_name, column_name) as (
|
|
values
|
|
('claims', 'owner_agent_id'),
|
|
('claims', 'proposition'),
|
|
('claims', 'body_markdown'),
|
|
('claims', 'scope'),
|
|
('claims', 'access_scope'),
|
|
('claims', 'admission_state'),
|
|
('claims', 'revision_criteria'),
|
|
('claims', 'revision_kind'),
|
|
('claims', 'supersedes_id'),
|
|
('claims', 'accepted_by_proposal_id'),
|
|
('sources', 'canonical_url'),
|
|
('sources', 'storage_uri'),
|
|
('sources', 'content_hash'),
|
|
('sources', 'access_scope'),
|
|
('sources', 'ingestion_origin'),
|
|
('sources', 'provenance_status'),
|
|
('sources', 'source_class'),
|
|
('sources', 'auto_promotion_policy'),
|
|
('sources', 'accepted_by_proposal_id'),
|
|
('claim_evidence', 'excerpt'),
|
|
('claim_evidence', 'locator_json'),
|
|
('claim_evidence', 'source_content_hash'),
|
|
('claim_evidence', 'rationale'),
|
|
('claim_evidence', 'polarity'),
|
|
('claim_evidence', 'accepted_from_proposal_id'),
|
|
('claim_edges', 'relation_type'),
|
|
('claim_edges', 'rationale'),
|
|
('claim_edges', 'scope_or_conditions'),
|
|
('claim_edges', 'accepted_by_proposal_id')
|
|
), present_objects(object_name) as (
|
|
select pg_catalog.format('%I.%I', columns.table_name, columns.column_name)
|
|
from information_schema.columns columns
|
|
join expected_columns expected
|
|
on expected.table_name = columns.table_name
|
|
and expected.column_name = columns.column_name
|
|
where columns.table_schema = 'public'
|
|
union all
|
|
select 'public.claim_evidence_assessments'
|
|
where pg_catalog.to_regclass('public.claim_evidence_assessments') is not null
|
|
union all
|
|
select 'kb_stage.teleo_v3_installation_ownership'
|
|
where pg_catalog.to_regclass('kb_stage.teleo_v3_installation_ownership') is not null
|
|
)
|
|
select pg_catalog.string_agg(object_name, ', ' order by object_name)
|
|
into v_preexisting_objects
|
|
from present_objects;
|
|
|
|
if v_preexisting_objects is not null then
|
|
raise exception using
|
|
errcode = '55000',
|
|
message = pg_catalog.format(
|
|
'teleo_v3_epistemic_contract: preexisting V3 schema objects lack installation ownership: %s',
|
|
v_preexisting_objects
|
|
);
|
|
end if;
|
|
elsif v_contract_state in ('V3', 'INVALID') then
|
|
if pg_catalog.to_regclass('kb_stage.teleo_v3_installation_ownership') is null then
|
|
if v_contract_state = 'INVALID' then
|
|
raise exception using
|
|
errcode = '55000',
|
|
message = 'teleo_v3_epistemic_contract: partial or altered V3 contract lacks exact installation ownership';
|
|
end if;
|
|
raise exception using
|
|
errcode = '55000',
|
|
message = 'teleo_v3_epistemic_contract: existing V3 contract lacks exact installation ownership';
|
|
end if;
|
|
|
|
select owner_role.rolname = 'kb_gate_owner'
|
|
into v_ownership_valid
|
|
from pg_catalog.pg_class relation
|
|
join pg_catalog.pg_namespace namespace on namespace.oid = relation.relnamespace
|
|
join pg_catalog.pg_roles owner_role on owner_role.oid = relation.relowner
|
|
where namespace.nspname = 'kb_stage'
|
|
and relation.relname = 'teleo_v3_installation_ownership'
|
|
and relation.relkind in ('r', 'p');
|
|
|
|
if not coalesce(v_ownership_valid, false) then
|
|
raise exception using
|
|
errcode = '55000',
|
|
message = 'teleo_v3_epistemic_contract: installation ownership ledger owner is invalid';
|
|
end if;
|
|
|
|
execute $ownership_query$
|
|
select count(*) = 1
|
|
and bool_and(
|
|
contract_name = 'livingip.teleo-v3-epistemic-contract'
|
|
and contract_version = 1
|
|
and installation_token = 'collision-free-install-v1'
|
|
)
|
|
from kb_stage.teleo_v3_installation_ownership
|
|
$ownership_query$ into v_ownership_valid;
|
|
|
|
if not coalesce(v_ownership_valid, false) then
|
|
raise exception using
|
|
errcode = '55000',
|
|
message = 'teleo_v3_epistemic_contract: installation ownership ledger is not exact';
|
|
end if;
|
|
end if;
|
|
end
|
|
$preflight$;
|
|
|
|
create table if not exists kb_stage.teleo_v3_installation_ownership (
|
|
contract_name text primary key,
|
|
contract_version integer not null,
|
|
installation_token text not null,
|
|
constraint teleo_v3_installation_contract_check check (
|
|
contract_name = 'livingip.teleo-v3-epistemic-contract' and contract_version = 1
|
|
),
|
|
constraint teleo_v3_installation_token_check check (
|
|
installation_token = 'collision-free-install-v1'
|
|
)
|
|
);
|
|
alter table kb_stage.teleo_v3_installation_ownership owner to kb_gate_owner;
|
|
revoke all privileges on table kb_stage.teleo_v3_installation_ownership
|
|
from public, kb_apply, kb_review;
|
|
insert into kb_stage.teleo_v3_installation_ownership (
|
|
contract_name, contract_version, installation_token
|
|
) values (
|
|
'livingip.teleo-v3-epistemic-contract',
|
|
1,
|
|
'collision-free-install-v1'
|
|
)
|
|
on conflict (contract_name) do nothing;
|
|
|
|
alter table public.claims
|
|
add column if not exists owner_agent_id uuid,
|
|
add column if not exists proposition text,
|
|
add column if not exists body_markdown text,
|
|
add column if not exists scope text,
|
|
add column if not exists access_scope text not null default 'collective_internal',
|
|
add column if not exists admission_state text not null default 'legacy',
|
|
add column if not exists revision_criteria text,
|
|
add column if not exists revision_kind text,
|
|
add column if not exists supersedes_id uuid,
|
|
add column if not exists accepted_by_proposal_id uuid;
|
|
|
|
alter table public.sources
|
|
add column if not exists canonical_url text,
|
|
add column if not exists storage_uri text,
|
|
add column if not exists content_hash text,
|
|
add column if not exists access_scope text,
|
|
add column if not exists ingestion_origin text,
|
|
add column if not exists provenance_status text,
|
|
add column if not exists source_class text,
|
|
add column if not exists auto_promotion_policy text,
|
|
add column if not exists accepted_by_proposal_id uuid;
|
|
|
|
alter table public.claim_evidence
|
|
add column if not exists excerpt text,
|
|
add column if not exists locator_json jsonb,
|
|
add column if not exists source_content_hash text,
|
|
add column if not exists rationale text,
|
|
add column if not exists polarity text,
|
|
add column if not exists accepted_from_proposal_id uuid;
|
|
|
|
alter table public.claim_edges
|
|
add column if not exists relation_type text,
|
|
add column if not exists rationale text,
|
|
add column if not exists scope_or_conditions text,
|
|
add column if not exists accepted_by_proposal_id uuid;
|
|
|
|
create table if not exists public.claim_evidence_assessments (
|
|
id uuid primary key default gen_random_uuid(),
|
|
claim_id uuid not null references public.claims(id),
|
|
support_tier text,
|
|
challenge_tier text,
|
|
overall_state text not null,
|
|
rationale text not null,
|
|
evidence_set_hash text not null,
|
|
accepted_by_proposal_id uuid not null references kb_stage.kb_proposals(id),
|
|
supersedes_assessment_id uuid references public.claim_evidence_assessments(id),
|
|
created_at timestamptz not null default now(),
|
|
constraint teleo_v3_assessment_support_tier_check check (
|
|
support_tier is null or support_tier in ('none', 'weak', 'moderate', 'strong')
|
|
),
|
|
constraint teleo_v3_assessment_challenge_tier_check check (
|
|
challenge_tier is null or challenge_tier in ('none', 'weak', 'moderate', 'strong')
|
|
),
|
|
constraint teleo_v3_assessment_overall_state_check check (
|
|
overall_state in ('insufficient', 'support_dominant', 'challenge_dominant', 'mixed')
|
|
),
|
|
constraint teleo_v3_assessment_rationale_check check (btrim(rationale) <> ''),
|
|
constraint teleo_v3_assessment_evidence_set_hash_check check (
|
|
evidence_set_hash ~ '^[0-9a-f]{64}$'
|
|
),
|
|
constraint teleo_v3_assessment_not_self_superseding_check check (
|
|
supersedes_assessment_id is null or supersedes_assessment_id <> id
|
|
)
|
|
);
|
|
|
|
do $constraints$
|
|
begin
|
|
if not exists (
|
|
select 1 from pg_constraint
|
|
where conrelid = 'public.claims'::regclass
|
|
and conname = 'teleo_v3_claim_owner_agent_fkey'
|
|
) then
|
|
alter table public.claims
|
|
add constraint teleo_v3_claim_owner_agent_fkey
|
|
foreign key (owner_agent_id) references public.agents(id);
|
|
end if;
|
|
|
|
if not exists (
|
|
select 1 from pg_constraint
|
|
where conrelid = 'public.claims'::regclass
|
|
and conname = 'teleo_v3_claim_supersedes_fkey'
|
|
) then
|
|
alter table public.claims
|
|
add constraint teleo_v3_claim_supersedes_fkey
|
|
foreign key (supersedes_id) references public.claims(id);
|
|
end if;
|
|
|
|
if not exists (
|
|
select 1 from pg_constraint
|
|
where conrelid = 'public.claims'::regclass
|
|
and conname = 'teleo_v3_claim_accepted_proposal_fkey'
|
|
) then
|
|
alter table public.claims
|
|
add constraint teleo_v3_claim_accepted_proposal_fkey
|
|
foreign key (accepted_by_proposal_id) references kb_stage.kb_proposals(id);
|
|
end if;
|
|
|
|
if not exists (
|
|
select 1 from pg_constraint
|
|
where conrelid = 'public.claims'::regclass
|
|
and conname = 'teleo_v3_claim_admission_check'
|
|
) then
|
|
alter table public.claims
|
|
add constraint teleo_v3_claim_admission_check check (
|
|
admission_state = 'legacy'
|
|
or (
|
|
admission_state = 'admitted'
|
|
and type in ('empirical', 'predictive', 'causal', 'normative', 'conceptual')
|
|
and proposition is not null and btrim(proposition) <> ''
|
|
and text = proposition
|
|
and body_markdown is not null and btrim(body_markdown) <> ''
|
|
and scope is not null and btrim(scope) <> ''
|
|
and access_scope in ('agent_private', 'collective_internal', 'public')
|
|
and (access_scope <> 'agent_private' or owner_agent_id is not null)
|
|
and revision_criteria is not null and btrim(revision_criteria) <> ''
|
|
and revision_kind is not null
|
|
and revision_kind in ('original', 'editorial', 'semantic', 'metadata_correction')
|
|
and status in ('active', 'contested', 'retracted', 'superseded')
|
|
and accepted_by_proposal_id is not null
|
|
and (
|
|
(revision_kind = 'original' and supersedes_id is null)
|
|
or (revision_kind <> 'original' and supersedes_id is not null)
|
|
)
|
|
)
|
|
);
|
|
end if;
|
|
|
|
if not exists (
|
|
select 1 from pg_constraint
|
|
where conrelid = 'public.sources'::regclass
|
|
and conname = 'teleo_v3_source_accepted_proposal_fkey'
|
|
) then
|
|
alter table public.sources
|
|
add constraint teleo_v3_source_accepted_proposal_fkey
|
|
foreign key (accepted_by_proposal_id) references kb_stage.kb_proposals(id);
|
|
end if;
|
|
|
|
if not exists (
|
|
select 1 from pg_constraint
|
|
where conrelid = 'public.sources'::regclass
|
|
and conname = 'teleo_v3_source_contract_check'
|
|
) then
|
|
alter table public.sources
|
|
add constraint teleo_v3_source_contract_check check (
|
|
accepted_by_proposal_id is null
|
|
or (
|
|
content_hash is not null
|
|
and content_hash ~ '^[0-9a-f]{64}$'
|
|
and hash = content_hash
|
|
and (
|
|
(canonical_url is not null and btrim(canonical_url) <> '')
|
|
or (storage_uri is not null and btrim(storage_uri) <> '')
|
|
)
|
|
and access_scope is not null
|
|
and access_scope in ('agent_private', 'collective_internal', 'public')
|
|
and ingestion_origin is not null
|
|
and ingestion_origin in ('operator_upload', 'agent_fetch', 'contributor', 'system_import', 'unknown')
|
|
and provenance_status is not null
|
|
and provenance_status in ('verified', 'unverified', 'disputed')
|
|
and source_class is not null
|
|
and source_class in (
|
|
'primary_record', 'peer_reviewed', 'institutional', 'journalistic', 'self_published', 'unknown'
|
|
)
|
|
and auto_promotion_policy is not null
|
|
and auto_promotion_policy in ('eligible', 'human_required', 'blocked')
|
|
)
|
|
);
|
|
end if;
|
|
|
|
if not exists (
|
|
select 1 from pg_constraint
|
|
where conrelid = 'public.claim_evidence'::regclass
|
|
and conname = 'teleo_v3_evidence_accepted_proposal_fkey'
|
|
) then
|
|
alter table public.claim_evidence
|
|
add constraint teleo_v3_evidence_accepted_proposal_fkey
|
|
foreign key (accepted_from_proposal_id) references kb_stage.kb_proposals(id);
|
|
end if;
|
|
|
|
if not exists (
|
|
select 1 from pg_constraint
|
|
where conrelid = 'public.claim_evidence'::regclass
|
|
and conname = 'teleo_v3_evidence_contract_check'
|
|
) then
|
|
alter table public.claim_evidence
|
|
add constraint teleo_v3_evidence_contract_check check (
|
|
accepted_from_proposal_id is null
|
|
or (
|
|
polarity is not null
|
|
and polarity in ('supports', 'challenges', 'illustrates')
|
|
and (
|
|
(polarity = 'supports' and role::text = 'grounds')
|
|
or (polarity = 'challenges' and role::text = 'contradicts')
|
|
or (polarity = 'illustrates' and role::text = 'illustrates')
|
|
)
|
|
and locator_json is not null
|
|
and jsonb_typeof(locator_json) = 'object'
|
|
and locator_json <> '{}'::jsonb
|
|
and source_content_hash is not null
|
|
and source_content_hash ~ '^[0-9a-f]{64}$'
|
|
and rationale is not null and btrim(rationale) <> ''
|
|
and weight is null
|
|
)
|
|
);
|
|
end if;
|
|
|
|
if not exists (
|
|
select 1 from pg_constraint
|
|
where conrelid = 'public.claim_edges'::regclass
|
|
and conname = 'teleo_v3_edge_accepted_proposal_fkey'
|
|
) then
|
|
alter table public.claim_edges
|
|
add constraint teleo_v3_edge_accepted_proposal_fkey
|
|
foreign key (accepted_by_proposal_id) references kb_stage.kb_proposals(id);
|
|
end if;
|
|
|
|
if not exists (
|
|
select 1 from pg_constraint
|
|
where conrelid = 'public.claim_edges'::regclass
|
|
and conname = 'teleo_v3_edge_contract_check'
|
|
) then
|
|
alter table public.claim_edges
|
|
add constraint teleo_v3_edge_contract_check check (
|
|
accepted_by_proposal_id is null
|
|
or (
|
|
relation_type is not null
|
|
and relation_type in ('supports', 'challenges', 'depends_on', 'contradicts')
|
|
and (
|
|
relation_type = edge_type::text
|
|
or (relation_type = 'depends_on' and edge_type::text = 'requires')
|
|
)
|
|
and rationale is not null and btrim(rationale) <> ''
|
|
and from_claim <> to_claim
|
|
and weight is null
|
|
)
|
|
);
|
|
end if;
|
|
|
|
if not exists (
|
|
select 1 from pg_constraint
|
|
where conrelid = 'kb_stage.kb_proposals'::regclass
|
|
and conname = 'teleo_v3_proposal_apply_receipt_check'
|
|
) then
|
|
alter table kb_stage.kb_proposals
|
|
add constraint teleo_v3_proposal_apply_receipt_check check (
|
|
(
|
|
status::text = 'applied'
|
|
and applied_at is not null
|
|
and (
|
|
applied_by_agent_id is not null
|
|
or nullif(btrim(applied_by_handle), '') is not null
|
|
)
|
|
)
|
|
or (
|
|
status::text <> 'applied'
|
|
and applied_at is null
|
|
and applied_by_agent_id is null
|
|
and nullif(btrim(applied_by_handle), '') is null
|
|
)
|
|
) not valid;
|
|
end if;
|
|
end
|
|
$constraints$;
|
|
|
|
create unique index if not exists teleo_v3_claim_one_admitted_successor_idx
|
|
on public.claims (supersedes_id)
|
|
where admission_state = 'admitted' and supersedes_id is not null;
|
|
|
|
create unique index if not exists teleo_v3_assessment_one_successor_idx
|
|
on public.claim_evidence_assessments (supersedes_assessment_id)
|
|
where supersedes_assessment_id is not null;
|
|
|
|
create index if not exists teleo_v3_evidence_claim_polarity_idx
|
|
on public.claim_evidence (claim_id, polarity)
|
|
where accepted_from_proposal_id is not null;
|
|
|
|
create index if not exists teleo_v3_edge_typed_adjacency_idx
|
|
on public.claim_edges (from_claim, relation_type, to_claim)
|
|
where accepted_by_proposal_id is not null;
|
|
|
|
-- kb_apply intentionally has SELECT-only access to the proposal ledger, so a
|
|
-- fixed SECURITY DEFINER helper takes only that table's transaction-held lock.
|
|
-- The apply takes the remaining relation locks directly under its narrow DML
|
|
-- grants. The exact helper body, owner, search_path, return type, and ACL are
|
|
-- part of the fail-closed catalog predicate above and below.
|
|
create or replace function kb_stage.teleo_v3_acquire_protected_relation_locks()
|
|
returns void
|
|
language plpgsql
|
|
security definer
|
|
set search_path = pg_catalog, pg_temp
|
|
as $function$
|
|
begin
|
|
lock table kb_stage.kb_proposals in row exclusive mode;
|
|
end
|
|
$function$;
|
|
|
|
alter function kb_stage.teleo_v3_acquire_protected_relation_locks() owner to kb_gate_owner;
|
|
revoke all on function kb_stage.teleo_v3_acquire_protected_relation_locks()
|
|
from public, kb_apply, kb_review;
|
|
grant execute on function kb_stage.teleo_v3_acquire_protected_relation_locks()
|
|
to kb_apply;
|
|
|
|
create or replace function kb_stage.teleo_v3_enforce_apply_contract_cutover()
|
|
returns trigger
|
|
language plpgsql
|
|
security definer
|
|
set search_path = pg_catalog, pg_temp
|
|
as $function$
|
|
declare
|
|
v_exact_v3 boolean;
|
|
begin
|
|
v_exact_v3 :=
|
|
new.proposal_type = 'approve_claim'
|
|
and pg_catalog.jsonb_typeof(new.payload) = 'object'
|
|
and pg_catalog.jsonb_typeof(new.payload->'apply_payload') = 'object'
|
|
and pg_catalog.jsonb_typeof(new.payload->'apply_payload'->'contract_version') = 'number'
|
|
and new.payload->'apply_payload'->>'contract_version' = '3';
|
|
|
|
if v_exact_v3 then
|
|
return new;
|
|
end if;
|
|
if tg_op = 'UPDATE'
|
|
and old.status::text in ('pending_review', 'approved')
|
|
and new.status::text in ('rejected', 'canceled')
|
|
and new.proposal_type is not distinct from old.proposal_type
|
|
and new.payload is not distinct from old.payload then
|
|
return new;
|
|
end if;
|
|
if new.status::text in ('pending_review', 'approved', 'applied')
|
|
and new.proposal_type in ('revise_strategy', 'add_edge', 'attach_evidence', 'approve_claim') then
|
|
raise exception using
|
|
errcode = '55000',
|
|
message = pg_catalog.format(
|
|
'teleo_v3 cutover: proposal %s uses a disabled V2 or malformed apply contract; migrate or reject it',
|
|
new.id
|
|
);
|
|
end if;
|
|
return new;
|
|
end
|
|
$function$;
|
|
|
|
alter function kb_stage.teleo_v3_enforce_apply_contract_cutover() owner to kb_gate_owner;
|
|
revoke all on function kb_stage.teleo_v3_enforce_apply_contract_cutover()
|
|
from public, kb_apply, kb_review;
|
|
|
|
create or replace function kb_stage.teleo_v3_require_accepted_proposal()
|
|
returns trigger
|
|
language plpgsql
|
|
security definer
|
|
set search_path = pg_catalog, pg_temp
|
|
as $function$
|
|
declare
|
|
proposal_id uuid;
|
|
proposal_status text;
|
|
proposal_reviewed_by_handle text;
|
|
proposal_reviewed_by_agent_id uuid;
|
|
proposal_reviewed_at timestamptz;
|
|
proposal_review_note text;
|
|
proposal_applied_by_handle text;
|
|
proposal_applied_by_agent_id uuid;
|
|
proposal_applied_at timestamptz;
|
|
begin
|
|
proposal_id := nullif(pg_catalog.to_jsonb(new) ->> tg_argv[0], '')::uuid;
|
|
if proposal_id is null then
|
|
return new;
|
|
end if;
|
|
|
|
select status::text, reviewed_by_handle, reviewed_by_agent_id, reviewed_at, review_note,
|
|
applied_by_handle, applied_by_agent_id, applied_at
|
|
into proposal_status, proposal_reviewed_by_handle, proposal_reviewed_by_agent_id,
|
|
proposal_reviewed_at, proposal_review_note, proposal_applied_by_handle,
|
|
proposal_applied_by_agent_id, proposal_applied_at
|
|
from kb_stage.kb_proposals
|
|
where id = proposal_id
|
|
for share;
|
|
|
|
if not found then
|
|
raise exception using
|
|
errcode = '23503',
|
|
message = pg_catalog.format('teleo_v3: proposal %s does not exist', proposal_id);
|
|
end if;
|
|
if proposal_status not in ('approved', 'accepted', 'applied')
|
|
or proposal_reviewed_at is null
|
|
or (proposal_reviewed_by_agent_id is null and nullif(pg_catalog.btrim(proposal_reviewed_by_handle), '') is null)
|
|
or nullif(pg_catalog.btrim(proposal_review_note), '') is null then
|
|
raise exception using
|
|
errcode = '23514',
|
|
message = pg_catalog.format('teleo_v3: proposal %s is not an accepted reviewed decision', proposal_id);
|
|
end if;
|
|
if proposal_status = 'applied'
|
|
and (
|
|
proposal_applied_at is null
|
|
or (
|
|
proposal_applied_by_agent_id is null
|
|
and nullif(pg_catalog.btrim(proposal_applied_by_handle), '') is null
|
|
)
|
|
) then
|
|
raise exception using
|
|
errcode = '23514',
|
|
message = pg_catalog.format('teleo_v3: applied proposal %s lacks immutable apply metadata', proposal_id);
|
|
end if;
|
|
if proposal_status <> 'applied'
|
|
and (
|
|
proposal_applied_at is not null
|
|
or proposal_applied_by_agent_id is not null
|
|
or nullif(pg_catalog.btrim(proposal_applied_by_handle), '') is not null
|
|
) then
|
|
raise exception using
|
|
errcode = '23514',
|
|
message = pg_catalog.format('teleo_v3: non-applied proposal %s carries invalid apply metadata', proposal_id);
|
|
end if;
|
|
return new;
|
|
end
|
|
$function$;
|
|
|
|
-- The trigger takes FOR SHARE on the proposal ledger so a decision cannot be
|
|
-- downgraded between validation and canonical insertion. Runtime writers do
|
|
-- not receive direct UPDATE/row-lock privilege on that ledger; the fixed,
|
|
-- trigger-only SECURITY DEFINER body is the sole narrow lock path.
|
|
alter function kb_stage.teleo_v3_require_accepted_proposal() owner to kb_gate_owner;
|
|
revoke all on function kb_stage.teleo_v3_require_accepted_proposal()
|
|
from public, kb_apply, kb_review;
|
|
|
|
do $security_definer_invariants$
|
|
declare
|
|
v_drift text;
|
|
begin
|
|
select pg_catalog.format(
|
|
'owner=%I security_definer=%s config=%s acl=%s',
|
|
owner_role.rolname,
|
|
procedure.prosecdef,
|
|
procedure.proconfig,
|
|
procedure.proacl
|
|
)
|
|
into v_drift
|
|
from pg_catalog.pg_proc procedure
|
|
join pg_catalog.pg_namespace namespace on namespace.oid = procedure.pronamespace
|
|
join pg_catalog.pg_roles owner_role on owner_role.oid = procedure.proowner
|
|
where namespace.nspname = 'kb_stage'
|
|
and procedure.proname in (
|
|
'teleo_v3_enforce_apply_contract_cutover',
|
|
'teleo_v3_require_accepted_proposal'
|
|
)
|
|
and (
|
|
owner_role.rolname <> 'kb_gate_owner'
|
|
or not procedure.prosecdef
|
|
or pg_catalog.array_to_string(procedure.proconfig, ',') is distinct from
|
|
'search_path=pg_catalog, pg_temp'
|
|
or exists (
|
|
select 1
|
|
from pg_catalog.aclexplode(
|
|
coalesce(
|
|
procedure.proacl,
|
|
pg_catalog.acldefault('f', procedure.proowner)
|
|
)
|
|
) acl
|
|
where acl.privilege_type = 'EXECUTE'
|
|
and acl.grantee <> procedure.proowner
|
|
)
|
|
);
|
|
if v_drift is not null then
|
|
raise exception 'teleo_v3_epistemic_contract: SECURITY DEFINER trigger invariants failed: %', v_drift;
|
|
end if;
|
|
end
|
|
$security_definer_invariants$;
|
|
|
|
create or replace function kb_stage.teleo_v3_require_linked_insert()
|
|
returns trigger
|
|
language plpgsql
|
|
security invoker
|
|
set search_path = pg_catalog, pg_temp
|
|
as $function$
|
|
declare
|
|
proposal_field text := tg_argv[0];
|
|
admission_field text := case when array_length(tg_argv, 1) > 1 then tg_argv[1] else null end;
|
|
proposal_value text;
|
|
admission_value text;
|
|
begin
|
|
proposal_value := pg_catalog.to_jsonb(new) ->> proposal_field;
|
|
if nullif(pg_catalog.btrim(proposal_value), '') is null then
|
|
raise exception using
|
|
errcode = '23514',
|
|
message = pg_catalog.format(
|
|
'teleo_v3: new %s.%s rows must be proposal-linked through %s',
|
|
tg_table_schema, tg_table_name, proposal_field
|
|
);
|
|
end if;
|
|
if admission_field is not null then
|
|
admission_value := pg_catalog.to_jsonb(new) ->> admission_field;
|
|
if admission_value is distinct from 'admitted' then
|
|
raise exception using
|
|
errcode = '23514',
|
|
message = pg_catalog.format(
|
|
'teleo_v3: new %s.%s rows must use %s=admitted; legacy is reserved for rows present at migration',
|
|
tg_table_schema, tg_table_name, admission_field
|
|
);
|
|
end if;
|
|
end if;
|
|
return new;
|
|
end
|
|
$function$;
|
|
|
|
create or replace function kb_stage.teleo_v3_guard_immutable_fields()
|
|
returns trigger
|
|
language plpgsql
|
|
security invoker
|
|
set search_path = pg_catalog, pg_temp
|
|
as $function$
|
|
declare
|
|
field_name text;
|
|
begin
|
|
if (
|
|
tg_table_schema = 'public'
|
|
and tg_table_name = 'claims'
|
|
and (pg_catalog.to_jsonb(old) ->> 'admission_state') is distinct from 'admitted'
|
|
) or (
|
|
tg_table_schema = 'public'
|
|
and tg_table_name = 'sources'
|
|
and (pg_catalog.to_jsonb(old) ->> 'accepted_by_proposal_id') is null
|
|
) or (
|
|
tg_table_schema = 'public'
|
|
and tg_table_name = 'claim_evidence'
|
|
and (pg_catalog.to_jsonb(old) ->> 'accepted_from_proposal_id') is null
|
|
) or (
|
|
tg_table_schema = 'public'
|
|
and tg_table_name = 'claim_edges'
|
|
and (pg_catalog.to_jsonb(old) ->> 'accepted_by_proposal_id') is null
|
|
) then
|
|
return new;
|
|
end if;
|
|
|
|
foreach field_name in array tg_argv loop
|
|
if (pg_catalog.to_jsonb(new) -> field_name) is distinct from (pg_catalog.to_jsonb(old) -> field_name) then
|
|
raise exception using
|
|
errcode = '55000',
|
|
message = pg_catalog.format(
|
|
'teleo_v3: immutable %s.%s field %s requires a new version', tg_table_schema, tg_table_name, field_name
|
|
);
|
|
end if;
|
|
end loop;
|
|
return new;
|
|
end
|
|
$function$;
|
|
|
|
create or replace function kb_stage.teleo_v3_reject_delete()
|
|
returns trigger
|
|
language plpgsql
|
|
security invoker
|
|
set search_path = pg_catalog, pg_temp
|
|
as $function$
|
|
begin
|
|
if (
|
|
tg_table_schema = 'public'
|
|
and tg_table_name = 'claims'
|
|
and (pg_catalog.to_jsonb(old) ->> 'admission_state') is distinct from 'admitted'
|
|
and (pg_catalog.to_jsonb(old) ->> 'accepted_by_proposal_id') is null
|
|
) or (
|
|
tg_table_schema = 'public'
|
|
and tg_table_name = 'sources'
|
|
and (pg_catalog.to_jsonb(old) ->> 'accepted_by_proposal_id') is null
|
|
) or (
|
|
tg_table_schema = 'public'
|
|
and tg_table_name = 'claim_evidence'
|
|
and (pg_catalog.to_jsonb(old) ->> 'accepted_from_proposal_id') is null
|
|
) or (
|
|
tg_table_schema = 'public'
|
|
and tg_table_name = 'claim_edges'
|
|
and (pg_catalog.to_jsonb(old) ->> 'accepted_by_proposal_id') is null
|
|
) then
|
|
return old;
|
|
end if;
|
|
|
|
raise exception using
|
|
errcode = '55000',
|
|
message = pg_catalog.format(
|
|
'teleo_v3: proposal-linked %s.%s rows are append-only; supersede or retract through a new reviewed version',
|
|
tg_table_schema, tg_table_name
|
|
);
|
|
end
|
|
$function$;
|
|
|
|
create or replace function kb_stage.teleo_v3_validate_claim_evidence()
|
|
returns trigger
|
|
language plpgsql
|
|
security invoker
|
|
set search_path = pg_catalog, pg_temp
|
|
as $function$
|
|
declare
|
|
claim_admission_state text;
|
|
source_hash text;
|
|
source_proposal_id uuid;
|
|
begin
|
|
if new.accepted_from_proposal_id is null then
|
|
return new;
|
|
end if;
|
|
|
|
select admission_state into claim_admission_state
|
|
from public.claims where id = new.claim_id;
|
|
select content_hash, accepted_by_proposal_id into source_hash, source_proposal_id
|
|
from public.sources where id = new.source_id;
|
|
|
|
if claim_admission_state is distinct from 'admitted' then
|
|
raise exception using
|
|
errcode = '23514',
|
|
message = 'teleo_v3: accepted evidence must target an admitted claim version';
|
|
end if;
|
|
if source_proposal_id is null then
|
|
raise exception using
|
|
errcode = '23514',
|
|
message = 'teleo_v3: accepted evidence must reference a proposal-linked source version';
|
|
end if;
|
|
if source_hash is distinct from new.source_content_hash then
|
|
raise exception using
|
|
errcode = '23514',
|
|
message = 'teleo_v3: evidence source_content_hash does not match the immutable source version';
|
|
end if;
|
|
return new;
|
|
end
|
|
$function$;
|
|
|
|
create or replace function kb_stage.teleo_v3_validate_claim_edge()
|
|
returns trigger
|
|
language plpgsql
|
|
security invoker
|
|
set search_path = pg_catalog, pg_temp
|
|
as $function$
|
|
declare
|
|
from_admission_state text;
|
|
to_admission_state text;
|
|
begin
|
|
if new.accepted_by_proposal_id is null then
|
|
return new;
|
|
end if;
|
|
|
|
select admission_state into from_admission_state
|
|
from public.claims where id = new.from_claim;
|
|
select admission_state into to_admission_state
|
|
from public.claims where id = new.to_claim;
|
|
if from_admission_state is distinct from 'admitted' or to_admission_state is distinct from 'admitted' then
|
|
raise exception using
|
|
errcode = '23514',
|
|
message = 'teleo_v3: accepted typed edges require two admitted claim versions';
|
|
end if;
|
|
return new;
|
|
end
|
|
$function$;
|
|
|
|
create or replace function kb_stage.teleo_v3_validate_evidence_assessment()
|
|
returns trigger
|
|
language plpgsql
|
|
security invoker
|
|
set search_path = pg_catalog, pg_temp
|
|
as $function$
|
|
declare
|
|
claim_type text;
|
|
claim_admission_state text;
|
|
begin
|
|
select type, admission_state into claim_type, claim_admission_state
|
|
from public.claims where id = new.claim_id;
|
|
if claim_admission_state is distinct from 'admitted' then
|
|
raise exception using
|
|
errcode = '23514',
|
|
message = 'teleo_v3: evidence assessments require an admitted claim version';
|
|
end if;
|
|
if claim_type in ('empirical', 'predictive', 'causal')
|
|
and (new.support_tier is null or new.challenge_tier is null) then
|
|
raise exception using
|
|
errcode = '23514',
|
|
message = 'teleo_v3: truth-apt claim assessments require support and challenge tiers';
|
|
end if;
|
|
if not exists (
|
|
select 1 from public.claim_evidence
|
|
where claim_id = new.claim_id and accepted_from_proposal_id is not null
|
|
) then
|
|
raise exception using
|
|
errcode = '23514',
|
|
message = 'teleo_v3: evidence assessments require accepted claim-specific evidence';
|
|
end if;
|
|
return new;
|
|
end
|
|
$function$;
|
|
|
|
create or replace function kb_stage.teleo_v3_guard_linked_proposal_decision()
|
|
returns trigger
|
|
language plpgsql
|
|
security invoker
|
|
set search_path = pg_catalog, pg_temp
|
|
as $function$
|
|
declare
|
|
proposal_is_linked boolean;
|
|
valid_apply_transition boolean;
|
|
begin
|
|
select
|
|
exists (select 1 from public.claims where accepted_by_proposal_id = old.id)
|
|
or exists (select 1 from public.sources where accepted_by_proposal_id = old.id)
|
|
or exists (select 1 from public.claim_evidence where accepted_from_proposal_id = old.id)
|
|
or exists (select 1 from public.claim_edges where accepted_by_proposal_id = old.id)
|
|
or exists (select 1 from public.claim_evidence_assessments where accepted_by_proposal_id = old.id)
|
|
into proposal_is_linked;
|
|
|
|
valid_apply_transition :=
|
|
old.status::text in ('approved', 'accepted')
|
|
and new.status::text = 'applied'
|
|
and old.applied_by_handle is null
|
|
and old.applied_by_agent_id is null
|
|
and old.applied_at is null
|
|
and new.applied_at is not null
|
|
and (
|
|
new.applied_by_agent_id is not null
|
|
or nullif(pg_catalog.btrim(new.applied_by_handle), '') is not null
|
|
);
|
|
|
|
if new.status::text = 'applied' and (
|
|
new.applied_at is null
|
|
or (
|
|
new.applied_by_agent_id is null
|
|
and nullif(pg_catalog.btrim(new.applied_by_handle), '') is null
|
|
)
|
|
) then
|
|
raise exception using
|
|
errcode = '55000',
|
|
message = pg_catalog.format('teleo_v3: applied proposal %s requires immutable apply metadata', old.id);
|
|
end if;
|
|
|
|
if new.status::text <> 'applied' and (
|
|
new.applied_at is not null
|
|
or new.applied_by_agent_id is not null
|
|
or nullif(pg_catalog.btrim(new.applied_by_handle), '') is not null
|
|
) then
|
|
raise exception using
|
|
errcode = '55000',
|
|
message = pg_catalog.format('teleo_v3: non-applied proposal %s cannot carry apply metadata', old.id);
|
|
end if;
|
|
|
|
if old.status::text = 'applied' and (
|
|
new.status::text <> 'applied'
|
|
or new.proposal_type is distinct from old.proposal_type
|
|
or new.payload is distinct from old.payload
|
|
or new.rationale is distinct from old.rationale
|
|
or new.reviewed_by_handle is distinct from old.reviewed_by_handle
|
|
or new.reviewed_by_agent_id is distinct from old.reviewed_by_agent_id
|
|
or new.reviewed_at is distinct from old.reviewed_at
|
|
or new.review_note is distinct from old.review_note
|
|
or new.applied_by_handle is distinct from old.applied_by_handle
|
|
or new.applied_by_agent_id is distinct from old.applied_by_agent_id
|
|
or new.applied_at is distinct from old.applied_at
|
|
) then
|
|
raise exception using
|
|
errcode = '55000',
|
|
message = pg_catalog.format('teleo_v3: applied proposal %s is immutable', old.id);
|
|
end if;
|
|
|
|
if (
|
|
new.applied_by_handle is distinct from old.applied_by_handle
|
|
or new.applied_by_agent_id is distinct from old.applied_by_agent_id
|
|
or new.applied_at is distinct from old.applied_at
|
|
) and not valid_apply_transition then
|
|
raise exception using
|
|
errcode = '55000',
|
|
message = pg_catalog.format('teleo_v3: proposal %s apply metadata may only be set during apply', old.id);
|
|
end if;
|
|
|
|
if proposal_is_linked and (
|
|
new.status::text not in ('approved', 'accepted', 'applied')
|
|
or (
|
|
old.status::text in ('approved', 'accepted')
|
|
and new.status::text not in (old.status::text, 'applied')
|
|
)
|
|
or (old.status::text = 'applied' and new.status::text <> 'applied')
|
|
or new.proposal_type is distinct from old.proposal_type
|
|
or new.payload is distinct from old.payload
|
|
or new.rationale is distinct from old.rationale
|
|
or new.reviewed_by_handle is distinct from old.reviewed_by_handle
|
|
or new.reviewed_by_agent_id is distinct from old.reviewed_by_agent_id
|
|
or new.reviewed_at is distinct from old.reviewed_at
|
|
or new.review_note is distinct from old.review_note
|
|
or new.reviewed_at is null
|
|
or (new.reviewed_by_agent_id is null and nullif(pg_catalog.btrim(new.reviewed_by_handle), '') is null)
|
|
or nullif(pg_catalog.btrim(new.review_note), '') is null
|
|
) then
|
|
raise exception using
|
|
errcode = '55000',
|
|
message = pg_catalog.format('teleo_v3: linked accepted proposal %s cannot lose its decision metadata', old.id);
|
|
end if;
|
|
return new;
|
|
end
|
|
$function$;
|
|
|
|
do $trigger_function_owners$
|
|
declare
|
|
v_function record;
|
|
begin
|
|
for v_function in
|
|
select distinct expected.function_schema, expected.function_name,
|
|
expected.function_identity_arguments
|
|
from pg_catalog.jsonb_to_recordset(
|
|
pg_catalog.current_setting('teleo.v3_expected_trigger_contract')::jsonb
|
|
) as expected(
|
|
function_schema text,
|
|
function_name text,
|
|
function_identity_arguments text
|
|
)
|
|
loop
|
|
if v_function.function_identity_arguments <> '' then
|
|
raise exception 'teleo_v3_epistemic_contract: trigger functions must have zero identity arguments';
|
|
end if;
|
|
execute pg_catalog.format(
|
|
'alter function %I.%I() owner to kb_gate_owner',
|
|
v_function.function_schema,
|
|
v_function.function_name
|
|
);
|
|
end loop;
|
|
end
|
|
$trigger_function_owners$;
|
|
|
|
do $triggers$
|
|
declare
|
|
v_trigger record;
|
|
begin
|
|
execute $ddl$
|
|
create or replace trigger teleo_v3_00_enforce_apply_contract_cutover
|
|
before insert or update of proposal_type, payload, status on kb_stage.kb_proposals
|
|
for each row execute function kb_stage.teleo_v3_enforce_apply_contract_cutover()
|
|
$ddl$;
|
|
|
|
execute $ddl$
|
|
create or replace trigger teleo_v3_00_claim_require_linked_insert
|
|
before insert on public.claims
|
|
for each row execute function kb_stage.teleo_v3_require_linked_insert(
|
|
'accepted_by_proposal_id', 'admission_state'
|
|
)
|
|
$ddl$;
|
|
|
|
execute $ddl$
|
|
create or replace trigger teleo_v3_claim_require_accepted_proposal
|
|
before insert or update of accepted_by_proposal_id on public.claims
|
|
for each row execute function kb_stage.teleo_v3_require_accepted_proposal('accepted_by_proposal_id')
|
|
$ddl$;
|
|
|
|
execute $ddl$
|
|
create or replace trigger teleo_v3_00_source_require_linked_insert
|
|
before insert on public.sources
|
|
for each row execute function kb_stage.teleo_v3_require_linked_insert('accepted_by_proposal_id')
|
|
$ddl$;
|
|
|
|
execute $ddl$
|
|
create or replace trigger teleo_v3_claim_immutable
|
|
before update on public.claims
|
|
for each row execute function kb_stage.teleo_v3_guard_immutable_fields(
|
|
'id', 'owner_agent_id', 'type', 'text', 'proposition', 'body_markdown', 'scope', 'access_scope',
|
|
'status', 'admission_state', 'revision_criteria', 'revision_kind', 'supersedes_id', 'superseded_by',
|
|
'confidence', 'tags', 'created_by', 'created_at', 'updated_at', 'accepted_by_proposal_id'
|
|
)
|
|
$ddl$;
|
|
|
|
execute $ddl$
|
|
create or replace trigger teleo_v3_claim_reject_delete
|
|
before delete on public.claims
|
|
for each row execute function kb_stage.teleo_v3_reject_delete()
|
|
$ddl$;
|
|
|
|
execute $ddl$
|
|
create or replace trigger teleo_v3_00_evidence_require_linked_insert
|
|
before insert on public.claim_evidence
|
|
for each row execute function kb_stage.teleo_v3_require_linked_insert('accepted_from_proposal_id')
|
|
$ddl$;
|
|
|
|
execute $ddl$
|
|
create or replace trigger teleo_v3_source_require_accepted_proposal
|
|
before insert or update of accepted_by_proposal_id on public.sources
|
|
for each row execute function kb_stage.teleo_v3_require_accepted_proposal('accepted_by_proposal_id')
|
|
$ddl$;
|
|
|
|
execute $ddl$
|
|
create or replace trigger teleo_v3_00_edge_require_linked_insert
|
|
before insert on public.claim_edges
|
|
for each row execute function kb_stage.teleo_v3_require_linked_insert('accepted_by_proposal_id')
|
|
$ddl$;
|
|
|
|
execute $ddl$
|
|
create or replace trigger teleo_v3_source_immutable
|
|
before update on public.sources
|
|
for each row execute function kb_stage.teleo_v3_guard_immutable_fields(
|
|
'id', 'source_type', 'url', 'storage_path', 'excerpt', 'hash', 'canonical_url', 'storage_uri',
|
|
'content_hash', 'access_scope', 'ingestion_origin', 'provenance_status', 'source_class',
|
|
'auto_promotion_policy', 'created_by', 'captured_at', 'created_at', 'accepted_by_proposal_id'
|
|
)
|
|
$ddl$;
|
|
|
|
execute $ddl$
|
|
create or replace trigger teleo_v3_source_reject_delete
|
|
before delete on public.sources
|
|
for each row execute function kb_stage.teleo_v3_reject_delete()
|
|
$ddl$;
|
|
|
|
execute $ddl$
|
|
create or replace trigger teleo_v3_evidence_require_accepted_proposal
|
|
before insert or update of accepted_from_proposal_id on public.claim_evidence
|
|
for each row execute function kb_stage.teleo_v3_require_accepted_proposal('accepted_from_proposal_id')
|
|
$ddl$;
|
|
|
|
execute $ddl$
|
|
create or replace trigger teleo_v3_evidence_validate_provenance
|
|
before insert or update on public.claim_evidence
|
|
for each row execute function kb_stage.teleo_v3_validate_claim_evidence()
|
|
$ddl$;
|
|
|
|
execute $ddl$
|
|
create or replace trigger teleo_v3_evidence_immutable
|
|
before update on public.claim_evidence
|
|
for each row execute function kb_stage.teleo_v3_guard_immutable_fields(
|
|
'id', 'claim_id', 'source_id', 'role', 'weight', 'excerpt', 'locator_json', 'source_content_hash',
|
|
'rationale', 'polarity', 'created_by', 'created_at', 'accepted_from_proposal_id'
|
|
)
|
|
$ddl$;
|
|
|
|
execute $ddl$
|
|
create or replace trigger teleo_v3_evidence_reject_delete
|
|
before delete on public.claim_evidence
|
|
for each row execute function kb_stage.teleo_v3_reject_delete()
|
|
$ddl$;
|
|
|
|
execute $ddl$
|
|
create or replace trigger teleo_v3_edge_require_accepted_proposal
|
|
before insert or update of accepted_by_proposal_id on public.claim_edges
|
|
for each row execute function kb_stage.teleo_v3_require_accepted_proposal('accepted_by_proposal_id')
|
|
$ddl$;
|
|
|
|
execute $ddl$
|
|
create or replace trigger teleo_v3_edge_validate_endpoints
|
|
before insert or update on public.claim_edges
|
|
for each row execute function kb_stage.teleo_v3_validate_claim_edge()
|
|
$ddl$;
|
|
|
|
execute $ddl$
|
|
create or replace trigger teleo_v3_edge_immutable
|
|
before update on public.claim_edges
|
|
for each row execute function kb_stage.teleo_v3_guard_immutable_fields(
|
|
'id', 'from_claim', 'to_claim', 'edge_type', 'weight', 'relation_type', 'rationale',
|
|
'scope_or_conditions', 'created_by', 'created_at', 'accepted_by_proposal_id'
|
|
)
|
|
$ddl$;
|
|
|
|
execute $ddl$
|
|
create or replace trigger teleo_v3_edge_reject_delete
|
|
before delete on public.claim_edges
|
|
for each row execute function kb_stage.teleo_v3_reject_delete()
|
|
$ddl$;
|
|
|
|
execute $ddl$
|
|
create or replace trigger teleo_v3_assessment_require_accepted_proposal
|
|
before insert or update of accepted_by_proposal_id on public.claim_evidence_assessments
|
|
for each row execute function kb_stage.teleo_v3_require_accepted_proposal('accepted_by_proposal_id')
|
|
$ddl$;
|
|
|
|
execute $ddl$
|
|
create or replace trigger teleo_v3_assessment_validate
|
|
before insert or update on public.claim_evidence_assessments
|
|
for each row execute function kb_stage.teleo_v3_validate_evidence_assessment()
|
|
$ddl$;
|
|
|
|
execute $ddl$
|
|
create or replace trigger teleo_v3_assessment_immutable
|
|
before update on public.claim_evidence_assessments
|
|
for each row execute function kb_stage.teleo_v3_guard_immutable_fields(
|
|
'id', 'claim_id', 'support_tier', 'challenge_tier', 'overall_state', 'rationale',
|
|
'evidence_set_hash', 'accepted_by_proposal_id', 'supersedes_assessment_id', 'created_at'
|
|
)
|
|
$ddl$;
|
|
|
|
execute $ddl$
|
|
create or replace trigger teleo_v3_assessment_reject_delete
|
|
before delete on public.claim_evidence_assessments
|
|
for each row execute function kb_stage.teleo_v3_reject_delete()
|
|
$ddl$;
|
|
|
|
execute $ddl$
|
|
create or replace trigger teleo_v3_guard_linked_proposal_decision
|
|
before update of proposal_type, status, rationale, payload, reviewed_by_handle,
|
|
reviewed_by_agent_id, reviewed_at, review_note, applied_by_handle, applied_by_agent_id, applied_at
|
|
on kb_stage.kb_proposals
|
|
for each row execute function kb_stage.teleo_v3_guard_linked_proposal_decision()
|
|
$ddl$;
|
|
|
|
for v_trigger in
|
|
select expected.schema_name, expected.table_name, expected.trigger_name
|
|
from pg_catalog.jsonb_to_recordset(
|
|
pg_catalog.current_setting('teleo.v3_expected_trigger_contract')::jsonb
|
|
) as expected(schema_name text, table_name text, trigger_name text)
|
|
loop
|
|
execute pg_catalog.format(
|
|
'alter table %I.%I enable trigger %I',
|
|
v_trigger.schema_name,
|
|
v_trigger.table_name,
|
|
v_trigger.trigger_name
|
|
);
|
|
end loop;
|
|
end
|
|
$triggers$;
|
|
|
|
do $contract_postflight$
|
|
declare
|
|
v_contract_state text;
|
|
begin
|
|
-- TELEO_V3_CONTRACT_POSTFLIGHT_QUERY_BEGIN
|
|
with expected_triggers as (
|
|
select expected.*
|
|
from pg_catalog.jsonb_to_recordset(pg_catalog.current_setting('teleo.v3_expected_trigger_contract')::jsonb) as expected(
|
|
schema_name text,
|
|
table_name text,
|
|
trigger_name text,
|
|
trigger_type integer,
|
|
update_columns jsonb,
|
|
when_expression text,
|
|
argument_count integer,
|
|
argument_hex text,
|
|
function_schema text,
|
|
function_name text,
|
|
function_identity_arguments text,
|
|
function_owner text,
|
|
function_security_definer boolean,
|
|
function_config jsonb,
|
|
function_source_sha256 text,
|
|
function_owner_only_execute boolean
|
|
)
|
|
), expected_functions as (
|
|
select distinct function_schema, function_name, function_identity_arguments,
|
|
function_owner, function_security_definer, function_config,
|
|
function_source_sha256, function_owner_only_execute
|
|
from expected_triggers
|
|
), actual_v3_functions as (
|
|
select procedure.oid, namespace.nspname as function_schema,
|
|
procedure.proname as function_name,
|
|
pg_catalog.pg_get_function_identity_arguments(procedure.oid) as function_identity_arguments,
|
|
owner_role.rolname as function_owner,
|
|
procedure.prosecdef as function_security_definer,
|
|
pg_catalog.to_jsonb(procedure.proconfig) as function_config,
|
|
pg_catalog.encode(
|
|
pg_catalog.sha256(pg_catalog.convert_to(procedure.prosrc, 'UTF8')),
|
|
'hex'
|
|
) as function_source_sha256,
|
|
not exists (
|
|
select 1
|
|
from pg_catalog.aclexplode(
|
|
coalesce(procedure.proacl, pg_catalog.acldefault('f', procedure.proowner))
|
|
) acl
|
|
where acl.privilege_type = 'EXECUTE'
|
|
and acl.grantee <> procedure.proowner
|
|
) as function_owner_only_execute,
|
|
language.lanname as function_language,
|
|
procedure.prokind as function_kind,
|
|
procedure.pronargs as function_argument_count,
|
|
procedure.prorettype
|
|
from pg_catalog.pg_proc procedure
|
|
join pg_catalog.pg_namespace namespace on namespace.oid = procedure.pronamespace
|
|
join pg_catalog.pg_roles owner_role on owner_role.oid = procedure.proowner
|
|
join pg_catalog.pg_language language on language.oid = procedure.prolang
|
|
where namespace.nspname = 'kb_stage'
|
|
and pg_catalog.left(procedure.proname, 9) = E'teleo_v3_'
|
|
and procedure.proname <> E'teleo_v3_acquire_protected_relation_locks'
|
|
), actual_v3_catalog_lock_functions as (
|
|
select procedure.oid,
|
|
owner_role.rolname as function_owner,
|
|
procedure.prosecdef as function_security_definer,
|
|
pg_catalog.to_jsonb(procedure.proconfig) as function_config,
|
|
pg_catalog.encode(
|
|
pg_catalog.sha256(pg_catalog.convert_to(procedure.prosrc, 'UTF8')),
|
|
'hex'
|
|
) as function_source_sha256,
|
|
language.lanname as function_language,
|
|
procedure.prokind as function_kind,
|
|
procedure.pronargs as function_argument_count,
|
|
pg_catalog.pg_get_function_identity_arguments(procedure.oid) as function_identity_arguments,
|
|
procedure.prorettype,
|
|
exists (
|
|
select 1
|
|
from pg_catalog.aclexplode(
|
|
coalesce(procedure.proacl, pg_catalog.acldefault('f', procedure.proowner))
|
|
) acl
|
|
join pg_catalog.pg_roles grantee on grantee.oid = acl.grantee
|
|
where acl.privilege_type = 'EXECUTE'
|
|
and grantee.rolname = 'kb_apply'
|
|
) as apply_execute,
|
|
not exists (
|
|
select 1
|
|
from pg_catalog.aclexplode(
|
|
coalesce(procedure.proacl, pg_catalog.acldefault('f', procedure.proowner))
|
|
) acl
|
|
where acl.privilege_type = 'EXECUTE'
|
|
and acl.grantee <> procedure.proowner
|
|
and acl.grantee <> (
|
|
select role.oid from pg_catalog.pg_roles role where role.rolname = 'kb_apply'
|
|
)
|
|
) as execute_acl_exact
|
|
from pg_catalog.pg_proc procedure
|
|
join pg_catalog.pg_namespace namespace on namespace.oid = procedure.pronamespace
|
|
join pg_catalog.pg_roles owner_role on owner_role.oid = procedure.proowner
|
|
join pg_catalog.pg_language language on language.oid = procedure.prolang
|
|
where namespace.nspname = 'kb_stage'
|
|
and procedure.proname = E'teleo_v3_acquire_protected_relation_locks'
|
|
), actual_v3_triggers as (
|
|
select trigger_row.*, table_namespace.nspname as schema_name,
|
|
relation.relname as table_name,
|
|
function_namespace.nspname as function_schema,
|
|
procedure.proname as function_name,
|
|
pg_catalog.pg_get_function_identity_arguments(procedure.oid) as function_identity_arguments,
|
|
coalesce((
|
|
select pg_catalog.jsonb_agg(attribute.attname order by attribute.attname)
|
|
from pg_catalog.unnest(trigger_row.tgattr::smallint[]) update_column(attnum)
|
|
join pg_catalog.pg_attribute attribute
|
|
on attribute.attrelid = trigger_row.tgrelid
|
|
and attribute.attnum = update_column.attnum
|
|
and not attribute.attisdropped
|
|
), '[]'::jsonb) as update_columns,
|
|
pg_catalog.pg_get_expr(trigger_row.tgqual, trigger_row.tgrelid, true) as when_expression,
|
|
pg_catalog.encode(trigger_row.tgargs, 'hex') as argument_hex
|
|
from pg_catalog.pg_trigger trigger_row
|
|
join pg_catalog.pg_class relation on relation.oid = trigger_row.tgrelid
|
|
join pg_catalog.pg_namespace table_namespace on table_namespace.oid = relation.relnamespace
|
|
join pg_catalog.pg_proc procedure on procedure.oid = trigger_row.tgfoid
|
|
join pg_catalog.pg_namespace function_namespace on function_namespace.oid = procedure.pronamespace
|
|
where pg_catalog.left(trigger_row.tgname, 9) = E'teleo_v3_'
|
|
), function_contract as (
|
|
select
|
|
(select pg_catalog.count(*) from actual_v3_functions) =
|
|
(select pg_catalog.count(*) from expected_functions)
|
|
and not exists (
|
|
select 1
|
|
from expected_functions expected
|
|
where not exists (
|
|
select 1
|
|
from actual_v3_functions actual
|
|
where actual.function_schema = expected.function_schema
|
|
and actual.function_name = expected.function_name
|
|
and actual.function_identity_arguments = expected.function_identity_arguments
|
|
and actual.function_owner = expected.function_owner
|
|
and actual.function_security_definer = expected.function_security_definer
|
|
and actual.function_config = expected.function_config
|
|
and actual.function_source_sha256 = expected.function_source_sha256
|
|
and (
|
|
expected.function_owner_only_execute is null
|
|
or actual.function_owner_only_execute = expected.function_owner_only_execute
|
|
)
|
|
and actual.function_language = 'plpgsql'
|
|
and actual.function_kind = 'f'
|
|
and actual.function_argument_count = 0
|
|
and actual.prorettype = 'pg_catalog.trigger'::pg_catalog.regtype
|
|
)
|
|
) as valid
|
|
), catalog_lock_function_contract as (
|
|
select pg_catalog.count(*) = 1
|
|
and pg_catalog.bool_and(
|
|
function_owner = E'kb_gate_owner'
|
|
and function_security_definer
|
|
and function_config = '["search_path=pg_catalog, pg_temp"]'::jsonb
|
|
and function_source_sha256 = E'897345944ba0f1b2a5c01b098fb17de6f881a122e906277086fa7889e76219e8'
|
|
and function_language = 'plpgsql'
|
|
and function_kind = 'f'
|
|
and function_argument_count = 0
|
|
and function_identity_arguments = ''
|
|
and prorettype = 'pg_catalog.void'::pg_catalog.regtype
|
|
and apply_execute
|
|
and execute_acl_exact
|
|
) as valid
|
|
from actual_v3_catalog_lock_functions
|
|
), trigger_contract as (
|
|
select
|
|
(select pg_catalog.count(*) from actual_v3_triggers) =
|
|
(select pg_catalog.count(*) from expected_triggers)
|
|
and not exists (
|
|
select 1
|
|
from expected_triggers expected
|
|
where not exists (
|
|
select 1
|
|
from actual_v3_triggers actual
|
|
where actual.schema_name = expected.schema_name
|
|
and actual.table_name = expected.table_name
|
|
and actual.tgname = expected.trigger_name
|
|
and not actual.tgisinternal
|
|
and actual.tgenabled = 'O'
|
|
and actual.tgtype = expected.trigger_type
|
|
and actual.update_columns = expected.update_columns
|
|
and actual.when_expression is not distinct from expected.when_expression
|
|
and actual.tgnargs = expected.argument_count
|
|
and actual.argument_hex = expected.argument_hex
|
|
and actual.tgconstraint = 0::pg_catalog.oid
|
|
and not actual.tgdeferrable
|
|
and not actual.tginitdeferred
|
|
and actual.tgconstrrelid = 0::pg_catalog.oid
|
|
and actual.tgparentid = 0::pg_catalog.oid
|
|
and actual.tgoldtable is null
|
|
and actual.tgnewtable is null
|
|
and actual.function_schema = expected.function_schema
|
|
and actual.function_name = expected.function_name
|
|
and actual.function_identity_arguments = expected.function_identity_arguments
|
|
and exists (
|
|
select 1
|
|
from actual_v3_functions function_row
|
|
where function_row.oid = actual.tgfoid
|
|
and function_row.function_schema = expected.function_schema
|
|
and function_row.function_name = expected.function_name
|
|
and function_row.function_identity_arguments = expected.function_identity_arguments
|
|
)
|
|
)
|
|
) as valid
|
|
), catalog_state as (
|
|
select
|
|
exists (select 1 from actual_v3_functions)
|
|
or exists (select 1 from actual_v3_catalog_lock_functions)
|
|
or exists (select 1 from actual_v3_triggers) as artifacts_present,
|
|
(select valid from function_contract) as functions_valid,
|
|
(select valid from catalog_lock_function_contract) as catalog_lock_function_valid,
|
|
(select valid from trigger_contract) as triggers_valid,
|
|
exists (
|
|
select 1
|
|
from pg_catalog.pg_roles owner_role
|
|
where owner_role.rolname = E'kb_gate_owner'
|
|
and not owner_role.rolcanlogin
|
|
and not owner_role.rolinherit
|
|
and not owner_role.rolsuper
|
|
and not owner_role.rolcreatedb
|
|
and not owner_role.rolcreaterole
|
|
and not owner_role.rolreplication
|
|
and not owner_role.rolbypassrls
|
|
) as owner_role_valid,
|
|
not exists (
|
|
select 1
|
|
from pg_catalog.pg_auth_members membership
|
|
join pg_catalog.pg_roles member_role on member_role.oid = membership.member
|
|
join pg_catalog.pg_roles granted_role on granted_role.oid = membership.roleid
|
|
where member_role.rolname in (E'kb_gate_owner', E'kb_review', E'kb_apply')
|
|
or granted_role.rolname in (E'kb_gate_owner', E'kb_review', E'kb_apply')
|
|
) as memberships_valid
|
|
)
|
|
select case
|
|
when not artifacts_present then 'V2'
|
|
when functions_valid and catalog_lock_function_valid and triggers_valid
|
|
and owner_role_valid and memberships_valid then 'V3'
|
|
else 'INVALID'
|
|
end
|
|
into v_contract_state
|
|
from catalog_state;
|
|
-- TELEO_V3_CONTRACT_POSTFLIGHT_QUERY_END
|
|
if v_contract_state <> 'V3' then
|
|
raise exception using
|
|
errcode = '55000',
|
|
message = pg_catalog.format(
|
|
'teleo_v3_epistemic_contract: installed trigger catalog is %s',
|
|
v_contract_state
|
|
);
|
|
end if;
|
|
end
|
|
$contract_postflight$;
|
|
|
|
commit;
|
|
|
|
select jsonb_build_object(
|
|
'contract', 'livingip.teleo-v3-epistemic-contract',
|
|
'version', 1,
|
|
'migration', 'installed',
|
|
'legacy_claims', (select count(*) from public.claims where admission_state = 'legacy'),
|
|
'admitted_claims', (select count(*) from public.claims where admission_state = 'admitted')
|
|
)::text;
|