Add reroutes table and Implementation#current_map with report rows #871

Closed
opened 2026-10-01 14:26:16 +00:00 by jared · 2 comments
Owner

Problem

ADR-0258 decision 5 keeps the default map frozen and records each map change as a reroutes row. No reroutes table exists, and every consumer reads implementation.map (the default): Routing (command/implementation/routing.rb:10), TransitWalk (transit_walk.rb:7), Landing (landing.rb:49), NextStep#map (pipeline/next_step.rb:70), StartSubagent#map (command/start_subagent.rb:32), CompletionIntake#map (command/completion_intake.rb:64), ObjectionsDigest (query/brief/objections_digest.rb:29), and Implementation#current_node/#dispatch_cap (model/implementation.rb:64-65). The implementation report (os-sdlc-implementation-report) shows no map history.

Reproduce

n/a: design change, nothing to reproduce.

Approach

Add the reroutes table in Db.create_ledger_tables (adapter/db.rb), modeled on objections, and bump SCHEMA_VERSION (ADR-0129/0155):

create_table(:reroutes) do
  primary_key :id
  foreign_key :implementation_id, :implementations, null: false
  foreign_key :dispatch_id, :dispatches, null: true   # the requesting dispatch; nil = orchestrator
  String :map, null: false
  String :node, null: false
  String :situation, text: true, null: false
  String :misfit, text: true, null: false
  TrueClass :draft, null: false, default: false
  DateTime :created_at, null: false
end

Add the Sequel model OsSdlc::Reroute < Sequel::Model and Implementation one_to_many :reroutes, order: [Sequel.desc(:created_at), Sequel.desc(:id)] (ADR-0259 decision 6; the id breaks same-second ties). Implementation#current_map loads reroutes.first&.map through Map::Catalog.default when a row exists, else returns map. map stays the default and is never mutated (ADR-0169). Do not memoize current_map across a new row (the associations[:map] memo in targeting.rb:6 must not serve it). Switch every consumer listed in Problem to current_map. dispatch_cap reads current_map.dispatch_cap; DispatchBudget already counts all dispatches of the implementation (ADR-0152, decision 8).

Implementation::Summary#to_h adds reroutes:, oldest first, each { map:, node:, by:, situation:, misfit:, draft: } where by is "orchestrator" when dispatch_id is nil, else "<agent_type> (dispatch N)".

This ticket writes no reroute rows in production code; the reroute gate ticket does. The draft column exists now; slice 4 resolves drafts.

Required work:

  1. Create the reroutes table — check: present create_table_with_timestamps(db, :reroutes) in plugins/os-sdlc/lib/os_sdlc/adapter/db.rb
  2. Add the reroutes association and current_map — check: present def current_map in plugins/os-sdlc/lib/os_sdlc/model/**/*.rb
  3. Add the Reroute model — check: present class Reroute < Sequel::Model in plugins/os-sdlc/lib/os_sdlc/**/*.rb
  4. Route through the current map — check: present current_map in plugins/os-sdlc/lib/os_sdlc/command/implementation/routing.rb
  5. Report reroute rows — check: present reroutes: in plugins/os-sdlc/lib/os_sdlc/presenter/implementation/summary.rb

Acceptance criteria

  1. Given an implementation with no reroutes, when its current map is read, then it is the default map.
  2. Given an implementation with two reroutes rows, when its current map is read, then it is the map of the newest row, and the default map name in the row is unchanged.
  3. Given a rerouted implementation positioned at a node that exists only in the new map, when the next step is computed, then the dispatch payload names that node's agent from the new map.
  4. Given a rerouted implementation whose new map has a different dispatch_cap, when routing checks the budget, then it uses the new map's cap and counts dispatches made under both maps.
  5. Given two reroutes rows with the same created_at, when the current map is read, then it is the map of the row with the higher id.
  6. Given an implementation with one orchestrator reroute and one by dispatch 7 (change-auditor), when os-sdlc-implementation-report runs, then the output lists both rows oldest first with by: orchestrator and by: change-auditor (dispatch 7).

Owner sketch

class OsSdlc::Reroute < Sequel::Model
  many_to_one :implementation
  many_to_one :dispatch
end

class OsSdlc::Implementation
  one_to_many :reroutes
  def current_map
end

Non-goals

Edge-spend bounds stay keyed by (from_node, verdict) (model/edge_spend.rb). A new map with the same node names shares those counters; this ticket does not change that.

Origin

  • Trigger: user ruling on the map reroute design, 2026-10-01 (ADR-0258; A2 in ADR-0259)
  • Improvised this session: none
  • Chain: a run cannot change maps ← Implementation#map is the only map source ← DESIGN (docs/adr/0169, docs/adr/0258 decision 5)
  • Root candidate: #859
  • Where: plugins/os-sdlc/lib/os_sdlc/model/implementation.rb
  • Session: 55a56279-83ee-47d5-a21e-ee67f9e28abd
  • Transcript: /home/jared/.claude/projects/-home-jared-dev-cc-os/55a56279-83ee-47d5-a21e-ee67f9e28abd.jsonl

Parent: #859
Blocked by: #868

## Problem ADR-0258 decision 5 keeps the default map frozen and records each map change as a `reroutes` row. No `reroutes` table exists, and every consumer reads `implementation.map` (the default): `Routing` (command/implementation/routing.rb:10), `TransitWalk` (transit_walk.rb:7), `Landing` (landing.rb:49), `NextStep#map` (pipeline/next_step.rb:70), `StartSubagent#map` (command/start_subagent.rb:32), `CompletionIntake#map` (command/completion_intake.rb:64), `ObjectionsDigest` (query/brief/objections_digest.rb:29), and `Implementation#current_node`/`#dispatch_cap` (model/implementation.rb:64-65). The implementation report (`os-sdlc-implementation-report`) shows no map history. ## Reproduce n/a: design change, nothing to reproduce. ## Approach Add the `reroutes` table in `Db.create_ledger_tables` (adapter/db.rb), modeled on `objections`, and bump `SCHEMA_VERSION` (ADR-0129/0155): ```ruby create_table(:reroutes) do primary_key :id foreign_key :implementation_id, :implementations, null: false foreign_key :dispatch_id, :dispatches, null: true # the requesting dispatch; nil = orchestrator String :map, null: false String :node, null: false String :situation, text: true, null: false String :misfit, text: true, null: false TrueClass :draft, null: false, default: false DateTime :created_at, null: false end ``` Add the Sequel model `OsSdlc::Reroute < Sequel::Model` and `Implementation one_to_many :reroutes, order: [Sequel.desc(:created_at), Sequel.desc(:id)]` (ADR-0259 decision 6; the id breaks same-second ties). `Implementation#current_map` loads `reroutes.first&.map` through `Map::Catalog.default` when a row exists, else returns `map`. `map` stays the default and is never mutated (ADR-0169). Do not memoize `current_map` across a new row (the `associations[:map]` memo in targeting.rb:6 must not serve it). Switch every consumer listed in Problem to `current_map`. `dispatch_cap` reads `current_map.dispatch_cap`; `DispatchBudget` already counts all dispatches of the implementation (ADR-0152, decision 8). `Implementation::Summary#to_h` adds `reroutes:`, oldest first, each `{ map:, node:, by:, situation:, misfit:, draft: }` where `by` is `"orchestrator"` when `dispatch_id` is nil, else `"<agent_type> (dispatch N)"`. This ticket writes no reroute rows in production code; the reroute gate ticket does. The `draft` column exists now; slice 4 resolves drafts. Required work: 1. Create the reroutes table — check: present `create_table_with_timestamps(db, :reroutes)` in `plugins/os-sdlc/lib/os_sdlc/adapter/db.rb` 2. Add the reroutes association and `current_map` — check: present `def current_map` in `plugins/os-sdlc/lib/os_sdlc/model/**/*.rb` 3. Add the Reroute model — check: present `class Reroute < Sequel::Model` in `plugins/os-sdlc/lib/os_sdlc/**/*.rb` 4. Route through the current map — check: present `current_map` in `plugins/os-sdlc/lib/os_sdlc/command/implementation/routing.rb` 5. Report reroute rows — check: present `reroutes:` in `plugins/os-sdlc/lib/os_sdlc/presenter/implementation/summary.rb` ## Acceptance criteria 1. Given an implementation with no reroutes, when its current map is read, then it is the default map. 2. Given an implementation with two reroutes rows, when its current map is read, then it is the map of the newest row, and the default map name in the row is unchanged. 3. Given a rerouted implementation positioned at a node that exists only in the new map, when the next step is computed, then the dispatch payload names that node's agent from the new map. 4. Given a rerouted implementation whose new map has a different `dispatch_cap`, when routing checks the budget, then it uses the new map's cap and counts dispatches made under both maps. 5. Given two reroutes rows with the same `created_at`, when the current map is read, then it is the map of the row with the higher id. 6. Given an implementation with one orchestrator reroute and one by dispatch 7 (change-auditor), when `os-sdlc-implementation-report` runs, then the output lists both rows oldest first with `by: orchestrator` and `by: change-auditor (dispatch 7)`. ## Owner sketch ```ruby class OsSdlc::Reroute < Sequel::Model many_to_one :implementation many_to_one :dispatch end class OsSdlc::Implementation one_to_many :reroutes def current_map end ``` ## Non-goals Edge-spend bounds stay keyed by `(from_node, verdict)` (model/edge_spend.rb). A new map with the same node names shares those counters; this ticket does not change that. ## Origin - Trigger: user ruling on the map reroute design, 2026-10-01 (ADR-0258; A2 in ADR-0259) - Improvised this session: none - Chain: a run cannot change maps ← `Implementation#map` is the only map source ← DESIGN (docs/adr/0169, docs/adr/0258 decision 5) - Root candidate: #859 - Where: plugins/os-sdlc/lib/os_sdlc/model/implementation.rb - Session: 55a56279-83ee-47d5-a21e-ee67f9e28abd - Transcript: /home/jared/.claude/projects/-home-jared-dev-cc-os/55a56279-83ee-47d5-a21e-ee67f9e28abd.jsonl Parent: #859 Blocked by: #868
Author
Owner

