Record each escalation as an Escalation row #884

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

Problem

ADR-0259 decision 2 records each escalation as a row in a new escalations table with a Sequel model Escalation. Today Landing#escalate! (command/implementation/landing.rb:31) calls Implementation#escalate! (model/implementation/lifecycle.rb:34). That writes escalated_at and failed_reason on the implementation row. A second escalation would overwrite both columns, against ADR-0165 append-only. Also, when no gate escalates and no bound is spent (for example the change-auditor fail: escalate edge), EscalationReason returns nil and the escalation has no reason.

Reproduce

n/a: design change, nothing to reproduce.

Approach

Add the table in Db.create_ledger_tables (adapter/db.rb), modeled on objections, and bump SCHEMA_VERSION (ADR-0129/0155; do not bump while a run is live):

create_table_with_timestamps(db, :escalations) do |t|
  t.foreign_key :implementation_id, :implementations, null: false
  t.foreign_key :dispatch_id, :dispatches, null: true   # nil = orchestrator or runner
  t.String :reason, text: true, null: false
end

Add OsSdlc::Escalation < Sequel::Model (many_to_one :implementation, many_to_one :dispatch). Implementation one_to_many :escalations, order: [Sequel.desc(:created_at), Sequel.desc(:id)] (ADR-0259 decision 6; the id breaks same-second ties). Implementation#current_escalation returns escalations.first.

Landing#escalate! adds one Escalation row through add_escalation with dispatch_id = the landing dispatch and reason = escalate_reason. When escalate_reason is nil, the reason is verdict:<dispatch verdict>, the same form as fail_reason, because the column is not null. This ticket keeps the escalated_at stamp; #872 removes it and makes escalation a waiting position.

Required work:

  1. Create the escalations table — check: present :escalations in plugins/os-sdlc/lib/os_sdlc/adapter/db.rb
  2. Add the Escalation model — check: present class Escalation < Sequel::Model in plugins/os-sdlc/lib/os_sdlc/**/*.rb
  3. Add the newest-first accessor — check: present def current_escalation in plugins/os-sdlc/lib/os_sdlc/model/**/*.rb
  4. Write the row on escalation — check: present add_escalation in plugins/os-sdlc/lib/os_sdlc/command/implementation/landing.rb

Acceptance criteria

  1. Given an active run whose dispatch spends an edge bound, when the transition lands on the escalation, then one escalations row exists with that dispatch id and reason bound_exhausted:<node>/<verdict>.
  2. Given a change-auditor dispatch with verdict fail whose edge leads to escalate, when the transition lands, then the escalations row has reason verdict:fail.
  3. Given two escalations rows for one implementation with the same created_at, when current_escalation is read, then it returns the row with the higher id.

Owner sketch

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

class OsSdlc::Implementation
  one_to_many :escalations
  def current_escalation
end

Origin

  • Trigger: user ruling A1/A2 on the map reroute design, 2026-10-01 (ADR-0259 decisions 2, 6)
  • Improvised this session: none
  • Chain: a second escalation loses the first ← escalation is a stamp on the implementation row ← DESIGN (docs/adr/0165, docs/adr/0259)
  • Root candidate: #859
  • Where: plugins/os-sdlc/lib/os_sdlc/command/implementation/landing.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

