sessions(time): training sessions with explicit time semantics - #26
Conversation
Milestone 3 slice 2 per ADR 0008 and the new ADR 0009: training_session carries the agency-local business date, IANA timezone snapshot, and verbatim local start/end beside server-resolved UTC instants; jiff resolves local times against the embedded tzdb with RFC 5545 compatible disambiguation, pinned by DST gap and fold golden tests. Migration 0007 enforces domain invariants 6-8 — end never precedes start, active intervals for one trainee never overlap (open sessions unbounded, contiguity legal, cancelled sessions released), no per-date uniqueness — plus pinned-version phase references and the one-trainer floor. Session trainer membership requires author_evaluation, with member and coordinator scoped work gates; the enrollment page gains the sessions panel and trainers a My-sessions view, proven by browser e2e. Closes #25 Refs #22 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: 5488793a80
ℹ️ 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".
Address the Codex round on #26, both verified: an empty or whitespace local_end normalizes to no end before the disposition rules (typed end_required instead of a constraint 500; a blank end still records an open session), and editing an open session validates only an actual phase change against the current pin — an unchanged phase recorded under an earlier pin survives the edit, as migration 0007 permits. Refs #25 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: 2a0bbf8a9e
ℹ️ 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".
Address the second Codex round on #26, all three verified: - training_session stamps the enrollment's pin at creation (database-enforced, immutable), invariant 5 becomes a composite foreign key against that stamp, and session reads present the stamped version — so a later enrollment version change never rewrites what a session said, and phase changes validate against the session's own version; - initial trainer members are audited like every later access grant; and - open sessions gain close controls on the My-sessions list, giving ad-hoc members the actions their membership authorizes, proven in the browser e2e. Refs #25 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: d03b41c6a3
ℹ️ 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".
Reject UPDATE of session_trainer identity at the database: grants are inserted and removed through the audited service path only, so an out-of-band UPDATE can neither transfer session access nor drain a session past the one-trainer floor. Raw-update refusal tested. Refs #25 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: 4c52a87651
ℹ️ 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".
Address the fourth Codex round per the review discipline: - jiff now always uses its bundled IANA database (no host zoneinfo fallback), so local-time resolution has the one-executable semantics ADR 0009 documents on every platform — the DST goldens prove the bundle resolves; - session_created is recorded before the initial membership grants, so append-only ids never show access preceding the session it opens; - the deferred-transaction SQLITE_BUSY_SNAPSHOT shape is the whole repository's write pattern, raised once as #27 rather than widened here; and - the trainer floor at raw INSERT stays service-enforced: SQLite has no deferred constraints, and transactional application services are a named enforcement mechanism (docs/domain-model.md). Refs #25 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: c841b6a4f8
ℹ️ 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".
…ntity The session module crossed the 1,000-line boundary during review, so the slice includes its ownership reorganization (AGENTS.md): local-time resolution (ADR 0009) moves to session_time with its own TimeRefusal, trainer membership — eligibility, audited grants and removals, the one-trainer floor — moves to session_membership, and training_sessions keeps the lifecycle: gates, disposition rules, create/edit/close, reads, and the shared domain vocabulary. No persisted or HTTP-visible impact; the proof is the unchanged integration suite. Migration 0007 also freezes enrollment.user_id: the overlap triggers and session reads derive the trainee from it, so a raw reassignment could slip past the interval invariant and rewrite whose training the sessions recorded. An enrollment belongs to its trainee; a different trainee is a new enrollment. Covered by a raw-UPDATE refusal test. Refs #25. 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: 92200d3ac5
ℹ️ 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".
The service already refuses edits to closed sessions, but the schema did not: a raw update could rewrite a closed session's dates, times, phase, or disposition in place while keeping the CHECKs consistent. Closed sessions are settled history — corrections are successor versions or amendments (PRINCIPLES.md), arriving with the correction machinery — so migration 0007 now refuses every update to a row whose disposition is set. The close itself still lands: OLD.disposition is null while it runs. Covered by a raw-UPDATE refusal test. Refs #25. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DNP3dkn9c84v2YoNwx7KTg
Primary Issue
Closes #25
Refs #22
Problem And Outcome
Milestone 3 slice 2:
TrainingSessionfrom the domain model, on the slice-1 substrate. After merge: coordinators and assigned trainers record sessions open or retroactively complete, with agency-local meaning (business date, timezone snapshot, verbatim local times) kept separate from the UTC instants that ordering, duration, and the overlap invariant reason about (PRINCIPLES.md 6) — the anchor slice 3's evaluation drafts attach to.Changes
0007_sessions.sql:training_sessionandsession_trainer, with database-enforced domain invariants — UTC end never precedes start (6); active intervals for one trainee never overlap across all their enrollments, where an open session is right-unbounded, interval ends are exclusive so contiguous handoffs are legal, and cancelled sessions release their interval (7); deliberately no per-trainee-and-date uniqueness (8); each session stamps the enrollment's pin at creation, with phases checked by a composite foreign key against the stamp (5); immutable history — an enrollment keeps its trainee, a session keeps its enrollment and version, closed sessions never change in place, membership rows never move; and a one-trainer floorjiffwithtzdb-bundle-always), RFC 5545 compatible disambiguation (gap rolls forward, fold takes the earlier offset), unknown zones refusedsession_timeresolves entered local times (ADR 0009);session_membershipowns trainer-membership grants — eligibility (anyauthor_evaluationholder, per Milestone 3 design: sessions, drafts, and attribution (owner decisions needed) #22 decision 2), audited add by members and coordinators, coordinator-only remove, the floor;training_sessionsowns the lifecycle — create (open or retroactively complete), edit-open, close with disposition (completed/interrupted take an end; cancelled takes none), and reads, with typed refusals ahead of every database backstop and scoped gates: create takesassign_trainingor authoring plus an active assignment, work takes coordination or membership, session reads extend to members without opening the enrollment historytraining_http; audit kinds andSubject::Sessiontests/training_sessions.rs— DST gap and fold goldens asserted against fixed-offset instants, verbatim round-trip, overlap at service and database (including across enrollments of one trainee and after cancellation releases the interval), contiguity and same-date legality, gates and membership rules, identity- and freeze-trigger refusals, close rules, API round trip with audit ordering;web/e2e/sessions.spec.tsdrives the browser flow including the overlap refusal surfacing in the interfaceScope
Verification
Review And Merge Notes
training_sessions.rsauthor_evaluationholders; the newjiffdependency embeds the IANA timezone database in the binary🤖 Generated with Claude Code
https://claude.ai/code/session_01DNP3dkn9c84v2YoNwx7KTg
Generated by Claude Code