Ask Flex Auth list, not read, for each overview row (INFD-WP-0004-T03)
The overview now requests the new list action and its rows carry only the memo id, version, question, live approval status and the person's own responses without notes. Opening a memo still asks read; every act asks its own action. The fixture Flex Auth package admits list under a re-derived pin. Not deployable until flex-auth answers INFD-IN-0008. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Assistant: claude-code Assistant-Model: opus Assistant-Process: 359683@bnt-lap001 Assistant-Session: eebdc939-7a9b-4e50-9d39-c8437e8a14ec
This commit is contained in:
parent
180370ed44
commit
98c8de078e
10 changed files with 85 additions and 33 deletions
|
|
@ -16,7 +16,7 @@ from .http_transport import JSONTransport, TransportError, fixed_origin
|
|||
from .oidc import HumanSession
|
||||
|
||||
DIGEST = re.compile(r"sha256:[0-9a-f]{64}")
|
||||
ACTIONS = frozenset({"read", "acknowledge", "accept", "return", "discuss", "decline"})
|
||||
ACTIONS = frozenset({"read", "list", "acknowledge", "accept", "return", "discuss", "decline"})
|
||||
CONTRACT = "flex-auth.decision-record.v1"
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -39,13 +39,15 @@ OPEN_STATUSES = ("requested", "approved")
|
|||
|
||||
@dataclass(frozen=True)
|
||||
class OverviewRow:
|
||||
"""Only what a `list` allow may disclose: never brief, packet, highlights or notes."""
|
||||
memo_id: str
|
||||
group: str
|
||||
memo: object = None
|
||||
question: str | None = None
|
||||
version: int | None = None
|
||||
engine_status: str | None = None
|
||||
reason: str | None = None
|
||||
intent: dict | None = None
|
||||
history: tuple = ()
|
||||
approved_at: str | None = None
|
||||
history: tuple = () # ({"verb", "memo_version", "at", "state"}, ...), the person's own
|
||||
|
||||
|
||||
class ReviewController:
|
||||
|
|
@ -142,8 +144,9 @@ class ReviewController:
|
|||
def overview(self, session):
|
||||
"""The signed-in person's memos, classified. Never presents, binds or stores engine state.
|
||||
|
||||
Every row passes its own fresh PDP read before any memo content is
|
||||
returned; a refused or failed row keeps only its id. Engine status is
|
||||
Every row passes its own fresh PDP `list` decision before its question
|
||||
is returned; a refused or failed row keeps only its id. `list` never
|
||||
stands in for `read`: opening or acting on a memo asks for its own. Engine status is
|
||||
read live by the approval id the memo already carries — never a poll.
|
||||
"""
|
||||
self._session(session)
|
||||
|
|
@ -152,7 +155,7 @@ class ReviewController:
|
|||
|
||||
def _overview_row(self, session, memo):
|
||||
try:
|
||||
_, observation = self._authorize(session, memo, "read")
|
||||
_, observation = self._authorize(session, memo, "list")
|
||||
except ReviewError as exc:
|
||||
if exc.code == "session_expired":
|
||||
raise
|
||||
|
|
@ -188,7 +191,10 @@ class ReviewController:
|
|||
group = "open"
|
||||
else:
|
||||
group = "closed"
|
||||
return OverviewRow(memo.id, group, memo, status, reason, intent, history)
|
||||
return OverviewRow(memo.id, group, memo.question, memo.version, status, reason,
|
||||
intent.get("approved_at") if intent else None,
|
||||
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))
|
||||
|
||||
def acknowledge(self, session, presentation_id, highlight_ids):
|
||||
memo, p = self._presentation(session, presentation_id, current=True)
|
||||
|
|
|
|||
|
|
@ -143,20 +143,20 @@ OVERVIEW_REASONS = {
|
|||
|
||||
def _overview_row(row):
|
||||
link = "/review?memo_id=" + quote(row.memo_id, safe="")
|
||||
if row.memo is None:
|
||||
if row.question is None:
|
||||
reason = OVERVIEW_REASONS.get(row.reason, "The permission check could not complete. Reload later.")
|
||||
return (f'<div class="record"><strong>{text(row.memo_id)}</strong>'
|
||||
f'<p class="muted">{text(reason)}</p></div>')
|
||||
facts = [f'Memo {text(row.memo_id)} · version {row.memo.version}']
|
||||
facts = [f'Memo {text(row.memo_id)} · version {row.version}']
|
||||
if row.engine_status:
|
||||
facts.append(f'Approval status: {text(row.engine_status)}')
|
||||
elif row.reason:
|
||||
facts.append(text(OVERVIEW_REASONS.get(row.reason, "Approval status is unavailable.")))
|
||||
if row.intent and row.intent.get("approved_at"):
|
||||
facts.append(f'Entry recorded {text(row.intent["approved_at"])}')
|
||||
history = ''.join(f'<li>{text(d.verb.value.capitalize())} · version {d.memo_version} · {text(d.at)}'
|
||||
+ (f' · {text(s["state"])}' if s else '') + '</li>' for d, s in row.history)
|
||||
return (f'<div class="record"><a href="{link}"><strong>{text(row.memo.question)}</strong></a>'
|
||||
if row.approved_at:
|
||||
facts.append(f'Entry recorded {text(row.approved_at)}')
|
||||
history = ''.join(f'<li>{text(h["verb"].capitalize())} · version {h["memo_version"]} · {text(h["at"])}'
|
||||
+ (f' · {text(h["state"])}' if h["state"] else '') + '</li>' for h in row.history)
|
||||
return (f'<div class="record"><a href="{link}"><strong>{text(row.question)}</strong></a>'
|
||||
f'<p class="muted">{" · ".join(facts)}</p>'
|
||||
+ (f'<details><summary>Your responses</summary><ul>{history}</ul></details>' if history else '')
|
||||
+ '</div>')
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue