HANDOFF NOTE: target project "webhook"

SCOPE
Only the webhook project is covered here. All "archived work record" entries (archive-east/west/north/south-NN) belong to other projects and were discarded. They set no webhook values.

CURRENT WEBHOOK SETTINGS (after Updates 1-5)
Each update replaced only the keys it named.
- scope_key = "tenant" (Update 1). Dedupe by (tenant,id).
- latest_by = "sequence" (Update 1). Sort field is seq.
- tie_policy = "first" (Update 1). Keep the first event among ties.
- delete_wins = true (Update 1).
- allow_resurrection = true (Update 4; replaced false from Update 2).
- amount_mode = "absolute" (Update 4; replaced delta from Update 2).
- gap_policy = "accept" (Update 5; replaced ignore from Update 2).
- precision = 0 (Update 5; replaced 2 from Update 2). Output has no decimal digits and no decimal point.
- audit_duplicate = false (Update 5). The duplicates list is always empty.
- order = "input" (Update 5). Entities are sorted by the earliest retained original index in each group, measured BEFORE tie filtering.
- max_age = 60 (Update 3).
- inclusive_age = true (Update 3). An event with now-ts == 60 is kept; discard only when now-ts > 60.

UNRESOLVED
- No values have been given for payload "now" or the events. They arrive at call time.
- The contract does not define input validation, for example malformed amounts, missing fields, or the type of the "ts" field. Ask before assuming anything.
- Behavior is unspecified for an empty event list. The natural result is entities [] and duplicates [].
- The next update and later questions have not been seen yet. Apply any further update only to the keys it names.

FIXED CONTRACT (unchanged)
evaluate(payload) -> {entities:[{tenant,entity,value:string,deleted:boolean}], duplicates:[event_id]}. Do not mutate the input.

Pipeline:
1. Age filter: discard if now-ts > max_age. Also discard on equality unless inclusive_age is true.
2. Dedupe the remaining events, keeping the first. The key is (tenant,id) if scope_key=tenant, otherwise id alone. Append the duplicate id to duplicates only if audit_duplicate is true. Duplicates keep encounter order.
3. Group by (tenant,entity).
4. Tie handling: within each group, events with equal values of the chosen sort field are reduced to one, keeping the first or last according to tie_policy. Process the remaining events in ascending order of the sort field.
5. Initial state: seq=0, value=0, deleted=false.
6. gap_policy=ignore: process an event only if event.seq == previous processed seq + 1, and skipped events leave seq unchanged. With "accept" (current), every event is processed.
7. Every processed event sets seq to its own seq.
8. A delete event sets deleted=true.
9. A set event applies only if not deleted or allow_resurrection. It resets deleted=false. In absolute mode it assigns the amount; in delta mode it adds the amount.
10. delete_wins: if any retained event is a delete, force final deleted=true, even if the gap filter skipped it. It does not change value.
11. Rounding: round value to precision decimal places using exact ROUND_HALF_UP and output a fixed-point string with exactly precision digits. Use exact decimal arithmetic, not floats.
12. Entity ordering: by (tenant,entity) if order=key. Otherwise use the earliest retained original index of each group, measured before tie filtering (current setting).

Implication of the current config: because allow_resurrection is true and delete_wins is true, a set after a delete clears deleted during processing, but delete_wins forces final deleted=true whenever any retained delete exists.