Work started on branch ticket-871 (os-sdlc pipeline, map poodr-ticket-implementation).

Work started on branch ticket-871 (os-sdlc pipeline, map poodr-ticket-implementation).
Author
Owner

Resolution

Done: reroutes table + Reroute model; Implementation#current_map owns the map choice and every listed consumer reads it; the implementation report/summary shows reroute rows; SCHEMA_VERSION bumped. Deviation: the Runner commit left plugins/os-sdlc/lib/os_sdlc/model/dispatch.rb (Dispatch has many reroutes) in dirty_paths because no agent listed it; committed by hand as f28dff08 (the suite had run with it).

Evidence: main 91f8a7e6 (Runner commit, implementation 3 implementation_complete, 13 dispatches, 1 retry), f28dff08, History row; required-work gate passed in suite-check; bin/test on main: 9 suites passed.

Follow-ups: none

## Resolution **Done:** reroutes table + Reroute model; Implementation#current_map owns the map choice and every listed consumer reads it; the implementation report/summary shows reroute rows; SCHEMA_VERSION bumped. Deviation: the Runner commit left plugins/os-sdlc/lib/os_sdlc/model/dispatch.rb (Dispatch has many reroutes) in dirty_paths because no agent listed it; committed by hand as f28dff08 (the suite had run with it). **Evidence:** main 91f8a7e6 (Runner commit, implementation 3 implementation_complete, 13 dispatches, 1 retry), f28dff08, History row; required-work gate passed in suite-check; bin/test on main: 9 suites passed. **Follow-ups:** none
jared closed this issue 2026-10-01 16:51:50 +00:00
jared referenced this issue from a commit 2026-10-01 18:24:32 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Reference
jared/cc-os#871
No description provided.