From 13ca3a5372e0d73fd5c61ae024e7cae4ef5a615e Mon Sep 17 00:00:00 2001 From: tegwick Date: Sun, 27 Sep 2026 20:38:14 +0200 Subject: [PATCH] Surface the requesting party on the decision overview The single-memo review page already fetched and labeled approval-engine's binding.principal "Requesting party" -- the party the approval was issued on behalf of, distinct from memo.binding.principal (the reviewer being bound). The overview/list page fetched the same live approval record for status but never surfaced this field. OverviewRow gains requesting_party, populated only when the fetched binding's digest matches the memo's pinned digest (same trust condition status already uses); ui.py renders it as "Requested by: ...". No new approval-engine call or flex-auth scope needed -- the record was already being read. Co-Authored-By: Claude Sonnet 5 Assistant: claude-code Assistant-Model: sonnet Assistant-Process: 169987@bnt-lap001 Assistant-Session: 322ef1ef-9048-4021-8570-b6d6f6347999 --- informed_decision/review.py | 6 ++++-- informed_decision/ui.py | 2 ++ tests/test_review_controller.py | 21 +++++++++++++++++++++ 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/informed_decision/review.py b/informed_decision/review.py index d45dc57..49d718d 100644 --- a/informed_decision/review.py +++ b/informed_decision/review.py @@ -47,6 +47,7 @@ class OverviewRow: engine_status: str | None = None reason: str | None = None approved_at: str | None = None + requesting_party: str | None = None history: tuple = () # ({"verb", "memo_version", "at", "state"}, ...), the person's own @@ -160,11 +161,12 @@ class ReviewController: if exc.code == "session_expired": raise return OverviewRow(memo.id, "unavailable", reason=exc.code) - status, reason = None, None + status, reason, requesting_party = None, None, None try: current = self.approval_factory(session).get_approval(memo.approval_id) if current["binding"]["digest"] == memo.approval_binding_digest: status = current["status"] + requesting_party = current["binding"].get("principal") else: reason = "binding_changed" except (ApprovalEngineError, ValueError): @@ -192,7 +194,7 @@ class ReviewController: else: group = "closed" return OverviewRow(memo.id, group, memo.question, memo.version, status, reason, - intent.get("approved_at") if intent else None, + intent.get("approved_at") if intent else None, requesting_party, tuple({"verb": d.verb.value, "memo_version": d.memo_version, "at": d.at, "state": s["state"] if s else None} for d, s in history)) diff --git a/informed_decision/ui.py b/informed_decision/ui.py index 930d839..4e185a5 100644 --- a/informed_decision/ui.py +++ b/informed_decision/ui.py @@ -148,6 +148,8 @@ def _overview_row(row): return (f'
{text(row.memo_id)}' f'

{text(reason)}

') facts = [f'Memo {text(row.memo_id)} ยท version {row.version}'] + if row.requesting_party: + facts.append(f'Requested by: {text(row.requesting_party)}') if row.engine_status: facts.append(f'Approval status: {text(row.engine_status)}') elif row.reason: diff --git a/tests/test_review_controller.py b/tests/test_review_controller.py index adc69b9..2d701c1 100644 --- a/tests/test_review_controller.py +++ b/tests/test_review_controller.py @@ -346,9 +346,29 @@ def test_overview_degrades_one_row_when_engine_is_unreachable(review): [row]=controller.overview(session) assert (row.group,row.engine_status,row.reason)==('open',None,'engine_unavailable') assert row.question==memo.question + assert row.requesting_party is None transport.request=original +def test_overview_shows_the_requesting_party(review): + """approval-engine's binding.principal is the party the approval was + issued on behalf of -- a different role from memo.binding.principal, the + reviewer (docs/memo.py's `_binding_document` docstring). The overview + surfaces the former alongside status so a reviewer can tell who asked.""" + controller,session,app,memo,engine,transport=review + [row]=controller.overview(session) + assert row.requesting_party=='factory-fixture' + + +def test_overview_does_not_surface_a_requesting_party_when_binding_changed(review): + controller,session,app,memo,engine,transport=review + stale=replace(memo,version=memo.version+1,approval_binding_digest='sha256:'+'0'*64) + controller.store.save_memo(stale) + [row]=controller.overview(session) + assert row.reason=='binding_changed' + assert row.requesting_party is None + + def test_overview_surfaces_unconfirmed_submission(review): controller,session,app,memo,engine,transport=review;p=opened(review).presentation controller.acknowledge(session,p.id,['h-1']) @@ -379,6 +399,7 @@ def test_home_lists_open_then_accepted_memos_and_keeps_the_manual_form(review): assert r['status']==200 and 'Open for you (1)' in r['body'] and 'Accepted (' not in r['body'] assert 'Approve the synthetic factory delivery?' in r['body'] assert 'Approval status: requested' in r['body'] and 'Open a decision review' in r['body'] + assert 'Requested by: factory-fixture' in r['body'] assert 'private-brief-sentinel' not in r['body'] and 'private-packet-sentinel' not in r['body'] assert len(controller.store.evidence())==0 p=opened(review).presentation;controller.acknowledge(session,p.id,['h-1'])