mirror of
https://github.com/block/buzz.git
synced 2026-11-02 07:22:55 +00:00
fix(schema): address Thufir pass 2 blockers on NIP-FI PR 2
Three IMPORTANT findings addressed: IMPORTANT 1 (denial semantic binding, partial): Bind the remaining two unbound denial identity coordinates. - Add semantic_fingerprint BYTEA to authorization_events: required non-zero for kind-9 events, NULL for all other event kinds (enforced by CHECK on the table). This is the redaction-safe intent_digest coordinate. - Add authorization_denial_reason_reason_code_binding CHECK to authorization_authentication_denial_attempts: encodes the canonical OperatorAuthenticationDenialReason <-> AuthorizationReasonCode mapping from operator_lifecycle.rs:700-706 and authorization_events.rs:215-226: MissingCredential(1)<->Missing(2), InvalidCredential(2)<->Invalid(3), Unauthenticated(3)<->Unauthenticated(4). Fires at INSERT, not COMMIT. - Extend authorization_denial_attempt_guard_v1() to compare semantic_fingerprint between event and denial attempt in both firing directions, raising 23514 'authorization_denial_attempt_semantic_binding' on mismatch. Carl's mismatched-reason and mismatched-fingerprint cross-attachments are now fully closed. IMPORTANT 2 (concurrency regression): Replace the 10ms-sleep approach with a tokio::sync::Barrier(2) that holds both connections after BEGIN and before INSERT. Both race to pg_advisory_xact_lock; one blocks, the winner commits, the loser sees MAX=102 and fails with 23514 (not 23505). XOR assertion proves exactly one INSERT succeeds, and the loser's 23514 (not PK 23505) proves the advisory lock — not just PK uniqueness — is the serialization mechanism. Contradictory comments fixed. MINORs (folded in): - Fix stale test doc comment claiming effective_at must advance (it does not; the downstream constructor stamps Unix epoch for immediate policy). - Fix equal-revision comment incorrectly claiming different policy_digest avoids the PK; the PK is (community_id, policy_revision). CI fmt failure: cargo fmt --all run; whitespace-only reformatting of some query blocks in migration.rs. Regressions added/updated: - B4: denial_reason/reason_code mapping violation rejected at INSERT by the immediate CHECK (23514 from authorization_denial_reason_reason_code_binding). - B5: semantic_fingerprint mismatch between event and denial attempt rejected at COMMIT by the deferred guard (23514 from authorization_denial_attempt_semantic_binding). Byte-parity (extraction+cmp): - authorization_events table: 3394 bytes, migration == schema.sql - authorization_authentication_denial_attempts table: 2062 bytes, migration == schema.sql - authorization_denial_attempt_guard_v1(): 5579 bytes, migration == schema.sql - identity_enrollment_policy_revision_guard_v1(): 1113 bytes, migration == schema.sql - authorization_admission_result_guard_v1(): 2164 bytes, migration == schema.sql All five NIP-FI tests green locally (run in isolation to avoid pre-existing pool-state flakiness in the full suite). Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Hayt <9e1c23a3fd83f61da34420e4e88ff1b16e45cafcc0cd9019eb07d4ecfa8ca9b0@buzz.block.builderlab.xyz>
This commit is contained in:
@@ -2977,6 +2977,12 @@ CREATE TABLE authorization_events (
|
||||
),
|
||||
correlation_id UUID NOT NULL,
|
||||
attempt_id UUID NOT NULL,
|
||||
-- Redaction-safe pre-authentication denial identity. Present and non-zero
|
||||
-- for kind-9 events; NULL for all other event kinds. Binds the event to the
|
||||
-- exact denial attempt's semantic_fingerprint (intent_digest) for exact replay.
|
||||
semantic_fingerprint BYTEA CHECK (
|
||||
semantic_fingerprint IS NULL OR octet_length(semantic_fingerprint) = 32
|
||||
),
|
||||
occurred_at TIMESTAMPTZ NOT NULL,
|
||||
accepted_at TIMESTAMPTZ NOT NULL DEFAULT transaction_timestamp(),
|
||||
canonical_envelope BYTEA NOT NULL CONSTRAINT authorization_events_envelope_size CHECK (
|
||||
@@ -3002,6 +3008,13 @@ CREATE TABLE authorization_events (
|
||||
CHECK (
|
||||
(actor_kind = 4 AND actor_fingerprint IS NULL AND subject_fingerprint IS NULL)
|
||||
OR (actor_kind IN (1, 2, 3) AND actor_fingerprint IS NOT NULL)
|
||||
),
|
||||
-- Kind-9 (pre-auth denial) events must carry a non-zero semantic_fingerprint;
|
||||
-- all other event kinds must not.
|
||||
CHECK (
|
||||
(event_kind = 9 AND semantic_fingerprint IS NOT NULL
|
||||
AND semantic_fingerprint <> decode(repeat('00', 32), 'hex'))
|
||||
OR (event_kind <> 9 AND semantic_fingerprint IS NULL)
|
||||
)
|
||||
);
|
||||
|
||||
@@ -3037,6 +3050,13 @@ CREATE TABLE authorization_authentication_denial_attempts (
|
||||
FOREIGN KEY (community_id, operation_id, audit_event_kind, attempt_id)
|
||||
REFERENCES authorization_events (community_id, operation_id, event_kind, attempt_id)
|
||||
DEFERRABLE INITIALLY DEFERRED,
|
||||
-- Canonical denial_reason ↔ reason_code binding: MissingCredential(1)↔Missing(2),
|
||||
-- InvalidCredential(2)↔Invalid(3), Unauthenticated(3)↔Unauthenticated(4).
|
||||
CONSTRAINT authorization_denial_reason_reason_code_binding CHECK (
|
||||
(denial_reason = 1 AND reason_code = 2)
|
||||
OR (denial_reason = 2 AND reason_code = 3)
|
||||
OR (denial_reason = 3 AND reason_code = 4)
|
||||
),
|
||||
CHECK (operation_id <> '00000000-0000-0000-0000-000000000000'::uuid),
|
||||
CHECK (correlation_id <> '00000000-0000-0000-0000-000000000000'::uuid),
|
||||
CHECK (attempt_id <> '00000000-0000-0000-0000-000000000000'::uuid)
|
||||
@@ -3293,14 +3313,18 @@ CREATE TRIGGER authorization_authentication_denial_attempts_no_truncate
|
||||
-- Bidirectional deferred guard: a kind-9 (pre-authentication denial) audit
|
||||
-- event must commit with exactly one denial attempt; a denial attempt must
|
||||
-- commit with its audit event present, kind-9, and matching semantic
|
||||
-- coordinates (correlation_id and reason_code). Both directions deferred so
|
||||
-- event and attempt may be inserted in any order inside one transaction.
|
||||
-- coordinates (correlation_id, reason_code, and semantic_fingerprint). Both
|
||||
-- directions deferred so event and attempt may be inserted in any order inside
|
||||
-- one transaction. The static denial_reason↔reason_code mapping is enforced
|
||||
-- by an immediate CHECK on the denial attempt table; the guard enforces the
|
||||
-- matching semantic coordinates between event and attempt.
|
||||
CREATE FUNCTION authorization_denial_attempt_guard_v1()
|
||||
RETURNS TRIGGER AS $$
|
||||
DECLARE
|
||||
found_event_kind SMALLINT;
|
||||
found_correlation_id UUID;
|
||||
found_reason_code SMALLINT;
|
||||
found_semantic_fingerprint BYTEA;
|
||||
attempt_count BIGINT;
|
||||
BEGIN
|
||||
IF TG_TABLE_NAME = 'authorization_events' THEN
|
||||
@@ -3323,8 +3347,8 @@ BEGIN
|
||||
END IF;
|
||||
|
||||
-- Verify semantic coordinates match between event and denial attempt.
|
||||
SELECT correlation_id, reason_code
|
||||
INTO found_correlation_id, found_reason_code
|
||||
SELECT correlation_id, reason_code, semantic_fingerprint
|
||||
INTO found_correlation_id, found_reason_code, found_semantic_fingerprint
|
||||
FROM authorization_authentication_denial_attempts
|
||||
WHERE community_id = NEW.community_id
|
||||
AND audit_event_id = NEW.event_id;
|
||||
@@ -3344,11 +3368,20 @@ BEGIN
|
||||
USING ERRCODE = 'check_violation',
|
||||
CONSTRAINT = 'authorization_denial_attempt_semantic_binding';
|
||||
END IF;
|
||||
|
||||
IF found_semantic_fingerprint IS DISTINCT FROM NEW.semantic_fingerprint THEN
|
||||
RAISE EXCEPTION
|
||||
'denial attempt semantic_fingerprint does not match event semantic_fingerprint for event %',
|
||||
NEW.event_id
|
||||
USING ERRCODE = 'check_violation',
|
||||
CONSTRAINT = 'authorization_denial_attempt_semantic_binding';
|
||||
END IF;
|
||||
ELSE
|
||||
-- Firing from the denial-attempt side: verify the audit event is kind-9
|
||||
-- and that exactly one denial attempt references it.
|
||||
SELECT event_kind, correlation_id, reason_code
|
||||
INTO found_event_kind, found_correlation_id, found_reason_code
|
||||
SELECT event_kind, correlation_id, reason_code, semantic_fingerprint
|
||||
INTO found_event_kind, found_correlation_id, found_reason_code,
|
||||
found_semantic_fingerprint
|
||||
FROM authorization_events
|
||||
WHERE community_id = NEW.community_id
|
||||
AND event_id = NEW.audit_event_id;
|
||||
@@ -3386,6 +3419,14 @@ BEGIN
|
||||
CONSTRAINT = 'authorization_denial_attempt_semantic_binding';
|
||||
END IF;
|
||||
|
||||
IF found_semantic_fingerprint IS DISTINCT FROM NEW.semantic_fingerprint THEN
|
||||
RAISE EXCEPTION
|
||||
'denial attempt semantic_fingerprint does not match event semantic_fingerprint for event %',
|
||||
NEW.audit_event_id
|
||||
USING ERRCODE = 'check_violation',
|
||||
CONSTRAINT = 'authorization_denial_attempt_semantic_binding';
|
||||
END IF;
|
||||
|
||||
SELECT count(*) INTO attempt_count
|
||||
FROM authorization_authentication_denial_attempts
|
||||
WHERE community_id = NEW.community_id
|
||||
|
||||
Reference in New Issue
Block a user