Skip to content

create_event saves exact and near-duplicate events: no check for the same title on the same day #520

Open

Description

@alex-mextner

What

Priority P1 (data integrity). handleCreateEvent (src/services/ai/tool-handlers/events.ts) inserts whatever the model sends. Production, user 716928723: (1) 2026-09-10 21:25Z and 21:27Z events 591 and 592 'Алекс барбершоп' with the same start 2026-09-12T16:00Z — the first run replied only '[SKIP]', she re-sent, and the morning agenda and both batch reminders on 12.09 then listed the barbershop twice; (2) 2026-09-07 18:40Z she asked to move a visit to 18:00 ('Бояна завтра в 18'); the model created event 589 next to the existing 566 and itself labelled them '(старое время)' / '(новое время)', so she had to delete 566 by hand; (3) 2026-09-23 18:55Z 634 and 635 with the same title a few minutes apart. Add a deterministic guard: before inserting, look up the owner's (or group's) non-deleted events on the same local day with the same normalized title; an identical start returns not_applied 'DUPLICATE_EVENT' with the existing id; a different time on the same day returns a hint that this looks like a move (use update_event) and requires an explicit confirmed flag to create a second one.

Why (motivation)

Weaker fallback models re-run creates after lost replies and create instead of moving; code must stop the second row.

User impact

People get the same appointment twice in their calendar, agenda and reminders, or an old and a new copy of a moved appointment.

Cost of inaction

Every retry or 'move it to 18:00' request can silently double the calendar, doubling reminders and invitations.

Acceptance criteria

  • Test: a second create_event with the same normalized title, start and end for the same owner within 10 minutes returns success=true with alreadyExists naming the existing event id (a retry after a lost reply must not look like a failure), writes nothing, and the events table still has one row
  • Test: same title on the same local day at a different time is not inserted without the explicit confirmation flag; the result tells the model to use update_event for a move.
  • Test: different titles, different days, deleted events and other owners are not blocked; group scope compares within the group only.
  • The tool schema/description advertises the confirmation flag with an example that passes the real handler (prompt/tool contract fixture).

Screenshots

(none)

Deploy

Deployed: no

Links

Skipped gates

Activity

  1. alex-mextner commented on Nov 1, 2026

    @alex-mextner
    OwnerAuthor

    Partly addressed in branch fix/chat-serialization-20261101 (not merged, not deployed), as part of #929:

    • Exact repeats: create_event now returns the existing event as a SUCCESSFUL receipt ("Event already exists ...", no second row, no reminders/notifications/calendar push, not added to createdEventIds) when the same calendar (owner incl. secretary owner_id, or the same group) already has a live, non-recurring event with the same normalized title, all-day flag, start and end created within 10 minutes. Decided and inserted in one immediate SQLite transaction (EventRepository.createUnlessRecentDuplicate), so it holds across connections and restarts. This deliberately differs from criterion 1 as written (success=false, not_applied): a retry after a lost reply must be reported to the user as created once, not as a failure.
    • Explicit way to make a second copy: allow_duplicate: true (tool schema, tool description, tool-schemas validation, tests). create_event saves exact and near-duplicate events: no check for the same title on the same day #520 names an unspecified confirmation flag; this is that flag for the exact-repeat case.
    • Different titles, other days/times/end, deleted or cancelled rows, rows older than the window, other owners, other groups and recurring events are never matched (tests).
    • NOT done: the same title on the same local day at a DIFFERENT time still creates a second row (criterion 2, the "move" case with update_event guidance) and the matching schema example fixture (criterion 4 for that flag). Next action: a separate change that rejects that case without the flag; it needs prompt/tool fixtures and a measured token budget (the tool catalog is within 100 characters of its 36000 budget in test/services/ai/payload-budget.test.ts).
  2. alex-mextner commented on Nov 4, 2026

    @alex-mextner
    OwnerAuthor

    The duplicate-create part of this ticket shipped with PR #942 (d0bc0769d20897b5441b1e3f568611b8bdef4deb, deployed 2026-11-04 03:04 UTC, verified against the odroid receipt and the running image; details in #929). create_event now matches live events of the same calendar in one SQLite transaction and answers alreadyExists or a SAME_DAY_EVENT conflict. Proven by tests (events-duplicate-create.test.ts, 34 pass, and the overlap regression); not exercised against a real user's calendar. Single-seat review via omp Opus; full-roster review owed in #961. Left open here: the receipt wording in write-outcomes.ts (the hand-over note in #929).

  3. alex-mextner commented on Nov 7, 2026

    @alex-mextner
    OwnerAuthor

    Follow-up shipped: PR #974, merge commit 128846b, deployed (production image tag equals the merge SHA, /health ok, /ready ok (unverified), no level 40/50/60 records since start).

    The idempotent create result (success: true, alreadyExists naming the existing event id) is now read by the write receipt as the same event: one created line for a creation plus its repeat, "Already in the calendar" for a repeat of an event the run did not create, never "Not created", and no turn-finish guard when nothing failed. The ledger folds by the structured event id, not by the English output text. Tests run the real handler into the real ledger in ru and en.

    This PR touches the receipt only, not the create guard itself, so it changes no acceptance criterion of this ticket; the ticket stays open.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions