records(packet): trainee packets over the export units - #50
Conversation
Deliver Milestone 5 slice 2 (#48; #44 decision 3): everything retained about one enrollment leaves as one verifiable archive. - trainee_packet: the record export's units byte for byte (shared ArchiveWriter, rows released as written) plus four canonical-JSON documents with typed shapes — the enrollment's lifecycle and phase history, every acknowledgment, every amendment with predecessor and successor, the full task signoff history — named with hashes by one packet manifest of its own format; authorization reuses the training-history read rule, the trainee's own view_own_records, and export_records; trainee_packet_exported audit events; the trainee's own-enrollment list. - export_verify dispatches on the manifest's declared format; packet_verify owns the packet's checks (unit trainee scope, documents present, hashed, canonical, typed, referencing carried versions); zip_container owns the raw container reading. The split keeps every module under the reorganization threshold. - exports_http: GET /api/enrollments/{id}/packet and GET /api/my/enrollments; main: the CLI prints the archive kind, enrollment, and every document. - web: a packet download beside the enrollment export and a My packets section on My records; the drafts e2e downloads the trainee's packet and verifies it with the CLI. - docs: docs/formats/trainee-packet.md (normative), ADR 0015, the TraineePacket domain-model entry. - tests: contents against stored rows, byte identity with the record export's units, determinism, every document finding, authorization, the empty enrollment, the API, and the CLI. Refs #44 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DNP3dkn9c84v2YoNwx7KTg
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. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 8099fba089
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Codex review of #50 raised two findings. Document `kind` members were plain strings; they now reuse the closed sets their tables constrain (EnrollmentEventKind, PhaseEventKind, the new AckKind union, SignoffKind), so production refuses a stored value outside the set and verification names one as DocumentInvalid. The record envelope reader likewise types attribution kinds, scale kinds, record types, and review decisions. Packet production reads the enrollment, its units, and every document inside one transaction, so a packet describes one committed state; storage::installation_id accepts any executor so the read joins it. Tests cover the closed-set forgeries for packets and envelopes; the format document and ADR 0015 say both. Refs #48, #44. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DNP3dkn9c84v2YoNwx7KTg
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 41b12618ff
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Codex round 2 on #50. Every person a document names is now an {id, display_name} object — the stable identity beside the stored name snapshot (records-integrity.md) — and lifecycle events, phase events, and signoffs carry the installation's row identity, so their recorded and effective orders are verifiable. The verifier checks each document's mandated order (strictly ascending, so a duplicate is a finding) and the cross-member rules the stored tables impose: acknowledgment response and speaker by kind and the bound person being the packet's trainee, amendment reasons, version references exactly for a version change, phase references by kind and effective before recorded, and signoff overrides recording a reason. Packet production authorizes before opening its read transaction, so no request holds a pooled connection while waiting for another; a one-connection pool test proves it. The format document and ADR 0015 say all of it. Refs #48, #44. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DNP3dkn9c84v2YoNwx7KTg
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 80036643d5
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Codex round 3 on #50. A packet carries every retained version, so a unit whose predecessor is not carried is now a finding (PredecessorNotCarried) rather than the record export's neutral "not in export". The amendments document is cross-checked against the lineage the manifest's unit hashes establish, both ways: a sealed amendment names the carried version that succeeds the one it corrected, an amendment in progress has no carried successor, and every carried successor has its amendment recorded (DocumentLineage). The signoff history refuses a revocation as a task's first row, mirroring the task_signoff_revocations_supersede trigger. Tests cover each forgery and a packet with version 1 removed; the format document and ADR 0015 state the rules. Refs #48, #44. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DNP3dkn9c84v2YoNwx7KTg
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 03fabda5a7
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Codex round 4 on #50. Every person a document names must have a non-empty name, as every stored snapshot column and the user table require, so an emptied name is a DocumentInvalid finding for the acknowledging trainee and recorder, an amendment's authority, a signer, and an event's actor. The My records page no longer turns a failed enrollment request into "No enrollments": it keeps a loading state and shows the failure, as the records request beside it does. Tests cover each empty-name forgery; the format document states the rule. Refs #48, #44. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DNP3dkn9c84v2YoNwx7KTg
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b6c3811875
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Codex round 5 on #50. Each signoff row now carries the pinned program version whose task it signed ({version_number, label}), so a history that spans a version change keeps its configuration provenance outside the installation. The shape rules now mirror every remaining table constraint the documents touch: task prompts, competency names, and phase names non-empty, version numbers at least 1, and the manifest's enrollment member well-formed (positive ids, username, display name, program name, version number), reported as ManifestEnrollmentInvalid. Tests cover each forgery; the format document states the rules. Refs #48, #44. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DNP3dkn9c84v2YoNwx7KTg
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d30b61ccab
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…tion Codex round 6 on #50. Packet authorization now runs on the packet's own transaction connection — capabilities::user_has_on, assignments::is_assigned_on, and lifecycle::may_read_on carry the one implementation each pool-taking function delegates to — so permission and contents describe one committed state and the request still holds a single pooled connection. The signoff history refuses two rows that describe one task differently: the pinned version, competency, and prompt are configuration the version fixes. Tests cover both forgeries; the format document and ADR 0015 say so. Refs #48, #44. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DNP3dkn9c84v2YoNwx7KTg
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 31c647a6cc
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Codex round 7 on #50. Phase events now carry the pinned version whose phase they name and the version-change event that opened their pin epoch (null under the original pin), read through lifecycle's phase event listing. The verifier derives the enrollment's pin history from the manifest's current pin and the lifecycle events' version changes — the original pin, each change leaving the version pinned at that point and reaching the next, the history ending at the manifest's pin, every version labelled one way — and holds every program version the documents name to it: signoffs and phase events name pinned versions with the packet's labels, and a phase event's version is the one its epoch reached (DocumentPinHistory). The document pass now parses first and cross-checks after. The test seed enters a phase so a real phase event is proven; a new test forges each pin-history contradiction. Refs #48, #44. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DNP3dkn9c84v2YoNwx7KTg
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c19460ac84
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Primary Issue
Closes #48
Refs #44 (Milestone 5 design; decision 3 and slice plan item 2)
Problem And Outcome
#46 made finalized versions leave the installation as verifiable export units, but a trainee leaving with their records needs more than the versions. After merge, one enrollment packs into one archive that carries everything retained about it: every retained version of every record as record-export units, byte for byte, plus typed documents for the enrollment's lifecycle and phase history, every acknowledgment, every amendment with its predecessor and successor, and the full task signoff history, all named with hashes by one packet manifest. The same enrollment packed at the same instant is byte-identical,
export verifychecks a packet from the file alone, and the trainee downloads their own packet from My records. The format is normative indocs/formats/trainee-packet.md; the decisions are ADR 0015.Changes
trainee_packet.rs: the packet manifest (consolebook-trainee-packet, version 1) with the enrollment, trainee, and program as presented at export, the unit list exactly as the record export lists it, and a closed, ordered set of four documents with SHA-256 hashes; typed document shapes (deny_unknown_fields, nullable members required present, everykinda closed set reused from the module that owns the table:EnrollmentEventKind,PhaseEventKind,AckKind,SignoffKind; every person anActor { id, display_name }with a non-empty name, stable identity beside the stored name snapshot; lifecycle events, phase events, and signoffs carrying the installation's row identity so recorded order is verifiable; each signoff and each phase event carrying the pinned program version it belongs to, each phase event also the version change that opened its pin epoch, every row for one task describing it alike; each shape stating the cross-member rules its stored table imposes, the signoff triggers and every non-empty and range constraint the documents derive from included) built from stored snapshots wherever a snapshot exists; one read transaction in which the enrollment, the authorization that governs it, its units, and all four documents are read, so a packet describes one committed state and the request holds a single pooled connection; authorization as existing read contracts (training-history readers, the trainee's ownview_own_records,export_records), evaluated through new connection-taking forms (capabilities::user_has_on,assignments::is_assigned_on,lifecycle::may_read_on) that the pool-taking functions delegate to;trainee_packet_exportedaudit events with the trainee as subject; the trainee's own-enrollment list. An enrollment with no finalized version still packs (its history is the truth); an unknown enrollment is refused.lifecycle.rs:PhaseEventandlist_phase_eventscarry the named phase's program version and the storedversion_change_event_id, the pin epoch the event was recorded under.record_export.rs: the container writer becomes a sharedArchiveWriter(stored entries, export-instant timestamps,0644, rows released as written) so packets and exports write identical units through one path;collect(over one connection),unit_entries, andstampshared.storage::installation_idaccepts any executor so a snapshot read can include it.record_envelope.rs: the envelope reader's vocabularies are enums (ContributorEventKind,ScaleKind,RecordType,ReviewDecisionKind), so a record type, scale kind, attribution kind, or review decision no schema names isEnvelopeInvalid.acknowledgments.rsgainsAckKind, the union of the trainee and attested kinds (migration 0011's closed set), for readers.export_verify.rsnow dispatches on the manifest's declaredformat; the record export's unit checks are shared;DocumentOutOfOrder,DocumentLineage,DocumentPinHistory,PredecessorNotCarried, andManifestEnrollmentInvalidjoin the findings.packet_verify.rs(new) owns what a packet adds: the manifest'senrollmentmember well-formed; every unit's envelope must name the packet's trainee; every unit's predecessor must be carried (a packet carries every retained version, so a hole in the lineage is a finding where a record export reads it as scope); each document kind exactly once in path order at its derived path, present and hashing to the manifest, canonical JSON of its kind's shape (closed kinds included), rows strictly ascending by the format's key for the kind (so a duplicate is a finding), the cross-member rules (every named person has a name, configured text and version numbers as the tables constrain them, acknowledgment response and speaker by kind and the bound person being the packet's trainee, amendment reasons, version references exactly for a version change, phase references by kind and effective before recorded, signoff overrides recording a reason, a revocation never first, every signoff for one task describing it alike), acknowledgments and amendments referring only to versions the packet carries, the amendments agreeing with the lineage the unit hashes establish both ways (a sealed amendment names the carried successor, one in progress has none, every carried successor has its amendment), the pin history the lifecycle events define being coherent (each version change leaving the version pinned at that point, the history ending at the manifest's pin, every version labelled one way) with every program version the signoffs and phase events name belonging to it and each phase event's version being the one its epoch reached, andenrollment.jsonnaming the manifest's enrollment. Documents are parsed in one pass and cross-checked in the next.zip_container.rs(new) owns raw container reading (entry bytes, the central-directory walk). The split keeps every module under the 1,000-line threshold.exports_http.rs:GET /api/enrollments/{id}/packetandGET /api/my/enrollments;main.rs: the CLI prints the archive kind, the enrollment, and one line per document.docs/formats/trainee-packet.md(normative; states the one-snapshot rule for contents and authorization alike, the closed vocabularies, the identity-beside-name rule, each document's order and cross-member rules, the lineage agreement, the pin history, the carried-predecessor rule, the manifest's enrollment shape, and the verification checks one-to-one),docs/decisions/0015-trainee-packet.md, aTraineePacketentry indocs/domain-model.md. Follow-ups filed from review: Trainees cannot view their own task signoff history in the web interface #49 (trainees cannot yet view their own signoff history outside the packet), storage(schema): enrollment_event permits a non-version-change event with one version reference #51 (a looser-than-intended CHECK onenrollment_event), records(packet): bind signoffs and phase events to the pin timeline in verification #52 (binding signoffs and phase events to the pin timeline, not only the pin history, in verification).Scope
enrollment_eventCHECK (storage(schema): enrollment_event permits a non-version-change event with one version reference #51), timeline checks against the pin history (records(packet): bind signoffs and phase events to the pin timeline in verification #52).Verification
The cargo gates were re-run after each review-round commit (41b1261, 8003664, 03fabda, b6c3811, d30b61c, 31c647a, c19460a); the web gates were re-run on b6c3811, the last commit touching a web file, and the e2e suite on the final head. The pool regression test was confirmed to fail against an ordering that acquired twice (pool acquire timeout) before the fix, and still passes with authorization inside the transaction.
tests/trainee_packet.rsproves: the packet's entry list and order; its units byte-identical to the record export's; the manifest's enrollment, trainee, program, unit list, and document hashes; each document's contents against the stored rows (an acknowledgment from its snapshots with its typed kind and both actors, an amendment with predecessor 1, successor 2, and its authority, two signoff rows in ascending row order with task text, typed kinds, their signers, and the pinned version they signed under, a withdraw event with reason and actor, a phase entry with its version and null epoch); verification as a packet with two units and four documents; determinism; audit with the trainee as subject; findings for a document altered with and without its hash, a dangling amendment successor, a mistyped document, a non-canonical document, an acknowledgment kind, signoff kind, and lifecycle event kind outside their closed sets, an enrollment document naming another enrollment, a missing document, a document dropped from the manifest, a manifest naming another trainee, and an unknown packet version; order and shape forgeries (reversed signoffs, a duplicated acknowledgment and amendment, a withdraw with version references, a version change without them, a pause naming no phase, an advance effective after it was recorded, phase events out of effective order, a plain acknowledgment with a response, one recorded by someone else, one binding another person, a blank amendment reason, a signoff override without a reason, a revocation as a task's first signoff, an emptied name on a trainee, an authority, a signer, and an actor, an empty task prompt, competency name, and phase name, a version 0, a manifest trainee with an empty display name, and two rows describing one task differently); lineage forgeries (an amendment in progress while its successor is carried, one naming a non-successor, an emptied amendments document, and a packet with version 1 removed and its documents trimmed to match); pin-history forgeries (signoffs naming a never-pinned version or a divergent label, a version change ending elsewhere than the manifest's pin, one leaving the wrong version, one relabelling the manifest's version, and phase events under an unrecorded epoch, under an epoch that reached another version, and under the original pin naming another version); a packet produced through a one-connection pool; authorization for the trainee, assigned trainer, coordinator, administrator, and anexport_records-only user against another trainee and an unassigned trainer; an empty enrollment's packet; the own-enrollment list; the API's headers, body, and refusals; and the CLI's output and exit codes.tests/record_export.rsadds envelope forgeries with a record type, scale kind, and attribution kind no schema names.Review And Merge Notes
export_verifydispatch and shared unit checks,packet_verify,zip_container); the authorization line in ADR 0015, including the trainee's own packet carrying their signoff history; authorization and contents sharing one snapshot intrainee_packet::export_at; the shape, lineage, and pin-history rules as mirrors of the migrations' constraints, of the units' own hashes, and of the lifecycle events. Eight Codex rounds addressed; the two round-8 findings are tracked in records(packet): bind signoffs and phase events to the pin timeline in verification #52 with a proposed design.export verifyoutput, a newtrainee_packet_exportedaudit kind; the enrollment page's phase events gain three members in their JSON. No migration.🤖 Generated with Claude Code
https://claude.ai/code/session_01DNP3dkn9c84v2YoNwx7KTg
Generated by Claude Code