HANDOFF NOTE - Target Project: webhook

Project scope:
- Only project "webhook" is authoritative. All archived projects and their settings are unrelated and discarded.
- Implement the fixed interface: evaluate(payload) -> {entities:[{tenant,entity,value:string,deleted:boolean}], duplicates:[event_id]}
- Do not mutate input.

Current configuration (final, after all replacements; no superseded values retained):
{
  "allow_resurrection": true,
  "amount_mode": "absolute",
  "audit_duplicate": false,
  "delete_wins": true,
  "gap_policy": "accept",
  "inclusive_age": true,
  "latest_by": "sequence",
  "max_age": 60,
  "order": "input",
  "precision": 0,
  "scope_key": "tenant",
  "tie_policy": "first"
}

Fixed algorithm contract:
- Input: now is integer. Each event contains id, tenant, entity, seq, ts, kind (set/delete), amount as nonnegative decimal string.
- Age filter: discard events where now - ts > max_age. Equality with max_age is also discarded unless inclusive_age is true. With current settings, age == 60 is kept and age > 60 is discarded.
- Deduplication: after age filtering, deduplicate by (tenant, id) when scope_key=tenant; otherwise by id globally. Keep the first occurrence. Append the duplicate event_id to duplicates only if audit_duplicate is true. With current settings, duplicates are not audited, so duplicates remains empty for deduplicated events. Duplicates preserve encounter order when audited.
- Group retained events by (tenant, entity).
- Sorting/ties: latest_by chooses the sort field: "sequence" means seq, "timestamp" means ts. For ties on the chosen field, retain first or last event according to tie_policy. Current tie_policy is "first".
- Processing: process retained events in ascending order of the chosen sort field. Initialize seq=0, value=0, deleted=false.
- Gap handling: if gap_policy=ignore, process an event only when event.seq == previous processed seq + 1; skipped events do not change seq. Any processed event updates seq. Current gap_policy is "accept", so events are processed regardless of sequence gaps.
- Event effects:
  - Delete sets deleted=true.
  - Set updates only if not deleted, or if allow_resurrection is true. It resets deleted=false and assigns amount in absolute mode, or adds amount in delta mode. Current mode is absolute and resurrection is allowed.
  - delete_wins additionally forces final deleted=true if any retained event in the group is a delete, even if the gap filter skipped it. It does not change value. Current delete_wins is true.
- Output value: round value to precision decimal places using exact ROUND_HALF_UP, returning a fixed-point string with exactly precision digits, with no decimal digits when precision is zero. Current precision is 0.
- Entity ordering: sort entities by (tenant, entity) if order=key; otherwise order by earliest retained original index in each group BEFORE tie filtering. Current order is "input", so use earliest retained original index before tie filtering.
- duplicates list preserves encounter order.

Exceptions:
- None recorded beyond the algorithm rules above.

Unresolved items:
- None. All 12 configuration keys above are currently resolved.