@consiliency/agent-board-schema 1.12.0 → 1.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/fixtures/already-applied.json +160 -0
- package/migrations/20260731000100_agent_board_agentsdir_column_scope.sql +88 -0
- package/migrations/20260801000100_agent_board_leaveidem_terminal_noop.sql +317 -0
- package/migrations/20260802000100_agent_board_rolereset_reentry_clamp.sql +237 -0
- package/migrations/20260803000100_agent_board_rowid_total_order.sql +1201 -0
- package/migrations/20260804000100_agent_board_hygiene_actor_binding_rls.sql +229 -0
- package/migrations/20260806000100_agent_board_consent_substrate.sql +339 -0
- package/migrations/20260807000100_agent_board_consent_emission.sql +619 -0
- package/migrations/20260808000100_agent_board_rejectsem.sql +145 -0
- package/migrations/20260809000100_agent_board_consent_gating.sql +492 -0
- package/migrations/20260810000100_agent_board_consent_locking.sql +485 -0
- package/migrations/20260811000100_agent_board_retention_metadata_revoke.sql +23 -0
- package/migrations/20260812000100_agent_board_pubexec_class_revoke.sql +54 -0
- package/migrations/20260813000100_agent_board_invitepolicy_block.sql +420 -0
- package/migrations/20260814000100_agent_board_obsgate_thread_post_policy.sql +239 -0
- package/migrations/20260815000100_agent_board_obsgate2_authoring_gates.sql +287 -0
- package/migrations/20260816000100_agent_board_obsgate3_task_lifecycle.sql +118 -0
- package/migrations/20260817000100_agent_board_obsgate4_claims_lifecycle.sql +250 -0
- package/migrations/20260818000100_agent_board_fleet_machines_rls_correlation.sql +43 -0
- package/migrations/20260819000100_agent_board_resolve_noop_retire.sql +166 -0
- package/migrations/20260820000100_agent_board_roomdeleg_lane_b.sql +206 -0
- package/migrations/20260821000100_agent_board_claimgate_claimkey.sql +372 -0
- package/migrations/20260822000100_agent_board_mint_harden.sql +710 -0
- package/migrations/20260822000200_agent_board_mint_harden_fingerprint_backfill.sql +207 -0
- package/migrations/20260823000100_agent_board_fleet_machine_hijack.sql +106 -0
- package/migrations/20260824000100_agent_board_claimgate_shadow.sql +443 -0
- package/migrations/20260825000100_agent_board_claimgate_enforce.sql +660 -0
- package/migrations/20260826000100_agent_board_fleet_machine_reactivation.sql +514 -0
- package/migrations/20260828000100_agent_board_delegation_cap_ceiling.sql +474 -0
- package/migrations/20260829000100_agent_board_fleet_machine_reclaim.sql +432 -0
- package/migrations/20260830000100_agent_board_delegation_sole_writer.sql +41 -0
- package/migrations/20260831000100_agent_board_fleet_domain_reclaim.sql +389 -0
- package/migrations/20260901000100_agent_board_atomic_fleet_onboarding.sql +1496 -0
- package/migrations/20260901000110_agent_board_fleet_onboarding_ceiling_lock.sql +604 -0
- package/migrations/20260901000120_agent_board_human_revocation_evidence_completeness.sql +78 -0
- package/migrations/20260901000130_agent_board_fedsession_human_revocation_gate.sql +74 -0
- package/migrations/20260901000140_agent_board_fedsession_board_owner_lock_order.sql +102 -0
- package/migrations/20260902000100_agent_board_outbox_wake_ordering.sql +184 -0
- package/migrations/20260903000100_agent_board_enroll_identity_board_owner_lock_order.sql +604 -0
- package/migrations/20260903000200_agent_board_fedsession_provider_absence_gate.sql +177 -0
- package/migrations/20260904000100_agent_board_wake_recovery_surface.sql +155 -0
- package/package.json +1 -1
- package/schema-manifest.json +363 -3
- package/src/tooling.test.mjs +113 -2
- package/tests/agent_board_actorspoof.test.sql +349 -0
- package/tests/agent_board_agentsdir.test.sql +154 -0
- package/tests/agent_board_atomic_fleet_onboarding.test.sql +623 -0
- package/tests/agent_board_atomic_fleet_onboarding_backfill.test.sql +142 -0
- package/tests/agent_board_atomic_fleet_onboarding_runtime.test.sql +1250 -0
- package/tests/agent_board_authbound_boundary.test.sql +9 -1
- package/tests/agent_board_claimgate_enforce.test.sql +464 -0
- package/tests/agent_board_claimgate_predicate.test.sql +360 -0
- package/tests/agent_board_claimkey.test.sql +229 -0
- package/tests/agent_board_consent_backfill.test.sql +48 -0
- package/tests/agent_board_consent_emission.test.sql +601 -0
- package/tests/agent_board_consent_gating.test.sql +489 -0
- package/tests/agent_board_consent_substrate.test.sql +720 -0
- package/tests/agent_board_delegation_cap_ceiling.test.sql +303 -0
- package/tests/agent_board_delegation_enforcement.test.sql +55 -6
- package/tests/agent_board_delegation_policies.test.sql +107 -1
- package/tests/agent_board_enroll_federated_identity.test.sql +3 -3
- package/tests/agent_board_enroll_pop_binding.test.sql +13 -3
- package/tests/agent_board_enrollment_audit_content.test.sql +11 -7
- package/tests/agent_board_federated_lock_order_class.test.sql +390 -0
- package/tests/agent_board_fedsession.test.sql +51 -5
- package/tests/agent_board_fedsession_provider_absence_gate.test.sql +170 -0
- package/tests/agent_board_fleet_domain_reclaim.test.sql +419 -0
- package/tests/agent_board_fleet_machine_hijack.test.sql +190 -0
- package/tests/agent_board_fleet_machine_reactivation.test.sql +426 -0
- package/tests/agent_board_fleet_machine_reclaim.test.sql +509 -0
- package/tests/agent_board_fleet_machines_rls.test.sql +79 -0
- package/tests/agent_board_fleet_policy.test.sql +2 -2
- package/tests/agent_board_grants_hygiene.test.sql +408 -0
- package/tests/agent_board_identity_providers.test.sql +9 -0
- package/tests/agent_board_inbox_reads.test.sql +45 -31
- package/tests/agent_board_inbox_rpcs.test.sql +44 -34
- package/tests/agent_board_invitepolicy.test.sql +205 -0
- package/tests/agent_board_leaveidem.test.sql +194 -0
- package/tests/agent_board_mint_fleet_agent_session.test.sql +10 -10
- package/tests/agent_board_mint_harden.test.sql +303 -0
- package/tests/agent_board_mint_harden_backfill.test.sql +95 -0
- package/tests/agent_board_obsgate.test.sql +178 -0
- package/tests/agent_board_obsgate2.test.sql +153 -0
- package/tests/agent_board_obsgate3.test.sql +170 -0
- package/tests/agent_board_obsgate4.test.sql +196 -0
- package/tests/agent_board_outbox_wake_ordering.test.sql +452 -0
- package/tests/agent_board_portal_offline_invariant.test.sql +14 -3
- package/tests/agent_board_pubexec_class.test.sql +53 -0
- package/tests/agent_board_rejectsem.test.sql +271 -0
- package/tests/agent_board_resigning_refresh.test.sql +1 -1
- package/tests/agent_board_resolve_noop_retire.test.sql +71 -0
- package/tests/agent_board_retention_metadata_acl.test.sql +53 -0
- package/tests/agent_board_revoke_agent_subtree.test.sql +3 -3
- package/tests/agent_board_rolereset.test.sql +351 -0
- package/tests/agent_board_roomdeleg.test.sql +5 -3
- package/tests/agent_board_roomdeleg_lane_b.test.sql +144 -0
- package/tests/agent_board_roomlink.test.sql +27 -11
- package/tests/agent_board_rowid.test.sql +1274 -0
- package/tests/agent_board_signature_rls_reads.test.sql +3 -3
- package/tests/agent_board_signed_write_gateway.test.sql +3 -3
- package/tests/agent_board_signing_challenge_pending_cap.test.sql +2 -2
- package/tests/agent_board_signing_challenge_reaper.test.sql +2 -2
- package/tests/agent_board_signing_proof_rpcs.test.sql +4 -4
- package/tests/agent_board_signing_store.test.sql +6 -6
- package/tests/agent_board_wake_recovery.test.sql +756 -0
- package/tests/fixtures/isolation-substrate.sql +93 -0
- package/tests/fixtures/test-prereqs.sql +33 -0
- package/tests/isolation/expected/atomic_fedsession_fleet_lock_order.out +79 -0
- package/tests/isolation/expected/atomic_fedsession_human_revoke.out +87 -0
- package/tests/isolation/expected/atomic_fleet_authority_races.out +469 -0
- package/tests/isolation/expected/atomic_fleet_binding_revoke.out +103 -0
- package/tests/isolation/expected/atomic_fleet_board_archive.out +117 -0
- package/tests/isolation/expected/atomic_fleet_human_fresh_revoke.out +136 -0
- package/tests/isolation/expected/atomic_fleet_identity_disposition.out +61 -0
- package/tests/isolation/expected/atomic_fleet_lifecycle_races.out +1479 -0
- package/tests/isolation/expected/atomic_fleet_onboarding.out +75 -0
- package/tests/isolation/expected/atomic_fleet_subtree_lock_order.out +60 -0
- package/tests/isolation/expected/ceiling_setter_lock_before_upsert.out +95 -0
- package/tests/isolation/expected/claimgate_ownerless_quota.out +36 -0
- package/tests/isolation/expected/claimkey_renew_race.out +38 -0
- package/tests/isolation/expected/consent_approval_lock.out +43 -0
- package/tests/isolation/expected/consent_emit_actual_state.out +37 -0
- package/tests/isolation/expected/domain_reclaim_revoke_race.out +40 -0
- package/tests/isolation/expected/enroll_identity_board_owner_lock_order.out +55 -0
- package/tests/isolation/expected/fedsession_provider_absent_owner_lock.out +37 -0
- package/tests/isolation/expected/onboarding_ceiling_lock_order.out +123 -0
- package/tests/isolation/expected/outbox_wake_ordering.out +86 -0
- package/tests/isolation/expected/reactivate_revoke_race.out +80 -0
- package/tests/isolation/expected/reclaim_revoke_race.out +40 -0
- package/tests/isolation/expected/wake_recovery_late_commit.out +89 -0
- package/tests/isolation/specs/atomic_fedsession_fleet_lock_order.spec +117 -0
- package/tests/isolation/specs/atomic_fedsession_human_revoke.spec +66 -0
- package/tests/isolation/specs/atomic_fleet_authority_races.spec +235 -0
- package/tests/isolation/specs/atomic_fleet_binding_revoke.spec +127 -0
- package/tests/isolation/specs/atomic_fleet_board_archive.spec +134 -0
- package/tests/isolation/specs/atomic_fleet_human_fresh_revoke.spec +142 -0
- package/tests/isolation/specs/atomic_fleet_identity_disposition.spec +118 -0
- package/tests/isolation/specs/atomic_fleet_lifecycle_races.spec +244 -0
- package/tests/isolation/specs/atomic_fleet_onboarding.spec +106 -0
- package/tests/isolation/specs/atomic_fleet_subtree_lock_order.spec +147 -0
- package/tests/isolation/specs/ceiling_setter_lock_before_upsert.spec +117 -0
- package/tests/isolation/specs/claimgate_ownerless_quota.spec +77 -0
- package/tests/isolation/specs/claimkey_renew_race.spec +67 -0
- package/tests/isolation/specs/consent_approval_lock.spec +98 -0
- package/tests/isolation/specs/consent_emit_actual_state.spec +79 -0
- package/tests/isolation/specs/domain_reclaim_revoke_race.spec +88 -0
- package/tests/isolation/specs/enroll_identity_board_owner_lock_order.spec +117 -0
- package/tests/isolation/specs/fedsession_provider_absent_owner_lock.spec +63 -0
- package/tests/isolation/specs/onboarding_ceiling_lock_order.spec +183 -0
- package/tests/isolation/specs/outbox_wake_ordering.spec +202 -0
- package/tests/isolation/specs/reactivate_revoke_race.spec +115 -0
- package/tests/isolation/specs/reclaim_revoke_race.spec +94 -0
- package/tests/isolation/specs/wake_recovery_late_commit.spec +207 -0
- package/tests/seeds/20260806000100_agent_board_consent_substrate.sql.pre.sql +19 -0
- package/tests/seeds/20260822000200_agent_board_mint_harden_fingerprint_backfill.sql.pre.sql +39 -0
- package/tests/seeds/20260901000100_agent_board_atomic_fleet_onboarding.sql.pre.sql +110 -0
|
@@ -328,6 +328,166 @@
|
|
|
328
328
|
{
|
|
329
329
|
"id": "20260730000200_agent_board_membstate_authz",
|
|
330
330
|
"sha256": "a4d935614f970a946f27a5925c0166e1d41510a82078e559fb769f4ea39afc2e"
|
|
331
|
+
},
|
|
332
|
+
{
|
|
333
|
+
"id": "20260731000100_agent_board_agentsdir_column_scope",
|
|
334
|
+
"sha256": "e05ddbbcf778828dbc4ba2631a5a288567d361371a60373c7e9090b718aa7433"
|
|
335
|
+
},
|
|
336
|
+
{
|
|
337
|
+
"id": "20260801000100_agent_board_leaveidem_terminal_noop",
|
|
338
|
+
"sha256": "72b20a3a365d0c84969cd1129cbc04578ee487a70e28a4245171c2abc68ec779"
|
|
339
|
+
},
|
|
340
|
+
{
|
|
341
|
+
"id": "20260802000100_agent_board_rolereset_reentry_clamp",
|
|
342
|
+
"sha256": "d04f596cdc97dc2ddc83348ee49c48888b334d12c31394586e7058409fa48cce"
|
|
343
|
+
},
|
|
344
|
+
{
|
|
345
|
+
"id": "20260803000100_agent_board_rowid_total_order",
|
|
346
|
+
"sha256": "a3c58ff1f55d3134c6096d10bf56df09db573938e7b4b92c13b7389451dd4c3c"
|
|
347
|
+
},
|
|
348
|
+
{
|
|
349
|
+
"id": "20260804000100_agent_board_hygiene_actor_binding_rls",
|
|
350
|
+
"sha256": "3feac0b5002bbff56c89adc3b9f071e21e1bb5ab2c4eea1571cfb61f254caa88"
|
|
351
|
+
},
|
|
352
|
+
{
|
|
353
|
+
"id": "20260806000100_agent_board_consent_substrate",
|
|
354
|
+
"sha256": "91ba2ec5885dfcaaa138ab3da3665a6779b6149147045773dfcbb707cfd9db07"
|
|
355
|
+
},
|
|
356
|
+
{
|
|
357
|
+
"id": "20260807000100_agent_board_consent_emission",
|
|
358
|
+
"sha256": "b6fb6f8254169d4fc3ab8016ab2f9ec2a2800c800178aebb602ff1db4aa0e7ee"
|
|
359
|
+
},
|
|
360
|
+
{
|
|
361
|
+
"id": "20260808000100_agent_board_rejectsem",
|
|
362
|
+
"sha256": "945a719e127573816cb9d18e318cf0778a99f3b0f8239b8e6ae0f8e62b04d919"
|
|
363
|
+
},
|
|
364
|
+
{
|
|
365
|
+
"id": "20260809000100_agent_board_consent_gating",
|
|
366
|
+
"sha256": "666e413ae1474e5e00de4d2bf2e7f7733f6ad149c91c3ef9a938e049115e387c"
|
|
367
|
+
},
|
|
368
|
+
{
|
|
369
|
+
"id": "20260810000100_agent_board_consent_locking",
|
|
370
|
+
"sha256": "d027468d3adb8563563f4fac4b13140013b68f28f39abe37c1de487c7fd36978"
|
|
371
|
+
},
|
|
372
|
+
{
|
|
373
|
+
"id": "20260811000100_agent_board_retention_metadata_revoke",
|
|
374
|
+
"sha256": "8d08dc986d532862d6593fe49b2e38e7447e57006360cae3406127c820ec19d6"
|
|
375
|
+
},
|
|
376
|
+
{
|
|
377
|
+
"id": "20260812000100_agent_board_pubexec_class_revoke",
|
|
378
|
+
"sha256": "34dabae96c11ab7fce68d71f1aa782eccef1bce5de4ad0567013d73949815606"
|
|
379
|
+
},
|
|
380
|
+
{
|
|
381
|
+
"id": "20260813000100_agent_board_invitepolicy_block",
|
|
382
|
+
"sha256": "8ee82c196483bf7d80091a46f6189328934fda2345947aea905505e37090719d"
|
|
383
|
+
},
|
|
384
|
+
{
|
|
385
|
+
"id": "20260814000100_agent_board_obsgate_thread_post_policy",
|
|
386
|
+
"sha256": "b36433d8aa78413b3d1e5119c45f8a24d37de053d03f4c4fd47b449814c4becb"
|
|
387
|
+
},
|
|
388
|
+
{
|
|
389
|
+
"id": "20260815000100_agent_board_obsgate2_authoring_gates",
|
|
390
|
+
"sha256": "7e76aa13aef8450aba50eb08d9da66b75a55457f41e3c92662137a1f6d7e2850"
|
|
391
|
+
},
|
|
392
|
+
{
|
|
393
|
+
"id": "20260816000100_agent_board_obsgate3_task_lifecycle",
|
|
394
|
+
"sha256": "921cc40511e4070c61f7b164a7528362a9662da2b902252a3e92091f01ebf0fd"
|
|
395
|
+
},
|
|
396
|
+
{
|
|
397
|
+
"id": "20260817000100_agent_board_obsgate4_claims_lifecycle",
|
|
398
|
+
"sha256": "6dd2d7928df2dff87c61b52e7edb75c5ec781fec5a85dd7c4273dadfd162f8ef"
|
|
399
|
+
},
|
|
400
|
+
{
|
|
401
|
+
"id": "20260818000100_agent_board_fleet_machines_rls_correlation",
|
|
402
|
+
"sha256": "f8bca2d75755589a1fa72d0c30ca799bd20db776d73a3a5e27832e112b57ea5d"
|
|
403
|
+
},
|
|
404
|
+
{
|
|
405
|
+
"id": "20260819000100_agent_board_resolve_noop_retire",
|
|
406
|
+
"sha256": "68bcb6d1b7e8ee8424e6d1d13a5a7d2c7d4554f348cc2cbf946ec95999517ac5"
|
|
407
|
+
},
|
|
408
|
+
{
|
|
409
|
+
"id": "20260820000100_agent_board_roomdeleg_lane_b",
|
|
410
|
+
"sha256": "2b10b8507b9bc704e9419b93d6d5d0af6d24f0ceef28c3eaa520516158f6c3e2"
|
|
411
|
+
},
|
|
412
|
+
{
|
|
413
|
+
"id": "20260821000100_agent_board_claimgate_claimkey",
|
|
414
|
+
"sha256": "caf8a83d4f39fc0a3d76d5b97077f0c3cf281305e47c50d370e940497a211022"
|
|
415
|
+
},
|
|
416
|
+
{
|
|
417
|
+
"id": "20260822000100_agent_board_mint_harden",
|
|
418
|
+
"sha256": "67585b17791598391515e63fa680c35cedaf27edc7ba3296f98242b925b9f490"
|
|
419
|
+
},
|
|
420
|
+
{
|
|
421
|
+
"id": "20260822000200_agent_board_mint_harden_fingerprint_backfill",
|
|
422
|
+
"sha256": "8bfd8e1bde5dbe85f21f5e0eb6c8f34462d16b92a763977ad9930539ac259698"
|
|
423
|
+
},
|
|
424
|
+
{
|
|
425
|
+
"id": "20260823000100_agent_board_fleet_machine_hijack",
|
|
426
|
+
"sha256": "73dcb65af8f8c754e2c6cc1c29f8649619a7653519a36a6b3acddf1a344a2bee"
|
|
427
|
+
},
|
|
428
|
+
{
|
|
429
|
+
"id": "20260824000100_agent_board_claimgate_shadow",
|
|
430
|
+
"sha256": "da42126b6cf40b0b044513313b0921394cd25de13426b16bcebd09bbcf5310a1"
|
|
431
|
+
},
|
|
432
|
+
{
|
|
433
|
+
"id": "20260825000100_agent_board_claimgate_enforce",
|
|
434
|
+
"sha256": "7264c8ff93f366238788f1a68c72a7ca0651da6e1b0cd9e27516e9acc01e6d3f"
|
|
435
|
+
},
|
|
436
|
+
{
|
|
437
|
+
"id": "20260826000100_agent_board_fleet_machine_reactivation",
|
|
438
|
+
"sha256": "fe437413046fba73b5306f1e21de81116369989a5d6e934a57c83c91bd5abb4b"
|
|
439
|
+
},
|
|
440
|
+
{
|
|
441
|
+
"id": "20260828000100_agent_board_delegation_cap_ceiling",
|
|
442
|
+
"sha256": "b9556374b6e512cc4f2ffc6a6d80d2abceca9ae1963e0f0ecdcd0485f6580e9e"
|
|
443
|
+
},
|
|
444
|
+
{
|
|
445
|
+
"id": "20260829000100_agent_board_fleet_machine_reclaim",
|
|
446
|
+
"sha256": "6ba17d49d5ef9ced3824f4f7ef7dbb40dd9ac3564576dad8b479545b2bab3b19"
|
|
447
|
+
},
|
|
448
|
+
{
|
|
449
|
+
"id": "20260830000100_agent_board_delegation_sole_writer",
|
|
450
|
+
"sha256": "26f5febc3c63f665eeb6eca2909d72187f16bae2c2643065e8ddb7c919086a86"
|
|
451
|
+
},
|
|
452
|
+
{
|
|
453
|
+
"id": "20260831000100_agent_board_fleet_domain_reclaim",
|
|
454
|
+
"sha256": "8d24ec4d682969a5d9339aec0573fad65ee95993dce2f8dca214906549b5ebe8"
|
|
455
|
+
},
|
|
456
|
+
{
|
|
457
|
+
"id": "20260901000100_agent_board_atomic_fleet_onboarding",
|
|
458
|
+
"sha256": "4cf07673b7c7da4ee13550e71d2bfe606b4b9c56971169e1ef61ac0d545ac6ef"
|
|
459
|
+
},
|
|
460
|
+
{
|
|
461
|
+
"id": "20260901000110_agent_board_fleet_onboarding_ceiling_lock",
|
|
462
|
+
"sha256": "310a32931562be6b4ad323b5e2926ebe459d6cd31bd6f601c7b862f354b5ce58"
|
|
463
|
+
},
|
|
464
|
+
{
|
|
465
|
+
"id": "20260901000120_agent_board_human_revocation_evidence_completeness",
|
|
466
|
+
"sha256": "e8ccb3605fb6dfa4e8dfbefbbb4328179a61aec00616c1abef27d6a09c2a913e"
|
|
467
|
+
},
|
|
468
|
+
{
|
|
469
|
+
"id": "20260901000130_agent_board_fedsession_human_revocation_gate",
|
|
470
|
+
"sha256": "f76d78973b87cc66c54ca0af4754358221ede19c8e0644df81a08e65dab0e89b"
|
|
471
|
+
},
|
|
472
|
+
{
|
|
473
|
+
"id": "20260901000140_agent_board_fedsession_board_owner_lock_order",
|
|
474
|
+
"sha256": "2388528532e974ab27c35337f9b00263056c1b16d973b7b3a0cf8cebb7724cc4"
|
|
475
|
+
},
|
|
476
|
+
{
|
|
477
|
+
"id": "20260902000100_agent_board_outbox_wake_ordering",
|
|
478
|
+
"sha256": "d3557acc29bf146862920fe903c3ac2e9e0e0317f3ca2134c5bb0835620fd2da"
|
|
479
|
+
},
|
|
480
|
+
{
|
|
481
|
+
"id": "20260903000100_agent_board_enroll_identity_board_owner_lock_order",
|
|
482
|
+
"sha256": "f8dc4ae6c5c6ea56183258402ab283cd424929b8e637984a59cf6ecd0685f681"
|
|
483
|
+
},
|
|
484
|
+
{
|
|
485
|
+
"id": "20260903000200_agent_board_fedsession_provider_absence_gate",
|
|
486
|
+
"sha256": "79c581011be386d0efd97db723d3aa07947ad7b996667878ed1387cdd106b000"
|
|
487
|
+
},
|
|
488
|
+
{
|
|
489
|
+
"id": "20260904000100_agent_board_wake_recovery_surface",
|
|
490
|
+
"sha256": "6349f705751104c5d60ec6602ea534e7c8b7de5c4c6ad2d84dbda680a5267848"
|
|
331
491
|
}
|
|
332
492
|
]
|
|
333
493
|
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
-- AGENTSDIR — stop exposing identity-linkage columns of agent_board.agents to `authenticated`.
|
|
2
|
+
-- Tracks message-board#80.
|
|
3
|
+
--
|
|
4
|
+
-- THE DEFECT. The SELECT policy on agent_board.agents is
|
|
5
|
+
-- agent_board_authenticated_select_agents USING (agent_board.current_agent_id() is not null)
|
|
6
|
+
-- (20260512000003_agent_board_auth_rls.sql:48-54) — effectively TRUE for any principal that
|
|
7
|
+
-- resolves to an active agent. Combined with a TABLE-LEVEL `grant select` to `authenticated` and
|
|
8
|
+
-- PostgREST exposing the agent_board schema, ANY authenticated JWT reads EVERY agent row and
|
|
9
|
+
-- EVERY column. Measured live as role `authenticated` with RLS enforced: 6 of 6 agents visible,
|
|
10
|
+
-- 5 exposing auth_user_id.
|
|
11
|
+
--
|
|
12
|
+
-- Post-FEDSESSION every Portal-federated human has an agents row, so this is cross-tenant: one
|
|
13
|
+
-- signed-in human enumerates every other human and agent on the deployment.
|
|
14
|
+
--
|
|
15
|
+
-- WHY COLUMN SCOPE, NOT ROW SCOPE. The 4-seat plan panel unanimously rejected row-scoping, and
|
|
16
|
+
-- each objection was reproduced live before this migration was written:
|
|
17
|
+
-- * `agents` IS the product's participant directory by contract — specs/schema.md:179 ("the
|
|
18
|
+
-- canonical participant directory for both humans and automation"), and specs/read-models.md
|
|
19
|
+
-- designates v_agent_dashboard the sanctioned substitute precisely so clients need not read
|
|
20
|
+
-- this table. Handle/display_name are the feature, not the leak.
|
|
21
|
+
-- * A co-member row predicate is DEAD CODE: board_memberships' own SELECT policy is self-scoped
|
|
22
|
+
-- (20260515000000:465-472) and RLS applies inside a policy's USING expression, so a join to
|
|
23
|
+
-- another agent's membership row can never match. Verified: a board owner was invisible to
|
|
24
|
+
-- their own co-member.
|
|
25
|
+
-- * ~18 views INNER JOIN agents, so an invisible row DELETES the row rather than hiding a name.
|
|
26
|
+
-- Verified: after an author left a board, v_board_feed went 1 -> 0 — historical messages
|
|
27
|
+
-- vanished for everyone still on the board. Same class would drop stranger-created `open`
|
|
28
|
+
-- boards from discovery (v_board_directory) and escalations from the supervision feed
|
|
29
|
+
-- (v_open_escalations).
|
|
30
|
+
-- * Row scope kills invite-by-handle: resolve_agent_handle is SECURITY INVOKER and bounded by
|
|
31
|
+
-- this very policy (20260727000100:119), so you could only invite people you ALREADY share a
|
|
32
|
+
-- board with — which is what an invite creates.
|
|
33
|
+
-- * Board co-membership is not a trust boundary anyway: a creator can force-join any agent
|
|
34
|
+
-- without consent (message-board#82, verified live), so it is manufacturable in two calls.
|
|
35
|
+
-- What actually leaks is IDENTITY LINKAGE, which is a column property:
|
|
36
|
+
-- auth_user_id / owner_user_id — raw auth.users UUIDs, cross-linkable to Portal identity
|
|
37
|
+
-- parent_agent_id / root_agent_id — the delegation lineage graph
|
|
38
|
+
-- metadata — the field LOCALAUTH deliberately keeps plaintext email OUT of precisely because
|
|
39
|
+
-- this table is broadly readable (20260726000100:15-22)
|
|
40
|
+
-- No view granted to `authenticated` projects ANY of those columns, and no client/CLI/MCP package
|
|
41
|
+
-- reads this table directly (they go through views and RPCs) — so scoping columns removes the
|
|
42
|
+
-- exposure at ~zero product risk.
|
|
43
|
+
--
|
|
44
|
+
-- MECHANISM NOTE (this is why the migration is shaped this way). A column-level
|
|
45
|
+
-- `revoke select (col, ...)` is a NO-OP while a table-level `grant select` exists — the
|
|
46
|
+
-- table-level grant covers every column, including ones added later. Verified live: the
|
|
47
|
+
-- "revoke columns" form left auth_user_id readable. The table-level grant must be revoked FIRST
|
|
48
|
+
-- and only the permitted columns re-granted.
|
|
49
|
+
--
|
|
50
|
+
-- MAINTENANCE OBLIGATION: because the grant is now an explicit column list, a future
|
|
51
|
+
-- `alter table agent_board.agents add column ...` is NOT readable by `authenticated` until it is
|
|
52
|
+
-- added here. That is the correct fail-closed default; a new column must be a deliberate decision.
|
|
53
|
+
--
|
|
54
|
+
-- NOT CHANGED: the row policy itself (unchanged — see above), the service_role ALL policy
|
|
55
|
+
-- (`using (true)`), and every SECURITY DEFINER RPC (they run as the table owner, RLS- and
|
|
56
|
+
-- grant-exempt, so list_board_pending_memberships et al. keep returning handles/display names).
|
|
57
|
+
-- `anon` holds no grants on this table and none are added.
|
|
58
|
+
--
|
|
59
|
+
-- BREAKING (fail-loud, intended): `select *` on agent_board.agents as `authenticated` now raises
|
|
60
|
+
-- 42501 rather than returning narrowed rows. No in-repo consumer does this; a non-SDK consumer
|
|
61
|
+
-- (e.g. the Portal) doing `select=*` will break visibly rather than silently leaking.
|
|
62
|
+
--
|
|
63
|
+
-- Additive, forward-only. Grants/RLS only — no function, table or column changes. Writes no
|
|
64
|
+
-- agent_board.schema_version (stays 8). No PostgREST reload required (no signature change), but a
|
|
65
|
+
-- reload is harmless.
|
|
66
|
+
|
|
67
|
+
revoke select on agent_board.agents from authenticated;
|
|
68
|
+
|
|
69
|
+
grant select (
|
|
70
|
+
id,
|
|
71
|
+
handle,
|
|
72
|
+
display_name,
|
|
73
|
+
kind,
|
|
74
|
+
agent_type,
|
|
75
|
+
status,
|
|
76
|
+
created_at,
|
|
77
|
+
updated_at
|
|
78
|
+
) on agent_board.agents to authenticated;
|
|
79
|
+
|
|
80
|
+
comment on table agent_board.agents is
|
|
81
|
+
'Canonical participant directory for humans and automation. READ POSTURE (AGENTSDIR, #80): '
|
|
82
|
+
'`authenticated` may select ONLY the directory columns (id, handle, display_name, kind, '
|
|
83
|
+
'agent_type, status, created_at, updated_at). The identity-linkage columns — auth_user_id, '
|
|
84
|
+
'owner_user_id, parent_agent_id, root_agent_id — and metadata are NOT granted to '
|
|
85
|
+
'`authenticated`, because raw auth.users UUIDs and the delegation lineage are cross-tenant '
|
|
86
|
+
'identity linkage and metadata is an acknowledged PII-hazard field. service_role and '
|
|
87
|
+
'SECURITY DEFINER RPCs are unaffected. A NEWLY ADDED COLUMN IS NOT READABLE by `authenticated` '
|
|
88
|
+
'until explicitly granted here — fail closed by design.';
|
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
-- LEAVEIDEM — terminal membership states are TRUE no-ops, and departure time is not
|
|
2
|
+
-- caller-controlled. Closes message-board#81.
|
|
3
|
+
--
|
|
4
|
+
-- THEME (one invariant, two functions): re-asserting a terminal membership state must not mutate
|
|
5
|
+
-- the row, and `left_at` / the reserved `ops_*` metadata namespace must not be writable by the
|
|
6
|
+
-- agent whose retention they govern.
|
|
7
|
+
--
|
|
8
|
+
-- DEFECT 1 — leave_board is no longer idempotent (a regression introduced by MEMBSTATE, #77).
|
|
9
|
+
-- `leave_board` (20260512000002:154-158) delegates into update_board_membership(..., 'left').
|
|
10
|
+
-- MEMBSTATE's self-branch permits 'left' only FROM joined|subscribed|invited|requested, so a
|
|
11
|
+
-- SECOND leave_board raises 42501 'self membership change not permitted'. Proven live. A naturally
|
|
12
|
+
-- idempotent operation now hard-fails, and defensive client retries surface an authorization error.
|
|
13
|
+
-- The 'removed' exclusion in that branch was correct and is PRESERVED — a kicked agent still may
|
|
14
|
+
-- not erase its own kick marker (#79). Only the same-state terminal case is opened.
|
|
15
|
+
--
|
|
16
|
+
-- DEFECT 2 — `left_at` is caller-restampable, and it is the retention sweep's cutoff key.
|
|
17
|
+
-- `_upsert_board_membership_state` unconditionally sets
|
|
18
|
+
-- left_at = case when p_membership_state in ('left','removed') then now() else null end
|
|
19
|
+
-- on EVERY call, so any repeat call into a terminal state moves the departure timestamp forward.
|
|
20
|
+
-- `mark_retention_metadata` sweeps board_memberships on `left_at < cutoff`
|
|
21
|
+
-- (20260513000001:115-121), so an agent able to restamp its own `left_at` can postpone its own
|
|
22
|
+
-- row's retention indefinitely. This is NOT limited to the double-leave path: an owner re-kicking
|
|
23
|
+
-- an already-`removed` member resets that row's clock too. Fixed at the mutator so every caller
|
|
24
|
+
-- benefits: the FIRST departure time is preserved via coalesce(bm.left_at, now()).
|
|
25
|
+
-- (Transitions back to a non-terminal state still NULL `left_at` — required by the
|
|
26
|
+
-- board_memberships state/left_at CHECK, 20260515000000:18-25 — and that is correct: a
|
|
27
|
+
-- re-activated membership has no departure time.)
|
|
28
|
+
--
|
|
29
|
+
-- DEFECT 3 — the reserved `ops_*` metadata namespace is caller-writable.
|
|
30
|
+
-- The mutator merges caller-supplied p_metadata verbatim on every path. The retention sweep SKIPS
|
|
31
|
+
-- rows whose `metadata->>'ops_retained_at'` is non-empty (20260513000001:122). So any caller that
|
|
32
|
+
-- can reach the mutator — join_board, request_join_board, invite_board_member,
|
|
33
|
+
-- resolve_board_request, update_board_membership/leave_board — can write that key and exclude the
|
|
34
|
+
-- row from retention permanently. Note this is reachable on a FIRST leave, so the no-op fix alone
|
|
35
|
+
-- would not close it. Caller-supplied keys in the reserved `ops_` namespace are now stripped
|
|
36
|
+
-- before the merge; operational writers set them directly (mark_retention_metadata writes the
|
|
37
|
+
-- column, it does not go through this mutator).
|
|
38
|
+
--
|
|
39
|
+
-- AUTHZ PLACEMENT (deliberate divergence from the review's suggestion). The panel proposed the
|
|
40
|
+
-- early return sit between the existence check and the authorization block. That would hand an
|
|
41
|
+
-- UNAUTHORIZED caller a successful no-op instead of 42501, turning the RPC into a membership-state
|
|
42
|
+
-- oracle for arbitrary (board, agent) pairs. Instead the self-branch permits the same-state
|
|
43
|
+
-- terminal call, and the no-op return happens AFTER the authorization block — so authority is
|
|
44
|
+
-- still required, the 42501 posture for unauthorized callers is unchanged, and the caller who is
|
|
45
|
+
-- entitled to act simply gets an unchanged row back.
|
|
46
|
+
--
|
|
47
|
+
-- Additive, forward-only. No signature change (so no PostgREST reload and no client/mcp/cli
|
|
48
|
+
-- surface change); leave_board inherits the fix automatically through its delegation. Writes no
|
|
49
|
+
-- agent_board.schema_version (stays 8).
|
|
50
|
+
|
|
51
|
+
-- 1. Mutator: preserve the first departure time; strip the reserved ops_ namespace. -------------
|
|
52
|
+
create or replace function agent_board._upsert_board_membership_state(
|
|
53
|
+
p_board_id uuid,
|
|
54
|
+
p_agent_id uuid,
|
|
55
|
+
p_membership_state text,
|
|
56
|
+
p_created_by_agent_id uuid default null,
|
|
57
|
+
p_metadata jsonb default '{}'::jsonb
|
|
58
|
+
)
|
|
59
|
+
returns agent_board.board_memberships
|
|
60
|
+
language plpgsql
|
|
61
|
+
security definer
|
|
62
|
+
set search_path = agent_board, public
|
|
63
|
+
as $$
|
|
64
|
+
declare
|
|
65
|
+
v_membership agent_board.board_memberships%rowtype;
|
|
66
|
+
v_metadata jsonb;
|
|
67
|
+
begin
|
|
68
|
+
if p_membership_state not in ('requested', 'invited', 'joined', 'subscribed', 'left', 'removed') then
|
|
69
|
+
raise exception 'invalid board membership state' using errcode = '22023';
|
|
70
|
+
end if;
|
|
71
|
+
|
|
72
|
+
-- Metadata must be a JSON OBJECT. Previously a scalar/array was stored verbatim on insert and
|
|
73
|
+
-- concatenated on update; the reserved-key strip below iterates keys, so a non-object would raise
|
|
74
|
+
-- an opaque internal error. Fail explicitly instead. Reachable from the CLI `--metadata`, which
|
|
75
|
+
-- parses arbitrary JSON — this is a deliberate, declared behavior change for ALL mutator callers.
|
|
76
|
+
if jsonb_typeof(coalesce(p_metadata, '{}'::jsonb)) <> 'object' then
|
|
77
|
+
raise exception 'board membership metadata must be a JSON object' using errcode = '22023';
|
|
78
|
+
end if;
|
|
79
|
+
|
|
80
|
+
-- Reserved namespace: `ops_*` keys are operational state (the retention sweep's skip marker
|
|
81
|
+
-- `ops_retained_at`, read for board_memberships at 20260513000001:122) and must never be writable
|
|
82
|
+
-- by a caller whose own row they govern. Strip them from caller input; operational writers set
|
|
83
|
+
-- the column directly.
|
|
84
|
+
select coalesce(jsonb_object_agg(e.key, e.value), '{}'::jsonb)
|
|
85
|
+
into v_metadata
|
|
86
|
+
from jsonb_each(coalesce(p_metadata, '{}'::jsonb)) as e(key, value)
|
|
87
|
+
where e.key not like 'ops\_%';
|
|
88
|
+
|
|
89
|
+
select *
|
|
90
|
+
into v_membership
|
|
91
|
+
from agent_board.board_memberships bm
|
|
92
|
+
where bm.board_id = p_board_id
|
|
93
|
+
and bm.agent_id = p_agent_id
|
|
94
|
+
order by bm.created_at desc
|
|
95
|
+
limit 1
|
|
96
|
+
for update;
|
|
97
|
+
|
|
98
|
+
if v_membership.id is null then
|
|
99
|
+
insert into agent_board.board_memberships(board_id, agent_id, membership_state, created_by_agent_id, left_at, metadata)
|
|
100
|
+
values (
|
|
101
|
+
p_board_id,
|
|
102
|
+
p_agent_id,
|
|
103
|
+
p_membership_state,
|
|
104
|
+
p_created_by_agent_id,
|
|
105
|
+
case when p_membership_state in ('left', 'removed') then now() else null end,
|
|
106
|
+
v_metadata
|
|
107
|
+
)
|
|
108
|
+
returning * into v_membership;
|
|
109
|
+
else
|
|
110
|
+
update agent_board.board_memberships bm
|
|
111
|
+
set membership_state = p_membership_state,
|
|
112
|
+
created_by_agent_id = coalesce(p_created_by_agent_id, bm.created_by_agent_id),
|
|
113
|
+
-- PRESERVE the first departure time. Restamping it would let the departing agent (or a
|
|
114
|
+
-- re-kicking owner) push its own row past the retention cutoff (20260513000001:121).
|
|
115
|
+
left_at = case
|
|
116
|
+
when p_membership_state in ('left', 'removed') then
|
|
117
|
+
case
|
|
118
|
+
-- SAME terminal state re-asserted: preserve the FIRST departure time so a
|
|
119
|
+
-- repeat call cannot push the retention cutoff forward.
|
|
120
|
+
when v_membership.membership_state = p_membership_state
|
|
121
|
+
then coalesce(v_membership.left_at, now())
|
|
122
|
+
-- GENUINE transition (notably left -> removed, an owner barring
|
|
123
|
+
-- invite-link re-entry for an already-departed agent) stamps NOW. R-LINK-7
|
|
124
|
+
-- deliberately does not block voluntary 'left' (20260724000100:321-322),
|
|
125
|
+
-- so this is the real kick moment; inheriting the earlier departure would
|
|
126
|
+
-- both lose the audit answer and make the marker row retention-eligible
|
|
127
|
+
-- prematurely.
|
|
128
|
+
else now()
|
|
129
|
+
end
|
|
130
|
+
else null
|
|
131
|
+
end,
|
|
132
|
+
metadata = coalesce(bm.metadata, '{}'::jsonb) || v_metadata,
|
|
133
|
+
updated_at = now()
|
|
134
|
+
where bm.id = v_membership.id
|
|
135
|
+
returning * into v_membership;
|
|
136
|
+
end if;
|
|
137
|
+
|
|
138
|
+
return v_membership;
|
|
139
|
+
end;
|
|
140
|
+
$$;
|
|
141
|
+
|
|
142
|
+
comment on function agent_board._upsert_board_membership_state(uuid, uuid, text, uuid, jsonb) is
|
|
143
|
+
'UNAUTHORIZED PRIMITIVE — accepts any membership state and never writes `role`; every caller '
|
|
144
|
+
'MUST gate. LEAVEIDEM (#81): preserves the FIRST left_at across repeated terminal transitions '
|
|
145
|
+
'(the retention sweep keys on left_at, 20260513000001:121) and strips the reserved `ops_*` '
|
|
146
|
+
'metadata namespace from caller input (the sweep skips rows carrying ops_retained_at).';
|
|
147
|
+
|
|
148
|
+
-- 2. update_board_membership: same-state terminal calls are true no-ops, AFTER authorization. ---
|
|
149
|
+
create or replace function agent_board.update_board_membership(
|
|
150
|
+
p_board_id uuid,
|
|
151
|
+
p_agent_id uuid,
|
|
152
|
+
p_membership_state text,
|
|
153
|
+
p_metadata jsonb default '{}'::jsonb
|
|
154
|
+
)
|
|
155
|
+
returns table(membership_id uuid, board_id uuid, agent_id uuid, membership_state text, left_at timestamptz, updated_at timestamptz)
|
|
156
|
+
language plpgsql
|
|
157
|
+
security definer
|
|
158
|
+
set search_path = agent_board, public
|
|
159
|
+
as $$
|
|
160
|
+
declare
|
|
161
|
+
v_membership agent_board.board_memberships%rowtype;
|
|
162
|
+
v_board agent_board.boards%rowtype;
|
|
163
|
+
v_actor uuid := agent_board.current_agent_id();
|
|
164
|
+
v_is_service boolean := agent_board._is_service_role();
|
|
165
|
+
v_existing_state text;
|
|
166
|
+
v_target_role text;
|
|
167
|
+
v_actor_role text;
|
|
168
|
+
begin
|
|
169
|
+
-- (1) Unresolved principal fails closed (GATENULL #58 posture). Raised before any lookup so this
|
|
170
|
+
-- cannot be used as a membership-existence oracle.
|
|
171
|
+
if v_actor is null and not v_is_service then
|
|
172
|
+
raise exception 'current agent principal not resolved' using errcode = '42501';
|
|
173
|
+
end if;
|
|
174
|
+
|
|
175
|
+
-- (2) AUTHORITY BEFORE DISCLOSURE. For a caller acting on ANOTHER agent's row the owner/admin
|
|
176
|
+
-- check runs BEFORE the membership lookup, so an unauthorized caller cannot distinguish
|
|
177
|
+
-- P0002 'board membership not found' from 42501 and thus cannot probe whether an arbitrary
|
|
178
|
+
-- (board_id, agent_id) pair has a membership row. (The self branch legitimately learns about its
|
|
179
|
+
-- own membership, and the service path is the operator.)
|
|
180
|
+
if not v_is_service and v_actor <> p_agent_id then
|
|
181
|
+
select * into v_board from agent_board.boards where id = p_board_id and status = 'active';
|
|
182
|
+
if v_board.id is null then
|
|
183
|
+
raise exception 'board not found' using errcode = 'P0002';
|
|
184
|
+
end if;
|
|
185
|
+
select bm.role
|
|
186
|
+
into v_actor_role
|
|
187
|
+
from agent_board.board_memberships bm
|
|
188
|
+
where bm.board_id = p_board_id
|
|
189
|
+
and bm.agent_id = v_actor
|
|
190
|
+
and bm.membership_state in ('joined', 'subscribed')
|
|
191
|
+
limit 1;
|
|
192
|
+
if v_actor_role is null or v_actor_role not in ('owner', 'admin') then
|
|
193
|
+
raise exception 'board membership change requires owner or admin role'
|
|
194
|
+
using errcode = '42501';
|
|
195
|
+
end if;
|
|
196
|
+
end if;
|
|
197
|
+
|
|
198
|
+
-- (3) Pre-existing precondition, preserved. Also captures the target's CURRENT role for the
|
|
199
|
+
-- rank check below.
|
|
200
|
+
select bm.membership_state, bm.role
|
|
201
|
+
into v_existing_state, v_target_role
|
|
202
|
+
from agent_board.board_memberships bm
|
|
203
|
+
where bm.board_id = p_board_id
|
|
204
|
+
and bm.agent_id = p_agent_id
|
|
205
|
+
order by bm.created_at desc
|
|
206
|
+
limit 1;
|
|
207
|
+
if v_existing_state is null then
|
|
208
|
+
raise exception 'board membership not found' using errcode = 'P0002';
|
|
209
|
+
end if;
|
|
210
|
+
|
|
211
|
+
-- (4) MEMBSTATE authorization. Skipped ONLY for a genuine service_role/operator caller; every
|
|
212
|
+
-- authenticated caller (whose actor is guaranteed non-null by step 1) is gated.
|
|
213
|
+
if not v_is_service then
|
|
214
|
+
if v_actor = p_agent_id then
|
|
215
|
+
-- SELF-SERVICE. Deliberately NO board lookup on this branch: the board row is only needed
|
|
216
|
+
-- for the authority checks below, and requiring an ACTIVE board here would strand an agent
|
|
217
|
+
-- holding a live membership on an ARCHIVED board with no way to leave it (leave_board
|
|
218
|
+
-- delegates straight into this RPC, 20260512000002:154-158).
|
|
219
|
+
--
|
|
220
|
+
-- Leaving is permitted only FROM a live/pending state, and joined<->subscribed may be
|
|
221
|
+
-- toggled. Promotion INTO the board is NOT permitted — that belongs to the approval path.
|
|
222
|
+
-- The 'removed' exclusion is load-bearing: allowing removed -> left would let a kicked agent
|
|
223
|
+
-- erase the 'removed' marker (memberships are updated IN PLACE, so the row IS the record),
|
|
224
|
+
-- which is exactly the marker _redeem_invite_for_agent's R-LINK-7 defense reads
|
|
225
|
+
-- (20260724000100:323-335) — i.e. it would launder a kick into invite-link re-entry.
|
|
226
|
+
--
|
|
227
|
+
-- LEAVEIDEM (#81): a same-state TERMINAL call is additionally permitted. It is a no-op
|
|
228
|
+
-- (returned unchanged at step 5), so it neither erases nor refreshes the marker — it simply
|
|
229
|
+
-- restores idempotence to leave_board without widening the transition set.
|
|
230
|
+
if not (
|
|
231
|
+
(p_membership_state = 'left'
|
|
232
|
+
and v_existing_state in ('joined', 'subscribed', 'invited', 'requested'))
|
|
233
|
+
or (p_membership_state in ('joined', 'subscribed')
|
|
234
|
+
and v_existing_state in ('joined', 'subscribed'))
|
|
235
|
+
or (p_membership_state = v_existing_state
|
|
236
|
+
and p_membership_state in ('left', 'removed'))
|
|
237
|
+
) then
|
|
238
|
+
raise exception 'self membership change not permitted; use the join/approval path'
|
|
239
|
+
using errcode = '42501';
|
|
240
|
+
end if;
|
|
241
|
+
else
|
|
242
|
+
-- ACTING ON ANOTHER AGENT'S MEMBERSHIP. The board-active check and the ACTIVE owner/admin
|
|
243
|
+
-- requirement already ran in step (2) (authority before disclosure). This RPC is the
|
|
244
|
+
-- schema's only eviction path — no sibling can move a JOINED member to removed
|
|
245
|
+
-- (resolve_board_request only resolves requested/invited, 20260725000100:212) — so it is
|
|
246
|
+
-- gated more strictly than its siblings, with NO creator exemption: a bare
|
|
247
|
+
-- created_by_agent_id is deliberately NOT sufficient. ROOMOWN seeds the creator's owner
|
|
248
|
+
-- membership at create_board time (20260723000100), so a legitimate creator holds one, while
|
|
249
|
+
-- a DEMOTED or REMOVED creator must not retain eviction power (the R-DELEG-1 lesson).
|
|
250
|
+
--
|
|
251
|
+
-- STRICT RANK SUPERIORITY (owner=4 > admin=3 > member=2 > observer=1). Without it an admin
|
|
252
|
+
-- could evict the OWNER, or admins could evict each other.
|
|
253
|
+
if agent_board._role_rank(v_actor_role)
|
|
254
|
+
<= agent_board._role_rank(coalesce(v_target_role, '')) then
|
|
255
|
+
raise exception 'board membership change requires a higher board role than the target'
|
|
256
|
+
using errcode = '42501';
|
|
257
|
+
end if;
|
|
258
|
+
end if;
|
|
259
|
+
end if;
|
|
260
|
+
|
|
261
|
+
-- (5) LEAVEIDEM (#81): a same-state TERMINAL call is a TRUE no-op — return the row untouched
|
|
262
|
+
-- rather than re-entering the mutator, which would restamp left_at and re-merge metadata.
|
|
263
|
+
-- Placed AFTER the authorization block on purpose: returning early before step (2)/(4) would
|
|
264
|
+
-- hand an UNAUTHORIZED caller a successful no-op instead of 42501, turning this RPC into a
|
|
265
|
+
-- membership-state oracle for arbitrary (board, agent) pairs. Authority is still required; the
|
|
266
|
+
-- entitled caller simply gets an unchanged row.
|
|
267
|
+
-- Scoped to the AUTHENTICATED path on purpose: a service_role/operator caller re-asserting a
|
|
268
|
+
-- terminal state may legitimately be attaching an annotation via p_metadata (CLI --metadata), and
|
|
269
|
+
-- silently dropping it would be a lossy success. The operator falls through to the mutator, which
|
|
270
|
+
-- now preserves left_at for an UNCHANGED terminal state anyway — so the operator keeps the
|
|
271
|
+
-- metadata merge without reacquiring the restamp defect.
|
|
272
|
+
if not v_is_service
|
|
273
|
+
and p_membership_state = v_existing_state
|
|
274
|
+
and p_membership_state in ('left', 'removed') then
|
|
275
|
+
return query
|
|
276
|
+
select bm.id, bm.board_id, bm.agent_id, bm.membership_state, bm.left_at, bm.updated_at
|
|
277
|
+
from agent_board.board_memberships bm
|
|
278
|
+
where bm.board_id = p_board_id
|
|
279
|
+
and bm.agent_id = p_agent_id
|
|
280
|
+
order by bm.created_at desc
|
|
281
|
+
limit 1;
|
|
282
|
+
return;
|
|
283
|
+
end if;
|
|
284
|
+
|
|
285
|
+
select * into v_membership
|
|
286
|
+
from agent_board._upsert_board_membership_state(
|
|
287
|
+
p_board_id,
|
|
288
|
+
p_agent_id,
|
|
289
|
+
p_membership_state,
|
|
290
|
+
null,
|
|
291
|
+
p_metadata
|
|
292
|
+
);
|
|
293
|
+
|
|
294
|
+
return query
|
|
295
|
+
select v_membership.id, v_membership.board_id, v_membership.agent_id, v_membership.membership_state, v_membership.left_at, v_membership.updated_at;
|
|
296
|
+
end;
|
|
297
|
+
$$;
|
|
298
|
+
|
|
299
|
+
comment on function agent_board.update_board_membership(uuid, uuid, text, jsonb) is
|
|
300
|
+
'Updates a board membership state. MEMBSTATE (#75): service_role retains full '
|
|
301
|
+
'modify-on-behalf-of; an authenticated caller with an unresolved actor fails closed (42501); a '
|
|
302
|
+
'caller acting on its OWN membership may only leave or toggle joined<->subscribed (promotion '
|
|
303
|
+
'into a board belongs to the join/approval path, so a requested/invited/left/removed principal '
|
|
304
|
+
'cannot self-approve or re-enter); a caller acting on ANOTHER agent''s membership must hold an '
|
|
305
|
+
'active owner/admin membership AND strictly higher board rank than the target. LEAVEIDEM (#81): '
|
|
306
|
+
'a same-state terminal call (left->left, removed->removed) is a TRUE no-op returned unchanged '
|
|
307
|
+
'AFTER authorization — leave_board is idempotent again and left_at is never restamped.';
|
|
308
|
+
|
|
309
|
+
-- Re-assert grants (create or replace preserves them, but be explicit so a fresh replay is
|
|
310
|
+
-- order-independent) — house convention, 20260725000100:229-232.
|
|
311
|
+
revoke all on function agent_board._upsert_board_membership_state(uuid, uuid, text, uuid, jsonb)
|
|
312
|
+
from public, anon, authenticated;
|
|
313
|
+
grant execute on function agent_board._upsert_board_membership_state(uuid, uuid, text, uuid, jsonb)
|
|
314
|
+
to service_role;
|
|
315
|
+
revoke all on function agent_board.update_board_membership(uuid, uuid, text, jsonb) from public, anon;
|
|
316
|
+
grant execute on function agent_board.update_board_membership(uuid, uuid, text, jsonb)
|
|
317
|
+
to authenticated, service_role;
|