## Problem ADR-0259 decision 2 records each escalation as a row in a new `escalations` table with a Sequel model `Escalation`. Today `Landing#escalate!` (command/implementation/landing.rb:31) calls `Implementation#escalate!` (model/implementation/lifecycle.rb:34). That writes `escalated_at` and `failed_reason` on the implementation row. A second escalation would overwrite both columns, against ADR-0165 append-only. Also, when no gate escalates and no bound is spent (for example the change-auditor `fail: escalate` edge), `EscalationReason` returns nil and the escalation has no reason. ## Reproduce n/a: design change, nothing to reproduce. ## Approach Add the table in `Db.create_ledger_tables` (adapter/db.rb), modeled on `objections`, and bump `SCHEMA_VERSION` (ADR-0129/0155; do not bump while a run is live): ```ruby create_table_with_timestamps(db, :escalations) do |t| t.foreign_key :implementation_id, :implementations, null: false t.foreign_key :dispatch_id, :dispatches, null: true # nil = orchestrator or runner t.String :reason, text: true, null: false end ``` Add `OsSdlc::Escalation < Sequel::Model` (`many_to_one :implementation`, `many_to_one :dispatch`). `Implementation one_to_many :escalations, order: [Sequel.desc(:created_at), Sequel.desc(:id)]` (ADR-0259 decision 6; the id breaks same-second ties). `Implementation#current_escalation` returns `escalations.first`. `Landing#escalate!` adds one Escalation row through `add_escalation` with `dispatch_id` = the landing dispatch and `reason` = `escalate_reason`. When `escalate_reason` is nil, the reason is `verdict:<dispatch verdict>`, the same form as `fail_reason`, because the column is not null. This ticket keeps the `escalated_at` stamp; #872 removes it and makes escalation a waiting position. Required work: 1. Create the escalations table — check: present `:escalations` in `plugins/os-sdlc/lib/os_sdlc/adapter/db.rb` 2. Add the Escalation model — check: present `class Escalation < Sequel::Model` in `plugins/os-sdlc/lib/os_sdlc/**/*.rb` 3. Add the newest-first accessor — check: present `def current_escalation` in `plugins/os-sdlc/lib/os_sdlc/model/**/*.rb` 4. Write the row on escalation — check: present `add_escalation` in `plugins/os-sdlc/lib/os_sdlc/command/implementation/landing.rb` ## Acceptance criteria 1. Given an active run whose dispatch spends an edge bound, when the transition lands on the escalation, then one escalations row exists with that dispatch id and reason `bound_exhausted:<node>/<verdict>`. 2. Given a change-auditor dispatch with verdict `fail` whose edge leads to `escalate`, when the transition lands, then the escalations row has reason `verdict:fail`. 3. Given two escalations rows for one implementation with the same `created_at`, when `current_escalation` is read, then it returns the row with the higher id. ## Owner sketch ```ruby class OsSdlc::Escalation < Sequel::Model many_to_one :implementation many_to_one :dispatch end class OsSdlc::Implementation one_to_many :escalations def current_escalation end ``` ## Origin - Trigger: user ruling A1/A2 on the map reroute design, 2026-10-01 (ADR-0259 decisions 2, 6) - Improvised this session: none - Chain: a second escalation loses the first ← escalation is a stamp on the implementation row ← DESIGN (docs/adr/0165, docs/adr/0259) - Root candidate: #859 - Where: plugins/os-sdlc/lib/os_sdlc/command/implementation/landing.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
Author
Owner

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

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

Resolution

Done: Escalation rows: escalations table + Escalation model (implementation, dispatch, reason); the Runner landing adds one row per escalation; Implementation#current_escalation returns the newest; SCHEMA_VERSION 27. Deviation: the Runner commit left plugins/os-sdlc/lib/os_sdlc/model/dispatch.rb (Dispatch has many escalations) in dirty_paths; committed by hand as 4028738c (the suite had run with it).

Evidence: main 643add26 (Runner commit, implementation 1 implementation_complete, 11 dispatches, contract-auditor pass on round 2), 4028738c, History row; bin/test on main: 9 suites passed.

Follow-ups: none

## Resolution **Done:** Escalation rows: escalations table + Escalation model (implementation, dispatch, reason); the Runner landing adds one row per escalation; Implementation#current_escalation returns the newest; SCHEMA_VERSION 27. Deviation: the Runner commit left plugins/os-sdlc/lib/os_sdlc/model/dispatch.rb (Dispatch has many escalations) in dirty_paths; committed by hand as 4028738c (the suite had run with it). **Evidence:** main 643add26 (Runner commit, implementation 1 implementation_complete, 11 dispatches, contract-auditor pass on round 2), 4028738c, History row; bin/test on main: 9 suites passed. **Follow-ups:** none
jared closed this issue 2026-10-01 16:18:01 +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#884
No description provided.