Skip to content

records(packet): trainee packets over the export units - #50

Merged
TusanHomichi merged 8 commits into
mainfrom
claude/milestone-2-org-rename-q8b2q8
Sep 1, 2026
Merged

records(packet): trainee packets over the export units#50
TusanHomichi merged 8 commits into
mainfrom
claude/milestone-2-org-rename-q8b2q8

Conversation

@TusanHomichi

@TusanHomichi TusanHomichi commented Sep 1, 2026

Copy link
Copy Markdown
Member

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 verify checks a packet from the file alone, and the trainee downloads their own packet from My records. The format is normative in docs/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, every kind a closed set reused from the module that owns the table: EnrollmentEventKind, PhaseEventKind, AckKind, SignoffKind; every person an Actor { 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 own view_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_exported audit 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: PhaseEvent and list_phase_events carry the named phase's program version and the stored version_change_event_id, the pin epoch the event was recorded under.
  • record_export.rs: the container writer becomes a shared ArchiveWriter (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, and stamp shared. storage::installation_id accepts 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 is EnvelopeInvalid. acknowledgments.rs gains AckKind, the union of the trainee and attested kinds (migration 0011's closed set), for readers.
  • export_verify.rs now dispatches on the manifest's declared format; the record export's unit checks are shared; DocumentOutOfOrder, DocumentLineage, DocumentPinHistory, PredecessorNotCarried, and ManifestEnrollmentInvalid join the findings. packet_verify.rs (new) owns what a packet adds: the manifest's enrollment member 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, and enrollment.json naming 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}/packet and GET /api/my/enrollments; main.rs: the CLI prints the archive kind, the enrollment, and one line per document.
  • Web: "Download trainee packet" beside the enrollment export, and a My packets section on My records listing the trainee's enrollments with status and finalized-version counts, with loading and error states so a failed request is shown as a failure rather than an empty list. The drafts e2e downloads the trainee's own packet and verifies it with the CLI.
  • Docs: 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, a TraineePacket entry in docs/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 on enrollment_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

Verification

  • Listed the exact verification commands run below
  • Added or updated tests when behavior changed
  • Added or updated an ADR when a durable decision changed
  • All fixtures and examples are invented; no real agency data
- cargo fmt --check
- cargo clippy --workspace --all-targets -- -D warnings
- cargo test --workspace   (118 tests; new tests/trainee_packet.rs: 8)
- web/: npm run check, npm run build
- web/: CONSOLEBOOK_E2E_CHROMIUM=/opt/pw-browsers/chromium npm run e2e   (5 specs; drafts.spec downloads the trainee's packet from My records and verifies it with the CLI)

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.rs proves: 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 an export_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.rs adds envelope forgeries with a record type, scale kind, and attribution kind no schema names.

Review And Merge Notes

  • Review focus: the packet verification checks against the format document (meant to be one-to-one); the ownership split (export_verify dispatch 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 in trainee_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.
  • User or operator impact: two new download controls, a new My packets section, a new CLI archive kind in export verify output, a new trainee_packet_exported audit 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

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
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 1, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-01T22:53:51.682374Z c19460a New commits
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread crates/consolebook-server/src/trainee_packet.rs Outdated
Comment thread crates/consolebook-server/src/trainee_packet.rs Outdated
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

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread crates/consolebook-server/src/trainee_packet.rs Outdated
Comment thread crates/consolebook-server/src/packet_verify.rs Outdated
Comment thread crates/consolebook-server/src/packet_verify.rs Outdated
Comment thread crates/consolebook-server/src/trainee_packet.rs Outdated
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

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread crates/consolebook-server/src/packet_verify.rs
Comment thread crates/consolebook-server/src/packet_verify.rs Outdated
Comment thread crates/consolebook-server/src/trainee_packet.rs Outdated
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

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread crates/consolebook-server/src/trainee_packet.rs
Comment thread web/src/routes/records/+page.svelte
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

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread crates/consolebook-server/src/trainee_packet.rs
Comment thread crates/consolebook-server/src/packet_verify.rs
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

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread crates/consolebook-server/src/trainee_packet.rs
Comment thread crates/consolebook-server/src/trainee_packet.rs Outdated
…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

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread crates/consolebook-server/src/packet_verify.rs
Comment thread crates/consolebook-server/src/trainee_packet.rs
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

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread crates/consolebook-server/src/packet_verify.rs
Comment thread crates/consolebook-server/src/packet_verify.rs
@TusanHomichi
TusanHomichi merged commit a85b909 into main Sep 1, 2026
1 check passed
@TusanHomichi
TusanHomichi deleted the claude/milestone-2-org-rename-q8b2q8 branch September 4, 2026 23:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Milestone 5 slice 2: trainee packets over the export units

2 participants