fix(integration-ever-works): apply key results a goal was synced without once Key Result sync is on

Merged
EV
7d ago
+338-11

Fixes the Greptile finding deferred on the develop → stage cascade #11096 (thread): Resync leaves key results behind.

Problem

With a mapping's KeyResult sync setting off (OQ4, #11114), applyGoal() skips the goal's key results but records the goal's Ever Works version and returns applied. After an admin turns the setting on, nothing brings them:

  • a resync reads the unchanged goal under the same initial event id, which is already applied, so it is stored as a duplicate and never reaches the handler;
  • an event that does reach it (a resync of a goal first synced by webhook) is at the recorded version and is skipped as already applied (version).

Key results stayed missing or stale until the goal changed again in Ever Works.

Fix (goals/ever-works-goals.service.ts)

  • The goal's link records unfinished key-result work in ewOnly.keyResultsPending, with the reason:

    • sync: the mapping did not sync key results;
    • owner: some key results could not be created for want of an owner employee (Greptile on this PR).

    An inbound apply that finishes the work clears the marker; a push leaves it as it was. Links with nothing pending keep exactly the ewOnly they had.

  • apply(): once the mapping syncs key results, an event at the goal's recorded version is applied instead of skipped while key results are pending (older versions are still skipped). An id-only event for such a goal, or one without the fields key results are built from (goalKind, criteria, dodCriteria), reads the goal itself, so key results are never built from the last sync's snapshot. A read records the version it read when that is newer than the event's, so a delayed older event is skipped (Greptile on this PR; this also applies to the existing first, unlinked read).

  • The goal sweep (every 10 min per connection) reads each pending goal from Ever Works and applies it as the sync. This covers the duplicate-event case without waiting for a new Ever Works edit. It reads an owner goal only once someone can own its key results: the goal's agent or owner is paired (its lead, as leadOf() finds it, from the agents kept on the link meanwhile), or the connecting admin has an employee. So nothing is polled while nobody can own them. It does nothing while the setting is off or goals do not sync from Ever Works, one failing goal (its owner lookup included) never stops the others, and a read older than the link's version is never applied. While a goal waits for an owner, an event that leaves out its agents keeps the ones on the link.

Additive only: no column, setting, endpoint or default changes, and no migration.

Tests

goals/ever-works-goals.service.spec.ts, nine new cases in the OQ4 block:

  1. Off → goal applied without key results; still off, the same version stays skipped. On → the same version is applied, both key results are created, the marker is gone, the snapshot has Ever Works' Definition of Done, and a third delivery is skipped again.

  2. Off → the sweep reads nothing. On → the sweep reads the goal once and creates both key results with their links; the next sweep reads nothing.

  3. Pending at v1, an id-only v1 event reads v3: the link records 3, and a late v2 event is skipped without overwriting v3's title.

  4. No owner employee: the marker is owner and the sweep reads nothing. Once the admin has an employee, one read creates both key results (owner e-admin) and clears the marker; the next sweep reads nothing.

  5. With the admin still without an employee, pairing the goal's owner (or 6. its assigned agent) gives one read, and both key results are owned by that employee.

  6. A pending goal's event with a title but no key-result fields reads the goal and creates both key results.

  7. An event without the agents keeps the agent kept for the owner; pairing it later still retries.

  8. An owner lookup that throws for one goal does not stop the sweep for the next.

Cases 1–2 fail on develop (skipped instead of applied; 0 reads instead of 1). Cases 3–9 fail on the commit before their fix (version 1 instead of 3; no owner marker; 0 reads; a nulled agent; a rejected sweep). All nine pass. Locally (Node 26, --experimental-vm-modules): goals 40/40; the whole plugin with develop merged in: 45/45 suites.

🤖 Generated with Claude Code