mirror of
https://github.com/block/buzz.git
synced 2026-10-31 06:21:34 +00:00
fix(schema): address Thufir pass 1 blockers on NIP-FI PR 2
Three IMPORTANT findings fixed:
1. Drop effective_at monotonicity from policy revision guard
identity_enrollment_policy_revision_guard_v1() now enforces only
strict-greater policy_revision per community. The effective_at check
had no NIP-FI basis (FI-INV-06 defines assertion_policy_id stability,
not revision chronology) and would reject legitimately-sequenced
revisions — the downstream constructor stamps every immediately-
effective revision with Unix epoch, so revision 2 would fail after
revision 1 under the old guard.
2. Bind semantic coordinates in denial attempt guard
authorization_authentication_denial_attempts gains attempt_id UUID
NOT NULL with a deferred FK to authorization_events on
(community_id, operation_id, event_kind, attempt_id). The guard
authorization_denial_attempt_guard_v1() now additionally compares
correlation_id and reason_code between the event and its denial
attempt row, raising check_violation (23514) with named constraint
authorization_denial_attempt_semantic_binding on mismatch. This
closes the Carl finding 3 gap: a kind-9 event for correlation A /
reason X can no longer be paired with a denial row carrying
correlation B / reason Y.
3. Rewrite regression tests to prove the contracts
- Policy test: seeds a gap (100->101) then inserts unused revision 99
and asserts 23514 from the named guard (not 23505, which would
fire on a PK duplicate and not prove the monotonic comparison).
Adds a two-transaction concurrency regression: two distinct forward
revisions (102, 103) race through separate connections; both commit
because the advisory lock serializes them and each is valid.
- Denial test: replaces the ambiguous negative-B (23503 OR 23514)
with three single-coordinate-mismatch cases attributed to the named
guard: B1 correlation_id mismatch (23514), B2 reason_code mismatch
(23514), B3 attempt_id mismatch (23503 via deferred FK).
- Admission test: adds negative C -- mismatched request_fingerprint
rejected by the immediate composite FK (23503) at INSERT, proving
the coordinate binding half of Carl finding 2.
All four changed files byte-identical between migration files and
schema/schema.sql (verified by extraction+cmp). All 5 NIP-FI tests,
admin_schema_parity, and 2 unit tests pass.
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:
@@ -2309,16 +2309,14 @@ CREATE INDEX identity_lifecycle_selectors_asserted_history
|
||||
(community_id, asserted_history_id, selector_kind);
|
||||
|
||||
-- Serializes policy-revision inserts per community: each new revision must
|
||||
-- strictly exceed the current maximum, and each effective_at must strictly
|
||||
-- exceed the current maximum effective_at (FI-INV-06 — stable assertion
|
||||
-- policy; a revision that moves either coordinate backward is incoherent).
|
||||
-- The per-community advisory lock prevents two concurrent writers from both
|
||||
-- passing a plain SELECT MAX() check and committing conflicting revisions.
|
||||
-- strictly exceed the current maximum (FI-INV-06 — stable assertion policy
|
||||
-- anchor; a backfilled or replayed revision is incoherent). The per-community
|
||||
-- advisory lock prevents two concurrent writers from both passing a plain
|
||||
-- SELECT MAX() check and committing conflicting revisions.
|
||||
CREATE FUNCTION identity_enrollment_policy_revision_guard_v1() RETURNS TRIGGER AS $$
|
||||
DECLARE
|
||||
lock_key BIGINT;
|
||||
max_revision BIGINT;
|
||||
max_effective_at TIMESTAMPTZ;
|
||||
BEGIN
|
||||
-- Acquire a per-community exclusive transaction-scoped advisory lock so
|
||||
-- that concurrent insertions serialize here. The key is a stable hash of
|
||||
@@ -2329,8 +2327,8 @@ BEGIN
|
||||
);
|
||||
PERFORM pg_advisory_xact_lock(lock_key);
|
||||
|
||||
SELECT MAX(policy_revision), MAX(effective_at)
|
||||
INTO max_revision, max_effective_at
|
||||
SELECT MAX(policy_revision)
|
||||
INTO max_revision
|
||||
FROM identity_enrollment_policies
|
||||
WHERE community_id = NEW.community_id;
|
||||
|
||||
@@ -2344,16 +2342,6 @@ BEGIN
|
||||
CONSTRAINT = 'identity_enrollment_policy_revision_monotonic';
|
||||
END IF;
|
||||
|
||||
IF max_effective_at IS NOT NULL
|
||||
AND NEW.effective_at <= max_effective_at
|
||||
THEN
|
||||
RAISE EXCEPTION
|
||||
'effective_at % does not strictly exceed current maximum % for community %',
|
||||
NEW.effective_at, max_effective_at, NEW.community_id
|
||||
USING ERRCODE = 'check_violation',
|
||||
CONSTRAINT = 'identity_enrollment_policy_revision_monotonic';
|
||||
END IF;
|
||||
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
@@ -3031,6 +3019,7 @@ CREATE TABLE authorization_authentication_denial_attempts (
|
||||
reason_code SMALLINT NOT NULL CHECK (
|
||||
reason_code IN (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16)
|
||||
),
|
||||
attempt_id UUID NOT NULL,
|
||||
audit_event_id UUID NOT NULL,
|
||||
audit_event_kind SMALLINT NOT NULL DEFAULT 9 CHECK (audit_event_kind = 9),
|
||||
recorded_at TIMESTAMPTZ NOT NULL DEFAULT transaction_timestamp(),
|
||||
@@ -3045,8 +3034,12 @@ CREATE TABLE authorization_authentication_denial_attempts (
|
||||
FOREIGN KEY (community_id, audit_event_id, audit_event_kind, operation_id)
|
||||
REFERENCES authorization_events (community_id, event_id, event_kind, operation_id)
|
||||
DEFERRABLE INITIALLY DEFERRED,
|
||||
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,
|
||||
CHECK (operation_id <> '00000000-0000-0000-0000-000000000000'::uuid),
|
||||
CHECK (correlation_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)
|
||||
);
|
||||
|
||||
-- Exact per-operation authority-version attribution for restore. Empty
|
||||
@@ -3299,12 +3292,15 @@ 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 and kind-9. Both directions deferred so
|
||||
-- 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.
|
||||
CREATE FUNCTION authorization_denial_attempt_guard_v1()
|
||||
RETURNS TRIGGER AS $$
|
||||
DECLARE
|
||||
found_event_kind SMALLINT;
|
||||
found_correlation_id UUID;
|
||||
found_reason_code SMALLINT;
|
||||
attempt_count BIGINT;
|
||||
BEGIN
|
||||
IF TG_TABLE_NAME = 'authorization_events' THEN
|
||||
@@ -3325,10 +3321,34 @@ BEGIN
|
||||
USING ERRCODE = 'check_violation',
|
||||
CONSTRAINT = 'authorization_denial_attempt_event_cardinality';
|
||||
END IF;
|
||||
|
||||
-- Verify semantic coordinates match between event and denial attempt.
|
||||
SELECT correlation_id, reason_code
|
||||
INTO found_correlation_id, found_reason_code
|
||||
FROM authorization_authentication_denial_attempts
|
||||
WHERE community_id = NEW.community_id
|
||||
AND audit_event_id = NEW.event_id;
|
||||
|
||||
IF found_correlation_id IS DISTINCT FROM NEW.correlation_id THEN
|
||||
RAISE EXCEPTION
|
||||
'denial attempt correlation_id % does not match event correlation_id % for event %',
|
||||
found_correlation_id, NEW.correlation_id, NEW.event_id
|
||||
USING ERRCODE = 'check_violation',
|
||||
CONSTRAINT = 'authorization_denial_attempt_semantic_binding';
|
||||
END IF;
|
||||
|
||||
IF found_reason_code IS DISTINCT FROM NEW.reason_code THEN
|
||||
RAISE EXCEPTION
|
||||
'denial attempt reason_code % does not match event reason_code % for event %',
|
||||
found_reason_code, NEW.reason_code, 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 INTO found_event_kind
|
||||
SELECT event_kind, correlation_id, reason_code
|
||||
INTO found_event_kind, found_correlation_id, found_reason_code
|
||||
FROM authorization_events
|
||||
WHERE community_id = NEW.community_id
|
||||
AND event_id = NEW.audit_event_id;
|
||||
@@ -3349,6 +3369,23 @@ BEGIN
|
||||
CONSTRAINT = 'authorization_denial_attempt_event_kind';
|
||||
END IF;
|
||||
|
||||
-- Verify semantic coordinates match.
|
||||
IF found_correlation_id IS DISTINCT FROM NEW.correlation_id THEN
|
||||
RAISE EXCEPTION
|
||||
'denial attempt correlation_id % does not match event correlation_id % for event %',
|
||||
NEW.correlation_id, found_correlation_id, NEW.audit_event_id
|
||||
USING ERRCODE = 'check_violation',
|
||||
CONSTRAINT = 'authorization_denial_attempt_semantic_binding';
|
||||
END IF;
|
||||
|
||||
IF found_reason_code IS DISTINCT FROM NEW.reason_code THEN
|
||||
RAISE EXCEPTION
|
||||
'denial attempt reason_code % does not match event reason_code % for event %',
|
||||
NEW.reason_code, found_reason_code, 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