From 9706eb9d0e3b0ac694c835a05d9f772b563c100f Mon Sep 17 00:00:00 2001 From: Jeroen Schweitzer Date: Thu, 16 Jul 2026 19:25:54 +0200 Subject: [PATCH] =?UTF-8?q?chore(meta):=20close=20atlas-layer=20batch=20?= =?UTF-8?q?=E2=80=94=20T-1112/T-1113=20done=20(PR=20#179=20merged);=20file?= =?UTF-8?q?=20T-1118=20climate=20overlay,=20T-1119=20quarter-footprint=20w?= =?UTF-8?q?iring,=20T-1120=20Atlas=20screenshot=20goldens?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .pql/changelog/ticket_history/2026-07.sql | 2 ++ .pql/changelog/ticket_idmap/2026-07.sql | 3 +++ .pql/changelog/tickets/2026-07.sql | 28 +++++++++++++++++++++++ 3 files changed, 33 insertions(+) diff --git a/.pql/changelog/ticket_history/2026-07.sql b/.pql/changelog/ticket_history/2026-07.sql index a3d2893b5..bde8b38f0 100644 --- a/.pql/changelog/ticket_history/2026-07.sql +++ b/.pql/changelog/ticket_history/2026-07.sql @@ -1405,3 +1405,5 @@ INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, chang INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FNTXEM9NM5M5SX9WQAAA40SC', 'assigned_to', NULL, 'tyre', NULL, '2026-07-16 16:29:22', '2026-07-16 16:29:22.162', '2026-07-16 16:29:22.162', NULL, 'c9cfbbab6a28aa1e01b21c60f30624f5', 2) ON CONFLICT(hash) DO NOTHING; INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FNTXEM9NM5M5SX9WQAAA40SC', 'status', 'in_progress', 'review', NULL, '2026-07-16 17:08:34', '2026-07-16 17:08:34.002', '2026-07-16 17:08:34.002', NULL, '164946c94d6d95e2e7d6b1403d70d68a', 2) ON CONFLICT(hash) DO NOTHING; INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FNTXF0JB8E6PDQRASQ18E9ER', 'status', 'in_progress', 'review', NULL, '2026-07-16 17:08:34', '2026-07-16 17:08:34.005', '2026-07-16 17:08:34.005', NULL, 'bc87116dbb3c7aa92437d29d457a2b51', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FNTXEM9NM5M5SX9WQAAA40SC', 'status', 'review', 'done', NULL, '2026-07-16 17:24:54', '2026-07-16 17:24:54.844', '2026-07-16 17:24:54.844', NULL, '8a99c8b7ea756572523471e9dc89ccf2', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FNTXF0JB8E6PDQRASQ18E9ER', 'status', 'review', 'done', NULL, '2026-07-16 17:24:54', '2026-07-16 17:24:54.846', '2026-07-16 17:24:54.846', NULL, '71b133f7f788c56ae887746d097290e9', 2) ON CONFLICT(hash) DO NOTHING; diff --git a/.pql/changelog/ticket_idmap/2026-07.sql b/.pql/changelog/ticket_idmap/2026-07.sql index 3010c14ed..335a6781d 100644 --- a/.pql/changelog/ticket_idmap/2026-07.sql +++ b/.pql/changelog/ticket_idmap/2026-07.sql @@ -31,3 +31,6 @@ INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_ INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FPM32KT5Z1JBP9BCN2KC53EG', 'T-1115', '2026-07-16 08:30:20.629', '2026-07-16 08:30:20.629', NULL, 'fa7bddc579e332fec1543c418824f1cd', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FPPM0KP850FN6WQ5PDEX9ZS4', 'T-1116', '2026-07-16 14:23:57.874', '2026-07-16 14:23:57.874', NULL, 'bbe975b6d023ec3f9383249335b835cd', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FPPYPF4PCTW99YMKXPA60Y40', 'T-1117', '2026-07-16 15:10:38.380', '2026-07-16 15:10:38.380', NULL, 'eea5f2d9b1f0e825ccfdd290305cda22', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FPQXFFHXEEDQEM7PGXAH3534', 'T-1118', '2026-07-16 17:25:07.605', '2026-07-16 17:25:07.605', NULL, '9d4ed5c2648929ff7fb627072fd23e0b', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FPQXHDSMSVD3VADV38NG9STW', 'T-1119', '2026-07-16 17:25:23.533', '2026-07-16 17:25:23.533', NULL, 'b74d71903cc29ddc298cdc6476cdac7f', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FPQXK8D6YVWVC2TB2ZR25K6G', 'T-1120', '2026-07-16 17:25:38.537', '2026-07-16 17:25:38.537', NULL, '778c2b6f758329ac15fbeba31b7489d2', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); diff --git a/.pql/changelog/tickets/2026-07.sql b/.pql/changelog/tickets/2026-07.sql index b439c1e12..863a4fc55 100644 --- a/.pql/changelog/tickets/2026-07.sql +++ b/.pql/changelog/tickets/2026-07.sql @@ -1914,3 +1914,31 @@ Key implementation facts (Si-verified against post-PR#178 main): - Client wire-decode IS in scope (one line): protocol.gd::atlas_response_from_raw raw.get("region_grid") passthrough, matching district_grid. The VISUAL overlay (atlas_viewer/marker_overlay/legend) is explicitly OUT of scope — small follow-up per D-226 amendment (a) incremental-overlay pattern. - Edge-fuzz (D-243 §4) is consumer-side sampling behavior already implemented in region_baseline_at_district — not this ticket''s concern. - Q-105 resolved into D-243; RegionClock tick-phase callbacks stay deferred — only the mean-state fields are served.', 'review', 'medium', 'dudley', 'server', 'D-243', '2026-07-13 21:50:34.130', '2026-07-16 17:08:34.005', NULL, '3e46d1a1deedd537dea1d2d214b26c37', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FNTXEM9NM5M5SX9WQAAA40SC', 'task', '06FB0TNSRZXCHGS16BFHSSGSV4', 'Design: coarse quarter-footprint Atlas layer (L4 skeleton on the planetary map)', 'Si audit of T-960 (2026-07-13): L4 quarter/block skeletons (T-957/T-976) auto-generate server-side into BodyWorldState.quarters, but carry full per-block zoning/street/tag detail — too fine for a planetary map, and unlike the district grid there is no existing coarse shape to reuse. DESIGN ticket (Araminta encoding + Tyre data shape) before any wiring: define a QuarterFootprintLayer (city outline + aggregate stats?) for AtlasLayerResponse and its overlay encoding per the D-226 amended (2026-07-13) overlay pattern. Wiring is a follow-up once the design is recorded. + +--- +Refinement (2026-07-16, /whats-next Si review): READY, one scoping note made explicit. + +- DELIVERABLE FORMAT (was unpinned): record the design as a **D-226 amendment** — the established mechanism (the 2026-07-13 overlay pattern itself was recorded that way); no docs/design Atlas-overlay doc precedent exists (T-1046 shipped design-in-ticket+code-comments). The design pass ends at the recorded amendment; the wiring follow-up ticket is filed by the lead afterwards, not by the design pass. +- BINDING CONSTRAINT: D-226 amendment (d) is a hard ceiling — planetary Atlas tops out at settlement/quarter-skeleton granularity with AT MOST aggregate stats. No per-block zoning/street/tag detail at planetary scale. City outline + aggregate stats only. +- DATA SOURCE: BodyWorldState.quarters stores QuarterWorldState{skeleton: QuarterSkeleton, block_tags: BTreeMap<(u8,u8), Vec>}; QuarterSkeleton carries [[BlockSkeleton;4];4] with zoning/district_type/density_pct/landmark + reservations/corridors. All aggregatable (density avg, dominant zoning/district-type mix, landmark count, corridor count) without inventing new simulation truth — same derivation class as build_district_grid/build_settlement_layer. +- PRECEDENT TO IMITATE (five touch points, each with a working example): server AtlasLayerResponse field + build_quarter_footprint_layer() mirroring build_district_grid (keyed off non-empty state.quarters); client OVERLAY_DEFS gen_* id (atlas_viewer.gd), _draw_gen_* fn (atlas_marker_overlay.gd, imitate _draw_gen_district ~185-188), GENERATION_LEGEND entry (atlas_legend_panel.gd). These are named in the amendment for the wiring follow-up — not implemented in this ticket. +- PARTICIPANT SPLIT: Tyre owns the aggregate-stats data shape (fields, structure, D-010 integer discipline); Araminta owns the overlay encoding (outline vs filled block, color axis per the shape-vs-color convention in atlas_legend_panel.gd) — the same division D-226 originally used. Two coordinated agents, not a workshop round. +- pql metadata quirk noted: the ticket''s decision_ref D-226 resolves to a wrong title in show-summary; `pql decisions read D-226` returns the correct record.', 'done', 'medium', 'tyre', 'client', 'D-226', '2026-07-13 21:50:30.989', '2026-07-16 17:24:54.843', NULL, 'fe5dfefb93177b0fc3f85e78a69490aa', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FNTXF0JB8E6PDQRASQ18E9ER', 'task', '06FB0TNSRZXCHGS16BFHSSGSV4', 'Wire region climate stack into the cascade (CascadeLayer::Region + proxy exposure)', 'Si audit of T-960 (2026-07-13): region_profile.rs (T-1078 — RegionProfile, derive_regions_for_body, region_baseline_at_district) is implemented and unit-tested but never called from cascade.rs: no CascadeLayer::Region variant, nothing on BodyWorldState, nothing in the layer proxy. Prerequisite for any climate Atlas overlay (explicitly out of T-960 scope per 2026-07-13 ruling): add the cascade layer, populate BodyWorldState, expose via AtlasLayerResponse following the T-1046 district-grid precedent. The climate OVERLAY itself is a small follow-up once this serves data. + +--- +Refinement (2026-07-16, /whats-next Si review): READY, no gaps. + +Key implementation facts (Si-verified against post-PR#178 main): +- region_profile.rs (T-1078) exposes exactly what''s needed: RegionProfile{pos, clock: RegionClock{season, weather, mean_temp_c}, latitude_deg, moisture_q}, derive_regions_for_body(...) -> BTreeMap, region_baseline_at_district(...). Deterministic, unit-tested. +- CRITICAL: district_profile.rs::derive_all_districts (~1434-1509) ALREADY builds the region cache internally (incl. neighbours for edge-fuzz) and discards it — this ticket is retention, not new derivation. Chosen shape: option (a) — independent CascadeLayer::Region step in run_cascade_from_heightmap (one-layer-one-concern, mirrors existing layers; accept the cheap double-derive). +- CascadeLayer Ord today: Heightmap < Topography < Settlement < DistrictProfile < RoadGraph. Region slots as its own step. +- BodyWorldState: add pub regions: BTreeMap per the districts precedent (same determinism rationale, same empty-until-run convention). Footprint trivial (~195×98 max). +- layer_proxy.rs doc comment (76-79) already earmarks T-1113 as one of the two final layer fields. Mirror build_district_grid: RegionGridLayer + build_region_grid(state) -> Option<...>, called from handle_atlas_request cache-hit branch. Encoding: dense row-major grid per the T-1046 precedent (implementer''s low-stakes call on exact fields/quantization). +- Client wire-decode IS in scope (one line): protocol.gd::atlas_response_from_raw raw.get("region_grid") passthrough, matching district_grid. The VISUAL overlay (atlas_viewer/marker_overlay/legend) is explicitly OUT of scope — small follow-up per D-226 amendment (a) incremental-overlay pattern. +- Edge-fuzz (D-243 §4) is consumer-side sampling behavior already implemented in region_baseline_at_district — not this ticket''s concern. +- Q-105 resolved into D-243; RegionClock tick-phase callbacks stay deferred — only the mean-state fields are served.', 'done', 'medium', 'dudley', 'server', 'D-243', '2026-07-13 21:50:34.130', '2026-07-16 17:24:54.846', NULL, '695184b0c9a6834a67955695fc129658', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FPQXFFHXEEDQEM7PGXAH3534', 'task', '06FB0TNSRZXCHGS16BFHSSGSV4', 'Atlas climate overlay — render region_grid (temp/season/weather/moisture) in the viewer', 'Follow-up to T-1113 (merged PR #179): the server now serves RegionGridLayer (dense row-major; season/weather repr(u8) discriminants, mean_temp deci-degC i16 with i16::MIN airless sentinel, moisture_q u8) through AtlasLayerResponse.region_grid, and protocol.gd passes it through. Build the client overlay per the D-226 incremental-overlay pattern: OVERLAY_DEFS gen_* id (atlas_viewer.gd), _draw_gen_* renderer (atlas_marker_overlay.gd — a region-grid fill analogous to _draw_gen_district but at region granularity), GENERATION_LEGEND entry (atlas_legend_panel.gd). Pick ONE visual channel (mean temp ramp is the obvious first; season/weather as tooltip or variant overlays). Also add a populated region_grid wire fixture (all three committed fixtures carry nil — noted in PR #179).', 'backlog', 'medium', NULL, 'client', 'D-226', '2026-07-16 17:25:07.599', '2026-07-16 17:25:07.599', NULL, 'ade35c5892731c5c656f4a09c7d625ac', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FPQXHDSMSVD3VADV38NG9STW', 'task', '06FB0TNSRZXCHGS16BFHSSGSV4', 'Wire QuarterFootprintLayer per the D-226 T-1112 amendment (six touch points)', 'Implements the design recorded in the D-226 amendment (2026-07-16, T-1112, merged PR #179). Six touch points, each named in the amendment SS4: (1) ZoningType gains PartialOrd/Ord derives mirroring DistrictType''s T-994 precedent (enables the dominant_zoning declaration-order tie-break); (2) AtlasLayerResponse.quarter_footprints field + build_quarter_footprint_layer() mirroring build_district_grid, keyed off non-empty state.quarters, aggregates keyed by city_id (join via the same deterministic QuarterId derivation the dispatch uses); (3) protocol.gd passthrough; (4) OVERLAY_DEFS gen_* id (atlas_viewer.gd); (5) _draw_gen_* scaled-glyph renderer anchored on the L3 settlement dot — shape=dominant district type, color=density ramp on the settlement-gold family, zoom-gated at SETTLEMENT_LABEL_MIN_ZOOM (atlas_marker_overlay.gd); (6) GENERATION_LEGEND entry (atlas_legend_panel.gd). Five aggregate fields: density_avg_pct u8, dominant_district_type, dominant_zoning, landmark_count u8, corridor_count u8; landmark/corridor tooltip-only per the D-226(d) ceiling.', 'backlog', 'medium', NULL, 'server', 'D-226', '2026-07-16 17:25:23.533', '2026-07-16 17:25:23.533', NULL, 'b6b8cd20b3fe7d1229c917674d46b0cc', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FPQXK8D6YVWVC2TB2ZR25K6G', 'task', '06FBPPMZNNEV052DBYYY3A897C', 'Atlas screenshot capture matrix + golden smoke (visual_capture extension)', 'Jeroen''s ask (2026-07-16): in-game Atlas screenshots at several zoom levels across a varied body sample, treated as goldens for smoke tests. Recon (this session) mapped the path — reuse, don''t rebuild: extend client/tests/visual_capture.gd with an atlas-matrix mode (one Godot boot looping body x zoom x overlay-set), scenarios modeled on visual_scenarios.gd atlas_gen_open (currently GJ71c-hardcoded — parameterize); server lifecycle per tests/run-visual (--test-mode --port 0, parse LISTENING, SR_LIVE=1 — Atlas data is 100% systems.db regardless of world); waits on SimBridge.atlas_layers_received/city_names_received (status Ready) not fixed delays; capture via the existing _capture() (frame_post_draw -> save_png); xvfb-run + --rendering-driver opengl3, NEVER --headless (blank PNGs under dummy driver). Promote a curated subset into client/tests/golden/visual/ + tests/visual.json (existing tooling/visual-diff tolerance machinery, make visual-update). Known warts: no public zoom/pan setter on AtlasViewer (reach into _view_zoom/_view_offset + _apply_transform per existing convention, or add a small set_view() API); Sol uses the legacy markers path (exclude); async races need per-shot wait/timeout. Body sample suggestion: GJ380c, GJ144e, GJ338Bd, GJ820Bc, GJ251c (honest T-1116 water/no-roads exhibit), + a frozen body and a moon.', 'backlog', 'medium', NULL, 'client', NULL, '2026-07-16 17:25:38.537', '2026-07-16 17:25:38.537', NULL, '1cfe732a65ed0be3d7f4724c2ec2389c', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);