feat(bookkeeping): show who committed a verifikat, and mark agent work in Granskning history (#1591)

Flows build plan prereq 3 (provenance display). Pure UI over columns that
have existed since migration 20260619120000:

- types: JournalEntry gains committed_actor_type/committed_actor_label
  (the detail/chain APIs already select('*'), the type just lacked them)
- voucher detail: new "Bokford av" row in the Details card, derived from
  actor type + credential label, with the Bot mark for non-user actors
- Granskning Historik rows get the same actor circle pending rows have
  (Bot vs ClipboardCheck) so agent-originated history reads at a glance
- run-turn: correct the staged_operation params comment (tool-use input
  is a superset of pending_operations.params, not the same values)

Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Jakob Wennberg
2026-08-13 17:01:15 +02:00
committed by GitHub
co-authored by Jakob Wennberg Claude Fable 5
parent 857dd575d0
commit 7881f757a9
7 changed files with 74 additions and 4 deletions
+38 -1
View File
@@ -8,7 +8,7 @@ import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
import { Button } from '@/components/ui/button'
import { AccountNumber } from '@/components/ui/account-number'
import { Textarea } from '@/components/ui/textarea'
import { Loader2, ArrowLeft, Paperclip, AlertTriangle, Lock, MessageSquare, Pencil, Check, X, Copy, ChevronDown, CalendarClock, FileText, Link2, RotateCcw, Scissors, PenLine } from 'lucide-react'
import { Loader2, ArrowLeft, Paperclip, AlertTriangle, Lock, MessageSquare, Pencil, Check, X, Copy, ChevronDown, CalendarClock, FileText, Link2, RotateCcw, Scissors, PenLine, Bot } from 'lucide-react'
import {
DropdownMenu,
DropdownMenuTrigger,
@@ -65,6 +65,34 @@ type RattelseLogRow = {
created_at: string
}
/**
* Human "who committed this" line from the committed_actor_* snapshot
* (WHO relayed the commit; commit_method records HOW). The claude.ai MCP
* connector mints a gnubok_sk_ API key, so MCP traffic arrives as
* actor_type='api_key' with the key name as the label.
*/
function committedByLabel(
actorType: string,
actorLabel: string | null,
t: (key: string, values?: Record<string, string>) => string,
): string {
switch (actorType) {
case 'user':
return actorLabel || t('committed_by_user')
case 'api_key':
case 'mcp_oauth':
return actorLabel ? t('committed_by_ai', { label: actorLabel }) : t('committed_by_ai_plain')
case 'agent_chat':
return t('committed_by_agent_chat')
case 'cron':
return t('committed_by_cron')
case 'system':
return t('committed_by_system')
default:
return actorType
}
}
export default function JournalEntryDetailPage({ params }: { params: Promise<{ id: string }> }) {
const { id } = use(params)
const router = useRouter()
@@ -580,6 +608,15 @@ export default function JournalEntryDetailPage({ params }: { params: Promise<{ i
<span>{formatDate(entry.committed_at)}</span>
</div>
)}
{entry.committed_at && entry.committed_actor_type && (
<div className="flex justify-between">
<span className="text-muted-foreground">{t('field_committed_by')}</span>
<span className="flex items-center gap-1.5 text-right">
{entry.committed_actor_type !== 'user' && <Bot className="h-3.5 w-3.5 shrink-0" />}
{committedByLabel(entry.committed_actor_type, entry.committed_actor_label, t)}
</span>
</div>
)}
<div className="flex justify-between">
<span className="text-muted-foreground">{t('field_type')}</span>
<span>{sourceTypeLabels[entry.source_type] || entry.source_type}</span>
+8
View File
@@ -887,6 +887,14 @@ export default function PendingOperationsPage() {
detailOpId === op.id ? 'bg-secondary/25' : 'hover:bg-secondary/35',
)}
>
{/* Same actor mark as the pending rows, minus the curved
op-thread (it points at an action row history lacks). */}
<span
className="flex h-7 w-7 shrink-0 items-center justify-center rounded-full border border-border text-muted-foreground"
aria-hidden
>
{isAgent ? <Bot className="h-3.5 w-3.5" /> : <ClipboardCheck className="h-3.5 w-3.5" />}
</span>
<Badge
variant={
isAutoExpired(op)
+5 -3
View File
@@ -77,9 +77,11 @@ export type StreamEvent =
kind: 'staged_operation'
tool_use_id: string
tool_name: string
// The tool-use input: the same values the staging tool stored as
// pending_operations.params. Carried so chat previews that need
// params (e.g. attach_document's DocumentViewButton) work live.
// The tool-use input: a superset of what the staging tool stored as
// pending_operations.params (it may also carry transport fields such
// as idempotency_key/dry_run). Carried so chat previews that need
// params (e.g. attach_document's DocumentViewButton) work live;
// previews read only the fields they need.
params: Record<string, unknown>
staged: StagedOperationResult
}
+7
View File
@@ -4707,6 +4707,13 @@
"details_title": "Journal entry details",
"field_date": "Date",
"field_posted_at": "Posted",
"field_committed_by": "Posted by",
"committed_by_user": "User",
"committed_by_ai": "AI assistant · {label}",
"committed_by_ai_plain": "AI assistant",
"committed_by_agent_chat": "The AI assistant (chat)",
"committed_by_cron": "Automated job",
"committed_by_system": "System",
"field_type": "Type",
"field_source_voucher": "Original voucher",
"field_note": "Note",
+7
View File
@@ -4707,6 +4707,13 @@
"details_title": "Verifikationsdetaljer",
"field_date": "Datum",
"field_posted_at": "Bokförd",
"field_committed_by": "Bokförd av",
"committed_by_user": "Användare",
"committed_by_ai": "AI-assistent · {label}",
"committed_by_ai_plain": "AI-assistent",
"committed_by_agent_chat": "AI-assistenten (chatt)",
"committed_by_cron": "Automatiskt jobb",
"committed_by_system": "System",
"field_type": "Typ",
"field_source_voucher": "Ursprungligt verifikat",
"field_note": "Anteckning",
+2
View File
@@ -258,6 +258,8 @@ export function makeJournalEntry(overrides: Partial<JournalEntry> = {}): Journal
attachment_urls: null,
notes: null,
commit_method: null,
committed_actor_type: null,
committed_actor_label: null,
rubric_version: null,
created_at: '2024-06-15T14:30:00Z',
updated_at: '2024-06-15T14:30:00Z',
+7
View File
@@ -1789,6 +1789,13 @@ export interface JournalEntry {
attachment_urls: string[] | null
notes: string | null
commit_method: string | null
// WHO relayed the commit; complements commit_method = HOW. Stamped at
// commit time since migration 20260619120000. actor_type is NULL or one of
// 'user' | 'api_key' | 'mcp_oauth' | 'cron' | 'system' | 'agent_chat'
// (the DB CHECK is the authority); actor_label is a credential snapshot
// (e.g. the API key name).
committed_actor_type: string | null
committed_actor_label: string | null
rubric_version: string | null
source_voucher_series: string | null
source_voucher_number: number | null