signoffs(read): let a trainee read their own complete history - #68
Conversation
`task_signoffs::matrix` projects the currently pinned version's tasks and is gated by the training-history rule, so a trainee holding `view_own_records` could not read the signoffs recorded about them, and the interface disagreed with the trainee packet that already carries the full history (#49). `task_signoffs::history` is a separate typed, service-owned read: driven by the append-only rows themselves, it reports every retained signoff in recorded order within its task, with kind, reason, signer identity, and the stored name snapshot, plus each task's program version so a version change leaves prior signoffs discoverable and labelled rather than hidden. It admits the readers `lifecycle::may_read` admits, and the trainee on their own enrollment with `view_own_records`; it does not widen that rule and grants no write, so `matrix` and every recording rule are unchanged. The client contract lives in `web/src/lib/api/signoffs.ts` and the read-only presentation in `web/src/lib/signoffs/`, reachable from My records. Proof: `tests/signoff_history.rs` covers the authorization line including the trainee's own revoked grant, observed/demonstrated/revoked histories, stored-name stability, same-second and per-task recorded order, version changes and a real re-pin of a prior version, the bound to this enrollment's own pinned vocabularies, a genuine one-connection pool, and agreement with the packet's own signoff document row for row; `web/e2e/signoff-history.spec.ts` shows the trainee's real full history, that another trainee holding the same grant is refused, and that no write control appears. ADR 0021 records the contract and its epoch limit.
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
TusanHomichi
left a comment
There was a problem hiding this comment.
Source review at ea2cea7: no blocking finding identified in this slice. I inspected the service and HTTP changes, typed client, read-only presentation, ADR 0021, and the new service/browser tests. The new read admits existing history readers or the enrollment's own trainee with view_own_records; authorization and data use the same read transaction/connection. The existing matrix and record paths are unchanged. Retained earlier-version rows remain discoverable, signer names come from stored snapshots, and re-pinning behavior is explicitly documented and tested rather than silently reinterpreted.
GitHub pr-gate run 34661988482 is successful; its web install/check/build, fmt, Clippy, Rust tests, explicit binary build, browser install, and browser tests all completed successfully. There are no inline review threads on this PR at review time. I did not execute Rust or browser tests independently in this review environment; this is source/test review plus verification of GitHub's CI evidence.
This PR can merge independently of #67 and #69. Those two still require their own corrections and revalidation. #59 is not closed by this review.
Primary Issue
Closes #49
Problem And Outcome
task_signoffs::matrixprojects the enrollment's currently pinned program version and is gated by the training-history rule, so a trainee holdingview_own_recordscould not read the signoffs recorded about them at all — and the interface disagreed with the trainee packet that already carries that history.task_signoffs::historyis a new typed, service-owned read: every retained signoff row, in recorded order within its task, with the closed kind, the recorded reason, the signer's identity and stored name snapshot, and the recorded instant, plus each task's program version and whether it is the current pin. It admits the readerslifecycle::may_readalready admits, plus the trainee on their own enrollment withview_own_records; it does not widen that rule and grants no write. My records gains a read-only presentation.Changes
crates/consolebook-server/src/task_signoffs.rs—historyand its rows;matrixandrecordare unchanged, including their gates.crates/consolebook-server/src/training_http.rs—GET /api/enrollments/{id}/signoff-history, beside the unchanged matrix route.crates/consolebook-server/tests/signoff_history.rs— 10 tests.web/src/lib/api/signoffs.ts,web/src/lib/signoffs/SignoffHistoryPanel.svelte,web/src/routes/records/+page.svelte— the typed client and the read-only section on My records: current state per task, the full retained chain behind an accessible disclosure, and earlier program versions in their own labelled section. No recording, override, revoke, or contest control.docs/decisions/0021-trainee-signoff-history-read.md— the read contract, its epoch limit, and the rejected alternatives.docs/development.md,web/e2e/signoff-history.spec.ts.Scope
matrix, torecord, or to any recording authority; schema changes (none); epoch reconstruction, which stays the enrollment event stream's job.Verification
The suite covers: the trainee's own enrollment allowed and another trainee denied; the trainee's own
view_own_recordsgrant revoked and their own enrollment refused (the case an identity-only rule would pass); assigned trainer, coordinator, and explicit administrator readers unchanged; an unassigned trainer denied; trainee writes still refused; observed/demonstrated/revoked histories with reasons; the stored name snapshot surviving a rename; same-second ordering by row identity; per-task recorded order and grouping; a version change and a real re-pin of a prior version with both epochs readable; the bound to this enrollment's own pinned vocabularies; a genuine one-connection pool; and row-for-row agreement with the packet's own signoff document.Review And Merge Notes
view_own_records, on the read's own connection inside its transaction) and the stated epoch semantics in ADR 0021.