merge: task 5 works and matching
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
@@ -81,3 +81,26 @@ instead of 26. Where `AGENTS.md` or the spec say "ULID", read "UUIDv7".
|
||||
|
||||
**Consequences:** Rule changes happen in one file, and the pointer files must stay thin. Plans are working
|
||||
artefacts: kept after merge, they go stale and contradict the code, so the spec is the record.
|
||||
|
||||
## 2026-10-30: Catalogue matching is race-safe through unique keys plus whole-transaction retry
|
||||
**Status:** accepted
|
||||
**Context:** Two syncs matching the same new work at once both miss the lookup and both insert. On
|
||||
Postgres the loser hit the `external_ids (namespace, value)` unique key and crashed the sync; seasons and
|
||||
episodes had no key at all and were duplicated.
|
||||
**Decision:**
|
||||
- Migration `0002_works_position_unique` adds a unique index on `works (parent_id, position)`. NULLs are
|
||||
distinct on both dialects, so top-level works and position-less children are unaffected.
|
||||
- `matchWork` re-runs the whole resolve in a fresh transaction on a unique violation (SQLite
|
||||
"UNIQUE constraint failed", Postgres SQLSTATE 23505), up to 3 attempts. Postgres aborts the transaction
|
||||
on the error, so a statement-level retry is not possible.
|
||||
- Adding missing external IDs to an existing work uses `ON CONFLICT DO NOTHING`.
|
||||
- An ID match only counts if the work is shared or owned by the matching user, and private works can
|
||||
never carry external IDs (`createWork` refuses; global merges accept only shared works).
|
||||
- A global merge repoints children of `from` to `to` unless `to` already has a child at that position.
|
||||
|
||||
**Alternatives:**
|
||||
- Advisory or table locks: dialect-specific, and SQLite already serialises writes.
|
||||
- Upserts throughout `createWork`: would hide genuine conflicts and need per-dialect `RETURNING` handling.
|
||||
|
||||
**Consequences:** Parents resolved without IDs or position (title/year reuse) still have no unique key
|
||||
and can be duplicated under concurrency; tracked in `docs/TODO.md`.
|
||||
|
||||
@@ -24,4 +24,7 @@ Small, specific, independently actionable. Remove only when actually done.
|
||||
file, and compare steps and token use.
|
||||
|
||||
## Known issues
|
||||
- [ ] Admin merge does not merge colliding children recursively; colliding children stay under the tombstoned parent.
|
||||
- [ ] Matching: a shared parent resolved without external IDs or position (title/year reuse in
|
||||
`packages/core/src/matching.ts`) has no unique key, so concurrent matches can create it twice.
|
||||
- [ ] `bun run typecheck` fails with TS18003 until source files exist under `apps/`, `packages/` or `plugins/`.
|
||||
|
||||
Reference in New Issue
Block a user