Skip to content

reader: surface first_break_at + reason in /audit/doctor chain block #39

@ashish-jabble

Description

@ashish-jabble

Problem

/audit/doctor calls verify_chain() and gets back a ChainVerifyResult with first_break_at (monotonic_seq of the broken row) and reason (e.g. "row 5201: hash mismatch"), but the reader collapses it to a binary breaks: 0|1 and discards both. UI consumers showing a red "chain tampered" banner can't tell the operator which row broke or why.

Current

python/fasten/reader/router.py:218-226:

result = verify_chain(recent)
chain_block = {
    "verified": result.ok,
    "breaks": 0 if result.ok else 1,
    "last_verified_at": datetime.now(timezone.utc).isoformat(
        timespec="milliseconds"
    ),
}

Proposed

result = verify_chain(recent)
chain_block = {
    "verified": result.ok,
    "breaks": 0 if result.ok else 1,
    "first_break_at": result.first_break_at,
    "reason": result.reason,
    "last_verified_at": datetime.now(timezone.utc).isoformat(
        timespec="milliseconds"
    ),
}

Both fields are already Optional on the dataclass, so they're null on verified=true — purely additive.

Why

Today a red banner reads "1 break detected — investigate immediately." With the fields surfaced it can read "row #5201: hash mismatch — investigate immediately" → operator can jump straight to that monotonic_seq. Saves them a psql round trip on the worst-case path (the path that actually matters for compliance evidence).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions