TUI: establish direct-mail persistence contracts#648
Open
TZZheng wants to merge 1 commit into
Open
Conversation
This was referenced Jul 14, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
To, and CC casesSessionCachedisk ownership explicit with zero-safeNoPersistand sole-writerMainAggregateWriterroles enforced inside both write primitivesPart of #645 (the first independent root PR in the issue's implementation DAG). This is a behavior-preserving semantic foundation: it does not add the Agent rail, switch the Main viewport to strict direct mail, wire ordinary Agent threads, change the kernel mail schema, or alter legacy mailbox labels.
Behavior and scope
Direct-mail semantics
NormalizeMailEndpointsproduces one trimmed, order-preserving, deduplicated endpoint list from scalar strings,[]string, and JSON-decoded lists.IsDirectMailaccepts only these two relationships:ToToCC never establishes direct-thread membership. The topology builder reuses only the shared normalizer, eliminating its duplicate parser without coupling display behavior to the direct-thread predicate. Literal duplicate
Toentries now normalize to one endpoint rather than double-counting the same topology edge.Durable unread boundary
The new version-1 store lives at
.tui-asset/rail-last-seen.jsonand records the maximum accepted timestamp plus all IDs observed at that same timestamp. It:SyncTargetsto accept the current identity beforeMarkSeenThis PR exposes the foundation API only; later rail/lifecycle work owns accepted-snapshot wiring.
Session persistence ownership
SessionPersistenceRolehas a fail-closed zero value,NoPersist, and an explicitMainAggregateWriterrole. Authorization is checked inside bothrewriteFileandappend, so rebuild, explicit persist, rewrite, and steady refresh cannot bypass it. A short history withcomplete=trueis still never authorized to write.All current
MailModelcache construction sites explicitly useMainAggregateWriter, so the existing aggregatehuman/logs/session.jsonlbehavior and visible Main chat remain unchanged in this PR.Test-first evidence
The original contract test was written and run against base
d0167c35d696654899678e5a5498df04ebab6492before implementation:It exited
1after compiling and exercising the real steady-refresh path because the memory-only cache createdhuman/logs/session.jsonl. This was the intended missing-authorization failure, not a syntax, import, dependency, environment, or unrelated flaky-test failure. The contemporaneous RED record has SHA-25678aab5ba00a5084309aff12e81d52cf2727d69159cc4938f839ab356c170b79b.Parent review then added edge-contract tests before the corresponding production correction:
That run exited
1with the two intended failures: non-missing read errors lacked read context, andMarkSeenaccepted an identity that had not been synchronized. Its RED record has SHA-256ed41b8b14a8b4f985b6fcf17515bde07a5040bf99cdf0fc0b59d3f28c21c12af.Validation
Exact candidate:
05e73f17477f830b93af2033046ee19902dd3fcbAll listed commands passed. The repository-wide anatomy checker also passed with
447citations and0missing or out-of-range targets; changed citations were inspected semantically.The unfiltered race suite passed every package changed by this PR and failed only untouched
TestPortalURLTimeoutKillsChild, whose fake child has a 400 ms startup deadline. The same assertion reproduced on a clean detached canonical base 5 times in 50 race-stressed runs; the candidate reproduced it 2 times in 10. No portal code or test is changed here.Independent review of this exact final HEAD:
gpt-5.6-sol, high reasoning): PASS, no code or contract blockers; its read-only sandbox could not create Go temporary compilation directories, a limitation stated in its report and covered by the exact parent and Claude executable validation aboveA prior old-HEAD Codex review caught one inaccurate anatomy sentence about unconditional rewrite behavior. The sentence was corrected to describe the role-gated rewrite, the full parent gates were rerun, the commit was amended, and both final reviewers freshly reviewed the new exact HEAD; no old-HEAD verdict was carried forward.
Cleanup and bridge ledger
resolveRecipientsparsing is removedNewSessionCachecall supplies an explicit persistence rolecompleteremains only a window/completeness factRisks and non-goals
The unread store consumes accepted snapshots supplied by its future owner; it intentionally performs no mailbox scan and owns no timer in this PR. Later lifecycle work must call
SyncTargetswith the accepted project target set and must not install rejected or wrong-project snapshots.No visible rail, target switching, strict-Main projection, or ordinary-thread cache is introduced here.
Rollback
Before downstream consumers exist, reverting this commit removes the foundation as one unit and restores the former implicit persistence behavior. Do not roll back
NoPersistalone after ordinary Agent thread caches begin depending on it.