4.1 KiB
Gate 1 Review #2 — Exercise Identity Collision Rules
Status
GATE_1_REVIEW_01=IMPLEMENTED
GATE_1_REVIEW_02=IMPLEMENTED
GATE_1=VALIDATION_PENDING
TRAINLOG_FORMAT_V1=DRAFT
Purpose
Review #1 defined the shape of Trainlog v1.
Review #2 closes the remaining identity ambiguity between the Android exercise catalog and the canonical TUI catalog.
This review is intentionally narrow.
G1-R2-01 — Generated identifiers must be globally collision-resistant
The serialized format continues to treat exercise_id and session_id as opaque identifiers satisfying their documented syntax.
Trainlog implementations that create new identifiers must use random UUID version 4 values.
Recommended generated exercise identifier:
ex_550e8400-e29b-41d4-a716-446655440000
Recommended generated session identifier:
se_550e8400-e29b-41d4-a716-446655440000
The prefixes are part of the generated identifier.
Importers must not require the UUID generation pattern for already-existing conforming v1 documents.
This separates two concerns:
- the wire format accepts the documented opaque identifier syntax;
- official Trainlog implementations generate collision-resistant identifiers.
G1-R2-02 — Existing ID and compatible metadata
If an incoming exercise has the same exercise_id and the same tracking_mode as an existing local exercise, the existing identity is reused.
If the normalized display name is also equal:
action = reuse
No warning is needed.
G1-R2-03 — Existing ID with a different display name
If exercise_id and tracking_mode match but the display name differs:
action = reuse_with_name_warning
The session may import.
The local canonical exercise is not silently renamed.
The UI must surface a non-fatal metadata warning.
Deliberate rename/synchronization is a separate user action.
G1-R2-04 — Existing ID with incompatible tracking mode
If the same exercise_id arrives with a different tracking_mode:
action = reject_mode_conflict
The session import must not continue.
The conflict cannot be repaired by silently changing history because reps and duration have different semantics.
G1-R2-05 — Different IDs with equivalent normalized names
If an incoming exercise has a different exercise_id but its normalized name equals an existing local exercise name:
action = reject_name_identity_conflict
The importer must not:
- create a duplicate exercise;
- silently merge the two identities;
- silently rewrite the incoming session to the existing ID.
The user must explicitly reconcile the identity conflict.
This is the critical anti-duplicate rule for independently edited Android and TUI catalogs.
G1-R2-06 — New ID and unique normalized name
If neither the identifier nor normalized name conflicts with the local catalog:
action = create
The new exercise is inserted during the same import transaction as the session.
G1-R2-07 — Reconciliation is atomic
Any hard catalog conflict rejects the entire session import transaction.
The TUI must not partially:
- create some new exercises;
- insert the session;
- insert sets.
The database remains unchanged after a rejected import.
G1-R2-08 — Document validity versus local importability
A Trainlog JSON document can be structurally and semantically valid on its own yet still be non-importable into a particular local database because of a catalog identity conflict.
Therefore v1 has two distinct checks:
document validation
local catalog reconciliation
Gate 1 freezes both contracts.
Executable contract
Canonical reconciliation cases live in:
tests/contract/catalog-import-cases.json
Validate them with:
python tools/validate_import_contract.py
This validator is an executable specification used before the production C17 importer exists.
Freeze decision
After both Gate 1 reviews are validated, committed, pushed, and mirrored, the closure step may set:
GATE_1_REVIEW_01=PASS
GATE_1_REVIEW_02=PASS
GATE_1=PASS
TRAINLOG_FORMAT_V1=FROZEN