Record the verified principal type on approver entries (schema v4)
An entry stored subject_id, assurance and evidence_ref but nothing about what kind of principal bound the approval, and subject_id is a naming convention rather than a verified claim. /entries is not restricted by principal type — only /consume is — and the approval-engine-operator client holds approval:approve, so a service can supply approver evidence today. Whether it may is gate-house doctrine; that it is legible is ours. Add entries.principal_type, populate it from the verified token, surface it on the object and the audit evidence path (not the claim, which stays least-disclosure), and migrate v3 stores leaving legacy rows null rather than back-filling a claim nobody made. Also corrects two statements in the requirements issued to informed-decision: agent tokens are not barred from approval:approve, and an empty assurance object is accepted rather than refused. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HyybaE7DUXrWYrhbnESCTe Assistant: claude-code Assistant-Model: opus Assistant-Process: 1275879@bnt-lap001 Assistant-Session: eb464208-f821-41b2-bc5a-a6c33d92a8ad
This commit is contained in:
parent
8b8ada6c4a
commit
31da1af5e4
6 changed files with 160 additions and 20 deletions
|
|
@ -6,7 +6,7 @@ from pathlib import Path
|
|||
import pytest
|
||||
|
||||
from approval_engine.errors import Conflict, Unprocessable
|
||||
from approval_engine.store import Engine
|
||||
from approval_engine.store import LATEST_SCHEMA_VERSION, Engine
|
||||
from tests.conftest import FROZEN, approve, binding, validity
|
||||
|
||||
|
||||
|
|
@ -174,8 +174,8 @@ def test_successor_inherits_the_pdp_path_declaration(engine):
|
|||
assert successor.pdp_path is True
|
||||
|
||||
|
||||
def test_v2_database_migrates_to_v3_preserving_approvals():
|
||||
"""Schema v3 (GH-DEC-2026-008) must not disturb approvals issued under v2."""
|
||||
def test_v2_database_migrates_forward_preserving_approvals():
|
||||
"""A v2 store must upgrade to the current schema without disturbing approvals."""
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
path = Path(tmp) / "v2.sqlite"
|
||||
eng = Engine(path, clock=lambda: FROZEN)
|
||||
|
|
@ -189,7 +189,7 @@ def test_v2_database_migrates_to_v3_preserving_approvals():
|
|||
|
||||
upgraded = Engine(path, clock=lambda: FROZEN)
|
||||
version = int(upgraded._conn().execute("PRAGMA user_version").fetchone()[0])
|
||||
assert version == 3
|
||||
assert version == LATEST_SCHEMA_VERSION
|
||||
survivor = upgraded.get(obj.id)
|
||||
assert survivor.status == "approved"
|
||||
assert survivor.pdp_digest == "sha256:" + "12" * 32
|
||||
|
|
@ -197,4 +197,28 @@ def test_v2_database_migrates_to_v3_preserving_approvals():
|
|||
# a digest that happens to be present
|
||||
assert survivor.pdp_path is False
|
||||
assert upgraded.claim(obj.id)["binding"]["pdp_path"] is False
|
||||
# v4: a legacy entry carries no verified statement about the kind of
|
||||
# principal that bound it, and must not be back-filled into one
|
||||
assert survivor.entries[0]["principal_type"] is None
|
||||
upgraded.close()
|
||||
|
||||
|
||||
def test_v3_entries_migrate_to_v4_without_inventing_a_principal_type():
|
||||
"""The v4 column is added; pre-v4 approver entries stay unclassified."""
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
path = Path(tmp) / "v3.sqlite"
|
||||
eng = Engine(path, clock=lambda: FROZEN)
|
||||
obj = eng.create(binding(), validity())
|
||||
eng.add_entry(obj.id, "user:alice", principal_type="human")
|
||||
eng._conn().execute("ALTER TABLE entries DROP COLUMN principal_type")
|
||||
eng._conn().execute("PRAGMA user_version=3")
|
||||
eng._conn().commit()
|
||||
eng.close()
|
||||
|
||||
upgraded = Engine(path, clock=lambda: FROZEN)
|
||||
assert int(upgraded._conn().execute("PRAGMA user_version").fetchone()[0]) == 4
|
||||
survivor = upgraded.get(obj.id)
|
||||
assert survivor.status == "approved"
|
||||
assert survivor.entries[0]["subject_id"] == "user:alice"
|
||||
assert survivor.entries[0]["principal_type"] is None
|
||||
upgraded.close()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue