[{"slug":"ZV-2026-2006","server_name":"mcp.easyterritory.ai","severity":"breaking","title":"mcp.easyterritory.ai: Field guidance_handle was removed from account_build input; consumers still sending it may be rejected or silently ignored.","summary":"[risky] Description of account_build changed (75% word delta). [breaking] Field guidance_handle was removed from account_build input; consumers still sending it may be rejected or silently ignored. [risky] Description of analyze changed (74% word delta). [breaking] Field guidance_handle was removed from analyze input; consumers still sending it may be rejected or silently ignored. [risky] Description of analyze_routes changed (65% word delta). [breaking] Field guidance_handle was removed from analyze_routes input; consumers still sending it may be rejected or silently ignored. [risky] Description of auto_build changed (79% word delta). [breaking] Field guidance_handle was removed from auto_build input; consumers still sending it may be rejected or silently ignored. [risky] Description of calculate_route changed (72% word delta). [breaking] Field guidance_handle was removed from calculate_route input; consumers still sending it may be rejected or silently ignored. [risky] Description of cluster_points changed (73% word delta). [breaking] Field guidance_handle was removed from cluster_points input; consumers still sending it may be rejected or silently ignored. [risky] Description of configure_map changed (68% word delta). [breaking] Field guidance_handle was removed from configure_map input; consumers still sending it may be rejected or silently ignored. [risky] Description of create_territory_from_parts changed (74% word delta). [breaking] Field guidance_handle was removed from create_territory_from_parts input; consumers still sending it may be rejected or silently ignored. [risky] Description of delete_route changed (60% word delta). [breaking] Field guidance_handle was removed from delete_route input; consumers still sending it may be rejected or silently ignored. [risky] Description of delete_tal changed (59% word delta). [breaking] Field guidance_handle was removed from delete_tal input; consumers still sending it may be rejected or silently ignored. [risky] De","changes":[{"kind":"description_changed","tool":"account_build","after":"Builds territories that follow an account column, such as rep name or territory code from CRM: each part goes to the group holding most of its accounts (conflict_policy). Adds a new alignment (TAL). Needs an ingested point_layer, grouping_field, part_layer, and tal_label. Grouping values are labels, not balance metrics. part_scope: bbox_intersect (default; parts near the accounts) or explicit with part_filter, e.g. {state_abbr: TX}. A declared visit-frequency column needs visit_frequency_field or ignore_visit_frequency (CLARIFICATION_REQUIRED otherwise). With map_session_id the open map is the input. Returns a task; the result carries tal_id and ts_handle. analyze reports the statistics.","before":"[Tier 1 — Attribute Grouping Builder] When: territories should mirror an account attribute column from CRM exports (rep name, territory_name, territory code). Prerequisites: ingest_accounts with grouping column; part_layer chosen; viewer connected. Omit ts and ts_handle when the session id argument is already set. That session is the TS. Numeric codes are labels, not balance metrics; scoped builds use in-scope accounts only. Part scope defaults to bbox_intersect (bbox proximity of ingested accounts). When the user names a state/region ('TX ZIPs only'), pass part_scope=explicit and part_filter={state_abbr: TX} — not bbox_intersect. Scope fields are top-level part_filter/part_ids. Progress: linked tasks publish live subphases on Tasks status / MC overlay (grouping → radial seeds → inflate → empty-part assign → interlock polish) with cooperative cancel — same poll loop as auto_build (next_action / sleep_ms). VISIT FREQUENCY: when a cadence column is declared, pass visit_frequency_field to scale workload or ignore_visit_frequency=true for one visit per cycle — do not inherit the column silently. Modern form-capable clients (protocol >= 2026-07-28) may be prompted in-band for missing grouping_field / part_layer / tal_label; legacy hosts keep required-arg / INVALID_REQUEST errors. Next: analyze + load_analysis_panel. Scenarios: ACB-001..004, MC-011.","detail":"Description of `account_build` changed (75% word delta).","severity":"risky","descriptionDelta":0.75},{"kind":"input_property_removed","path":"inputSchema.properties.guidance_handle","tool":"account_build","before":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Guidance Handle","default":null},"detail":"Field `guidance_handle` was removed from `account_build` input; consumers still sending it may be rejected or silently ignored.","severity":"breaking"},{"kind":"description_changed","tool":"analyze","after":"Computes territory statistics for one or more alignments, i.e. how balanced the territories are and how alignments compare: account counts, declared metric totals (e.g. Revenue), workload hours, balance scores, and cross-TAL comparison; hypothetical_moves previews a realign. Input: map_session_id (the open map as it is now), ts_handle, ts, or a completed build job_id (pass tal_ids when several TALs exist). metrics: omit for every declared column, or name them; workload and account_count are always available. Workload needs dwell (dwell_time, the build's recorded dwell, or the layer's dwell_time_field); without it the result has every other statistic, no workload, and workload_omitted.ask_user, the question for the user. analysis_panel=single with map_session_id fills the map's analysis dock (analysis_panel.status=pushed). territories[].workload_total is minutes; hours are in territory_metric_grids. Drive-time alignments count accounts inside each polygon. Returns a task.","before":"[Tier 1 — Analysis] When: balance diagnostics, cross-TAL comparison, or post-mutation facts. Accepts ts, ts_handle, or completed job_id (inline ts or result.ts_handle from prior compute jobs), or map_session_id alone to analyze the open map as it is now, including points ingested after the build. A job_id or ts_handle is that job's snapshot and wins when both are passed, except when the snapshot has no points and the open map does: then the live map is analyzed and the result warns ANALYZED_LIVE_SESSION. Prerequisites: TAL exists; re-run after any TAL or point change (I-2)—never treat stale analysis as current. Returns JSON facts and presentation guidance URI; no prose. metrics: omit to analyze all declared point-layer columns, or pass explicit names. System dimensions (always valid, not point columns): workload (territory hours; alias total_workload_hours), account_count. Point columns must be fields declared at ingest (metric_fields/workload_fields, e.g. Revenue, Units Sold); undeclared columns are discarded at ingest and fail with UNDECLARED_FIELD — re-ingest with the column declared to analyze it. Response includes available_metrics. METRIC COLUMNS ALWAYS RENDER: the territory_metric_grids (and the MC dock) carry Total Count plus a Total <metric> column for EVERY declared metric_fields column of the point layer, in declaration order, even when metrics names only account_count or workload — a count-only panel is never the correct outcome when metrics are declared. Declared names may be bare strings (Designer pull) or {field,label,type} objects (ingest). Metric cells are parsed leniently: '14,651', '$1,200.50', and padded strings sum as numbers. DWELL / WORKLOAD (T-171): workload hours need onsite/dwell time — request dwell_time, TAL build_provenance.dwell_time (auto_build), or the point layer's dwell_time_field. When none resolves, Analyze STILL RUNS and reports counts, metrics, classification breakdowns, and balance on those dimensions; workload is OMITTED (no total_workload_hours column, workload_total null) — never drive-only hours. The result then carries workload_omitted {tal_ids, ask_user, retry}: report the statistics FIRST, then relay ask_user verbatim (it asks for an average onsite/dwell time). Re-run analyze with dwell_time only if the user answers; never invent a default (including 30 minutes). Do not ask for dwell before the first analyze just to avoid the note. DRIVE-TIME TALS (isochrone_build): accounts are counted inside each provider polygon (measured_by=area_polygon), in every area that contains them, so area totals can exceed the account count; area_overlaps lists accounts shared across origins. scope and hypothetical_moves do not apply. Prefer analysis_panel=single with map_session_id so the MC dock fills on this call — that is the one-shot post-build path. The completed result then has analysis_panel.status=pushed, and session state has analysis_panel.loaded=true. phase_timings_ms.panel_push is only a duration, not proof the dock opened. Otherwise pass the full Analyze result (or its task_id) to load_analysis_panel. WORKLOAD UNITS: territories[].workload_total is minutes (workload_unit=minutes). Quote hours only from territory_metric_grids cell total_workload_hours.value. When metrics is omitted and dwell resolves, balance_scores includes workload plus declared metrics — not a location-match score. DOCK HIDE: minimizing the table leaves analysis_panel.loaded true; do not call load_analysis_panel again just to restore it. A new analysis_panel_loaded event opens the dock. Chat-only JSON is not completion when a map is open. Use ezt://guidance/analysis-presentation for narrative. Full atom: ezt://guidance/workflows/analyze-and-present. Scenarios: AN-001..007, MC-009, S002.","detail":"Description of `analyze` changed (74% word delta).","severity":"risky","descriptionDelta":0.744408945686901},{"kind":"input_property_removed","path":"inputSchema.properties.guidance_handle","tool":"analyze","before":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Guidance Handle","default":null},"detail":"Field `guidance_handle` was removed from `analyze` input; consumers still sending it may be rejected or silently ignored.","severity":"breaking"},{"kind":"description_changed","tool":"analyze_routes","after":"Returns the total hours for each of the routes calculate_route has drawn: drive hours, dwell hours, route_workload_hours (provider drive time plus dwell, no visit-frequency multiplier), stop coordinates, centroid, and bbox. Synchronous. Needs map_session_id or ts_handle, and dwell from dwell_time or the dwell recorded on the route; unresolved dwell returns CLARIFICATION_REQUIRED (needs_dwell). A circuit's stop_count includes the return to the start, so that stop's dwell counts twice. Facts only: no capacity, ranking, or overload flags. These hours are a different measure from territory and cluster workload; territory statistics come from analyze.","before":"[Tier 2 — Route Facts] When: you need per-route numbers for routes calculate_route already drew — drive hours, dwell hours, route_workload_hours, stop coordinates, centroid, bbox (e.g. 'which of my Houston runs has room for one more stop', 'total hours for each route'). Prerequisites: at least one route in the session/TS (map_session_id preferred, else ts_handle); dwell confirmed by the user unless calculate_route already stored it. route_workload_hours = provider drive time + confirmed dwell, with NO visit-frequency multiplier. dwell_hours is one dwell per stop in stop_count. A circuit's stop_count includes the return to the start, so that account is charged dwell twice. Cluster and territory workload charge each account once. This is a DIFFERENT quantity from those hours — never sum, compare, or substitute one for the other. FACTS ONLY: no capacity, no headroom, no ranking, no overloaded flag. Apply constraints like 'nearest route under 7 hours' yourself from centroid + route_workload_hours, then add the stop by re-running calculate_route with that route_id and the revised stop list. Anti-patterns: do NOT call analyze for routes (analyze is TAL/part-grained and returns territory workload); do NOT feed these hours into auto_build, territory_rebalance, or a territory workload figure. Unresolved dwell returns CLARIFICATION_REQUIRED / needs_dwell — ask the user, never invent a default (including 30 minutes). Synchronous: no task_id. Scenarios: RT-011.","detail":"Description of `analyze_routes` changed (65% word delta).","severity":"risky","descriptionDelta":0.6493506493506493},{"kind":"input_property_removed","path":"inputSchema.properties.guidance_handle","tool":"analyze_routes","before":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Guidance Handle","default":null},"detail":"Field `guidance_handle` was removed from `analyze_routes` input; consumers still sending it may be rejected or silently ignored.","severity":"breaking"},{"kind":"description_changed","tool":"auto_build","after":"Partitions ingested account points into balanced sales territories with equal workload, metric, or count over a part layer (e.g. ZIPs) and adds them as a new alignment (TAL); existing alignments stay. Needs the user's choices: build_mode (fixed_territory_count + territory_count, fixed_workload_target + target_workload in hours, or scoped_split), objective (workload_bias for workload = drive time + dwell; metric + metric_bias for a column such as Revenue; biases sum to 100), and dwell_time (a column or a scalar the user states). Each missing choice returns CLARIFICATION_REQUIRED with the question in ask_user; a declared visit-frequency column also needs visit_frequency_field or ignore_visit_frequency. part_scope: bbox_intersect (default; parts near the accounts, can reach national scale) or explicit with part_filter, e.g. {state_abbr: TX}. With map_session_id the open map is the input; ts and ts_handle are not needed. Returns a task; the result carries tal_id, ts_handle, and shape figures (min_solidity, polsby_popper). analyze reports the statistics.","before":"[Tier 1 — Balanced Territory Builder] When: partition account points into N balanced territories over a part layer (for example, ten territories; Mode A count, Mode B workload target, Scoped Split). Execution gate: after sizing, balance, dwell, and scope are known, call THIS tool immediately — searching the catalog, get_guidance, or polling Tasks never starts a build. A successful response with a new task_id is the only proof of submit; do not claim started/restarting until then. Then follow do_this_next only with that new task_id. When workflow_advisor returns next_tool=auto_build, call auto_build next. Canonical example — 3 TX ZIP territories, workload-only, 30-min dwell: build_mode={mode: fixed_territory_count, territory_count: 3}, objective={workload_bias: 100}, dwell_time={type: scalar, value: 30, unit: minutes}, part_scope=explicit, part_filter={state_abbr: TX}, map_session_id=<open MC>. Omit ts and ts_handle when the session id argument is already set. That session is the TS. Do not repost inline ts. build_mode.mode must be exactly one of: fixed_territory_count, fixed_workload_target, scoped_split. For workload targets (e.g. 40-hour territories), use build_mode={mode: fixed_workload_target, target_workload: 40} (territories only approximate the target). ALWAYS ask for dwell/onsite time before calling — Auto Build always computes territory workload hours (drive + dwell), even when balancing on a metric or account count. Pass dwell_time only after they confirm a column ({type: field, field, unit}) or scalar ({type: scalar, value, unit}). Never invent default dwell such as 1 hour or 30 minutes per visit. Server rejects auto_build without resolved dwell_time (CLARIFICATION_REQUIRED). VISIT FREQUENCY: when the point layer has a visit-frequency column, ask whether to aggregate workload across a schedule period (pass visit_frequency_field) or ignore it (pass ignore_visit_frequency=true). Do not inherit the column silently. If no visit-frequency column exists, omit both. Modern clients that negotiate protocol >= 2026-07-28 and declare form elicitation may answer missing sizing/dwell (and optional balance) in-band via Resolve/Elicit; legacy/Cursor hosts without form elicitation keep CLARIFICATION_REQUIRED / ask_user (HITL-025 — not a server failure). build_mode may be omitted when elicitation can fill it. Workload is drive time plus dwell — not Revenue or any metric column. When the user says 'balanced on workload' or 'workload-balanced', use workload_bias=100 with no objective.metric; do not ask which metric column workload means — ask dwell (Q5) only. Confirm balance dimension (Q2) and bias (Q3) with the user when unclear — a workload-hour target is sizing, not permission to silently set workload_bias=100 unless workload is already named as the balance dimension. Declare metric_fields at ingest but ask which column (if any) to balance on before auto_build when the user did not already choose workload balance. After build completes: analyze with map_session_id and analysis_panel=single (or load_analysis_panel) so stats appear in the MC dock — not chat-only. Part scope defaults to bbox_intersect (ZIPs intersecting the account-point bbox) — geographic proximity, not state/attribute scope. When the user names a state or region ('TX ZIPs only', 'Texas only'), pass part_scope=explicit and part_filter={state_abbr: TX} immediately; do not default to bbox_intersect (can pull tens of thousands of national ZIPs) and do not query_parts first for an obvious state filter. Scope fields are top-level part_filter/part_ids — never under objective. A genuinely nationwide ZIP request remains supported: when bbox_intersect resolves at national scale, the job continues at full ZIP-level quality and returns a NATIONAL_PART_SCOPE warning rather than silently switching geography. Long partitions publish named 35–42% subphases (travel cache, power solve, border refinement, compactness, seam polish) and renew their worker lease independently; keep polling via next_action/sleep_ms and do not infer a stall from one long quality pass. Prerequisites: ingest_accounts done, part_layer chosen, viewer connected (human-in-loop). Use direct_build for known assignments; account_build to group by attribute. Appends a TAL; does not replace existing alignments. Dissolved leaves report min_solidity beside mean_polsby_popper and min_polsby_popper. Gate shape from those figures rather than recomputing them from a download. Next: analyze with analysis_panel=single. Full atom: ezt://guidance/workflows/build-from-accounts. Scenarios: S003, AB-001..017, MC-011.","detail":"Description of `auto_build` changed (79% word delta).","severity":"risky","descriptionDelta":0.7941952506596306},{"kind":"input_property_removed","path":"inputSchema.properties.guidance_handle","tool":"auto_build","before":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Guidance Handle","default":null},"detail":"Field `guidance_handle` was removed from `auto_build` input; consumers still sending it may be rejected or silently ignored.","severity":"breaking"},{"kind":"description_changed","tool":"calculate_route","after":"Sequences and drives a set of stops, e.g. a technician from home to the depot, then the day's calls, and back home: returns the order, road distance, drive time, per-leg timings, and geometry from TomTom or Azure Maps, and draws the path with numbered stops on the open map (map_session_id). Creates no territory and changes no assignment. route_type is the user's choice: circuit (returns to start), tour (ends at the required end stop), or open_tour (one way). stop_sets are visited in the order given and no stop moves between sets; order is optimize or as_given; group_by_field adds priority bands. Stops are inline coordinates or point-layer references (e.g. source.filter.territory_id). A new route_id adds a second route; recalculating an existing route_id replaces it, e.g. to drop a stop. dwell_time is stored for analyze_routes. Route drive time is a different measure from territory workload. No provider key returns ROUTING_NOT_CONFIGURED; past the monthly quota, PROVIDER_QUOTA_EXCEEDED. Returns a task.","before":"[Tier 1 — Routing] When: drive a list of stops in the best sequence — a windshield itinerary, a service run, a delivery sheet (RT-001..RT-008). This tool routes and sequences existing stops; it never creates or rebalances territories. When a visit-frequency column is on the point layer, the word route is ambiguous — confirm the user wants a driving itinerary of those stops, not schedule_visits, before calling this tool. Prerequisites: a TomTom or Azure Maps key on the server; stops referenced by point_layer must already be ingested, or pass inline lat/lon. TERRITORY STOPS: after a TAL is built or analyze linkage runs, account points carry territory_id. Route one territory with stop_sets source.filter.territory_id set to the leaf id from the last build (leaf_territories[].territory_id), not a display label. start is exactly one stop (home, depot, or one point_id). The territory filter belongs on stop_sets, never on start. ORDERED STOP SETS: stop_sets are visited in the order supplied and optimization NEVER moves a stop between sets, which keeps 'start at home, hit the depot, then the day's calls' in the right sequence. Use order='optimize' to let the solver sequence a set, 'as_given' to keep it fixed. group_by_field splits one set into ordered bands on a column value (e.g. route_priority: every 1 precedes every 2, optimized inside each band). ROUTE TYPES: 'circuit' returns to the start; 'tour' finishes at a declared end stop (end is required); 'open_tour' is a one-way run that ends at the last stop the solver picks, never returning to the start. A circuit's stop_count includes that return. analyze_routes charges dwell once per stop, so the start account is charged dwell twice. Cluster and territory workload charge each account once — those hour figures are not this route's hours. Sequencing is solved server-side against straight-line distance, then one provider call returns road distances, durations, and geometry, so reported numbers are always road-accurate. Drive time here is not the territory workload model — auto_build keeps its own. MANY ROUTES ON ONE MAP: each route is stored in the TS under a route_id, so calling this tool again with a NEW route_id ADDS a route (10 Houston routes coexist, each with its own legend row and color). Reusing an EXISTING route_id REPLACES that route in place, which is the way to add or drop a stop: re-route the same route_id with the revised stop list. Never delete and re-add for a stop change, and never omit route_id when you meant to add a second route (an auto-minted id is fine for a one-off). Use delete_route to drop a whole route; per-route workload facts come from analyze_routes, never from analyze. Pass dwell_time when you want route workload recomputable later; it is stored as provenance and never invented. QUOTA: billable provider calls are capped per API key per calendar month, and a long stop list chunked to the vendor waypoint cap costs one call per chunk. A route is all-or-nothing: past the cap it returns PROVIDER_QUOTA_EXCEEDED with limit, used, remaining, and period_resets_at rather than a partial path. An operator must raise the cap, so report those numbers instead of retrying with fewer stops. Async: returns task_id — follow _meta.next_action (sleep_and_poll → consume_result). Pass map_session_id to draw the path and numbered stops on an open map. Full atom: ezt://guidance/workflows/routes-layer. Scenarios: RT-001..RT-011.","detail":"Description of `calculate_route` changed (72% word delta).","severity":"risky","descriptionDelta":0.7188498402555911},{"kind":"input_property_removed","path":"inputSchema.properties.guidance_handle","tool":"calculate_route","before":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Guidance Handle","default":null},"detail":"Field `guidance_handle` was removed from `calculate_route` input; consumers still sending it may be rejected or silently ignored.","severity":"breaking"},{"kind":"description_changed","tool":"cluster_points","after":"Groups the points of one ingested layer into balanced clusters directly, with no ZIP or part layer and no territory alignment. Writes group_field (default group_id) on every point and colors the layer by it, replacing any prior color classification (size and shape channels stay). Needs build_mode (fixed_territory_count or fixed_workload_target), objective, and dwell_time, as in auto_build; a missing one returns CLARIFICATION_REQUIRED with the question. point_filter (e.g. {STATE: FL}) clusters a subset of the layer; unmatched points stay, ungrouped. Max 10,000 points after the filter. seed_point_layer, a second point layer such as technician homes, anchors one group per seed (the seed count matches the group count, else seed_count_mismatch); seed_attraction 0-100 (default 70) is the pull of each seed on its group. Returns a task; the result lists groups with point_count, workload_hours, and point_ids.","before":"[Tier 1 — Balanced Point Grouping] Partition one ingested point layer directly into balanced point groups with CCPD, without using ZIPs/counties or creating a TAL. The tool adds group_field (default group_id) to every point and color-classifies that field in the linked Map Component. That write replaces the layer's existing color classification (including a leftover color_classification ramp). Size and shape channels stay. Keep a prior metric as a second encoding with configure_map channel=size or channel=shape after this job — never a second color classification on the same layer. Example — five TX point groups balanced on workload with confirmed 30-minute dwell: point_layer=accounts, build_mode={mode: fixed_territory_count, territory_count: 5}, objective={workload_bias: 100}, dwell_time={type: scalar, value: 30, unit: minutes}. Prerequisite: ingest_accounts completed for point_layer. Omit ts and ts_handle when the session id argument is already set. That session is the TS. Ask the same sizing, balance, bias, visit-frequency, and dwell questions; workload means in-group drive time plus dwell, never a metric column. Never invent dwell. build_mode supports fixed_territory_count and fixed_workload_target only. SUBSET: a named state or region on an already-ingested layer — Florida schools, TX accounts only — uses point_filter on the first call, e.g. point_filter={STATE: FL}. Keys are point properties (one value or a list). Do not re-ingest a filtered extract. Do not pass part_filter or part_scope (those belong to ZIP/part tools). Do not cluster the national layer. Unmatched points stay on the layer with group_field cleared and appear as an Ungrouped legend class. The 10,000-point cap applies after the filter. Empty match is EMPTY_POINT_FILTER; unknown property is UNKNOWN_POINT_PROPERTY. SEEDING BIAS (start locations): when the groups should line up with a set of start locations — technician homes, depots, branch offices — pass seed_point_layer=<that layer>. It must be a SECOND ingested point layer, not point_layer. Each seed anchors exactly ONE group, so the seed count must match the group count (fixed_workload_target must derive that same count) or the call fails INVALID_REQUEST with blocked_by=seed_count_mismatch. seed_attraction (0-100, default 70) controls the hold on each group centroid: 100 pins it at its seed, 0 lets the seeds pick only the starting points and the solver drift to the workload centroid. Groups stay workload/metric balanced either way — seeds carry NO workload and are never members of the group they anchor. Both layers receive the same group_field value and the same per-group color, so a technician home paints in its group's color; read result.seed_summary.groups for each pairing plus seed_to_centroid_km. Example — 10 technician homes anchor 10 workload-balanced account groups: point_layer=accounts, seed_point_layer=technician_homes, seed_attraction=100, build_mode={mode: fixed_territory_count, territory_count: 10}. This tool does not spatially join points to parts and does not produce a TAL. Use auto_build for territories. Follow the returned task. The result lists groups (group_id, point_count, workload_hours, point_ids). Full atom: ezt://guidance/workflows/cluster-points. Scenarios: CP-001..005.","detail":"Description of `cluster_points` changed (73% word delta).","severity":"risky","descriptionDelta":0.7328519855595668},{"kind":"input_property_removed","path":"inputSchema.properties.guidance_handle","tool":"cluster_points","before":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Guidance Handle","default":null},"detail":"Field `guidance_handle` was removed from `cluster_points` input; consumers still sending it may be rejected or silently ignored.","severity":"breaking"},{"kind":"description_changed","tool":"configure_map","after":"Updates durable map settings on the Territory Solution and refreshes the live map: project_name (map heading), loaded_part_layers ([] hides ZIP/part outlines; builds never load them), active_part_layer, active_tal_id, center and zoom (default view; the live map jumps there), and point symbology. point_layer_classifications entries are {point_layer, field, method: quantile | equal_interval | categorical | manual, class_count 2-12, channel: color | size | shape, optional colors/sizes/shapes/style}; the server computes the breaks and returns per-class counts. One classification per channel per layer; clear:true removes one. Shapes include circle, square, triangle, diamond, star, cross, house, pin, flag, hexagon, pentagon, shield, arrow_up, building. classification without a point_layer is a project-level setting that does not color points. Input: map_session_id (the live map), ts_handle, or ts.","before":"[Tier 2 — Durable Map Config] When: set or update project_name (the durable TS short name used as the Map Component heading), loaded_part_layers, active_part_layer, active_tal_id, point_layer_classifications, classification, presentation, center, or zoom on the TS. project_name is first-class: persists to ts.properties.map_config.project_name and emits config_changed so a linked session refreshes the heading in place. POINT SYMBOLOGY: point_layer_classifications is the way to recolor/resize/reshape points on an open map — never export GeoJSON, compute breaks client-side, and repost a TS. Each entry is {point_layer, field, method: quantile|equal_interval|categorical|manual, class_count (2-12), channel: color|size|shape, optional colors/sizes/shapes, optional style:{color,size,opacity,shape} for the layer base symbol}. Supported shapes: circle|square|triangle|diamond|star|cross|house|pin|flag|hexagon|pentagon|shield|arrow_up|building (aliases home→house, marker/map_pin→pin, hex→hexagon, arrow/up→arrow_up, warehouse/depot→building, plus/x→cross). The server computes breaks from the in-session points, writes point_layers[].<channel>_classification, pushes config_changed, and returns the applied classes with per-class counts. Categorical missing values become an Ungrouped class. One classification per channel per layer: a second color entry replaces the first. Two encodings on one layer mix channels (color+size or color+shape). clear:true with point_layer and channel drops that channel classification, including a same-channel classification object. Other channels and the base style stay. active_channels reports the channels still set. Method defaults to quantile for numeric fields and categorical otherwise; pass one entry per layer to give two point layers distinct colors or shapes. classification (without a point_layer) stays a project-level map_config patch and does NOT paint points; a point-layer-targeted classification is routed to symbology with a warning. PART LAYERS: builds never load a part layer overlay; ZIP/county outlines appear only when named. loaded_part_layers=[ids] shows them, loaded_part_layers=[] hides them all (active_part_layer clears too), and the choice persists across later builds. request_part_selection loads the part_layer it selects on. Optional center ([longitude, latitude]) and zoom (0-24) persist as the TS default camera and jump an open MC when included on this call; Monica's pan is not captured. Prefer map_session_id for an open MC — the live session TS is the patch base; a stale pre-ingest ts_handle must not strip points/part layers. Not the entry tool for browsing a TS — that is get_map_visualization (I-1). Prerequisites: map_session_id, ts_handle, or inline ts; points already ingested before point_layer_classifications; part_layer from ezt://part-layers before builds. Next: build or analyze; if active TAL changes, re-run analyze then load_analysis_panel (I-2). Scenarios: MC-010, MC-012, DS-001, baseline workflow step 4.","detail":"Description of `configure_map` changed (68% word delta).","severity":"risky","descriptionDelta":0.6781609195402298},{"kind":"input_property_removed","path":"inputSchema.properties.guidance_handle","tool":"configure_map","before":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Guidance Handle","default":null},"detail":"Field `guidance_handle` was removed from `configure_map` input; consumers still sending it may be rejected or silently ignored.","severity":"breaking"},{"kind":"description_changed","tool":"create_territory_from_parts","after":"Creates one leaf territory from a list of part_ids (e.g. a committed map selection), in a new alignment or an existing one (tal_id). With map_session_id the live map's Territory Solution is updated and repainted; otherwise ts_handle (a new ts_handle is returned). map_session_id wins when both are passed. territory_name is a display label; the result's created_territory.territory_id is the stable id for later edits, plus leaf_territories and selection_summary (duplicate_part_ids). Dwell is optional here and is not inherited from an earlier auto_build; dwell_time is recorded for later analyze. Without it, analyze reports every statistic except workload and returns workload_omitted.ask_user. Returns a task.","before":"[Tier 2 — Territory From Parts] When: create or update one leaf territory from committed part IDs (manual MC-005 build or RL-011/012). Prerequisites: part_ids from selection or agent list; part_layer; viewer connected. Pass map_session_id for the open MC — the server loads that session's TS, appends the new TAL, and rebinds the same session before map_refresh (do not call get_map_visualization just to show the new territory). If result.map_refresh.notified is false or status is rebind_required, call get_map_visualization(job_id=<this job>). With no open MC, pass ts_handle from the prior result (never repost inline ts when a handle exists); the new TAL is appended to that TS and the result returns a new ts_handle. map_session_id wins when both are passed. Completed result includes created_territory.territory_id, leaf_territories, and selection_summary (requested/unique counts plus duplicate_part_ids) — territory_name is a display label only (e.g. T1 → territory_id like tal-t1-t1). Later realign into this territory MUST use created_territory.territory_id (preferred) or the exact leaf display name when unique; never invent shorthand (T3 ≠ Territory 3) — if unclear, ask which leaf from leaf_territories. DWELL: this tool creates a NEW TAL and does not inherit dwell_time from a prior auto_build. Dwell is NOT required to create the territory (same as direct_build). Optional dwell_time={type:scalar,value,unit} stamps build_provenance for later Analyze. Without dwell, Analyze still runs and reports every other statistic but OMITS workload (result.workload_omitted) — present the stats, then relay its ask_user sentence; never invent a default (including 30 minutes). Modern form-capable clients may be prompted in-band for dwell when a hydrated TS shows points without dwell provenance; declining still allows create (HITL-038). Next: repeat for additional territories, realign with created_territory.territory_id, or analyze with confirmed dwell_time. Scenarios: MC-005, RL-011, RL-012.","detail":"Description of `create_territory_from_parts` changed (74% word delta).","severity":"risky","descriptionDelta":0.735},{"kind":"input_property_removed","path":"inputSchema.properties.guidance_handle","tool":"create_territory_from_parts","before":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Guidance Handle","default":null},"detail":"Field `guidance_handle` was removed from `create_territory_from_parts` input; consumers still sending it may be rejected or silently ignored.","severity":"breaking"},{"kind":"description_changed","tool":"delete_route","after":"Route delete: removes one whole route from the Territory Solution and the open map, in one synchronous call. Needs route_id or the route's exact unique label, plus map_session_id or ts_handle. A route that is already gone returns ok with already_absent=true; an ambiguous label returns AMBIGUOUS_ROUTE with matching_route_ids. A single stop is removed by recalculating the same route_id with calculate_route.","before":"[Tier 2 — Delete Route] When: drop a whole route from the map and the TS (e.g. 'delete the Tuesday A route', 'remove route houston-tue-a'). Prerequisites: map_session_id for an open MC (preferred), else ts_handle; plus route_id (or an exact unique route label). NOT for removing a single stop — for that, call calculate_route again with the SAME route_id and the remaining stops, which replaces the route in place. Already-absent routes return ok with already_absent=true and no error. An ambiguous label returns AMBIGUOUS_ROUTE with matching_route_ids — pass an explicit route_id rather than guessing. Synchronous: no task_id. Verify the open map legend no longer lists the route. Scenarios: RT-010.","detail":"Description of `delete_route` changed (60% word delta).","severity":"risky","descriptionDelta":0.6022727272727273},{"kind":"input_property_removed","path":"inputSchema.properties.guidance_handle","tool":"delete_route","before":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Guidance Handle","default":null},"detail":"Field `guidance_handle` was removed from `delete_route` input; consumers still sending it may be rejected or silently ignored.","severity":"breaking"},{"kind":"description_changed","tool":"delete_tal","after":"Deletes one whole territory alignment (TAL: 'the territory layer', 'all territories') from the Territory Solution and the open map, in one synchronous call. Needs tal_id or the TAL's exact unique label, plus map_session_id or ts_handle. Points, part-layer overlays, and routes stay; the map switches to a remaining alignment or to points only. A TAL that is already gone returns ok with already_absent=true; an ambiguous label returns AMBIGUOUS_TAL. One territory is removed with delete_territory instead.","before":"[Tier 1 — Delete TAL] When: wipe one whole Territory Alignment Layer (user language: territory layer, alignment, all territories, active alignment — not only 'TAL') from the TS and open map (e.g. 'remove the territory layer', 'wipe all territories', 'remove the alignment', 'clear tal-tx-10t before rebuild'). Prerequisites: map_session_id for an open MC (preferred), else ts_handle; plus tal_id (or an exact unique TAL label). NOT for deleting one leaf territory — that is delete_territory. Do NOT N× delete_territory to wipe an alignment. Synchronous: no task_id / no Realign progress overlay. Points, part-layer overlays, and routes stay; active_tal_id becomes a remaining TAL, __points__, or __empty__. Already-absent returns ok with already_absent=true. Ambiguous label returns AMBIGUOUS_TAL. Next: verify the legend dropped the alignment, then auto_build / account_build / direct_build when rebuilding. Scenarios: HITL-057, T-134.","detail":"Description of `delete_tal` changed (59% word delta).","severity":"risky","descriptionDelta":0.5865384615384616},{"kind":"input_property_removed","path":"inputSchema.properties.guidance_handle","tool":"delete_tal","before":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Guidance Handle","default":null},"detail":"Field `guidance_handle` was removed from `delete_tal` input; consumers still sending it may be rejected or silently ignored.","severity":"breaking"},{"kind":"description_changed","tool":"delete_territory","after":"Deletes one leaf territory from an alignment; its parts become unassigned. Needs tal_id, territory_id (the leaf id or its exact unique display name), and the Territory Solution from map_session_id, ts_handle, or ts. A territory that is already gone returns ok with already_absent=true and no task; otherwise returns a task and the open map repaints. Related: territory_merge hands a leaving rep's territory to its neighbors; delete_tal removes a whole alignment in one call.","before":"[Tier 1 — Delete Territory] When: delete one leaf territory from an existing TAL (e.g. 'delete T1', 'remove territory West'). Prerequisites: ONE current TS reference — prefer map_session_id for an open MC, else ts_handle or ts; tal_id; territory_id (stable leaf id from created_territory.territory_id / leaf_territories preferred, or an exact unique display name — do not invent shorthand like T3 for Territory 3; ask if unclear). Do NOT invent part_ids or call auto_build. This tool collects the leaf's part_ids and runs Realign remove_parts + remove_empty_territories (same engine as RL-013). To wipe an entire TAL / alignment before rebuild, call delete_tal instead — never N× this tool. When a rep leaves and the neighbors should take over the territory, call territory_merge instead — this tool leaves the deleted territory's parts unassigned. Already-absent territories return ok with already_absent=true (no job). Next: follow the returned Tasks status operation until completed, call its result operation once, then verify the open map legend no longer lists the territory AND the territory fill/outline is gone from the canvas (not just the legend) before claiming success. Geography-only delete does not require Analyze. Scenarios: feedback delete-territory, RL-013 wrapper, T-087 last-leaf paint clear.","detail":"Description of `delete_territory` changed (74% word delta).","severity":"risky","descriptionDelta":0.7350993377483444},{"kind":"input_property_removed","path":"inputSchema.properties.guidance_handle","tool":"delete_territory","before":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Guidance Handle","default":null},"detail":"Field `guidance_handle` was removed from `delete_territory` input; consumers still sending it may be rejected or silently ignored.","severity":"breaking"},{"kind":"description_changed","tool":"direct_build","after":"Builds territories from known part-to-territory assignments (a ZIP-to-territory sheet or file, including a three-level region/district/territory hierarchy via territory_path) and adds them to the Territory Solution as a new alignment (TAL). Assignments come inline or as an assignments_handle (aup_...) from request_assignment_upload. Sheets with Postal Code plus Territory/Region/Division columns are mapped automatically. Duplicate part_ids follow duplicate_part_policy (default keep_first). Returns a task; the result carries tal_id and ts_handle and paints the open map. Location points load with ingest_accounts; balanced partitions come from auto_build; grouping by a column is account_build.","before":"[Tier 1 — Known Assignments Builder] When: user has explicit part-to-territory assignments (spreadsheet, legacy file, hierarchical territory_path). assignments_handle must be a server upload handle (aup_...) from request_assignment_upload — csv_text/csv_file/assignments, or a POST of the CSV file to its key-less result.upload_url. Legacy spreadsheets (Postal Code + Territory/Region/Division) are auto-mapped when staged. Conflicting duplicate part_ids default to duplicate_part_policy=keep_first (first wins). Prerequisites: part_layer chosen; viewer connected for MC-first; ts/ts_handle optional. Not for account point locations—use ingest_accounts. Not for balanced partitioning—use auto_build. Not for attribute grouping—use account_build. Next: verify TAL in MC (MC-011), analyze + load_analysis_panel. Scenarios: DB-001..005, MC-011.","detail":"Description of `direct_build` changed (74% word delta).","severity":"risky","descriptionDelta":0.7441860465116279},{"kind":"input_property_removed","path":"inputSchema.properties.guidance_handle","tool":"direct_build","before":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Guidance Handle","default":null},"detail":"Field `guidance_handle` was removed from `direct_build` input; consumers still sending it may be rejected or silently ignored.","severity":"breaking"},{"kind":"description_changed","tool":"discover_intent","after":"Maps a free-text territory request to a workflow. Read-only keyword routing; no compute. Returns intent_category, the recommended tool order, the main tool (tier1_tool, null when the request is ambiguous) and alternatives, required_inputs, clarifying_questions for missing inputs, and the workflow guide inline as guidance {uri, title, excerpt}. Covers building territories (auto_build, account_build, direct_build, cluster_points, seed_build), editing (realign, split, merge, rebalance), analyze, geocoding and ingest, showing a ZIP or part layer, routes, drive-time areas (isochrone_build), recurring visit schedules (schedule_visits), and moving a Territory Solution to or from EasyTerritory Designer.","before":"[Tier 1 — Router] Deterministic entry point for ambiguous or open-ended territory requests. When: the user's goal or build type is unclear and you want the correct workflow before acting. Prerequisites: none (static keyword routing; no compute). Returns an intent_category, the recommended tool order, the Tier 1 tool + Tier 2 alternatives, required_inputs, clarifying_questions to ask when inputs are missing, a guidance_uri (ezt://guidance/workflows/{name}) for the full atom, and `guidance` — the same EMEP atom inlined as {uri, title, excerpt, truncated}, so you do NOT need a second resources/read to get the workflow text. Use it to disambiguate auto_build vs account_build vs direct_build and to route realign / restructure / analyze / delegation / geocode-ingest / load-part-layer-on-map (add zips, show zip codes) / route-stops (drive a list of stops in the best order) / reachable-area (a drive-time or drive-distance area around origins — service area, catchment, coverage, isochrone, which routes to isochrone_build) / periodic-scheduling (a recurring cadence — 'every 30 days', 'twice a month' — and which day each visit lands on, which routes to schedule_visits, never auto_build or cluster_points) / push-to-designer (an EasyTerritory Designer rolodex project from a TS: export_geojson then POST FromTerritorySolution to create or PUT .../Projects/{projectId}/... to update; territories, points, and routes import; ezt_pat_; there is no MCP push tool) / pull-from-designer (a Designer rolodex project into the MCP: GET REST/Agent/Projects then GET .../Projects/{projectId}/TerritorySolution with ezt_pat_, then import_geojson geojson_gzip; there is no MCP pull tool). Scenarios: AB-016, ACB-003, wrong-build-tool.","detail":"Description of `discover_intent` changed (73% word delta).","severity":"risky","descriptionDelta":0.7253886010362695},{"kind":"description_changed","tool":"ensure_map_viewer","after":"Reports whether the person has the map open (viewer_status.connected) for a map_session_id from get_map_visualization, waiting up to wait_seconds (max 30 s per call; the result reports requested_wait_seconds and max_wait_seconds). Works the same for the in-chat map and the map_url tab. Returns VIEWER_NOT_CONNECTED while the map is still closed; the same call can be repeated while the person opens it. Build, analyze, and selection results paint live once the viewer is connected.","before":"[Tier 1 — MC-First Gate] When: after get_map_visualization returns map_url and before any compute, build, analyze, or selection work with a human in the loop. Prerequisites: map_session_id from get_map_visualization. Next: ingest, configure_map, build, realign, or analyze once viewer_status.connected. Surface-agnostic: the in-chat MCP App shell connects the same session over the same SSE channel, so this gate works unchanged whether the human is looking at the in-chat map or the map_url tab. Do not skip it on an Apps host. wait_seconds blocks at most 30 s per call (larger values are clamped; the result reports requested_wait_seconds and max_wait_seconds). On VIEWER_NOT_CONNECTED, call again with the same map_session_id while the user opens map_url. Scenarios: MV-001, I-1.","detail":"Description of `ensure_map_viewer` changed (67% word delta).","severity":"risky","descriptionDelta":0.6698113207547169},{"kind":"input_property_removed","path":"inputSchema.properties.guidance_handle","tool":"ensure_map_viewer","before":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Guidance Handle","default":null},"detail":"Field `guidance_handle` was removed from `ensure_map_viewer` input; consumers still sending it may be rejected or silently ignored.","severity":"breaking"},{"kind":"description_changed","tool":"ep_graph_traverse","after":"Traverses the EMEP knowledge graph from a topic file (e.g. 'workflows/realign-by-selection.md') to related guidance, up to `depth` hops. Returns no edges unless the pack ships a _graph.yaml.","before":"[Knowledge Retrieval] Traverse the EMEP knowledge graph from a topic file (e.g. 'workflows/realign-by-selection.md') to find related guidance, up to `depth` hops. Returns no edges unless the pack ships a _graph.yaml.","detail":"Description of `ep_graph_traverse` changed (12% word delta).","severity":"safe","descriptionDelta":0.12121212121212122},{"kind":"description_changed","tool":"ep_list_topics","after":"Lists EMEP topics grouped by type (concept, workflow, interface, troubleshooting, decision). Optional `type` filter.","before":"[Knowledge Retrieval] List EMEP topics grouped by type (concept, workflow, interface, troubleshooting, decision). Use to discover what guidance exists before ep_search, or to browse the pack. Optional `type` filter.","detail":"Description of `ep_list_topics` changed (57% word delta).","severity":"risky","descriptionDelta":0.5714285714285714},{"kind":"description_changed","tool":"ep_search","after":"Semantic search over the EZT MCP Expert Pack (EMEP): workflow guidance, concepts, interfaces, and common mistakes. Returns ranked markdown chunks with source files; atoms pulled in via `requires` are flagged requires_expanded. Degrades to the ezt://guidance/... resources if retrieval is unavailable. Examples: 'build balanced territories from accounts', 'viewer not connected before compute', 'auto_build vs account_build'.","before":"[Knowledge Retrieval] Semantic search over the EZT MCP Expert Pack (EMEP) for targeted workflow guidance, concepts, interfaces, and common mistakes. When: you are unsure which tool or order to use, hit an error, or need product-specific domain context before acting. Returns ranked markdown chunks with source files; atoms pulled in via `requires` are flagged requires_expanded. Degrades to the ezt://guidance/... resources if retrieval is unavailable. Examples: 'build balanced territories from accounts', 'viewer not connected before compute', 'auto_build vs account_build'.","detail":"Description of `ep_search` changed (28% word delta).","severity":"risky","descriptionDelta":0.2777777777777778},{"kind":"description_changed","tool":"export_geojson","after":"Exports the territory solution as one GeoJSON file to share, archive, or open in EasyTerritory Designer: territory polygons with part_ids, points, and routes with numbered stops, each carrying its displayed style. tal_ids, point_layers, and route_layers narrow it; include_points / include_routes omit a family. Input: map_session_id, ts_handle, or a completed build job_id. Returns a task whose result is geojson_artifact plus a download_url (GET with the API key; gzip unless zip=false), not an inline body. import_geojson reopens the file.","before":"[Tier 2 — Project save] The only Territory Solution export. Materialize the working GeoJSON FeatureCollection (territory polygons with part_ids, point features, and route paths + numbered stops). Omit tal_ids / point_layers / route_layers for the full project. Async: returns task_id; follow its Tasks next_action/sleep_ms until completed, then fetch the result once. The result carries geojson_artifact {artifact_id, bytes}, zip (default true = gzip download), and download_url (GET with Bearer API key) — never an inline multi-MB body. Set zip=false for uncompressed GeoJSON. Prerequisites: ts_handle, completed build job_id, or map_session_id. Set include_points=false or include_routes=false to drop a family. Reopen with import_geojson, then get_map_visualization(ts_handle=...). Paint travels with the export: every feature and point_layers[] / route_layers[] entry carries style {color, opacity, size, shape, weight} exactly as shown, so reopen and Designer import need no restyling. An EasyTerritory Designer rolodex project is this download POSTed to Designer REST/Agent/Projects/FromTerritorySolution (new) or PUT to .../Projects/{projectId}/FromTerritorySolution (update). Territories, points, and routes import. There is no MCP push tool (ezt://guidance/workflows/push-to-designer).","detail":"Description of `export_geojson` changed (67% word delta).","severity":"risky","descriptionDelta":0.6713286713286714},{"kind":"input_property_removed","path":"inputSchema.properties.guidance_handle","tool":"export_geojson","before":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Guidance Handle","default":null},"detail":"Field `guidance_handle` was removed from `export_geojson` input; consumers still sending it may be rejected or silently ignored.","severity":"breaking"},{"kind":"description_changed","tool":"extract_tal_branch","after":"Extracts one regional branch of a hierarchical alignment (by branch_rollup_territory_id or branch_territory_path) into its own Territory Solution, so a delegate can edit just that region. Returns the extract plus branch_metadata, which reintegrate_branch needs later. exclude_locked (default true) leaves locked territories out.","before":"[Tier 1 — Delegation Extract] When: senior planner sends a regional subtree to a delegate for bounded editing. Prerequisites: master TS with hierarchical TAL; branch rollup or path identified. Next: store branch extract + branch_metadata; delegate opens MC on extract only. Scenarios: DL-001, DL-002.","detail":"Description of `extract_tal_branch` changed (89% word delta).","severity":"risky","descriptionDelta":0.8857142857142857},{"kind":"input_property_removed","path":"inputSchema.properties.guidance_handle","tool":"extract_tal_branch","before":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Guidance Handle","default":null},"detail":"Field `guidance_handle` was removed from `extract_tal_branch` input; consumers still sending it may be rejected or silently ignored.","severity":"breaking"},{"kind":"description_changed","tool":"ezt","after":"Plain-language orchestrator: takes request (the goal) and runs the matching workflow in one call, optionally with csv_file (a host file parameter), accounts_handle, assignments_handle, map_session_id, or ts_handle. An uploaded CSV is parsed, staged, and ingested onto the open map. target_hours / workload_target map to a fixed-workload auto_build. Missing build inputs (part layer, alignment name, sizing, balance, dwell) return CLARIFICATION_REQUIRED with the questions. The granular tools do the same steps individually.","before":"[Tier 1 — Plain-language orchestrator] Use when the exact granular tool is unclear or when a greedy/file-holding client wants to complete an action in one step. Prefer granular tools directly when you know the step. Pass request (your goal) plus optional csv_file / accounts_handle / assignments_handle / map_session_id / ts_handle. For an uploaded CSV, pass csv_file as the ChatGPT/OpenAI file parameter; ezt parses/stages it server-side and can run ingest_accounts with map_session_id so points appear on the open map. For fixed-workload builds, args.target_hours / args.workload_target are normalized to auto_build build_mode.mode=fixed_workload_target. Modern form-capable clients (protocol >= 2026-07-28) may answer missing part_layer, tal_label, sizing, dwell, and optional balance in-band (aligned with auto_build); legacy hosts keep CLARIFICATION_REQUIRED / ask_user (HITL-025).","detail":"Description of `ezt` changed (70% word delta).","severity":"risky","descriptionDelta":0.7007874015748031},{"kind":"input_property_removed","path":"inputSchema.properties.guidance_handle","tool":"ezt","before":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Guidance Handle","default":null},"detail":"Field `guidance_handle` was removed from `ezt` input; consumers still sending it may be rejected or silently ignored.","severity":"breaking"},{"kind":"description_changed","tool":"geocode_address","after":"Geocodes a list of customer addresses to coordinates with match confidence, without loading them as a point layer. Accepts address/address_line1/street, city, state, and postal columns (common CRM headers are mapped). accept_suboptimal_geocodes=true accepts ZIP-centroid matches. Cached addresses are free; billable lookups count against a monthly per-key quota, and past it the result is PROVIDER_QUOTA_EXCEEDED with limit, used, remaining, and period_resets_at. Returns geocoded_rows, which ingest_accounts can load. ingest_accounts also geocodes on its own.","before":"[Tier 1 — Geocode Only] When: geocode addresses without full account ingest, or headless bulk geocode (GC-001). Prerequisites: none for headless; ts_handle + MC-first when human verifies on map. Prefer ingest_accounts for territory builds (it geocodes inline with accept_suboptimal_geocodes default true). Use this tool when the user must verify geocode quality first; set accept_suboptimal_geocodes=true to accept ZIP-centroid/suboptimal matches. Address columns: address/address_line1/street, city, state, postal (CRM headers auto-mapped). QUOTA: billable provider calls are capped per API key per calendar month. Cache hits cost nothing and are never counted, so never set use_cache=false or force_regeocode=true to work around a cap — that turns free hits into paid calls. Past the cap the tool returns PROVIDER_QUOTA_EXCEEDED carrying limit, used, remaining, and period_resets_at; an operator must raise the cap, so report those numbers instead of retrying or splitting the batch. Next: pass result.geocoded_rows to ingest_accounts or export point layer. Scenarios: GC-001..006.","detail":"Description of `geocode_address` changed (69% word delta).","severity":"risky","descriptionDelta":0.6923076923076923},{"kind":"input_property_removed","path":"inputSchema.properties.guidance_handle","tool":"geocode_address","before":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Guidance Handle","default":null},"detail":"Field `guidance_handle` was removed from `geocode_address` input; consumers still sending it may be rejected or silently ignored.","severity":"breaking"},{"kind":"description_changed","tool":"get_guidance","after":"Returns the EZT MCP operating guide: the cross-tool workflow order and shared rules (result.text, same text as the server instructions), result.server_version, and result.seat for the caller's key (plan standard|trial, key_expires_at, and quota_remaining for geocoding, routing, and isochrone requests). Read-only. Useful at the start of a session or for a host that does not show server instructions.","before":"[Tier 1 — Startup Guide + Handle] CALL THIS FIRST. Read-only. When: session start, after blocked_by=guidance_required, or when the shared rules are unclear. Returns result.text (the short cross-tool brief, same text as server instructions), result.guidance_handle (the rotating code every territory/map tool requires), result.server_version (this server's product version; the same string as serverInfo.version), and result.seat (plan standard|trial, key_expires_at, and quota_remaining for geocode/route/isochrone — tell a trial user when the trial or an allowance is close to running out). Per-tool rules are on that tool's description. Workflow essays are inlined as guidance on discover_intent, workflow_advisor, and blocked_by. Exempt: get_guidance, submit_feedback, discover_intent, workflow_advisor, ep_* tools.","detail":"Description of `get_guidance` changed (67% word delta).","severity":"risky","descriptionDelta":0.6734693877551021},{"kind":"description_changed","tool":"get_map_selection","after":"Returns the most recent committed selection on an open map session (map_session_id): part_layer, part_ids, and selection_task_id when the selection came from request_part_selection. Covers selections the person started from the map legend. realign takes these part_ids as moves with a leaf territory_id.","before":"[Tier 2 — MC Selection Poll] When: read the latest committed MC session selection — especially when Monica started selection from the legend finger icon and then tells the agent what to do with 'my selection' (no prior request_part_selection in this turn). Prerequisites: map_session_id of the open MC. Returns part_layer + part_ids (+ selection_task_id when a first-class task was created). Prefer get_part_selection when you already have selection_task_id and it is still available. Next for 'add these to <territory>': call realign with tal_id, the same map_session_id, and moves=[{part_id, to_territory_id}] derived from every returned part_id using the stable leaf territory_id (created_territory.territory_id / leaf catalog). Display-name aliases need an exact unique name match — do not invent shorthand (T3 ≠ Territory 3). If unclear, ask which leaf. Do not resend the TS or request a new selection. Scenarios: MC-004 variants, user-initiated select.","detail":"Description of `get_map_selection` changed (79% word delta).","severity":"risky","descriptionDelta":0.7913043478260869},{"kind":"input_property_removed","path":"inputSchema.properties.guidance_handle","tool":"get_map_selection","before":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Guidance Handle","default":null},"detail":"Field `guidance_handle` was removed from `get_map_selection` input; consumers still sending it may be rejected or silently ignored.","severity":"breaking"},{"kind":"description_changed","tool":"get_map_visualization","after":"Opens the interactive territory map (Map Component) and returns map_url and map_session_id. Opens a blank map, or shows the latest state of an existing Territory Solution (e.g. yesterday's work) from ts, ts_handle, or a completed job_id. Idempotent per user_id: a second call returns the same map. new_project=true replaces the open workspace with an empty project. presentation.view_name: empty | points_only | review | selection. Apps-capable hosts render the map in chat (ui://easyterritory/map-viewer); other clients open map_url in a browser. Later map tools take map_session_id. ensure_map_viewer reports when the person has the map open.","before":"[Tier 1 — MC-First Entry] FIRST step of non-headless territory work (invariant I-1). When: open a blank map for planning, or open/reuse the user's Map Component for an existing Territory Solution (TS), ts_handle, or completed job. Pass new_project=true to replace the live workspace with an empty project; omitted empty+view reuses the existing project. Optional presentation.view_name: empty | points_only | review | selection (defaults from mode/TS content). Diagnostics: presentation.debug_panel=true. Prerequisites: none (idempotent per user_id). Next: hand user map_url, then ensure_map_viewer until viewer_status.connected. Dual surface (same map_session_id, one map): Apps-capable hosts render this map in chat from ui://easyterritory/map-viewer (_meta.ui.resourceUri); every other client — including Cursor — opens map_url in a browser tab. map_url is always returned. Do not open a second surface or call this tool again to 'fix' a map. Scenarios: MC-000, MC-001, MV-001, S004.","detail":"Description of `get_map_visualization` changed (60% word delta).","severity":"risky","descriptionDelta":0.6046511627906976},{"kind":"input_property_removed","path":"inputSchema.properties.guidance_handle","tool":"get_map_visualization","before":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Guidance Handle","default":null},"detail":"Field `guidance_handle` was removed from `get_map_visualization` input; consumers still sending it may be rejected or silently ignored.","severity":"breaking"},{"kind":"description_changed","tool":"get_part_selection","after":"Returns the state of a map selection task (selection_task_id from request_part_selection, or active_selection_task_id in ezt://map-sessions/{id}/state): status awaiting_user_selection, committed, expired, or cancelled, and once committed the selected part_ids (with assigned and unassigned part_ids when a TAL is active). While awaiting, the response carries recommended_poll_delay_ms. Committed part_ids feed realign, create_territory_from_parts, or analyze.","before":"[Tier 2 — Selection Poll] When: poll or retrieve committed part IDs after request_part_selection OR after Monica starts selection from the MC legend finger icon. Prerequisites: selection_task_id from request_part_selection or from map session state (active_selection_task_id on ezt://map-sessions/{id}/state). Poll loop: while status=awaiting_user_selection, sleep recommended_poll_delay_ms (fallback poll_interval_ms, min 250ms) and poll again until status=committed, expired, or cancelled. Response includes do_this_next and poll_loop while awaiting. If the user already committed and says 'add my selection to …', call once for the committed task (or use get_map_selection) and proceed — do not start a new selection. Next: realign, create_territory_from_parts, or analyze scoped to selection.part_ids. Scenarios: AN-007, RL-008.","detail":"Description of `get_part_selection` changed (67% word delta).","severity":"risky","descriptionDelta":0.6739130434782609},{"kind":"input_property_removed","path":"inputSchema.properties.guidance_handle","tool":"get_part_selection","before":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Guidance Handle","default":null},"detail":"Field `guidance_handle` was removed from `get_part_selection` input; consumers still sending it may be rejected or silently ignored.","severity":"breaking"},{"kind":"description_changed","tool":"import_geojson","after":"Opens a GeoJSON FeatureCollection (plain points, or a territory file this server produced, plain or gzip/zip) as a Territory Solution and returns ts_handle and a summary. Input: geojson (object), geojson_text, or geojson_gzip (base64). Territory polygons without part_ids return TERRITORY_POLYGONS_WITHOUT_PART_IDS unless part_layer plus overlay_policy=centroid_within assigns parts by centroid. point_layer names imported points; tal_label names the alignment. EasyTerritory Designer projects (REST/Agent/Projects/{projectId}/TerritorySolution) import as geojson_gzip.","before":"[Tier 2 — Project reopen] Import a GeoJSON FeatureCollection (standard points or an export_geojson artifact, including gzip/zip). Returns ts_handle + summary — pass ts_handle to get_map_visualization / analyze / export_geojson next; do not repost the GeoJSON body. Arbitrary territory polygons WITHOUT part_ids are rejected (TERRITORY_POLYGONS_WITHOUT_PART_IDS) unless you explicitly pass part_layer plus overlay_policy=\"centroid_within\" to assign parts whose centroids fall inside each polygon. Optional: point_layer (name for imported points), tal_label. Supply geojson (object), geojson_text (JSON or sniffed gzip/zip), or geojson_gzip (base64 gzip/zip). An EasyTerritory Designer rolodex project arrives here too: GET Designer REST/Agent/Projects (list) then GET .../Projects/{projectId}/TerritorySolution with the user's ezt_pat_ Bearer, and pass the (gzipped) file as geojson_gzip. There is no MCP pull tool (ezt://guidance/workflows/pull-from-designer).","detail":"Description of `import_geojson` changed (64% word delta).","severity":"risky","descriptionDelta":0.6428571428571428},{"kind":"input_property_removed","path":"inputSchema.properties.guidance_handle","tool":"import_geojson","before":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Guidance Handle","default":null},"detail":"Field `guidance_handle` was removed from `import_geojson` input; consumers still sending it may be rejected or silently ignored.","severity":"breaking"},{"kind":"description_changed","tool":"ingest_accounts","after":"Account ingest: loads account or location rows (CRM records, a CSV, a spreadsheet) as point data on a named point layer of the Territory Solution, to add them to the map, geocoding rows that lack coordinates. Rows come inline (rows) or as accounts_handle from request_account_upload, which handles large files; a client file path is not readable by the server. Coordinate headers latitude/lat and longitude/lon/lng pass through; other names need latitude_field/longitude_field, and unused coordinate-like columns return CLARIFICATION_REQUIRED (needs_coordinate_fields) instead of a bulk geocode. id_field is a unique key (default row_id, synthesized when absent); duplicates return needs_unique_row_id. Only declared columns are kept: metric_fields, workload_fields, dwell_time_field, visit_frequency_field, grouping_fields, display_fields, search_fields; later use of an undeclared column returns UNDECLARED_FIELD. Returns a task; the result reports failed_rows, map_refresh, and any geocode quota shortfall.","before":"[Tier 1 — Data Intake] When: load/add account/location rows or account CSV point data into a TS point layer. ALWAYS before account-derived builds (auto_build, account_build). Prerequisites: MC-first + viewer connected when human verifies points; ts/ts_handle optional. Geocodes rows lacking valid coordinates (accept_suboptimal_geocodes defaults true for bulk address-only CSVs). Recognized address columns: address/address_line1/street, city, state, postal columns, plus common CRM headers (e.g. Address 1: Street 1). Coordinate passthrough accepts case-insensitive GIS headers latitude/lat/LAT and longitude/lon/lng/long/LON (no rename required); pass explicit latitude_field/longitude_field for other headers (e.g. Address 1: Latitude). If unused numeric lat/lon-like columns exist and ingest would otherwise bulk-geocode, the job fails fast with CLARIFICATION_REQUIRED / blocked_by=needs_coordinate_fields — set latitude_field/longitude_field or force_regeocode=true; never wait on a national geocode crawl. After submit, if the first Tasks status shows coordinate_passthrough_count=0 with a large geocode_query_count on a file that had coord-like columns, call tasks/cancel or tasks_cancel and fix headers/fields. id_field must be a UNIQUE business key (default row_id). Never use label/name columns — duplicates fail with blocked_by=needs_unique_row_id. If no unique column exists, omit id_field (server synthesizes row-1, row-2, … when row_id is absent) or synthesize a unique row_id client-side before staging. Before this call, inspect headers/samples for metric, dwell-time, and visit-frequency columns. Pass confirmed business metrics as metric_fields and confirmed workload roles as workload_fields plus visit_frequency_field/dwell_time_field so downstream tools can reuse point-layer metadata. If no visit-frequency column exists, omit visit_frequency_field; do not ask for an average/default frequency. DECLARED FIELDS ARE THE RETENTION CONTRACT: the TS keeps only id, coordinates, label, and declared fields. Also declare grouping_fields (columns for account_build grouping), display_fields (columns shown in map callouts), and search_fields (columns searched in the MC) — undeclared columns are discarded at ingest and later operations on them fail with UNDECLARED_FIELD. NEVER pass a client file path in rows or accounts_handle — the server cannot read /mnt/data/... or other sandbox paths. LARGE FILES: parse the CSV client-side, then call request_account_upload(rows=<chunk> OR csv_text=<raw CSV>) (append via upload_handle), then call this tool with accounts_handle instead of rows. TO SHOW THE POINTS ON AN OPEN MAP: pass map_session_id (the open MC's session id from get_map_visualization / the map_url) — the server pushes the new point layer to that map on completion. WITHOUT map_session_id the points land in a DETACHED TS: read the job result (result_resource_uri) for result.ts_handle and result.map_binding, then bind via show_map_overlay / configure_map / get_map_visualization with that ts_handle. ASYNC COMPLETION IS MANDATORY: the submission/geocoding phase is not workflow completion. Follow the returned Tasks do_this_next and sleep_ms until status=completed, then call the indicated result operation once and inspect the terminal one-liner field, viewer_outcome, failed_rows, map_binding, and map_refresh. viewer_outcome.status painted (or sent_unconfirmed with notified=true) means the linked MC got the push; queued_no_viewer means map_refresh.status=viewer_disconnected (or no viewer yet) — follow recovery.args (ensure_map_viewer); needs_show_map_overlay / rebind_required / detached include literal recovery.args. Do not stop until the binding matches the requested map_session_id, map_refresh.notified=true, and the point layer is visible in the already-open MC (or the detached ts_handle is bound). A linked points-only ingest lands on the surrogate __points__ TAL — that is success (map_refresh.status is sent_unconfirmed or applied); do NOT rebind via get_map_visualization(job_id=...) when notified=true and active_tal_id=__points__. MULTI-OVERLAY COMPOSE: linked ingest is non-destructive for prior part-layer overlays — previously configured loaded_part_layers remain on the session with the new points. Verify both the point layer AND any expected part overlays are still present before claiming compose success; points-only adoption does not mean those overlays disappeared. GEOCODE QUOTA: billable provider calls are capped per API key per calendar month; cached addresses and coordinate passthrough are free and never counted. When the cap runs out mid-file the ingest is NOT refused — rows covered by the remaining budget are geocoded and ingested, and the rest arrive in failed_rows with reason_code=PROVIDER_QUOTA_EXCEEDED plus a result quota block naming limit, used, remaining, unserved_row_count, and period_resets_at. Report that instead of re-running the ingest; the blocked rows need a raised cap, not a retry. Next: configure_map if needed, then choose the build tool. Full atom: ezt://guidance/workflows/geocode-and-ingest. Scenarios: IA-001..005, GC-002, GC-003, S003.","detail":"Description of `ingest_accounts` changed (79% word delta).","severity":"risky","descriptionDelta":0.786967418546366},{"kind":"input_property_removed","path":"inputSchema.properties.guidance_handle","tool":"ingest_accounts","before":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Guidance Handle","default":null},"detail":"Field `guidance_handle` was removed from `ingest_accounts` input; consumers still sending it may be rejected or silently ignored.","severity":"breaking"},{"kind":"description_changed","tool":"isochrone_build","after":"Draws the area reachable by driving from one or more origins within a time or distance budget (drive-time area, service area, catchment) and adds each area as a territory in a drive-time alignment. origins: inline {latitude, longitude} or a point-layer reference. bands: [{time_budget_seconds} or {distance_budget_meters}], one unit per call (MIXED_BUDGET_TYPES otherwise); one band gives one area per origin, several give nested areas. Limits: 25 origins, 5 bands, 6 hours, 500 km; travel_mode car or truck; depart_at supported. Areas are the provider polygons and may overlap; analyze counts accounts in every area containing them. realign/split/merge/rebalance return ISOCHRONE_TAL_NOT_EDITABLE. A band budget is not territory workload or route time. No provider key returns ISOCHRONE_NOT_CONFIGURED; the full call count is checked against the monthly quota first (PROVIDER_QUOTA_EXCEEDED). Returns a task.","before":"[Tier 1 — Isochrone Build] When: draw the area reachable by DRIVING from one or more origins within a time or distance budget — '20-minute drive-time area around each branch', service area, catchment, coverage ring, reachable range, isochrone, isodistance (IS-001..IS-006). It draws reachable area. It has no objective and does not balance workload. Prerequisites: a TomTom or Azure Maps key on the server (no key → ISOCHRONE_NOT_CONFIGURED, no degraded mode — never substitute a straight-line radius); and, for point-layer origins, ingested points plus ts_handle or map_session_id. Inline origins need no TS. No part layer is involved. ORIGINS x BANDS: origins are inline {latitude, longitude, label?} or a point-layer reference {point_layer, point_ids?, filter?, label_field?} — '40 minutes around each of my 6 branches' is ONE call. bands are [{time_budget_seconds} | {distance_budget_meters}], one budget per band, and every band in a call must use the SAME unit (mixing returns MIXED_BUDGET_TYPES). One band → one leaf per origin; several bands → a rollup per origin with one leaf per band. Cost is origins x bands provider calls, capped by TOO_MANY_ORIGINS (default 25) / TOO_MANY_BANDS (default 5). QUOTA: those billable calls also draw on a per-API-key monthly cap. The whole fan-out is claimed before any call goes out, so a build that does not fit returns PROVIDER_QUOTA_EXCEEDED with limit, used, remaining, and period_resets_at having spent nothing. An operator must raise the cap; report those numbers instead of retrying with fewer bands. LIMITS: travel_mode is 'car' or 'truck' ONLY — neither provider offers a walking or cycling reachable range, unlike calculate_route. time_budget_seconds <= 21600, distance_budget_meters <= 500000 (BUDGET_OUT_OF_RANGE). depart_at is supported; arrive_at is not. avoid accepts toll_roads, motorways, ferries, unpaved_roads, carpools, border_crossings, tunnels, car_trains, low_emission_zones. GEOMETRY: each territory IS the provider's exact polygon — no part layer, no part_ids. Bands nest and nearby origins' polygons overlap, and the map paints them overlapping (tightest on top). analyze counts an account in EVERY area that contains it (a 20-minute band includes its 10-minute accounts) and reports the accounts overlapping areas share. realign / territory_split / territory_merge / territory_rebalance return ISOCHRONE_TAL_NOT_EDITABLE: re-run isochrone_build to change an area; delete_territory / delete_tal still remove one. A band budget is NOT territory workload and NOT route drive time — never report '20-minute band' as 20 minutes of workload or feed it to auto_build. Async: returns task_id — follow _meta.next_action (sleep_and_poll → consume_result) and sleep the authoritative sleep_ms, never estimated_remaining_ms. Partial provider failure still builds the TAL and reports failed_requests[]. Then: analyze(tal_ids=[<tal_id>], map_session_id, analysis_panel='single') for per-area accounts and metrics (needs an ingested point layer). Pass map_session_id here to paint the areas on an open map. Full atom: ezt://guidance/workflows/isochrone-territories. Scenarios: IS-001..IS-006.","detail":"Description of `isochrone_build` changed (73% word delta).","severity":"risky","descriptionDelta":0.7347670250896057},{"kind":"input_property_removed","path":"inputSchema.properties.guidance_handle","tool":"isochrone_build","before":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Guidance Handle","default":null},"detail":"Field `guidance_handle` was removed from `isochrone_build` input; consumers still sending it may be rejected or silently ignored.","severity":"breaking"},{"kind":"description_changed","tool":"load_analysis_panel","after":"Shows a completed analyze result in the open map's analysis dock (single or compare). Needs map_session_id and the analyze task_id/job_id or the full analyze result (with tal_analyses and ts_identity; a partial object returns INVALID_REQUEST). presentation_mode: single, compare, executive, or diagnostic. dismiss=true hides the dock and keeps the statistics on the session. Changes nothing in the Territory Solution. analyze with analysis_panel=single fills the dock in the same call.","before":"[Tier 2 — Analysis Panel Display] When: show Analyze JSON in the MC bottom dock (single or comparison) after a completed analyze that omitted analysis_panel. Prefer analyze(analysis_panel=single, map_session_id=...) so the dock fills without this second call. Prerequisites: map_session_id; a full Analyze result — pass task_id/job_id of the completed analyze job, or analyses=[<full Analyze result>]. The result must include tal_analyses and ts_identity (territory_metric_grids when metrics exist). A thin {tal_analyses} object is INVALID_REQUEST, not an empty dock. presentation_mode is single|compare|executive|diagnostic — territory_metric_grid is default_presentation.view inside the Analyze payload, not a presentation_mode. Does not mutate TS. Re-run analyze after any TAL or point change (I-2). dismiss=true hides the whole dock, header bar included, and keeps the stats on the session. analysis_panel.dismissed is then true. A later analyze with analysis_panel=single shows the dock again. Omit analyses on a dismiss call. Dual surface: the dock renders in the in-chat map on Apps-capable hosts and in the map_url tab everywhere else — same map_session_id, no extra call. Scenarios: MC-009, AN-006, MC-007.","detail":"Description of `load_analysis_panel` changed (70% word delta).","severity":"risky","descriptionDelta":0.7024793388429752},{"kind":"input_property_removed","path":"inputSchema.properties.guidance_handle","tool":"load_analysis_panel","before":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Guidance Handle","default":null},"detail":"Field `guidance_handle` was removed from `load_analysis_panel` input; consumers still sending it may be rejected or silently ignored.","severity":"breaking"},{"kind":"description_changed","tool":"query_parts","after":"Returns attributes (no geometry) for parts of a part layer listed at ezt://part-layers. Takes exactly one of filter (e.g. {state_abbr: TX}) or part_ids; neither or both returns INVALID_REQUEST. Paged with page_token.","before":"[Tier 2 — Part Metadata] When: enrich or filter parts before build, or inspect attributes without geometry. Prerequisites: part_layer from ezt://part-layers. Pass exactly one of filter (e.g. {state_abbr: TX}) or part_ids; neither or both is INVALID_REQUEST. To check that a part layer exists, read ezt://part-layers instead of probing with an empty query. Next: direct_build, configure_map, or agent-side join before build. Scenarios: DS-002. Returns part_id and attributes only; no geometry.","detail":"Description of `query_parts` changed (64% word delta).","severity":"risky","descriptionDelta":0.6363636363636364},{"kind":"input_property_removed","path":"inputSchema.properties.guidance_handle","tool":"query_parts","before":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Guidance Handle","default":null},"detail":"Field `guidance_handle` was removed from `query_parts` input; consumers still sending it may be rejected or silently ignored.","severity":"breaking"},{"kind":"description_changed","tool":"realign","after":"Moves ZIPs or other parts (typed ids or ones selected on the map) from one territory into another within one alignment (tal_id): a ZIP move. moves=[{part_id, to_territory_id}], or realign_operation with part_ids. The Territory Solution comes from map_session_id (the open map), ts_handle, or ts; expected_revision adds optimistic concurrency (STALE_TS_REVISION when the map changed). to_territory_id is a leaf territory_id or that leaf's exact, unique display name; an unknown or ambiguous value returns UNKNOWN_TERRITORY_ID or AMBIGUOUS_TERRITORY with available_leaf_territories. A committed map selection is read with get_map_selection; request_part_selection lets the person pick parts on the map. remove_empty_territories drops leaves left with no parts. Returns a task; the open map repaints. analyze refreshes statistics when the map has account points.","before":"[Tier 1 — Realign] When: move parts between leaf territories within ONE TAL. Prerequisites: existing TAL plus ONE current TS reference: map_session_id (preferred for an open MC), ts_handle, or ts; expected_revision is optional optimistic concurrency (STALE_TS_REVISION → reload and retry). For a committed ad-hoc map selection, call get_map_selection(map_session_id), then pass moves=[{part_id, to_territory_id}] for each returned selection.part_ids plus tal_id and the same map_session_id. Do not repost the full TS and do not start another selection. to_territory_id must be a leaf territory_id (prefer created_territory.territory_id / leaf_territories / legend catalog). A display-name alias is accepted only as an exact unique match to the leaf's actual name (e.g. 'Territory 3' or 'T1' when that is the name) — do NOT invent shorthand expansions (T3 ≠ Territory 3; no T→Territory mapping). It is NOT tal_id. On UNKNOWN_TERRITORY_ID / AMBIGUOUS_TERRITORY read error.details.available_leaf_territories and stop guessing; if the spoken destination is still unclear, ask the user which leaf (id or exact name). Visual moves with a destination known in advance: request_part_selection with purpose=realign. Structural rebalance: territory_split/merge/rebalance. Next: verify the live map refresh. Run analyze + load_analysis_panel only when the TS has a point layer (I-2). For a geography-only ZIP/part edit, skip analyze unless the user requested AN-004; do not create a NO_POINT_LAYER failure after a successful edit. Full atom: ezt://guidance/workflows/realign-by-selection. Scenarios: RL-001..013, S001, MC-004, EV-001.","detail":"Description of `realign` changed (75% word delta).","severity":"risky","descriptionDelta":0.7473684210526316},{"kind":"input_property_removed","path":"inputSchema.properties.guidance_handle","tool":"realign","before":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Guidance Handle","default":null},"detail":"Field `guidance_handle` was removed from `realign` input; consumers still sending it may be rejected or silently ignored.","severity":"breaking"},{"kind":"description_changed","tool":"reintegrate_branch","after":"Merges an edited branch (proposal Territory Solution from extract_tal_branch) back into the master. Needs branch_metadata and expected_master_revision; returns STALE_TS_REVISION when the master changed since the extract, in which case a fresh extract is needed. Merges apply one at a time.","before":"[Tier 1 — Delegation Reintegrate] When: merge an approved proposal TS into master. Prerequisites: branch_metadata, expected_master_revision; proposal TS at current lineage. STALE_TS_REVISION if master moved—request fresh extract. Sequential merges only. Next: refresh master Analysis panel (I-2). Scenarios: DL-003, DL-004, DL-006.","detail":"Description of `reintegrate_branch` changed (80% word delta).","severity":"risky","descriptionDelta":0.8},{"kind":"input_property_removed","path":"inputSchema.properties.guidance_handle","tool":"reintegrate_branch","before":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Guidance Handle","default":null},"detail":"Field `guidance_handle` was removed from `reintegrate_branch` input; consumers still sending it may be rejected or silently ignored.","severity":"breaking"},{"kind":"description_changed","tool":"request_account_upload","after":"Stages account rows for ingest_accounts and returns upload_handle, csv_headers, and suggested_ingest args with a geocode_plan. Four ways in: POST the CSV file to the returned upload_url (no API key; expires in 15 minutes; fastest for large files), csv_file (a host file parameter), csv_text (raw CSV, up to 16 MB, newlines preserved), or rows chunks appended with upload_handle. A local path in csv_file returns UNREADABLE_FILE_REFERENCE; an expired URL returns UPLOAD_TOKEN_EXPIRED and a call with upload_handle issues a new one. The handle is single-use (claimed by ingest_accounts) and expires after 1 hour by default.","before":"[Tier 2 — Data Intake helper] When: you have account/location rows or an account CSV that you cannot inline in a single ingest_accounts call (e.g. a large CSV, hundreds/thousands of rows). Stage the rows here in one or more chunks, then call ingest_accounts(accounts_handle=...). If the file includes metric, workload, visit-frequency, or dwell-time fields, stage the file here first, inspect returned csv_headers / row keys, confirm role candidates, then pass metric_fields/workload_fields/visit_frequency_field/dwell_time_field to ingest_accounts. Continue only after ingest_accounts has loaded the points. WAYS TO STAGE (all return an upload_handle): (A) CSV on disk + a shell with network (Cursor, Claude Code, Codex, CLI agents): call with no rows to get result.upload_url, then run result.curl_example — POST the raw file with Content-Type: text/csv. No API key: upload_url is the credential (expires in 15 min; each POST appends to the same handle). Fastest for large files; do not re-type rows into csv_text or rows chunks. (B) MCP csv_file: pass the uploaded CSV as a ChatGPT/OpenAI file parameter when available. (C) MCP csv_text: pass csv_text=<raw CSV string> (whole file in one call, up to 16 MB) when you have no shell or no network. Preserve multiline newlines — do NOT JSON-encode with PowerShell ConvertTo-Json (that corrupts rows into a single line). Use Python json.dumps or MCP csv_file instead. (D) MCP rows: call with rows=<chunk>; append more with upload_handle=<prior handle>. A local path in csv_file fails (UNREADABLE_FILE_REFERENCE); use (A). Every result carries a fresh upload_url for its handle; an expired URL returns UPLOAD_TOKEN_EXPIRED — call again with upload_handle for a new one. Then: ingest_accounts(accounts_handle=<upload_handle>, map_session_id=<MC session id>, label_field=<label field>). GIS coordinate headers latitude/lat/LAT and longitude/lon/lng/long/LON passthrough case-insensitively. CRM headers such as Address 1: Latitude and Address 1: Longitude are coordinates too — the sheet stays accounts when Region and Territory are also present. suggested_ingest.args sets latitude_field and longitude_field for those headers; pass them through and do not rename them. If first Tasks status shows coordinate_passthrough_count=0 with a large geocode_query_count on a file that had coord-like columns, call tasks/cancel or tasks_cancel and fix — never wait on national geocode. id_field only when a unique business key exists — never label/name; omit otherwise (server synthesizes row-N). On success the result includes suggested_ingest ({tool, args, confidence, notes, geocode_plan}) — prefer those args for the next ingest_accounts call (fill map_session_id from the open MC). geocode_plan states coordinate_fields_detected, will_geocode, eta_class, and passthrough_likely before you submit. Handle is single-use (claimed by the ingest job) and expires (default 1h). Scenarios: IA-001..005, S003.","detail":"Description of `request_account_upload` changed (78% word delta).","severity":"risky","descriptionDelta":0.7765151515151515},{"kind":"input_property_removed","path":"inputSchema.properties.guidance_handle","tool":"request_account_upload","before":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Guidance Handle","default":null},"detail":"Field `guidance_handle` was removed from `request_account_upload` input; consumers still sending it may be rejected or silently ignored.","severity":"breaking"},{"kind":"description_changed","tool":"request_assignment_upload","after":"Stages part-to-territory assignment rows (e.g. a ZIP-to-territory spreadsheet with Postal Code plus Territory/Region/Division) for direct_build and returns upload_handle. Same four ways in as request_account_upload: POST the file to upload_url (no API key; 15 minutes), csv_file, csv_text (newlines preserved), or assignment chunks appended with upload_handle. Legacy columns map to part_id and territory_path automatically. A file of locations rather than assignments is redirected to request_account_upload. The handle is single-use and expires after 1 hour by default.","before":"[Tier 2 — Direct Build intake] When: you have part-to-territory assignment rows or a legacy spreadsheet (e.g. Zip2Terr.csv with Postal Code + Territory/Region/Division) that you cannot inline in direct_build. Stage rows here, then call direct_build(assignments_handle=<upload_handle>). WAYS TO STAGE (all return upload_handle): (A) CSV on disk + a shell with network: call with no rows to get result.upload_url, then run result.curl_example — POST the raw file with Content-Type: text/csv. No API key: upload_url is the credential (expires in 15 min; each POST appends). (B) MCP csv_file: pass the uploaded CSV as a ChatGPT/OpenAI file parameter. (C) MCP csv_text: pass csv_text=<raw multiline CSV> — preserve newlines; do NOT JSON-encode with PowerShell ConvertTo-Json (corrupts rows). (D) MCP assignments: pass assignments=<chunk of row objects>; append with upload_handle. An expired upload_url returns UPLOAD_TOKEN_EXPIRED — call again with upload_handle. Legacy columns (Postal Code, Territory, Region, Division) auto-map to part_id + territory_path. Then: direct_build(assignments_handle=<handle>, part_layer=..., tal_label=..., map_session_id=...). Handle is single-use and expires (default 1h).","detail":"Description of `request_assignment_upload` changed (65% word delta).","severity":"risky","descriptionDelta":0.6541353383458647},{"kind":"input_property_removed","path":"inputSchema.properties.guidance_handle","tool":"request_assignment_upload","before":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Guidance Handle","default":null},"detail":"Field `guidance_handle` was removed from `request_assignment_upload` input; consumers still sending it may be rejected or silently ignored.","severity":"breaking"},{"kind":"description_changed","tool":"request_part_selection","after":"Lets the person adjust boundaries by picking parts (e.g. ZIPs) on the map: puts the open map into select mode on a part_layer, for purpose realign, a new territory, or a plain list. Returns selection_task_id; get_part_selection reports status (awaiting_user_selection, then committed, expired, or cancelled) and the committed part_ids, and its response carries recommended_poll_delay_ms. The commit happens on the map, not in chat. destination_territory_id and realign_operation pre-set a realign. Works on the in-chat map and the map_url tab. A selection the person started from the map legend is read with get_map_selection.","before":"[Tier 2 — Human Spatial Input] When: Monica selects parts on the map for realign, manual territory build, or return_list. Prerequisites: MC-first + viewer connected; part_layer; active TAL when realigning. Poll loop: after submit, poll get_part_selection(selection_task_id) using recommended_poll_delay_ms from the response (or scripts/wait_part_selection.py) until status=committed — never ask the human to type committed/done/go. User-initiated path: Monica may also start select mode from the MC legend finger icon on a part-layer row (no prior request_part_selection). When the user refers to 'my selection' / 'the parts I selected', read the open map session's latest committed selection via get_map_selection(map_session_id) or get_part_selection using active_selection_task_id from ezt://map-sessions/{id}/state — do not ask them to select again. Next: realign, create_territory_from_parts, or analyze with selection.part_ids. Dual surface: in an Apps-capable host the human selects on the in-chat map (ui://easyterritory/map-viewer); otherwise they select in the map_url tab. The commit poll loop above is identical on both surfaces. Scenarios: S001, MC-004..006, RL-006..013.","detail":"Description of `request_part_selection` changed (78% word delta).","severity":"risky","descriptionDelta":0.7828947368421053},{"kind":"input_property_removed","path":"inputSchema.properties.guidance_handle","tool":"request_part_selection","before":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Guidance Handle","default":null},"detail":"Field `guidance_handle` was removed from `request_part_selection` input; consumers still sending it may be rejected or silently ignored.","severity":"breaking"},{"kind":"description_changed","tool":"schedule_visits","after":"Schedules recurring visits: expands each account's cadence (e.g. every 7 days, twice a month) across a horizon and packs the visits into daily work clusters (buckets) under a technician-day cap. Decides which day each visit happens (auto_build and cluster_points divide accounts among people instead); creates no alignment and assigns no technician. Needs point_layer (ingested, with the cadence column declared, else UNDECLARED_FIELD), visit_frequency_field, dwell_time, and daily_capacity (e.g. {mode: not_to_exceed, hours: 8}); missing dwell or capacity returns CLARIFICATION_REQUIRED with the question. frequency_unit: interval_days (max days between visits) or visits_per_horizon. never_visit_values (default 0, null, empty) are excluded and listed in excluded_accounts. bucket_workload_hours is in-bucket drive plus dwell, a different measure from territory workload and route_workload_hours. The same visit_layer_name replaces a schedule; a new name adds one. Returns a task. calculate_route over one bucket_id drives that day.","before":"[Tier 1 — Periodic Scheduling] When: accounts carry a recurring cadence (every 7 days, twice a month) and the user asks which day each visit happens. Expands demand across a repeating horizon and packs it into daily work clusters (buckets) under one technician-day workload cap. This tool decides the day. Neighbors: auto_build, cluster_points, calculate_route. When the user said route and a visit-frequency column exists, confirm they want a schedule, not calculate_route, before calling. It creates no TAL and assigns no technician. Prerequisite: ingest_accounts completed for point_layer with the cadence column declared. Omit ts and ts_handle when the session id argument is already set. That session is the TS. An undeclared column fails with UNDECLARED_FIELD. Required: point_layer, visit_frequency_field, dwell_time, daily_capacity. Ask the user for dwell and the daily cap; never invent them. frequency_unit=interval_days means the value is the maximum days between visits (so a 3-day cadence in a 14-day horizon is 5 visits, not 4); visits_per_horizon means the count across the horizon. Values in never_visit_values (default 0, null, empty) are excluded and reported in excluded_accounts, never silently dropped. Example — weekly and biweekly accounts across 8-hour technician days: point_layer=accounts, visit_frequency_field=service_interval_days, dwell_time={type: scalar, value: 45, unit: minutes}, daily_capacity={mode: not_to_exceed, hours: 8}, max_buckets_per_day=3. bucket_workload_hours is in-bucket drive plus dwell with NO visit-frequency multiplier: it is neither territory workload nor route_workload_hours — never sum or compare them. Re-running with the same visit_layer_name replaces that schedule in place; a different name adds a second one for comparison. After submission, follow do_this_next with the returned task_id: sleep exactly sleep_ms while next_action=sleep_and_poll, fetch the result once on consume_result, and stop on stop_error. Next: calculate_route over the visits layer filtered to one bucket_id to drive that day. Full atom: ezt://guidance/workflows/periodic-visit-schedule. Scenarios: PS-001..PS-008.","detail":"Description of `schedule_visits` changed (62% word delta).","severity":"risky","descriptionDelta":0.6238532110091743},{"kind":"input_property_removed","path":"inputSchema.properties.guidance_handle","tool":"schedule_visits","before":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Guidance Handle","default":null},"detail":"Field `guidance_handle` was removed from `schedule_visits` input; consumers still sending it may be rejected or silently ignored.","severity":"breaking"},{"kind":"description_changed","tool":"seed_build","after":"Grows one territory outward from a seed {longitude, latitude} over a part layer until it holds a target: {type: location_count, value: N} or {type: metric_sum, field: <declared metric>, value: X}, counted from an ingested point layer. Adds the territory as a leaf of tal_id, or as a new alignment. Fit is closest: target_status is reached, closest_under, closest_over, frontier_exhausted, or max_parts_reached, with SEED_TARGET_UNDERSHOT/OVERSHOT warnings and the difference. Parts already in that alignment are never taken; the territory grows around them (blocked_part_count, seed_offset_km), and a seed inside an assigned part returns SEED_PART_ASSIGNED. part_scope=explicit with part_filter confines growth to an area. One seed per call. An undeclared metric field returns UNDECLARED_FIELD. Returns a task with created_territory and ts_handle; the open map repaints with map_session_id.","before":"[Tier 1 — Seed Build] When: grow ONE territory outward from a seed location until it holds a target number of locations or a target metric sum — 'a franchise territory around this address with 40 stores', 'grow from this point until it reaches $2M revenue', 'the ZIPs around our new branch that cover 300 stores'. The result is an ordinary part-based territory (ZIPs, counties) appended as a new leaf on an existing layer (tal_id) or as a new layer when tal_id is omitted. It grows one territory from one seed. It does not partition, balance, or route. Prerequisites: an ingested point layer (ingest_accounts) — its locations are the values counted or summed; a part_layer (ezt://part-layers); and the seed as {longitude, latitude}. Resolve an address or POI to coordinates first with the address geocoding tool; a map click already gives coordinates. TARGET: target={type: 'location_count', value: N} or {type: 'metric_sum', field: <column declared in metric_fields at ingest>, value: X}. An undeclared field returns UNDECLARED_FIELD — re-ingest with metric_fields. Fit is CLOSEST: growth adds the nearest adjacent part that still fits, then takes the smallest remaining neighbour only when overshooting lands nearer the target than stopping short. target_status reports reached | closest_under | closest_over | frontier_exhausted | max_parts_reached; a closest fit that misses the target also warns SEED_TARGET_UNDERSHOT / SEED_TARGET_OVERSHOT with the signed difference — tell the user, parts are indivisible. NO OVERLAP, EVER: with tal_id, parts already in any territory of that layer are never taken — the new territory drifts away from them instead (blocked_part_count, seed_offset_km). A seed inside an existing territory fails SEED_PART_ASSIGNED; pick another seed or reassign parts with realign. There is no allow_overlap flag. SCOPE: growth reads only the seed's neighbourhood — the point layer is indexed once and parts are materialised ring by ring outward from the seed part; it never joins every location to every part, so a national point layer costs the same as a local one. part_filter / part_ids are an ALLOWLIST (parts outside it do not exist for the walk), not a performance prerequisite: pass part_scope='explicit' + part_filter={state_abbr: 'TX'} only when the user wants the territory confined to that state. bbox_intersect / point_matched are accepted and ignored here; the result reports the materialised part count and ring depth the walk touched. ONE SEED PER CALL: for several franchisees call seed_build once per seed with the same tal_id; earlier territories become blocked for later seeds. Async: returns task_id — follow _meta.next_action (sleep_and_poll → consume_result) and sleep the authoritative sleep_ms. The result is handle-only (ts_handle, no inline ts) with created_territory, the grown part ids, target.{requested, achieved, delta}, and do_this_next. Then: analyze(tal_ids=[<tal_id>], map_session_id, analysis_panel='single') so the map dock shows the territory; without dwell Analyze omits workload and returns workload_omitted.ask_user to relay after the stats. Pass map_session_id here to paint the territory on the open map. Full atom: ezt://guidance/workflows/seed-grown-territory. Scenarios: SB-001..SB-004.","detail":"Description of `seed_build` changed (72% word delta).","severity":"risky","descriptionDelta":0.717948717948718},{"kind":"input_property_removed","path":"inputSchema.properties.guidance_handle","tool":"seed_build","before":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Guidance Handle","default":null},"detail":"Field `guidance_handle` was removed from `seed_build` input; consumers still sending it may be rejected or silently ignored.","severity":"breaking"},{"kind":"description_changed","tool":"set_map_state","after":"Changes the open map's session state: mode, active alignment (active_tal_id), pending job reference, or camera (center [longitude, latitude] and zoom 0-24). Session only: the Territory Solution is not changed (configure_map sets a durable default view). The result's render_ack is read at publish time, so sent_unconfirmed one version behind is normal; render_ack_hint says whether to reread ezt://map-sessions/{map_session_id}/state or resend. camera reports the view in effect and its source (requested, session, map_config, or viewer_current). A set camera stays until changed; switching alignment does not refit.","before":"[Tier 2 — Low-Level MC State] When: switch MC mode, active TAL, or pending job ref, or jump the open MC camera with center ([longitude, latitude]) and/or zoom (0-24). Camera here is session-only and does not write the TS — use configure_map to persist a default view. Prerequisites: map_session_id. Prefer configure_map for durable TS map_config. Prefer request_part_selection for selection workflows. The result's render_ack is read the instant the change is published, so state=sent_unconfirmed with applied_render_version one behind is normal; to confirm the paint, read ezt://map-sessions/{map_session_id}/state a second or two later. Only a sent_unconfirmed that persists with a render_ack failure detail means the viewer could not apply it; render_ack_hint says which case applies (reread_state or resend). Once you set center/zoom, the open MC keeps that view (no automatic refit) and a reload of map_url reopens there. Every result carries camera {center, zoom, source}: requested (this call), session (an earlier set_map_state), map_config (the TS default view), or viewer_current (no camera known; the MC keeps its view — switching active_tal_id never refits). Scenarios: residual backlog (no dedicated scenario by design).","detail":"Description of `set_map_state` changed (63% word delta).","severity":"risky","descriptionDelta":0.6312056737588653},{"kind":"input_property_removed","path":"inputSchema.properties.guidance_handle","tool":"set_map_state","before":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Guidance Handle","default":null},"detail":"Field `guidance_handle` was removed from `set_map_state` input; consumers still sending it may be rejected or silently ignored.","severity":"breaking"},{"kind":"description_changed","tool":"show_map_overlay","after":"Shows or hides something on the open map from a plain request ('add US zip codes', 'hide the zips', 'show my accounts') or overlay_kind (part_layer, point_layer, tal, route) with overlay_id. Part layers load at a zoom where they are visible (visibility.camera_action); hiding a part layer leaves territories unchanged. Point layers must already be ingested; a layer in the Territory Solution but missing from the map is re-pushed (status=refreshed, with render_ack). route re-shows or hides routes calculate_route drew (route_id targets one); with no route yet it returns blocked_by=needs_route. When the user named a camera, do_this_next carries the configure_map call that sets it. Input: map_session_id (preferred), ts_handle, or ts.","before":"[Tier 1 — Map Overlay] When: customer wants something on the map — US ZIP codes, counties, accounts/points, or a territory alignment — in any phrasing ('add US zip codes to the map', 'show zip codes', 'add zips'). Prerequisites: ts_handle (or inline ts) from get_map_visualization; viewer connected for live MC refresh. Pass user_request alone (e.g. 'add US zip codes to the map') or overlay_kind (part_layer | point_layer | tal | route) with optional overlay_id. Resolves the four MC overlay families and calls the correct underlying step (configure_map for part layers and active TAL; point layers must already be in the TS from ingest_accounts). Source the TS via ts_handle, inline ts, OR map_session_id (preferred for points: reads the LIVE session TS so points pushed by ingest_accounts(map_session_id=...) are found without threading a new ts_handle). Returns overlay_kind, overlay_id, viewer_hint, and for part layers a visibility block (state, min_zoom, camera_action). An open map_session_id zooms the MC to min_zoom (camera_action=fit_to_visible). When the user already named a center and zoom, do_this_next.tool is configure_map: call it once with that center and zoom before ingest or a point classification, so the fit does not replace it. Skip that call when no center was named, and do not invent one. Do not call configure_map again unless a later result also reports camera_action=fit_to_visible. Do not use ezt_test/focusAt. POINT LAYERS with map_session_id: status=already_on_map is verified against the live session render payload; when the layer is in the TS but missing from the session, the tool pushes a refresh and returns status=refreshed with a map_refresh block — check its render_ack before claiming points are visible. PART LAYERS are never loaded by a build; they show only when asked. 'Hide/remove the zips' returns status=hidden and takes that layer off the map (territories unchanged). ROUTE overlay (overlay_kind=route) needs map_session_id and only re-shows or hides routes calculate_route already drew — 'hide/remove/clear the route' clears them; with no route yet it returns blocked_by=needs_route pointing at calculate_route. Pass route_id to target ONE of several routes; omit it to act on all of them. To delete a route for good use delete_route; to change a route's stops re-run calculate_route with the same route_id. calculate_route already draws its own result, so this is recovery, not the normal path. Prefer this over raw configure_map for show/add-on-map asks. Part-layer overlays are written onto the live session even when map_push_status=viewer_not_connected (session durability); SSE paint still needs a connected viewer. A later linked ingest_accounts keeps those overlays (non-destructive compose with points). Dual surface: the overlay lands on the in-chat map on Apps-capable hosts and in the map_url tab everywhere else — same map_session_id, no extra call. Scenarios: MC-010, ChatGPT/external MCP.","detail":"Description of `show_map_overlay` changed (70% word delta).","severity":"risky","descriptionDelta":0.7008196721311475},{"kind":"input_property_removed","path":"inputSchema.properties.guidance_handle","tool":"show_map_overlay","before":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Guidance Handle","default":null},"detail":"Field `guidance_handle` was removed from `show_map_overlay` input; consumers still sending it may be rejected or silently ignored.","severity":"breaking"},{"kind":"description_changed","tool":"submit_feedback","after":"Sends product feedback to EasyTerritory: a blocked or partial workflow, a workaround, a confusing response, or a missing capability. Returns an acknowledgement with feedback_id. Secrets, emails, Territory Solution JSON, GeoJSON, and account/CSV tables are stripped before storage; the text is meant for a description of what happened, not data.","before":"[Tier 2 — Quality Feedback] When: workflow blocked, partial, workaround required, confusing tool response, or missing capability/docs. Prerequisites: none. Next: continue or end session after acknowledgement. Scenarios: GC-006. No secrets, credentials, raw customer data, or PII. Do not paste Territory Solution JSON, GeoJSON FeatureCollections, or account/CSV tables — those blocks are stripped.","detail":"Description of `submit_feedback` changed (68% word delta).","severity":"risky","descriptionDelta":0.6805555555555556},{"kind":"description_changed","tool":"tasks_cancel","after":"Tool equivalent of the native MCP tasks/cancel, listed for clients without the Tasks capability. Requests cooperative cancellation of a task_id and returns the Task status document. Whether the Territory Solution is unchanged or partially updated follows the originating tool.","before":"[Tier 2 — MCP Tasks mirror] Tool-only client equivalent of native tasks/cancel. Use only when this mirror appears in tools/list; Tasks-capable clients use tasks/cancel instead. Prerequisite: task_id from the immediately preceding async submission. Returns the same Task status fields and EZT poll metadata as the native method. Cancellation is cooperative; confirm TS unchanged or partial per the originating tool contract. Scenarios: OP-001.","detail":"Description of `tasks_cancel` changed (71% word delta).","severity":"risky","descriptionDelta":0.7058823529411764},{"kind":"description_changed","tool":"tasks_get","after":"Tool equivalent of the native MCP tasks/get, listed for clients without the Tasks capability. Returns the Task status document for a task_id. next_action is sleep_and_poll (wait sleep_ms, then one tasks_get), consume_result (one tasks_result call), or stop_error. status=cancelled means the user stopped it. While queued, _meta carries queue_position, queue_depth, queue_lane, and estimated_start_ms, which is advisory and not a wait time. Polling never starts work.","before":"[Tier 2 — MCP Tasks mirror] Tool-only client equivalent of native tasks/get. Use only when this mirror appears in tools/list; Tasks-capable clients use tasks/get. Prerequisite: task_id from the immediately preceding async submission. Polling never starts work. next_action and sleep_ms sit on this document (the same fields as the submit result) and are repeated under _meta. Follow next_action exactly: sleep_and_poll means sleep the single authoritative sleep_ms value, call tasks_get once, then re-read both fields; consume_result means call tasks_result once now; stop_error means stop. status=cancelled is a user stop — do not resubmit the same tool unless asked. Never sleep on estimated_remaining_ms and never poll HTTP /result for status. While a task is still queued, _meta also carries queue_position (1-based within its worker lane), queue_depth, queue_lane (io|compute), and estimated_start_ms — report these to explain a wait; estimated_start_ms is advisory and never a sleep duration. The result is the same Task status document returned by native tasks/get. Scenarios: OP-002, tool-only MCP clients.","detail":"Description of `tasks_get` changed (64% word delta).","severity":"risky","descriptionDelta":0.639344262295082},{"kind":"description_changed","tool":"tasks_result","after":"Tool equivalent of the native MCP tasks/result, listed for clients without the Tasks capability. Returns the originating tool's terminal result once the task status is completed (consume_result): ts_handle, counts, timings, and map binding, never an inline Territory Solution when a handle exists. A build's ts_handle or task_id (as job_id) is the input analyze takes.","before":"[Tier 2 — MCP Tasks mirror] Tool-only client equivalent of native tasks/result. Call exactly once after tasks_get returns status=completed and _meta.next_action=consume_result. Prerequisite: task_id from that completed Task. Returns the originating tool's terminal handle-only result (ts_handle, counts, timings, map binding/shortcut); it never returns inline TS when a handle exists. Do not poll this operation while working. After build results, pass ts_handle or the same task_id as job_id to analyze with explicit tal_ids; never resubmit a build to recover from an Analyze lookup error.","detail":"Description of `tasks_result` changed (56% word delta).","severity":"risky","descriptionDelta":0.5555555555555556},{"kind":"description_changed","tool":"territory_merge","after":"Merges a pair of adjacent territories and rebalances the rest of the alignment with minimal disruption (N to N-1 territories), e.g. when a rep leaves and the neighbors absorb the territory. territory_id_b is the territory that goes away; territory_id_a is an adjacent neighbor that keeps its id. The same job rebalances every remaining territory. One call removes one territory; non-adjacent ids return NON_ADJACENT_TERRITORIES. Needs source_tal_id and the Territory Solution from map_session_id, ts_handle, or ts; workload balance needs dwell_time (CLARIFICATION_REQUIRED otherwise). Writes a new derived alignment. Returns a task.","before":"[Tier 1 — Minimal-Disruption Merge] When: merge two adjacent territories and rebalance with minimal disruption — including a rep who quits or leaves (their territory is absorbed by its neighbors) and going from N to N-1 territories on an existing alignment. Pass the territory that goes away as territory_id_b and one adjacent neighbor as territory_id_a (it keeps its id); the same job then rebalances every remaining territory, so do not follow it with territory_rebalance. Not delete_territory (that leaves the parts unassigned) and not a new auto_build (that discards the alignment). One call removes one territory. On NON_ADJACENT_TERRITORIES pick another neighbor. Prerequisites: adjacent territory_id_a and territory_id_b in source TAL. Open MC preferred args: map_session_id + source_tal_id + territory_id_a/b (live session TS is authoritative — do not repost GeoJSON). Headless: ts_handle or Compact/GeoJSON ts. Modern form-capable clients may be prompted in-band for missing ids / workload dwell; legacy hosts keep CLARIFICATION_REQUIRED. Next: verify merged TAL in MC, analyze. Scenarios: MG-001..005, EV-001.","detail":"Description of `territory_merge` changed (65% word delta).","severity":"risky","descriptionDelta":0.6466165413533835},{"kind":"input_property_removed","path":"inputSchema.properties.guidance_handle","tool":"territory_merge","before":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Guidance Handle","default":null},"detail":"Field `guidance_handle` was removed from `territory_merge` input; consumers still sending it may be rejected or silently ignored.","severity":"breaking"},{"kind":"description_changed","tool":"territory_rebalance","after":"Rebalances an existing alignment after the account data changed, keeping it as close to the current alignment as possible. Needs source_tal_id and the Territory Solution from map_session_id, ts_handle, or ts. point_layer defaults to the alignment's build layer or the only point layer; otherwise CLARIFICATION_REQUIRED (ambiguous_point_layer, with details.point_layers). Workload balance needs dwell_time; zero_workload means no account maps to a part. The current alignment is the baseline: when nothing improves the objective the result is NO_IMPROVING_REBALANCE_FOUND and nothing changes. Writes a new derived alignment. Returns a task with retention_pct and solver_diagnostics.","before":"[Tier 1 — Minimal-Disruption Rebalance] When: underlying account data changed but alignment should stay close to current (MDR). Open MC preferred args: map_session_id + source_tal_id (+ objective/dwell) — the live session TS is authoritative; do not invent a ts_handle or repost multi-MB GeoJSON when a map session is open. Headless: ts_handle or Compact TS v2 / GeoJSON ts (both valid). Prerequisites: source TAL, refreshed point_layer; viewer connected for MC-first. point_layer may be omitted: the source TAL's build point layer, else the TS's only point layer, is used. CLARIFICATION_REQUIRED details.reason=ambiguous_point_layer (pass point_layer from details.point_layers) or zero_workload (workload_bias>0 but no account maps to a part — check point_layer and dwell; never rerun as is). The source assignment is the non-regression baseline: if no generated candidate improves the requested objective, the derived TAL stays unchanged and returns NO_IMPROVING_REBALANCE_FOUND. Read solver_diagnostics for source/generated/accepted objectives and convergence. Modern form-capable clients may be prompted in-band for missing source_tal_id / uninferable part_layer / unresolved workload dwell; legacy hosts keep CLARIFICATION_REQUIRED (HITL-025). Next: report retention_pct and diagnostics, then analyze before/after with analysis_panel=single. Scenarios: RB-001..005, EV-003.","detail":"Description of `territory_rebalance` changed (72% word delta).","severity":"risky","descriptionDelta":0.7215189873417722},{"kind":"input_property_removed","path":"inputSchema.properties.guidance_handle","tool":"territory_rebalance","before":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Guidance Handle","default":null},"detail":"Field `guidance_handle` was removed from `territory_rebalance` input; consumers still sending it may be rejected or silently ignored.","severity":"breaking"},{"kind":"description_changed","tool":"territory_split","after":"Territory split: divides one territory (e.g. an oversized one) into two while changing the rest of the alignment as little as possible (N to N+1 territories). Needs source_tal_id and target_territory_id, plus the Territory Solution from map_session_id (the open map), ts_handle, or ts. Workload balance needs dwell_time; a missing id or dwell returns CLARIFICATION_REQUIRED with the question. Writes a new derived alignment; the source stays. Returns a task; the result carries disruption_summary (retention_pct).","before":"[Tier 1 — Minimal-Disruption Split] When: split one oversized territory with minimal disruption to the rest of the alignment. Prerequisites: existing TAL with target territory; viewer connected for MC-first. Open MC preferred args: map_session_id + source_tal_id + target_territory_id (live session TS is authoritative — do not repost GeoJSON). Headless: ts_handle or Compact/GeoJSON ts. Distinct from auto_build Scoped Split (balanced-from-scratch). Modern form-capable clients may be prompted in-band for missing source/target ids (and unresolved workload dwell); legacy hosts keep CLARIFICATION_REQUIRED. Next: compare derived TAL in MC, analyze disruption_summary. Scenarios: SP-001..004.","detail":"Description of `territory_split` changed (75% word delta).","severity":"risky","descriptionDelta":0.75},{"kind":"input_property_removed","path":"inputSchema.properties.guidance_handle","tool":"territory_split","before":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Guidance Handle","default":null},"detail":"Field `guidance_handle` was removed from `territory_split` input; consumers still sending it may be rejected or silently ignored.","severity":"breaking"},{"kind":"description_changed","tool":"workflow_advisor","after":"Returns the next tool call for a multi-step workflow, given where the workflow stands. Read-only state machine; executes nothing. intent_category: build_from_accounts | account_grouping | known_assignments | realign_existing | restructure | load_part_layer | route_stops | reachable_area | periodic_scheduling. state: viewer_connected, point_layer, part_layer, tal_present, analysis_fresh, plus selection/realign or restructure flags. inputs: the values known so far (part_layer, territory_count, tal_id, operation, route_type, origins, visit_frequency_field, ...); missing_inputs names the keys still needed. Returns next_tool with drafted next_args, missing_inputs (reported, never invented), blocked_by, remaining_steps, and the workflow guide inline as guidance. Order rules applied: map before compute, viewer connected before compute, ingest before build, analyze after an edit when a point layer exists. Calling it after each step with the updated state walks the whole workflow.","before":"[Tier 1 — Workflow Advisor] Deterministic read-only next-action advisor for multi-step workflows. When: you know the intent (use discover_intent first if not) and want the validated next tool call instead of guessing the order. Prerequisites: none (static state machine; executes nothing). Pass intent_category (builds: build_from_accounts | account_grouping | known_assignments; edits: realign_existing | restructure; map overlay: load_part_layer for add/show ZIP or part layer; routing: route_stops for driving a known list of stops; reachable areas: reachable_area for a drive-time or drive-distance area around origins, with isochrone_build accepted as an alias; scheduling: periodic_scheduling for a recurring cadence, with schedule_visits accepted as an alias), agent-supplied state (viewer_connected, point_layer, part_layer, tal_present, analysis_fresh; realign adds selection_requested/selection_committed/realign_applied; restructure adds source_tal_id), and optional inputs (builds: part_layer, territory_count, grouping_field, assignments_handle, tal_label; realign: tal_id, moves or realign_operation+part_ids, to_territory_id, analysis_requested; restructure: operation=split|merge|rebalance, source_tal_id, target_territory_id, territory_id_a/territory_id_b; load_part_layer: user_request for ZIP inference; route_stops: route_type=circuit|tour|open_tour, start, stop_sets, end for tour, stops_need_ingest when the stops are not in the TS yet; reachable_area: origins, bands, part_layer, travel_mode=car|truck, origins_need_ingest when the origins are not in the TS yet — the advisor never invents a budget; periodic_scheduling: visit_frequency_field, frequency_unit, dwell_time or dwell_minutes/dwell_hours, daily_capacity or daily_capacity_hours, horizon_days, max_buckets_per_day — the advisor reports cadence/dwell/capacity as missing rather than inventing them). Returns next_tool + drafted next_args, missing_inputs, blocked_by, remaining_steps, guidance_uri, and `guidance` (the EMEP atom inlined as {uri, title, excerpt, truncated} — no resources/read needed); enforces mc_first, viewer_before_compute, build_after_ingest, and conditional analysis_freshness (realign only queues Analyze when a point layer exists or analysis_requested=true). Call it again after each completed step with updated state until done. Other categories (delegate, manual_selection, analyze_present, ...) return a discover_intent/guidance fallback. Scenarios: AB-016, ACB-003, DB-001, WI-001, WI-002, wrong-build-tool, build-before-ingest.","detail":"Description of `workflow_advisor` changed (71% word delta).","severity":"risky","descriptionDelta":0.7142857142857143}],"published_at":"2026-10-09T17:00:12.050Z"},{"slug":"ZV-2026-1861","server_name":"mcp.easyterritory.ai","severity":"breaking","title":"mcp.easyterritory.ai: repair_policy on account_build narrowed to a closed enum (default, strict, report_only); previously valid values may now be rejected.","summary":"[breaking] repair_policy on account_build narrowed to a closed enum (default, strict, report_only); previously valid values may now be rejected. [safe] Description of analyze changed (13% word delta). [breaking] repair_policy on auto_build narrowed to a closed enum (default, strict, report_only); previously valid values may now be rejected. [safe] Description of configure_map changed (9% word delta). [safe] Description of create_territory_from_parts changed (7% word delta). [risky] Optional field ts_handle was added to create_territory_from_parts; may shift model behaviour. [breaking] repair_policy on delete_territory narrowed to a closed enum (default, strict, report_only); previously valid values may now be rejected. [safe] Description of direct_build changed (13% word delta). [breaking] repair_policy on direct_build narrowed to a closed enum (default, strict, report_only); previously valid values may now be rejected. [safe] Description of get_guidance changed (8% word delta). [safe] Description of isochrone_build changed (19% word delta). [breaking] Field part_layer was removed from isochrone_build input; consumers still sending it may be rejected or silently ignored. [breaking] Field part_overlay was removed from isochrone_build input; consumers still sending it may be rejected or silently ignored. [risky] Description of query_parts changed (43% word delta). [breaking] repair_policy on realign narrowed to a closed enum (default, strict, report_only); previously valid values may now be rejected. [safe] Description of request_account_upload changed (18% word delta). [safe] Description of request_assignment_upload changed (24% word delta). [safe] Description of seed_build changed (3% word delta). [risky] Description of set_map_state changed (38% word delta). [safe] Description of show_map_overlay changed (4% word delta). [breaking] repair_policy on territory_merge narrowed to a closed enum (default, strict, report_only); previously valid values may now be rejected.","changes":[{"kind":"enum_narrowed","path":"inputSchema.properties.repair_policy","tool":"account_build","after":"enum[default,strict,report_only]","before":"open","detail":"`repair_policy` on `account_build` narrowed to a closed enum (default, strict, report_only); previously valid values may now be rejected.","severity":"breaking"},{"kind":"description_changed","tool":"analyze","after":"[Tier 1 — Analysis] When: balance diagnostics, cross-TAL comparison, or post-mutation facts. Accepts ts, ts_handle, or completed job_id (inline ts or result.ts_handle from prior compute jobs), or map_session_id alone to analyze the open map as it is now, including points ingested after the build. A job_id or ts_handle is that job's snapshot and wins when both are passed; a snapshot without points returns NO_POINT_LAYER with do_this_next naming the map_session_id retry. Prerequisites: TAL exists; re-run after any TAL or point change (I-2)—never treat stale analysis as current. Returns JSON facts and presentation guidance URI; no prose. metrics: omit to analyze all declared point-layer columns, or pass explicit names. System dimensions (always valid, not point columns): workload (territory hours; alias total_workload_hours), account_count. Point columns must be fields declared at ingest (metric_fields/workload_fields, e.g. Revenue, Units Sold); undeclared columns are discarded at ingest and fail with UNDECLARED_FIELD — re-ingest with the column declared to analyze it. Response includes available_metrics. METRIC COLUMNS ALWAYS RENDER: the territory_metric_grids (and the MC dock) carry Total Count plus a Total <metric> column for EVERY declared metric_fields column of the point layer, in declaration order, even when metrics names only account_count or workload — a count-only panel is never the correct outcome when metrics are declared. Declared names may be bare strings (Designer pull) or {field,label,type} objects (ingest). Metric cells are parsed leniently: '14,651', '$1,200.50', and padded strings sum as numbers. DWELL / WORKLOAD (T-171): workload hours need onsite/dwell time — request dwell_time, TAL build_provenance.dwell_time (auto_build), or the point layer's dwell_time_field. When none resolves, Analyze STILL RUNS and reports counts, metrics, classification breakdowns, and balance on those dimensions; workload is OMITTED (no total_workload_hours column, workload_total null) — never drive-only hours. The result then carries workload_omitted {tal_ids, ask_user, retry}: report the statistics FIRST, then relay ask_user verbatim (it asks for an average onsite/dwell time). Re-run analyze with dwell_time only if the user answers; never invent a default (including 30 minutes). Do not ask for dwell before the first analyze just to avoid the note. DRIVE-TIME TALS (isochrone_build): accounts are counted inside each provider polygon (measured_by=area_polygon), in every area that contains them, so area totals can exceed the account count; area_overlaps lists accounts shared across origins. scope and hypothetical_moves do not apply. Prefer analysis_panel=single with map_session_id so the MC dock fills on this call — that is the one-shot post-build path. The completed result then has analysis_panel.status=pushed, and session state has analysis_panel.loaded=true. phase_timings_ms.panel_push is only a duration, not proof the dock opened. Otherwise pass the full Analyze result (or its task_id) to load_analysis_panel. WORKLOAD UNITS: territories[].workload_total is minutes (workload_unit=minutes). Quote hours only from territory_metric_grids cell total_workload_hours.value. When metrics is omitted and dwell resolves, balance_scores includes workload plus declared metrics — not a location-match score. DOCK HIDE: minimizing the table leaves analysis_panel.loaded true; do not call load_analysis_panel again just to restore it. A new analysis_panel_loaded event opens the dock. Chat-only JSON is not completion when a map is open. Use ezt://guidance/analysis-presentation for narrative. Full atom: ezt://guidance/workflows/analyze-and-present. Scenarios: AN-001..007, MC-009, S002.","before":"[Tier 1 — Analysis] When: balance diagnostics, cross-TAL comparison, or post-mutation facts. Accepts ts, ts_handle, or completed job_id (inline ts or result.ts_handle from prior compute jobs). Prerequisites: TAL exists; re-run after any TAL or point change (I-2)—never treat stale analysis as current. Returns JSON facts and presentation guidance URI; no prose. metrics: omit to analyze all declared point-layer columns, or pass explicit names. System dimensions (always valid, not point columns): workload (territory hours; alias total_workload_hours), account_count. Point columns must be fields declared at ingest (metric_fields/workload_fields, e.g. Revenue, Units Sold); undeclared columns are discarded at ingest and fail with UNDECLARED_FIELD — re-ingest with the column declared to analyze it. Response includes available_metrics. METRIC COLUMNS ALWAYS RENDER: the territory_metric_grids (and the MC dock) carry Total Count plus a Total <metric> column for EVERY declared metric_fields column of the point layer, in declaration order, even when metrics names only account_count or workload — a count-only panel is never the correct outcome when metrics are declared. Declared names may be bare strings (Designer pull) or {field,label,type} objects (ingest). Metric cells are parsed leniently: '14,651', '$1,200.50', and padded strings sum as numbers. DWELL / WORKLOAD (T-171): workload hours need onsite/dwell time — request dwell_time, TAL build_provenance.dwell_time (auto_build), or the point layer's dwell_time_field. When none resolves, Analyze STILL RUNS and reports counts, metrics, classification breakdowns, and balance on those dimensions; workload is OMITTED (no total_workload_hours column, workload_total null) — never drive-only hours. The result then carries workload_omitted {tal_ids, ask_user, retry}: report the statistics FIRST, then relay ask_user verbatim (it asks for an average onsite/dwell time). Re-run analyze with dwell_time only if the user answers; never invent a default (including 30 minutes). Do not ask for dwell before the first analyze just to avoid the note. Prefer analysis_panel=single with map_session_id so the MC dock fills on this call — that is the one-shot post-build path. The completed result then has analysis_panel.status=pushed, and session state has analysis_panel.loaded=true. phase_timings_ms.panel_push is only a duration, not proof the dock opened. Otherwise pass the full Analyze result (or its task_id) to load_analysis_panel. WORKLOAD UNITS: territories[].workload_total is minutes (workload_unit=minutes). Quote hours only from territory_metric_grids cell total_workload_hours.value. When metrics is omitted and dwell resolves, balance_scores includes workload plus declared metrics — not a location-match score. DOCK HIDE: minimizing the table leaves analysis_panel.loaded true; do not call load_analysis_panel again just to restore it. A new analysis_panel_loaded event opens the dock. Chat-only JSON is not completion when a map is open. Use ezt://guidance/analysis-presentation for narrative. Full atom: ezt://guidance/workflows/analyze-and-present. Scenarios: AN-001..007, MC-009, S002.","detail":"Description of `analyze` changed (13% word delta).","severity":"safe","descriptionDelta":0.13103448275862073},{"kind":"enum_narrowed","path":"inputSchema.properties.repair_policy","tool":"auto_build","after":"enum[default,strict,report_only]","before":"open","detail":"`repair_policy` on `auto_build` narrowed to a closed enum (default, strict, report_only); previously valid values may now be rejected.","severity":"breaking"},{"kind":"description_changed","tool":"configure_map","after":"[Tier 2 — Durable Map Config] When: set or update project_name (the durable TS short name used as the Map Component heading), loaded_part_layers, active_part_layer, active_tal_id, point_layer_classifications, classification, presentation, center, or zoom on the TS. project_name is first-class: persists to ts.properties.map_config.project_name and emits config_changed so a linked session refreshes the heading in place. POINT SYMBOLOGY: point_layer_classifications is the way to recolor/resize/reshape points on an open map — never export GeoJSON, compute breaks client-side, and repost a TS. Each entry is {point_layer, field, method: quantile|equal_interval|categorical|manual, class_count (2-12), channel: color|size|shape, optional colors/sizes/shapes, optional style:{color,size,opacity,shape} for the layer base symbol}. Supported shapes: circle|square|triangle|diamond|star|cross|house|pin|flag|hexagon|pentagon|shield|arrow_up|building (aliases home→house, marker/map_pin→pin, hex→hexagon, arrow/up→arrow_up, warehouse/depot→building, plus/x→cross). The server computes breaks from the in-session points, writes point_layers[].<channel>_classification, pushes config_changed, and returns the applied classes with per-class counts. Categorical missing values become an Ungrouped class. One classification per channel per layer: a second color entry replaces the first. Two encodings on one layer mix channels (color+size or color+shape). clear:true with point_layer and channel drops that channel classification, including a same-channel classification object. Other channels and the base style stay. active_channels reports the channels still set. Method defaults to quantile for numeric fields and categorical otherwise; pass one entry per layer to give two point layers distinct colors or shapes. classification (without a point_layer) stays a project-level map_config patch and does NOT paint points; a point-layer-targeted classification is routed to symbology with a warning. PART LAYERS: builds never load a part layer overlay; ZIP/county outlines appear only when named. loaded_part_layers=[ids] shows them, loaded_part_layers=[] hides them all (active_part_layer clears too), and the choice persists across later builds. request_part_selection loads the part_layer it selects on. Optional center ([longitude, latitude]) and zoom (0-24) persist as the TS default camera and jump an open MC when included on this call; Monica's pan is not captured. Prefer map_session_id for an open MC — the live session TS is the patch base; a stale pre-ingest ts_handle must not strip points/part layers. Not the entry tool for browsing a TS — that is get_map_visualization (I-1). Prerequisites: map_session_id, ts_handle, or inline ts; points already ingested before point_layer_classifications; part_layer from ezt://part-layers before builds. Next: build or analyze; if active TAL changes, re-run analyze then load_analysis_panel (I-2). Scenarios: MC-010, MC-012, DS-001, baseline workflow step 4.","before":"[Tier 2 — Durable Map Config] When: set or update project_name (the durable TS short name used as the Map Component heading), loaded_part_layers, active_part_layer, active_tal_id, point_layer_classifications, classification, presentation, center, or zoom on the TS. project_name is first-class: persists to ts.properties.map_config.project_name and emits config_changed so a linked session refreshes the heading in place. POINT SYMBOLOGY: point_layer_classifications is the way to recolor/resize/reshape points on an open map — never export GeoJSON, compute breaks client-side, and repost a TS. Each entry is {point_layer, field, method: quantile|equal_interval|categorical|manual, class_count (2-12), channel: color|size|shape, optional colors/sizes/shapes, optional style:{color,size,opacity,shape} for the layer base symbol}. Supported shapes: circle|square|triangle|diamond|star|cross|house|pin|flag|hexagon|pentagon|shield|arrow_up|building (aliases home→house, marker/map_pin→pin, hex→hexagon, arrow/up→arrow_up, warehouse/depot→building, plus/x→cross). The server computes breaks from the in-session points, writes point_layers[].<channel>_classification, pushes config_changed, and returns the applied classes with per-class counts. Categorical missing values become an Ungrouped class. One classification per channel per layer: a second color entry replaces the first. Two encodings on one layer mix channels (color+size or color+shape). clear:true with point_layer and channel drops that channel classification, including a same-channel classification object. Other channels and the base style stay. active_channels reports the channels still set. Method defaults to quantile for numeric fields and categorical otherwise; pass one entry per layer to give two point layers distinct colors or shapes. classification (without a point_layer) stays a project-level map_config patch and does NOT paint points; a point-layer-targeted classification is routed to symbology with a warning. Optional center ([longitude, latitude]) and zoom (0-24) persist as the TS default camera and jump an open MC when included on this call; Monica's pan is not captured. Prefer map_session_id for an open MC — the live session TS is the patch base; a stale pre-ingest ts_handle must not strip points/part layers. Not the entry tool for browsing a TS — that is get_map_visualization (I-1). Prerequisites: map_session_id, ts_handle, or inline ts; points already ingested before point_layer_classifications; part_layer from ezt://part-layers before builds. Next: build or analyze; if active TAL changes, re-run analyze then load_analysis_panel (I-2). Scenarios: MC-010, MC-012, DS-001, baseline workflow step 4.","detail":"Description of `configure_map` changed (9% word delta).","severity":"safe","descriptionDelta":0.08870967741935487},{"kind":"description_changed","tool":"create_territory_from_parts","after":"[Tier 2 — Territory From Parts] When: create or update one leaf territory from committed part IDs (manual MC-005 build or RL-011/012). Prerequisites: part_ids from selection or agent list; part_layer; viewer connected. Pass map_session_id for the open MC — the server loads that session's TS, appends the new TAL, and rebinds the same session before map_refresh (do not call get_map_visualization just to show the new territory). If result.map_refresh.notified is false or status is rebind_required, call get_map_visualization(job_id=<this job>). With no open MC, pass ts_handle from the prior result (never repost inline ts when a handle exists); the new TAL is appended to that TS and the result returns a new ts_handle. map_session_id wins when both are passed. Completed result includes created_territory.territory_id, leaf_territories, and selection_summary (requested/unique counts plus duplicate_part_ids) — territory_name is a display label only (e.g. T1 → territory_id like tal-t1-t1). Later realign into this territory MUST use created_territory.territory_id (preferred) or the exact leaf display name when unique; never invent shorthand (T3 ≠ Territory 3) — if unclear, ask which leaf from leaf_territories. DWELL: this tool creates a NEW TAL and does not inherit dwell_time from a prior auto_build. Dwell is NOT required to create the territory (same as direct_build). Optional dwell_time={type:scalar,value,unit} stamps build_provenance for later Analyze. Without dwell, Analyze still runs and reports every other statistic but OMITS workload (result.workload_omitted) — present the stats, then relay its ask_user sentence; never invent a default (including 30 minutes). Modern form-capable clients may be prompted in-band for dwell when a hydrated TS shows points without dwell provenance; declining still allows create (HITL-038). Next: repeat for additional territories, realign with created_territory.territory_id, or analyze with confirmed dwell_time. Scenarios: MC-005, RL-011, RL-012.","before":"[Tier 2 — Territory From Parts] When: create or update one leaf territory from committed part IDs (manual MC-005 build or RL-011/012). Prerequisites: part_ids from selection or agent list; part_layer; viewer connected. Pass map_session_id for the open MC — the server loads that session's TS, appends the new TAL, and rebinds the same session before map_refresh (do not call get_map_visualization just to show the new territory). If result.map_refresh.notified is false or status is rebind_required, call get_map_visualization(job_id=<this job>). Completed result includes created_territory.territory_id, leaf_territories, and selection_summary (requested/unique counts plus duplicate_part_ids) — territory_name is a display label only (e.g. T1 → territory_id like tal-t1-t1). Later realign into this territory MUST use created_territory.territory_id (preferred) or the exact leaf display name when unique; never invent shorthand (T3 ≠ Territory 3) — if unclear, ask which leaf from leaf_territories. DWELL: this tool creates a NEW TAL and does not inherit dwell_time from a prior auto_build. Dwell is NOT required to create the territory (same as direct_build). Optional dwell_time={type:scalar,value,unit} stamps build_provenance for later Analyze. Without dwell, Analyze still runs and reports every other statistic but OMITS workload (result.workload_omitted) — present the stats, then relay its ask_user sentence; never invent a default (including 30 minutes). Modern form-capable clients may be prompted in-band for dwell when a hydrated TS shows points without dwell provenance; declining still allows create (HITL-038). Next: repeat for additional territories, realign with created_territory.territory_id, or analyze with confirmed dwell_time. Scenarios: MC-005, RL-011, RL-012.","detail":"Description of `create_territory_from_parts` changed (7% word delta).","severity":"safe","descriptionDelta":0.0674157303370787},{"kind":"input_property_added","path":"inputSchema.properties.ts_handle","tool":"create_territory_from_parts","after":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ts Handle","default":null},"detail":"Optional field `ts_handle` was added to `create_territory_from_parts`; may shift model behaviour.","severity":"risky"},{"kind":"enum_narrowed","path":"inputSchema.properties.repair_policy","tool":"delete_territory","after":"enum[default,strict,report_only]","before":"open","detail":"`repair_policy` on `delete_territory` narrowed to a closed enum (default, strict, report_only); previously valid values may now be rejected.","severity":"breaking"},{"kind":"description_changed","tool":"direct_build","after":"[Tier 1 — Known Assignments Builder] When: user has explicit part-to-territory assignments (spreadsheet, legacy file, hierarchical territory_path). assignments_handle must be a server upload handle (aup_...) from request_assignment_upload — csv_text/csv_file/assignments, or a POST of the CSV file to its key-less result.upload_url. Legacy spreadsheets (Postal Code + Territory/Region/Division) are auto-mapped when staged. Conflicting duplicate part_ids default to duplicate_part_policy=keep_first (first wins). Prerequisites: part_layer chosen; viewer connected for MC-first; ts/ts_handle optional. Not for account point locations—use ingest_accounts. Not for balanced partitioning—use auto_build. Not for attribute grouping—use account_build. Next: verify TAL in MC (MC-011), analyze + load_analysis_panel. Scenarios: DB-001..005, MC-011.","before":"[Tier 1 — Known Assignments Builder] When: user has explicit part-to-territory assignments (spreadsheet, legacy file, hierarchical territory_path). assignments_handle must be a server upload handle (aup_...) from POST /assignments/upload or request_assignment_upload — after parsing rows or passing csv_text/csv_file. Legacy spreadsheets (Postal Code + Territory/Region/Division) are auto-mapped when staged. Conflicting duplicate part_ids default to duplicate_part_policy=keep_first (first wins). Prerequisites: part_layer chosen; viewer connected for MC-first; ts/ts_handle optional. Not for account point locations—use ingest_accounts. Not for balanced partitioning—use auto_build. Not for attribute grouping—use account_build. Next: verify TAL in MC (MC-011), analyze + load_analysis_panel. Scenarios: DB-001..005, MC-011.","detail":"Description of `direct_build` changed (13% word delta).","severity":"safe","descriptionDelta":0.12903225806451613},{"kind":"enum_narrowed","path":"inputSchema.properties.repair_policy","tool":"direct_build","after":"enum[default,strict,report_only]","before":"open","detail":"`repair_policy` on `direct_build` narrowed to a closed enum (default, strict, report_only); previously valid values may now be rejected.","severity":"breaking"},{"kind":"description_changed","tool":"get_guidance","after":"[Tier 1 — Startup Guide + Handle] CALL THIS FIRST. Read-only. When: session start, after blocked_by=guidance_required, or when the shared rules are unclear. Returns result.text (the short cross-tool brief, same text as server instructions), result.guidance_handle (the rotating code every territory/map tool requires), and result.server_version (this server's product version; the same string as serverInfo.version). Per-tool rules are on that tool's description. Workflow essays are inlined as guidance on discover_intent, workflow_advisor, and blocked_by. Exempt: get_guidance, submit_feedback, discover_intent, workflow_advisor, ep_* tools.","before":"[Tier 1 — Startup Guide + Handle] CALL THIS FIRST. Read-only. When: session start, after blocked_by=guidance_required, or when the shared rules are unclear. Returns result.text (the short cross-tool brief, same text as server instructions) and result.guidance_handle (the rotating code every territory/map tool requires). Per-tool rules are on that tool's description. Workflow essays are inlined as guidance on discover_intent, workflow_advisor, and blocked_by. Exempt: get_guidance, submit_feedback, discover_intent, workflow_advisor, ep_* tools.","detail":"Description of `get_guidance` changed (8% word delta).","severity":"safe","descriptionDelta":0.08064516129032262},{"kind":"description_changed","tool":"isochrone_build","after":"[Tier 1 — Isochrone Build] When: draw the area reachable by DRIVING from one or more origins within a time or distance budget — '20-minute drive-time area around each branch', service area, catchment, coverage ring, reachable range, isochrone, isodistance (IS-001..IS-006). It draws reachable area. It has no objective and does not balance workload. Prerequisites: a TomTom or Azure Maps key on the server (no key → ISOCHRONE_NOT_CONFIGURED, no degraded mode — never substitute a straight-line radius); and, for point-layer origins, ingested points plus ts_handle or map_session_id. Inline origins need no TS. No part layer is involved. ORIGINS x BANDS: origins are inline {latitude, longitude, label?} or a point-layer reference {point_layer, point_ids?, filter?, label_field?} — '40 minutes around each of my 6 branches' is ONE call. bands are [{time_budget_seconds} | {distance_budget_meters}], one budget per band, and every band in a call must use the SAME unit (mixing returns MIXED_BUDGET_TYPES). One band → one leaf per origin; several bands → a rollup per origin with one leaf per band. Cost is origins x bands provider calls, capped by TOO_MANY_ORIGINS (default 25) / TOO_MANY_BANDS (default 5). QUOTA: those billable calls also draw on a per-API-key monthly cap. The whole fan-out is claimed before any call goes out, so a build that does not fit returns PROVIDER_QUOTA_EXCEEDED with limit, used, remaining, and period_resets_at having spent nothing. An operator must raise the cap; report those numbers instead of retrying with fewer bands. LIMITS: travel_mode is 'car' or 'truck' ONLY — neither provider offers a walking or cycling reachable range, unlike calculate_route. time_budget_seconds <= 21600, distance_budget_meters <= 500000 (BUDGET_OUT_OF_RANGE). depart_at is supported; arrive_at is not. avoid accepts toll_roads, motorways, ferries, unpaved_roads, carpools, border_crossings, tunnels, car_trains, low_emission_zones. GEOMETRY: each territory IS the provider's exact polygon — no part layer, no part_ids. Bands nest and nearby origins' polygons overlap, and the map paints them overlapping (tightest on top). analyze counts an account in EVERY area that contains it (a 20-minute band includes its 10-minute accounts) and reports the accounts overlapping areas share. realign / territory_split / territory_merge / territory_rebalance return ISOCHRONE_TAL_NOT_EDITABLE: re-run isochrone_build to change an area; delete_territory / delete_tal still remove one. A band budget is NOT territory workload and NOT route drive time — never report '20-minute band' as 20 minutes of workload or feed it to auto_build. Async: returns task_id — follow _meta.next_action (sleep_and_poll → consume_result) and sleep the authoritative sleep_ms, never estimated_remaining_ms. Partial provider failure still builds the TAL and reports failed_requests[]. Then: analyze(tal_ids=[<tal_id>], map_session_id, analysis_panel='single') for per-area accounts and metrics (needs an ingested point layer). Pass map_session_id here to paint the areas on an open map. Full atom: ezt://guidance/workflows/isochrone-territories. Scenarios: IS-001..IS-006.","before":"[Tier 1 — Isochrone Build] When: draw the area reachable by DRIVING from one or more origins within a time or distance budget — '20-minute drive-time area around each branch', service area, catchment, coverage ring, reachable range, isochrone, isodistance (IS-001..IS-006). It draws reachable area. It has no objective and does not balance workload. Prerequisites: a TomTom or Azure Maps key on the server (no key → ISOCHRONE_NOT_CONFIGURED, no degraded mode — never substitute a straight-line radius); a part_layer (read ezt://part-layers); and, for point-layer origins, ingested points plus ts_handle or map_session_id. Inline origins need no TS. ORIGINS x BANDS: origins are inline {latitude, longitude, label?} or a point-layer reference {point_layer, point_ids?, filter?, label_field?} — '40 minutes around each of my 6 branches' is ONE call. bands are [{time_budget_seconds} | {distance_budget_meters}], one budget per band, and every band in a call must use the SAME unit (mixing returns MIXED_BUDGET_TYPES). One band → one leaf per origin; several bands → a rollup per origin with one leaf per band. Cost is origins x bands provider calls, capped by TOO_MANY_ORIGINS (default 25) / TOO_MANY_BANDS (default 5). QUOTA: those billable calls also draw on a per-API-key monthly cap. The whole fan-out is claimed before any call goes out, so a build that does not fit returns PROVIDER_QUOTA_EXCEEDED with limit, used, remaining, and period_resets_at having spent nothing. An operator must raise the cap; report those numbers instead of retrying with fewer bands. LIMITS: travel_mode is 'car' or 'truck' ONLY — neither provider offers a walking or cycling reachable range, unlike calculate_route. time_budget_seconds <= 21600, distance_budget_meters <= 500000 (BUDGET_OUT_OF_RANGE). depart_at is supported; arrive_at is not. avoid accepts toll_roads, motorways, ferries, unpaved_roads, carpools, border_crossings, tunnels, car_trains, low_emission_zones. GEOMETRY AND PARTS: each territory stores the provider's exact polygon AND part_ids assigned by centroid_within overlay, so analyze and territory stats work normally while the map shows the true drive-time edge. Bands nest and their polygons overlap on purpose (tightest paints on top); part assignment is disjoint — a part joins the tightest band containing it, then the nearer origin. realign can move parts between bands but NEVER moves a polygon, so a realigned isochrone's assignment intentionally diverges from its drawn shape. A band budget is NOT territory workload and NOT route drive time — never report '20-minute band' as 20 minutes of workload or feed it to auto_build. Async: returns task_id — follow _meta.next_action (sleep_and_poll → consume_result) and sleep the authoritative sleep_ms, never estimated_remaining_ms. Partial provider failure still builds the TAL and reports failed_requests[]. Then: analyze(tal_ids=[<tal_id>], map_session_id, analysis_panel='single') for per-band accounts and metrics. Pass map_session_id here to paint the bands on an open map. Full atom: ezt://guidance/workflows/isochrone-territories. Scenarios: IS-001..IS-006.","detail":"Description of `isochrone_build` changed (19% word delta).","severity":"safe","descriptionDelta":0.1917808219178082},{"kind":"input_property_removed","path":"inputSchema.properties.part_layer","tool":"isochrone_build","before":{"type":"string","title":"Part Layer"},"detail":"Field `part_layer` was removed from `isochrone_build` input; consumers still sending it may be rejected or silently ignored.","severity":"breaking"},{"kind":"input_property_removed","path":"inputSchema.properties.part_overlay","tool":"isochrone_build","before":{"type":"string","title":"Part Overlay","default":"centroid_within"},"detail":"Field `part_overlay` was removed from `isochrone_build` input; consumers still sending it may be rejected or silently ignored.","severity":"breaking"},{"kind":"description_changed","tool":"query_parts","after":"[Tier 2 — Part Metadata] When: enrich or filter parts before build, or inspect attributes without geometry. Prerequisites: part_layer from ezt://part-layers. Pass exactly one of filter (e.g. {state_abbr: TX}) or part_ids; neither or both is INVALID_REQUEST. To check that a part layer exists, read ezt://part-layers instead of probing with an empty query. Next: direct_build, configure_map, or agent-side join before build. Scenarios: DS-002. Returns part_id and attributes only; no geometry.","before":"[Tier 2 — Part Metadata] When: enrich or filter parts before build, or inspect attributes without geometry. Prerequisites: part_layer from ezt://part-layers. Next: direct_build, configure_map, or agent-side join before build. Scenarios: DS-002. Returns part_id and attributes only; no geometry.","detail":"Description of `query_parts` changed (43% word delta).","severity":"risky","descriptionDelta":0.43333333333333335},{"kind":"enum_narrowed","path":"inputSchema.properties.repair_policy","tool":"realign","after":"enum[default,strict,report_only]","before":"open","detail":"`repair_policy` on `realign` narrowed to a closed enum (default, strict, report_only); previously valid values may now be rejected.","severity":"breaking"},{"kind":"description_changed","tool":"request_account_upload","after":"[Tier 2 — Data Intake helper] When: you have account/location rows or an account CSV that you cannot inline in a single ingest_accounts call (e.g. a large CSV, hundreds/thousands of rows). Stage the rows here in one or more chunks, then call ingest_accounts(accounts_handle=...). If the file includes metric, workload, visit-frequency, or dwell-time fields, stage the file here first, inspect returned csv_headers / row keys, confirm role candidates, then pass metric_fields/workload_fields/visit_frequency_field/dwell_time_field to ingest_accounts. Continue only after ingest_accounts has loaded the points. WAYS TO STAGE (all return an upload_handle): (A) CSV on disk + a shell with network (Cursor, Claude Code, Codex, CLI agents): call with no rows to get result.upload_url, then run result.curl_example — POST the raw file with Content-Type: text/csv. No API key: upload_url is the credential (expires in 15 min; each POST appends to the same handle). Fastest for large files; do not re-type rows into csv_text or rows chunks. (B) MCP csv_file: pass the uploaded CSV as a ChatGPT/OpenAI file parameter when available. (C) MCP csv_text: pass csv_text=<raw CSV string> (whole file in one call, up to 16 MB) when you have no shell or no network. Preserve multiline newlines — do NOT JSON-encode with PowerShell ConvertTo-Json (that corrupts rows into a single line). Use Python json.dumps or MCP csv_file instead. (D) MCP rows: call with rows=<chunk>; append more with upload_handle=<prior handle>. A local path in csv_file fails (UNREADABLE_FILE_REFERENCE); use (A). Every result carries a fresh upload_url for its handle; an expired URL returns UPLOAD_TOKEN_EXPIRED — call again with upload_handle for a new one. Then: ingest_accounts(accounts_handle=<upload_handle>, map_session_id=<MC session id>, label_field=<label field>). GIS coordinate headers latitude/lat/LAT and longitude/lon/lng/long/LON passthrough case-insensitively. CRM headers such as Address 1: Latitude and Address 1: Longitude are coordinates too — the sheet stays accounts when Region and Territory are also present. suggested_ingest.args sets latitude_field and longitude_field for those headers; pass them through and do not rename them. If first Tasks status shows coordinate_passthrough_count=0 with a large geocode_query_count on a file that had coord-like columns, call tasks/cancel or tasks_cancel and fix — never wait on national geocode. id_field only when a unique business key exists — never label/name; omit otherwise (server synthesizes row-N). On success the result includes suggested_ingest ({tool, args, confidence, notes, geocode_plan}) — prefer those args for the next ingest_accounts call (fill map_session_id from the open MC). geocode_plan states coordinate_fields_detected, will_geocode, eta_class, and passthrough_likely before you submit. Handle is single-use (claimed by the ingest job) and expires (default 1h). Scenarios: IA-001..005, S003.","before":"[Tier 2 — Data Intake helper] When: you have account/location rows or an account CSV that you cannot inline in a single ingest_accounts call (e.g. a large CSV, hundreds/thousands of rows). Stage the rows here in one or more chunks, then call ingest_accounts(accounts_handle=...). If the file includes metric, workload, visit-frequency, or dwell-time fields, stage the file here first, inspect returned csv_headers / row keys, confirm role candidates, then pass metric_fields/workload_fields/visit_frequency_field/dwell_time_field to ingest_accounts. Continue only after ingest_accounts has loaded the points. FOUR WAYS TO STAGE (all return an upload_handle): (A) MCP csv_file: pass the uploaded CSV as a ChatGPT/OpenAI file parameter when available. (B) MCP csv_text: pass csv_text=<raw CSV string> when file params are unavailable. Preserve multiline newlines — do NOT JSON-encode with PowerShell ConvertTo-Json (that corrupts rows into a single line). Use Python json.dumps or MCP csv_file instead. (C) MCP rows: call with rows=<chunk>; append more with upload_handle=<prior handle>. (D) HTTP: call with no rows/csv_text to get result.upload_url, then POST rows or csv_text/csv_file. Then: ingest_accounts(accounts_handle=<upload_handle>, map_session_id=<MC session id>, label_field=<label field>). GIS coordinate headers latitude/lat/LAT and longitude/lon/lng/long/LON passthrough case-insensitively. CRM headers such as Address 1: Latitude and Address 1: Longitude are coordinates too — the sheet stays accounts when Region and Territory are also present. suggested_ingest.args sets latitude_field and longitude_field for those headers; pass them through and do not rename them. If first Tasks status shows coordinate_passthrough_count=0 with a large geocode_query_count on a file that had coord-like columns, call tasks/cancel or tasks_cancel and fix — never wait on national geocode. id_field only when a unique business key exists — never label/name; omit otherwise (server synthesizes row-N). On success the result includes suggested_ingest ({tool, args, confidence, notes, geocode_plan}) — prefer those args for the next ingest_accounts call (fill map_session_id from the open MC). geocode_plan states coordinate_fields_detected, will_geocode, eta_class, and passthrough_likely before you submit. Handle is single-use (claimed by the ingest job) and expires (default 1h). Scenarios: IA-001..005, S003.","detail":"Description of `request_account_upload` changed (18% word delta).","severity":"safe","descriptionDelta":0.17692307692307696},{"kind":"description_changed","tool":"request_assignment_upload","after":"[Tier 2 — Direct Build intake] When: you have part-to-territory assignment rows or a legacy spreadsheet (e.g. Zip2Terr.csv with Postal Code + Territory/Region/Division) that you cannot inline in direct_build. Stage rows here, then call direct_build(assignments_handle=<upload_handle>). WAYS TO STAGE (all return upload_handle): (A) CSV on disk + a shell with network: call with no rows to get result.upload_url, then run result.curl_example — POST the raw file with Content-Type: text/csv. No API key: upload_url is the credential (expires in 15 min; each POST appends). (B) MCP csv_file: pass the uploaded CSV as a ChatGPT/OpenAI file parameter. (C) MCP csv_text: pass csv_text=<raw multiline CSV> — preserve newlines; do NOT JSON-encode with PowerShell ConvertTo-Json (corrupts rows). (D) MCP assignments: pass assignments=<chunk of row objects>; append with upload_handle. An expired upload_url returns UPLOAD_TOKEN_EXPIRED — call again with upload_handle. Legacy columns (Postal Code, Territory, Region, Division) auto-map to part_id + territory_path. Then: direct_build(assignments_handle=<handle>, part_layer=..., tal_label=..., map_session_id=...). Handle is single-use and expires (default 1h).","before":"[Tier 2 — Direct Build intake] When: you have part-to-territory assignment rows or a legacy spreadsheet (e.g. Zip2Terr.csv with Postal Code + Territory/Region/Division) that you cannot inline in direct_build. Stage rows here, then call direct_build(assignments_handle=<upload_handle>). FOUR WAYS TO STAGE (all return upload_handle): (A) MCP csv_file: pass the uploaded CSV as a ChatGPT/OpenAI file parameter. (B) MCP csv_text: pass csv_text=<raw multiline CSV> — preserve newlines; do NOT JSON-encode with PowerShell ConvertTo-Json (corrupts rows). (C) MCP assignments: pass assignments=<chunk of row objects>; append with upload_handle. (D) HTTP POST /assignments/upload with assignments, csv_text, or csv_file. Legacy columns (Postal Code, Territory, Region, Division) auto-map to part_id + territory_path. Then: direct_build(assignments_handle=<handle>, part_layer=..., tal_label=..., map_session_id=...). Handle is single-use and expires (default 1h).","detail":"Description of `request_assignment_upload` changed (24% word delta).","severity":"safe","descriptionDelta":0.24137931034482762},{"kind":"description_changed","tool":"seed_build","after":"[Tier 1 — Seed Build] When: grow ONE territory outward from a seed location until it holds a target number of locations or a target metric sum — 'a franchise territory around this address with 40 stores', 'grow from this point until it reaches $2M revenue', 'the ZIPs around our new branch that cover 300 stores'. The result is an ordinary part-based territory (ZIPs, counties) appended as a new leaf on an existing layer (tal_id) or as a new layer when tal_id is omitted. It grows one territory from one seed. It does not partition, balance, or route. Prerequisites: an ingested point layer (ingest_accounts) — its locations are the values counted or summed; a part_layer (ezt://part-layers); and the seed as {longitude, latitude}. Resolve an address or POI to coordinates first with the address geocoding tool; a map click already gives coordinates. TARGET: target={type: 'location_count', value: N} or {type: 'metric_sum', field: <column declared in metric_fields at ingest>, value: X}. An undeclared field returns UNDECLARED_FIELD — re-ingest with metric_fields. Fit is CLOSEST: growth adds the nearest adjacent part that still fits, then takes the smallest remaining neighbour only when overshooting lands nearer the target than stopping short. target_status reports reached | closest_under | closest_over | frontier_exhausted | max_parts_reached; a closest fit that misses the target also warns SEED_TARGET_UNDERSHOT / SEED_TARGET_OVERSHOT with the signed difference — tell the user, parts are indivisible. NO OVERLAP, EVER: with tal_id, parts already in any territory of that layer are never taken — the new territory drifts away from them instead (blocked_part_count, seed_offset_km). A seed inside an existing territory fails SEED_PART_ASSIGNED; pick another seed or reassign parts with realign. There is no allow_overlap flag. SCOPE: growth reads only the seed's neighbourhood — the point layer is indexed once and parts are materialised ring by ring outward from the seed part; it never joins every location to every part, so a national point layer costs the same as a local one. part_filter / part_ids are an ALLOWLIST (parts outside it do not exist for the walk), not a performance prerequisite: pass part_scope='explicit' + part_filter={state_abbr: 'TX'} only when the user wants the territory confined to that state. bbox_intersect / point_matched are accepted and ignored here; the result reports the materialised part count and ring depth the walk touched. ONE SEED PER CALL: for several franchisees call seed_build once per seed with the same tal_id; earlier territories become blocked for later seeds. Async: returns task_id — follow _meta.next_action (sleep_and_poll → consume_result) and sleep the authoritative sleep_ms. The result is handle-only (ts_handle, no inline ts) with created_territory, the grown part ids, target.{requested, achieved, delta}, and do_this_next. Then: analyze(tal_ids=[<tal_id>], map_session_id, analysis_panel='single') so the map dock shows the territory; without dwell Analyze omits workload and returns workload_omitted.ask_user to relay after the stats. Pass map_session_id here to paint the territory on the open map. Full atom: ezt://guidance/workflows/seed-grown-territory. Scenarios: SB-001..SB-004.","before":"[Tier 1 — Seed Build] When: grow ONE territory outward from a seed location until it holds a target number of locations or a target metric sum — 'a franchise territory around this address with 40 stores', 'grow from this point until it reaches $2M revenue', 'the ZIPs around our new branch that cover 300 stores'. The result is an ordinary part-based territory (ZIPs, counties) appended as a new leaf on an existing layer (tal_id) or as a new layer when tal_id is omitted. It grows one territory from one seed. It does not partition, balance, or route. Prerequisites: an ingested point layer (ingest_accounts) — its locations are the values counted or summed; a part_layer (ezt://part-layers); and the seed as {longitude, latitude}. Resolve an address or POI to coordinates first with the address geocoding tool; a map click already gives coordinates. TARGET: target={type: 'location_count', value: N} or {type: 'metric_sum', field: <column declared in metric_fields at ingest>, value: X}. An undeclared field returns UNDECLARED_FIELD — re-ingest with metric_fields. Fit is CLOSEST: growth adds the nearest adjacent part that still fits, then takes the smallest remaining neighbour only when overshooting lands nearer the target than stopping short. target_status reports reached | closest_under | closest_over | frontier_exhausted | max_parts_reached. NO OVERLAP, EVER: with tal_id, parts already in any territory of that layer are never taken — the new territory drifts away from them instead (blocked_part_count, seed_offset_km). A seed inside an existing territory fails SEED_PART_ASSIGNED; pick another seed or reassign parts with realign. There is no allow_overlap flag. SCOPE: growth reads only the seed's neighbourhood — the point layer is indexed once and parts are materialised ring by ring outward from the seed part; it never joins every location to every part, so a national point layer costs the same as a local one. part_filter / part_ids are an ALLOWLIST (parts outside it do not exist for the walk), not a performance prerequisite: pass part_scope='explicit' + part_filter={state_abbr: 'TX'} only when the user wants the territory confined to that state. bbox_intersect / point_matched are accepted and ignored here; the result reports the materialised part count and ring depth the walk touched. ONE SEED PER CALL: for several franchisees call seed_build once per seed with the same tal_id; earlier territories become blocked for later seeds. Async: returns task_id — follow _meta.next_action (sleep_and_poll → consume_result) and sleep the authoritative sleep_ms. The result is handle-only (ts_handle, no inline ts) with created_territory, the grown part ids, target.{requested, achieved, delta}, and do_this_next. Then: analyze(tal_ids=[<tal_id>], map_session_id, analysis_panel='single') so the map dock shows the territory; without dwell Analyze omits workload and returns workload_omitted.ask_user to relay after the stats. Pass map_session_id here to paint the territory on the open map. Full atom: ezt://guidance/workflows/seed-grown-territory. Scenarios: SB-001..SB-004.","detail":"Description of `seed_build` changed (3% word delta).","severity":"safe","descriptionDelta":0.03409090909090906},{"kind":"description_changed","tool":"set_map_state","after":"[Tier 2 — Low-Level MC State] When: switch MC mode, active TAL, or pending job ref, or jump the open MC camera with center ([longitude, latitude]) and/or zoom (0-24). Camera here is session-only and does not write the TS — use configure_map to persist a default view. Prerequisites: map_session_id. Prefer configure_map for durable TS map_config. Prefer request_part_selection for selection workflows. The result's render_ack is read the instant the change is published, so state=sent_unconfirmed with applied_render_version one behind is normal; to confirm the paint, read ezt://map-sessions/{map_session_id}/state a second or two later. Only a sent_unconfirmed that persists with a render_ack failure detail means the viewer could not apply it. Once you set center/zoom, the open MC keeps that view (no automatic refit). Scenarios: residual backlog (no dedicated scenario by design).","before":"[Tier 2 — Low-Level MC State] When: switch MC mode, active TAL, or pending job ref, or jump the open MC camera with center ([longitude, latitude]) and/or zoom (0-24). Camera here is session-only and does not write the TS — use configure_map to persist a default view. Prerequisites: map_session_id. Prefer configure_map for durable TS map_config. Prefer request_part_selection for selection workflows. Scenarios: residual backlog (no dedicated scenario by design).","detail":"Description of `set_map_state` changed (38% word delta).","severity":"risky","descriptionDelta":0.3789473684210526},{"kind":"description_changed","tool":"show_map_overlay","after":"[Tier 1 — Map Overlay] When: customer wants something on the map — US ZIP codes, counties, accounts/points, or a territory alignment — in any phrasing ('add US zip codes to the map', 'show zip codes', 'add zips'). Prerequisites: ts_handle (or inline ts) from get_map_visualization; viewer connected for live MC refresh. Pass user_request alone (e.g. 'add US zip codes to the map') or overlay_kind (part_layer | point_layer | tal | route) with optional overlay_id. Resolves the four MC overlay families and calls the correct underlying step (configure_map for part layers and active TAL; point layers must already be in the TS from ingest_accounts). Source the TS via ts_handle, inline ts, OR map_session_id (preferred for points: reads the LIVE session TS so points pushed by ingest_accounts(map_session_id=...) are found without threading a new ts_handle). Returns overlay_kind, overlay_id, viewer_hint, and for part layers a visibility block (state, min_zoom, camera_action). An open map_session_id zooms the MC to min_zoom (camera_action=fit_to_visible). When the user already named a center and zoom, do_this_next.tool is configure_map: call it once with that center and zoom before ingest or a point classification, so the fit does not replace it. Skip that call when no center was named, and do not invent one. Do not call configure_map again unless a later result also reports camera_action=fit_to_visible. Do not use ezt_test/focusAt. POINT LAYERS with map_session_id: status=already_on_map is verified against the live session render payload; when the layer is in the TS but missing from the session, the tool pushes a refresh and returns status=refreshed with a map_refresh block — check its render_ack before claiming points are visible. PART LAYERS are never loaded by a build; they show only when asked. 'Hide/remove the zips' returns status=hidden and takes that layer off the map (territories unchanged). ROUTE overlay (overlay_kind=route) needs map_session_id and only re-shows or hides routes calculate_route already drew — 'hide/remove/clear the route' clears them; with no route yet it returns blocked_by=needs_route pointing at calculate_route. Pass route_id to target ONE of several routes; omit it to act on all of them. To delete a route for good use delete_route; to change a route's stops re-run calculate_route with the same route_id. calculate_route already draws its own result, so this is recovery, not the normal path. Prefer this over raw configure_map for show/add-on-map asks. Part-layer overlays are written onto the live session even when map_push_status=viewer_not_connected (session durability); SSE paint still needs a connected viewer. A later linked ingest_accounts keeps those overlays (non-destructive compose with points). Dual surface: the overlay lands on the in-chat map on Apps-capable hosts and in the map_url tab everywhere else — same map_session_id, no extra call. Scenarios: MC-010, ChatGPT/external MCP.","before":"[Tier 1 — Map Overlay] When: customer wants something on the map — US ZIP codes, counties, accounts/points, or a territory alignment — in any phrasing ('add US zip codes to the map', 'show zip codes', 'add zips'). Prerequisites: ts_handle (or inline ts) from get_map_visualization; viewer connected for live MC refresh. Pass user_request alone (e.g. 'add US zip codes to the map') or overlay_kind (part_layer | point_layer | tal | route) with optional overlay_id. Resolves the four MC overlay families and calls the correct underlying step (configure_map for part layers and active TAL; point layers must already be in the TS from ingest_accounts). Source the TS via ts_handle, inline ts, OR map_session_id (preferred for points: reads the LIVE session TS so points pushed by ingest_accounts(map_session_id=...) are found without threading a new ts_handle). Returns overlay_kind, overlay_id, viewer_hint, and for part layers a visibility block (state, min_zoom, camera_action). An open map_session_id zooms the MC to min_zoom (camera_action=fit_to_visible). When the user already named a center and zoom, do_this_next.tool is configure_map: call it once with that center and zoom before ingest or a point classification, so the fit does not replace it. Skip that call when no center was named, and do not invent one. Do not call configure_map again unless a later result also reports camera_action=fit_to_visible. Do not use ezt_test/focusAt. POINT LAYERS with map_session_id: status=already_on_map is verified against the live session render payload; when the layer is in the TS but missing from the session, the tool pushes a refresh and returns status=refreshed with a map_refresh block — check its render_ack before claiming points are visible. ROUTE overlay (overlay_kind=route) needs map_session_id and only re-shows or hides routes calculate_route already drew — 'hide/remove/clear the route' clears them; with no route yet it returns blocked_by=needs_route pointing at calculate_route. Pass route_id to target ONE of several routes; omit it to act on all of them. To delete a route for good use delete_route; to change a route's stops re-run calculate_route with the same route_id. calculate_route already draws its own result, so this is recovery, not the normal path. Prefer this over raw configure_map for show/add-on-map asks. Part-layer overlays are written onto the live session even when map_push_status=viewer_not_connected (session durability); SSE paint still needs a connected viewer. A later linked ingest_accounts keeps those overlays (non-destructive compose with points). Dual surface: the overlay lands on the in-chat map on Apps-capable hosts and in the map_url tab everywhere else — same map_session_id, no extra call. Scenarios: MC-010, ChatGPT/external MCP.","detail":"Description of `show_map_overlay` changed (4% word delta).","severity":"safe","descriptionDelta":0.04347826086956519},{"kind":"enum_narrowed","path":"inputSchema.properties.repair_policy","tool":"territory_merge","after":"enum[default,strict,report_only]","before":"open","detail":"`repair_policy` on `territory_merge` narrowed to a closed enum (default, strict, report_only); previously valid values may now be rejected.","severity":"breaking"},{"kind":"enum_narrowed","path":"inputSchema.properties.repair_policy","tool":"territory_rebalance","after":"enum[default,strict,report_only]","before":"open","detail":"`repair_policy` on `territory_rebalance` narrowed to a closed enum (default, strict, report_only); previously valid values may now be rejected.","severity":"breaking"},{"kind":"enum_narrowed","path":"inputSchema.properties.repair_policy","tool":"territory_split","after":"enum[default,strict,report_only]","before":"open","detail":"`repair_policy` on `territory_split` narrowed to a closed enum (default, strict, report_only); previously valid values may now be rejected.","severity":"breaking"}],"published_at":"2026-10-05T19:49:11.896Z"}]