HANDOFF: webhook

Scope
Only webhook requirements are authoritative. Ignore all archived records for other projects. Apply future webhook updates only to their specified keys; leave other keys unchanged. No future updates or questions are available yet.

Current choices
{
  "scope_key": "tenant",
  "latest_by": "sequence",
  "tie_policy": "first",
  "delete_wins": true,
  "allow_resurrection": true,
  "amount_mode": "absolute",
  "precision": 0,
  "gap_policy": "accept",
  "audit_duplicate": false,
  "order": "input",
  "max_age": 60,
  "inclusive_age": true
}

Fixed interface
Implement evaluate(payload) returning:
{
  "entities": [
    {"tenant": ..., "entity": ..., "value": "string", "deleted": boolean}
  ],
  "duplicates": [event_id, ...]
}

payload supplies integer now and events. Events contain id, tenant, entity, seq, ts, kind ("set" or "delete"), and amount (nonnegative decimal string). Do not mutate input.

Required processing order and current behavior
1. Age filter:
   Discard events when now - ts > 60. Keep events at exactly age 60 because inclusive_age=true. No separate future-timestamp rejection is specified.

2. Deduplicate after age filtering:
   Key is (tenant, id). Keep the first remaining occurrence.
   audit_duplicate=false, so duplicates is always [] under current choices.
   In the general contract, append each discarded duplicate's id only when auditing is enabled, preserving encounter order.

3. Group deduplicated events by (tenant, entity).
   Record each group's earliest retained original input index BEFORE tie filtering. This determines current entity output order.

4. Tie filtering and processing:
   latest_by="sequence" selects seq as the sort field.
   Within each group, for each tied seq retain the first event in input encounter order; discard the other tied events.
   Process the resulting retained events in ascending seq order.

5. Per-group state:
   Initialize seq=0, value=0, deleted=false.
   gap_policy="accept": process every retained event, with no sequence-contiguity requirement.
   Every processed event updates state seq, including deletes and sets that would be blocked by resurrection rules.

6. Event effects:
   Delete sets deleted=true and does not alter value.
   Set is permitted because allow_resurrection=true, even after a delete. It resets deleted=false and assigns amount, replacing value because amount_mode="absolute".

7. Delete-wins override:
   delete_wins=true forces final deleted=true if ANY event retained after tie filtering is a delete. It does not change value.
   Thus a later set can replace value and resurrect during processing, but final deleted remains true when a retained delete exists.
   Deletes eliminated by age filtering, deduplication, or tie filtering do not trigger this override.
   The fixed contract also applies this override to retained deletes skipped by gap filtering, should a future update enable such filtering.

8. Final value formatting:
   Use exact decimal arithmetic and ROUND_HALF_UP.
   Current precision=0: round to an integer and return a fixed-point string with no decimal point or fractional digits.
   General contract requires exactly precision fractional digits when precision>0. Round only the final value.

9. Output ordering:
   order="input": order entities by each group's earliest retained original input index measured before tie filtering, not by processing order or the selected tie winner.
   General alternative order="key" sorts entities by (tenant, entity).

Other fixed-contract behavior to preserve if choices change
- scope_key="tenant" deduplicates by (tenant,id); otherwise deduplicate globally by id.
- latest_by selects seq or ts (current label "sequence" means seq).
- tie_policy chooses first or last event for each tied selected sort-field value.
- If inclusive_age=false, also discard events at exactly max_age.
- If gap_policy="ignore", process only when event.seq equals previous processed seq+1, starting from seq=0. Skipped events do not update seq.
- If allow_resurrection=false, sets while deleted do not change value or deleted, but still update seq when processed.
- amount_mode="delta" adds amount rather than replacing value.

Unresolved items
No outstanding target-project choice is unspecified: all 12 settings above are resolved. No implementation language, validation/error-handling rules, or additional payload fields have been specified. Do not invent requirements for these.