Connect policy-gated browser review and audit runtime
Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a07ff8-19d0-7820-b4d0-1353833cb7fc
This commit is contained in:
parent
2cc32168ac
commit
83849b75d4
35 changed files with 2381 additions and 83 deletions
|
|
@ -27,7 +27,7 @@ from .provenance import Claim, Route, assert_human_control_dischargeable
|
|||
from .stance import resolve
|
||||
from .records import disposition_from, dumps, memo_from, presentation_from
|
||||
|
||||
SCHEMA_VERSION = 1
|
||||
SCHEMA_VERSION = 2
|
||||
_SCHEMA = """
|
||||
CREATE TABLE documents (digest TEXT PRIMARY KEY, media_type TEXT NOT NULL, content BLOB NOT NULL);
|
||||
CREATE TABLE memos (id TEXT NOT NULL, version INTEGER NOT NULL, body TEXT NOT NULL, PRIMARY KEY(id,version));
|
||||
|
|
@ -99,8 +99,19 @@ class Store:
|
|||
triggers += (f"CREATE TRIGGER immutable_{table}_{operation} BEFORE {operation} ON {table} "
|
||||
"BEGIN SELECT RAISE(ABORT,'immutable evidence'); END;\n")
|
||||
db.executescript("BEGIN IMMEDIATE;" + _SCHEMA + triggers + "PRAGMA user_version=1; COMMIT;")
|
||||
elif version != SCHEMA_VERSION:
|
||||
elif version not in (1, SCHEMA_VERSION):
|
||||
raise StoreError("unsupported evidence schema version")
|
||||
if version in (0, 1):
|
||||
db.executescript("""BEGIN IMMEDIATE;
|
||||
CREATE TABLE policy_observations (id TEXT PRIMARY KEY, at TEXT NOT NULL,
|
||||
request TEXT NOT NULL, decision TEXT, outcome TEXT NOT NULL,
|
||||
usable_until REAL NOT NULL, response_sha256 TEXT,
|
||||
decision_attributable INTEGER NOT NULL CHECK(decision_attributable=0));
|
||||
CREATE TRIGGER immutable_policy_observations_UPDATE BEFORE UPDATE ON policy_observations
|
||||
BEGIN SELECT RAISE(ABORT,'immutable evidence'); END;
|
||||
CREATE TRIGGER immutable_policy_observations_DELETE BEFORE DELETE ON policy_observations
|
||||
BEGIN SELECT RAISE(ABORT,'immutable evidence'); END;
|
||||
PRAGMA user_version=2; COMMIT;""")
|
||||
|
||||
@contextmanager
|
||||
def _connection(self):
|
||||
|
|
@ -168,9 +179,35 @@ class Store:
|
|||
raise EvidenceUnavailable("informed-decision cannot produce the named memo version")
|
||||
return memo_from(json.loads(row["body"]))
|
||||
|
||||
def memo(self, memo_id):
|
||||
def memo(self, memo_id, version=None):
|
||||
with self._connection() as db:
|
||||
return self._memo(db, memo_id)
|
||||
return self._memo(db, memo_id, version)
|
||||
|
||||
def observe_policy(self, observation):
|
||||
request = json.loads(observation.request_json)
|
||||
_text(request["id"], "policy observation id")
|
||||
with self._transaction() as db:
|
||||
db.execute("INSERT INTO policy_observations VALUES (?,?,?,?,?,?,?,0)",
|
||||
(request["id"], _now(), observation.request_json, observation.decision_json,
|
||||
observation.outcome, observation.usable_until, observation.response_sha256))
|
||||
return request["id"]
|
||||
|
||||
def policy_observations(self):
|
||||
with self._connection() as db:
|
||||
return [dict(r) for r in db.execute("SELECT * FROM policy_observations ORDER BY rowid")]
|
||||
|
||||
def _guard_policy(self, db, policy_id, memo, subject, action):
|
||||
if policy_id is None:
|
||||
return # Existing internal domain API; the browser always supplies one.
|
||||
row = db.execute("SELECT * FROM policy_observations WHERE id=?", (policy_id,)).fetchone()
|
||||
if row is None or row["outcome"] != "allow" or row["usable_until"] <= time.time():
|
||||
raise Conflict("a current recorded policy observation is required")
|
||||
request = json.loads(row["request"])
|
||||
if (request["action"] != action or request["subject"]["id"] != subject
|
||||
or request["resource"]["id"] != "memo:" + memo.id
|
||||
or request["context"] != {"memo_version": memo.version, "approval_id": memo.approval_id,
|
||||
"approval_binding_digest": memo.approval_binding_digest}):
|
||||
raise Conflict("policy observation does not name this act")
|
||||
|
||||
def _presentation(self, db, presentation_id):
|
||||
row = db.execute("SELECT body FROM presentations WHERE id=?", (presentation_id,)).fetchone()
|
||||
|
|
@ -202,7 +239,8 @@ class Store:
|
|||
db.execute("INSERT INTO outbox(id) VALUES (?)", (commitment.id,))
|
||||
return commitment.id
|
||||
|
||||
def present(self, memo_id, *, principal_sub, tenant, principal_type, awareness=None):
|
||||
def present(self, memo_id, *, principal_sub, tenant, principal_type, awareness=None,
|
||||
expected_version=None, policy_id=None, approval_binding=None):
|
||||
_text(principal_sub, "principal")
|
||||
if (not isinstance(tenant, Claim) or tenant.value != "tenant:platform"
|
||||
or tenant.route not in (Route.DIRECTORY, Route.REGISTRATION)
|
||||
|
|
@ -210,17 +248,26 @@ class Store:
|
|||
raise ValueError("platform tenant and principal provenance are required")
|
||||
with self._transaction() as db:
|
||||
memo = self._memo(db, memo_id)
|
||||
if expected_version is not None and memo.version != expected_version:
|
||||
raise Conflict("memo changed after the entitlement check")
|
||||
self._guard_policy(db, policy_id, memo, principal_sub, "read")
|
||||
if approval_binding is not None and approval_binding.get("digest") != memo.approval_binding_digest:
|
||||
raise Conflict("observed act binding does not match this memo")
|
||||
presentation = render(memo, principal_sub=principal_sub, tenant=tenant,
|
||||
principal_type=principal_type, awareness=awareness)
|
||||
content = {"memo": json.loads(dumps(memo)), "presentation": json.loads(dumps(presentation)),
|
||||
"binding_document": memo.binding_document(), "awareness_document": memo.awareness_document(awareness)}
|
||||
if policy_id is not None:
|
||||
content["policy_observation_id"] = policy_id
|
||||
if approval_binding is not None:
|
||||
content["approval_binding"] = approval_binding # Observed act, never approval validity/state.
|
||||
db.execute("INSERT INTO presentations VALUES (?,?,?,?)",
|
||||
(presentation.id, memo.id, memo.version, dumps(presentation)))
|
||||
commitment = commit_presentation(presentation, custody="informed-decision:presentations:" + presentation.id)
|
||||
self._event(db, commitment, content)
|
||||
return presentation
|
||||
|
||||
def acknowledge(self, presentation_id, actor: Actor, highlight_ids):
|
||||
def acknowledge(self, presentation_id, actor: Actor, highlight_ids, *, policy_id=None):
|
||||
ids = frozenset(highlight_ids)
|
||||
with self._transaction() as db:
|
||||
p = self._presentation(db, presentation_id)
|
||||
|
|
@ -229,6 +276,7 @@ class Store:
|
|||
raise DispositionRefused("G_ACTOR", "actor did not receive this presentation")
|
||||
if memo.version != p.memo_version:
|
||||
raise DispositionRefused("G_PRES", "presentation is stale")
|
||||
self._guard_policy(db, policy_id, memo, actor.sub, "acknowledge")
|
||||
if ids - {h.id for h in memo.highlights}:
|
||||
raise DispositionRefused("G_ACK", "unknown highlight")
|
||||
fresh = ids - p.acked_highlight_ids
|
||||
|
|
@ -239,11 +287,11 @@ class Store:
|
|||
db.execute("INSERT INTO acknowledgments VALUES (?,?,?)", (p.id, highlight_id, at))
|
||||
p = replace(p, acked_highlight_ids=p.acked_highlight_ids | fresh)
|
||||
c = commit_presentation(p, custody="informed-decision:presentations:" + p.id)
|
||||
self._event(db, c, {"presentation": json.loads(dumps(p))},
|
||||
self._event(db, c, {"presentation": json.loads(dumps(p)), "policy_observation_id": policy_id},
|
||||
{"event_kind": "acknowledgment", "acknowledged_at": at})
|
||||
return p
|
||||
|
||||
def record_disposition(self, presentation_id, actor, verb, *, operation_id, reasons=(), note=None):
|
||||
def record_disposition(self, presentation_id, actor, verb, *, operation_id, reasons=(), note=None, policy_id=None):
|
||||
_text(operation_id, "operation id")
|
||||
if note is not None and (not isinstance(note, str) or len(note) > 8192):
|
||||
raise ValueError("disposition note is too large or invalid")
|
||||
|
|
@ -258,6 +306,7 @@ class Store:
|
|||
return disposition_from(json.loads(existing["body"]))
|
||||
p = self._presentation(db, presentation_id)
|
||||
memo = self._memo(db, p.memo_id)
|
||||
self._guard_policy(db, policy_id, memo, actor.sub, verb.value)
|
||||
d = record(memo, p, verb, actor, reasons=tuple(reasons), note=note)
|
||||
if verb in BINDING_VERBS:
|
||||
if p.principal_type is None:
|
||||
|
|
@ -272,11 +321,12 @@ class Store:
|
|||
if verb is Verb.ACCEPT:
|
||||
db.execute("INSERT INTO submissions(disposition_id,approval_id,subject) VALUES (?,?,?)", (d.id, memo.approval_id, actor.sub))
|
||||
c = commit_disposition(d, custody="informed-decision:dispositions:" + d.id)
|
||||
self._event(db, c, {"disposition": json.loads(dumps(d)), "presentation": json.loads(dumps(p))},
|
||||
self._event(db, c, {"disposition": json.loads(dumps(d)), "presentation": json.loads(dumps(p)),
|
||||
"policy_observation_id": policy_id},
|
||||
{"submission_state": "prepared"} if verb is Verb.ACCEPT else None)
|
||||
return d
|
||||
|
||||
def begin_submission(self, disposition_id):
|
||||
def begin_submission(self, disposition_id, *, policy_id=None):
|
||||
"""Reserve one external attempt, AFTER the caller's fresh policy check.
|
||||
|
||||
No network occurs in this store. A crash after this reservation stays
|
||||
|
|
@ -290,6 +340,7 @@ class Store:
|
|||
p = self._presentation(db, d.presentation_id)
|
||||
if self._memo(db, p.memo_id).version != p.memo_version:
|
||||
raise DispositionRefused("G_PRES", "presentation is stale")
|
||||
self._guard_policy(db, policy_id, self._memo(db, p.memo_id), d.actor.sub, "accept")
|
||||
attempt = str(uuid.uuid4())
|
||||
db.execute("UPDATE submissions SET state='in_flight',attempt=? WHERE disposition_id=?", (attempt, disposition_id))
|
||||
return attempt
|
||||
|
|
@ -323,6 +374,27 @@ class Store:
|
|||
row = db.execute("SELECT * FROM submissions WHERE disposition_id=?", (disposition_id,)).fetchone()
|
||||
return dict(row) if row else None
|
||||
|
||||
def intent_for(self, approval_id, subject):
|
||||
with self._connection() as db:
|
||||
row = db.execute("SELECT s.*,d.presentation_id,d.operation_id FROM submissions s "
|
||||
"JOIN dispositions d ON d.id=s.disposition_id WHERE s.approval_id=? AND s.subject=?",
|
||||
(approval_id, subject)).fetchone()
|
||||
return dict(row) if row else None
|
||||
|
||||
def dispositions_for(self, presentation_id):
|
||||
with self._connection() as db:
|
||||
return [disposition_from(json.loads(r[0])) for r in db.execute(
|
||||
"SELECT body FROM dispositions WHERE presentation_id=? ORDER BY rowid", (presentation_id,))]
|
||||
|
||||
def presentation_content(self, presentation_id):
|
||||
with self._connection() as db:
|
||||
row = db.execute("SELECT content FROM evidence WHERE class=? AND "
|
||||
"json_extract(envelope,'$.data.presentation_id')=? ORDER BY rowid LIMIT 1",
|
||||
(EventClass.PRESENTATION.value, presentation_id)).fetchone()
|
||||
if row is None:
|
||||
raise EvidenceUnavailable("informed-decision cannot produce the presentation content")
|
||||
return json.loads(row[0])
|
||||
|
||||
def retrieve_presentation(self, presentation_id):
|
||||
"""Internal custody retrieval, not a browser export/entitlement route."""
|
||||
with self._connection() as db:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue