Hook admission, submit, gates, and settlement read the stored worktree root #1436

Closed
opened 2026-10-27 18:06:29 +00:00 by jared · 2 comments
Owner

Problem

Two paths still take the project root from the caller's cwd after #1164. When the orchestrator runs in the main checkout M and the implementation bound worktree W, they act on M:

  • Gate runs. os-sdlc-gate binds io.cwd (boot/inputs.rb:17) and Gate::Run uses it for its project (gate/run.rb:38) and as the command directory (gate/run.rb:46 Gate.new(root: project_root)). The gate commands run in M.
  • Submit. os-sdlc-subagent-stop with a dispatch_id binds the cwd root (boot/inputs.rb:38, then Boot#third_argument). CompletionIntake passes it to CompletionPayload.for(..., root:) (completion_intake.rb:125), then NextStep (next_step.rb:15), then Transition and Landing, then Commit.new(root:) (landing.rb:33). The landing commit runs git in M, so the change in W is not committed. CompletionIntake#project (completion_intake.rb:82) also gives ObjectionIntake the M root for its goalpost check.

Spike evidence (2026-10-27, scratch worktree at 0faed2ff): the two tests in the Owner sketch fail at HEAD. A gate run from M records fail for a test command that passes only in W. A submit from M leaves W's HEAD at the seed commit.

Items from the first draft that are dropped, with evidence:

  • Hook admission (admission.rb:7). SubagentStart uses @root for one purpose only, Brief.for(..., root: @root) (subagent_start.rb:30). Since #1164, Brief.for resolves work_root(root). BriefPayload takes no root. Nothing is left to fix.
  • boot.rb:57 pipeline_db?. It checks only that .sdlc/pipeline.db exists at the cwd root (artifact_dir.rb:21). M holds the real file and W symlinks to it (ADR-0253), so the answer is the same from both. The bind after it opens the same shared database.
  • Settlement.build_repo (settlement.rb:22). It fills CompletionIntake::Request#repo, and no code reads that field (grep for repo in implementation/ and dispatch/ finds only the Struct member and the writer). The wrong root has no effect.

Reproduce

Run the two tests in the Owner sketch at HEAD. Both fail as stated there.

Approach

Resolve the stored root at the two places where the cwd root enters the per-implementation flow. Do not add a second resolver; use Implementation::Targeting#work_root from #1164.

  1. Fold the stored root into Targeting#project(root): snapshot.project(work_root(root)) (slice 1 audit note a). Every implementation.project(root) caller then gets W: Brief.for, Gate::Run#project, CompletionIntake#project, and Acceptance#mutation_check. Brief.for goes back to implementation.project(root), so it sends one message, not two.
  2. Gate::Run uses implementation.work_root(@project_root) as its root, so Gate.new(root:) runs the commands in W.
  3. NextStep#initialize stores implementation.work_root(root). NextStep is the one point that both the submit path (CompletionPayload) and the next path (NextInstruction) reach before Transition, Landing, Commit, and EscalationPayload use the root. So the landing commit runs in W from either path.

When worktree_root is null, work_root returns the cwd root, as today.

Required work:

  1. Implementation::Targeting#project(root) builds the project from work_root(root) (proven by AC 1 and AC 2).
  2. Dispatch::Brief.for calls implementation.project(root) without work_root — check: absent work_root in plugins/os-sdlc/lib/os_sdlc/dispatch/brief.rb
  3. Gate::Run runs its gate with the root from implementation.work_root (proven by AC 1).
  4. Implementation::NextStep resolves its root with implementation.work_root(root) (proven by AC 2).
  5. Remove work_root from the Bound stand-in in brief_map_name_test.rb, because Brief.for no longer sends it — check: absent def work_root in plugins/os-sdlc/tests/dispatch/brief_map_name_test.rb
  6. Remove work_root from the Bound stand-in in brief_drafted_map_test.rb — check: absent def work_root in plugins/os-sdlc/tests/dispatch/brief_drafted_map_test.rb
  7. Add the two test files in the Owner sketch.

Acceptance criteria

  1. Given an implementation opened in worktree W whose gate test command passes only in W, when os-sdlc-gate <dispatch_id> runs with cwd set to a different checkout M, then the gate reply's verdict is pass.
  2. Given an implementation opened in worktree W, an uncommitted file lib/widget.rb in W, and a pending dispatch on an agent-verdict node whose pass edge completes the implementation, when os-sdlc-subagent-stop receives that dispatch_id with a pass handoff that lists lib/widget.rb, with cwd set to M, then W's HEAD commit contains exactly lib/widget.rb.

Owner sketch

Object The one job it owns in this slice
Implementation::Targeting Builds the implementation's project from its stored worktree root, or from the given root when none is stored.
Gate::Run Runs the dispatch's gate commands in the implementation's stored worktree root.
Implementation::NextStep Gives Transition, Landing, Commit, and EscalationPayload the stored worktree root.
Dispatch::Brief Builds the brief's project with one message, implementation.project(root).

Test stand-ins for objects whose interface changes (slice 1 audit notes b and c):

  • Bound in plugins/os-sdlc/tests/dispatch/brief_map_name_test.rb and Bound in plugins/os-sdlc/tests/dispatch/brief_drafted_map_test.rb stand in for Implementation as Brief.for's collaborator. They define project(_root) and work_root(root). After the change Brief.for sends only project, so delete work_root from both.
  • No other test double stands in for the Implementation that Gate::Run or NextStep receives. tests/gate/run_test.rb, tests/gate/run_required_work_test.rb, tests/implementation/next_step_*_test.rb, commit_test.rb, and completion_payload_test.rb use real Implementation rows.

Spike check (not committed): with the change above, both tests pass, and the full suite passes except SystemModelCoverageTest, which fails only because the scratch worktree path has no plugins/os sibling.

Test 1: plugins/os-sdlc/tests/pipeline/gate_cli_worktree_root_test.rb

Red at HEAD: Expected: "pass" Actual: "fail", because the test command runs in M, where worktree-only is absent.

# frozen_string_literal: true

require_relative "../test_helper"

module OsSdlc
  class GateCliWorktreeRootTest < TmpdirTestCase
    MAP = <<~YAML
      entry: verify
      nodes:
        verify:
          agent: programmer
          instructions: check the change
          enrich: []
          verdict:
            source: gate
            gates: [test]
          edges:
            pass: done
            fail: { to: error }
            error: error
            escalate: escalate
        error:
          agent: null
          edges:
            default: failed
        escalate:
          agent: null
          edges:
            default: needs-orchestrator
        needs-orchestrator:
          agent: null
          waiting: true
          edges: {}
      terminals:
        done: implementation_complete
        failed: implementation_failed
    YAML

    def setup
      super
      @main = File.join(@dir, "main")
      @worktree = File.join(@dir, "worktree")
      [@main, @worktree].each { |root| seed_checkout(root) }
      TicketFixture.seed(@worktree, tickets: ["442"])
      install_map
    end

    def test_gate_run_from_the_main_checkout_runs_the_gate_commands_in_the_stored_worktree
      dispatch_id = open_and_dispatch_in(@worktree)
      File.write(File.join(@worktree, "worktree-only"), "")

      out, err, = InProcessDispatchRunner.call(@main, "gate", dispatch_id.to_s)

      assert_equal "pass", JSON.parse(out)["verdict"], "stderr: #{err}"
    end

    private

    def seed_checkout(root)
      ProjectScenario.new(root, name: "cc-os").tap(&:git_init)
                     .write_project_yaml_with(path: "lib", test_path: "test", test_command: "test -f worktree-only")
      %w[lib test].each { |sub| FileUtils.mkdir_p(File.join(root, sub)) }
    end

    def install_map
      maps_dir = File.join(@dir, "maps")
      FileUtils.mkdir_p(maps_dir)
      File.write(File.join(maps_dir, "verify.yaml"), MAP)
      MapCatalog.default = MapCatalog.new(maps_dir, drafts_dir: tmp_drafts_dir)
    end

    def open_and_dispatch_in(root)
      InProcessDispatchRunner.call(root, "implementation-open", "session-1", "442", "verify")
      out, = InProcessDispatchRunner.call(root, "subagent-start", "session-1", "programmer")
      JSON.parse(out).fetch("dispatch_id")
    end
  end
end

Test 2: plugins/os-sdlc/tests/pipeline/cli_subagent_stop_worktree_root_test.rb

Red at HEAD: Expected: ["lib/widget.rb"] Actual: ["README.md"], because the landing commit runs git in M, so W's HEAD stays at the seed commit.

# frozen_string_literal: true

require_relative "../test_helper"

module OsSdlc
  class CliSubagentStopWorktreeRootTest < TmpdirTestCase
    def setup
      super
      @main = File.join(@dir, "main")
      @worktree = File.join(@dir, "worktree")
      [@main, @worktree].each { |root| seed_checkout(root) }
      TicketFixture.seed(@worktree, tickets: ["442"])
    end

    def test_a_passing_submit_from_the_main_checkout_commits_the_change_in_the_stored_worktree
      dispatch_id = open_and_dispatch_in_worktree
      File.write(File.join(@worktree, "lib", "widget.rb"), "# frozen_string_literal: true\n")

      _out, err, status = submit_from_main(dispatch_id, items: ["lib/widget.rb"])

      assert_predicate status, :success?, "expected success, stderr: #{err}"
      assert_equal ["lib/widget.rb"], worktree_head_paths
    end

    private

    def worktree_head_paths
      GitRepoFixture.new(@worktree).run_git!("show", "--name-only", "--format=", "HEAD").split("\n")
    end

    def seed_checkout(root)
      %w[lib test].each { |sub| FileUtils.mkdir_p(File.join(root, sub)) }
      GitRepoFixture.new(root).init_seeded_repo
      ProjectScenario.new(root, name: "cc-os").write_project_yaml_with(path: "lib", test_path: "test",
                                                                       test_command: "true")
    end

    def open_and_dispatch_in_worktree
      InProcessDispatchRunner.call(@worktree, "implementation-open", "session-1", "442", fixture_map("agent_verdict"))
      out, = InProcessDispatchRunner.call(@worktree, "subagent-start", "session-1", "reviewer")
      JSON.parse(out).fetch("dispatch_id")
    end

    def submit_from_main(dispatch_id, items:)
      content = YAML.dump("summary" => "adds the widget", "verdict" => "pass", "items" => items)
      payload = { dispatch_id: dispatch_id, content: content }
      InProcessDispatchRunner.call(@main, "subagent-stop", stdin: StringIO.new(JSON.generate(payload)))
    end
  end
end

Out of scope

  • The worktree_root column, its migration, implementation-open storing it, and brief resolution: #1164.
  • Backfilling worktree_root for closed implementations.
  • Removing the unread CompletionIntake::Request#repo field and Settlement.build_repo. This is dead-code cleanup with no behavior change.
  • The acceptance checks (required-work-check, retired-tests-check, unchanged-check). They run as gate commands, so they run in W after required work item 3.

Depends on #1164 (merged as d2f8a213).

Origin

  • Trigger: decomposition of #1164 into a thin first slice on 2026-10-27; re-scoped from code on 2026-10-27 after #1164 merged
  • Improvised this session: none
  • Chain: test-reviewer got MISSING: test_changes ← brief read the main checkout ← root taken from caller cwd (boot/inputs.rb:10) ← DESIGN: no stored worktree root (#1164 Ruling)
  • Root candidate: #1164 (parent; slice 1 supplies the stored worktree_root column this ticket reads). Searched worktree_root, stored root, admission, Settlement.build_repo, submit, gate runs.
  • Where: plugins/os-sdlc/lib/os_sdlc/implementation/targeting.rb, gate/run.rb, implementation/next_step.rb, dispatch/brief.rb
  • Session: 82e65b81-56fa-4c14-acd0-f225c6f57314
  • Transcript: /home/jared/.claude/projects/-home-jared-dev-cc-os/82e65b81-56fa-4c14-acd0-f225c6f57314.jsonl
## Problem Two paths still take the project root from the caller's cwd after #1164. When the orchestrator runs in the main checkout M and the implementation bound worktree W, they act on M: - **Gate runs.** `os-sdlc-gate` binds `io.cwd` (`boot/inputs.rb:17`) and `Gate::Run` uses it for its project (`gate/run.rb:38`) and as the command directory (`gate/run.rb:46` `Gate.new(root: project_root)`). The gate commands run in M. - **Submit.** `os-sdlc-subagent-stop` with a `dispatch_id` binds the cwd root (`boot/inputs.rb:38`, then `Boot#third_argument`). `CompletionIntake` passes it to `CompletionPayload.for(..., root:)` (`completion_intake.rb:125`), then `NextStep` (`next_step.rb:15`), then `Transition` and `Landing`, then `Commit.new(root:)` (`landing.rb:33`). The landing commit runs `git` in M, so the change in W is not committed. `CompletionIntake#project` (`completion_intake.rb:82`) also gives `ObjectionIntake` the M root for its goalpost check. Spike evidence (2026-10-27, scratch worktree at `0faed2ff`): the two tests in the Owner sketch fail at HEAD. A gate run from M records `fail` for a test command that passes only in W. A submit from M leaves W's HEAD at the seed commit. Items from the first draft that are dropped, with evidence: - **Hook admission** (`admission.rb:7`). `SubagentStart` uses `@root` for one purpose only, `Brief.for(..., root: @root)` (`subagent_start.rb:30`). Since #1164, `Brief.for` resolves `work_root(root)`. `BriefPayload` takes no root. Nothing is left to fix. - **`boot.rb:57` `pipeline_db?`.** It checks only that `.sdlc/pipeline.db` exists at the cwd root (`artifact_dir.rb:21`). M holds the real file and W symlinks to it (ADR-0253), so the answer is the same from both. The bind after it opens the same shared database. - **`Settlement.build_repo`** (`settlement.rb:22`). It fills `CompletionIntake::Request#repo`, and no code reads that field (grep for `repo` in `implementation/` and `dispatch/` finds only the Struct member and the writer). The wrong root has no effect. ## Reproduce Run the two tests in the Owner sketch at HEAD. Both fail as stated there. ## Approach Resolve the stored root at the two places where the cwd root enters the per-implementation flow. Do not add a second resolver; use `Implementation::Targeting#work_root` from #1164. 1. Fold the stored root into `Targeting#project(root)`: `snapshot.project(work_root(root))` (slice 1 audit note a). Every `implementation.project(root)` caller then gets W: `Brief.for`, `Gate::Run#project`, `CompletionIntake#project`, and `Acceptance#mutation_check`. `Brief.for` goes back to `implementation.project(root)`, so it sends one message, not two. 2. `Gate::Run` uses `implementation.work_root(@project_root)` as its root, so `Gate.new(root:)` runs the commands in W. 3. `NextStep#initialize` stores `implementation.work_root(root)`. `NextStep` is the one point that both the submit path (`CompletionPayload`) and the `next` path (`NextInstruction`) reach before `Transition`, `Landing`, `Commit`, and `EscalationPayload` use the root. So the landing commit runs in W from either path. When `worktree_root` is null, `work_root` returns the cwd root, as today. Required work: 1. `Implementation::Targeting#project(root)` builds the project from `work_root(root)` (proven by AC 1 and AC 2). 2. `Dispatch::Brief.for` calls `implementation.project(root)` without `work_root` — check: absent `work_root` in `plugins/os-sdlc/lib/os_sdlc/dispatch/brief.rb` 3. `Gate::Run` runs its gate with the root from `implementation.work_root` (proven by AC 1). 4. `Implementation::NextStep` resolves its root with `implementation.work_root(root)` (proven by AC 2). 5. Remove `work_root` from the `Bound` stand-in in `brief_map_name_test.rb`, because `Brief.for` no longer sends it — check: absent `def work_root` in `plugins/os-sdlc/tests/dispatch/brief_map_name_test.rb` 6. Remove `work_root` from the `Bound` stand-in in `brief_drafted_map_test.rb` — check: absent `def work_root` in `plugins/os-sdlc/tests/dispatch/brief_drafted_map_test.rb` 7. Add the two test files in the Owner sketch. ## Acceptance criteria 1. Given an implementation opened in worktree W whose gate test command passes only in W, when `os-sdlc-gate <dispatch_id>` runs with cwd set to a different checkout M, then the gate reply's verdict is `pass`. 2. Given an implementation opened in worktree W, an uncommitted file `lib/widget.rb` in W, and a pending dispatch on an agent-verdict node whose pass edge completes the implementation, when `os-sdlc-subagent-stop` receives that `dispatch_id` with a pass handoff that lists `lib/widget.rb`, with cwd set to M, then W's HEAD commit contains exactly `lib/widget.rb`. ## Owner sketch | Object | The one job it owns in this slice | |---|---| | `Implementation::Targeting` | Builds the implementation's project from its stored worktree root, or from the given root when none is stored. | | `Gate::Run` | Runs the dispatch's gate commands in the implementation's stored worktree root. | | `Implementation::NextStep` | Gives `Transition`, `Landing`, `Commit`, and `EscalationPayload` the stored worktree root. | | `Dispatch::Brief` | Builds the brief's project with one message, `implementation.project(root)`. | Test stand-ins for objects whose interface changes (slice 1 audit notes b and c): - `Bound` in `plugins/os-sdlc/tests/dispatch/brief_map_name_test.rb` and `Bound` in `plugins/os-sdlc/tests/dispatch/brief_drafted_map_test.rb` stand in for `Implementation` as `Brief.for`'s collaborator. They define `project(_root)` and `work_root(root)`. After the change `Brief.for` sends only `project`, so delete `work_root` from both. - No other test double stands in for the `Implementation` that `Gate::Run` or `NextStep` receives. `tests/gate/run_test.rb`, `tests/gate/run_required_work_test.rb`, `tests/implementation/next_step_*_test.rb`, `commit_test.rb`, and `completion_payload_test.rb` use real `Implementation` rows. Spike check (not committed): with the change above, both tests pass, and the full suite passes except `SystemModelCoverageTest`, which fails only because the scratch worktree path has no `plugins/os` sibling. ### Test 1: `plugins/os-sdlc/tests/pipeline/gate_cli_worktree_root_test.rb` Red at HEAD: `Expected: "pass" Actual: "fail"`, because the test command runs in M, where `worktree-only` is absent. ```ruby # frozen_string_literal: true require_relative "../test_helper" module OsSdlc class GateCliWorktreeRootTest < TmpdirTestCase MAP = <<~YAML entry: verify nodes: verify: agent: programmer instructions: check the change enrich: [] verdict: source: gate gates: [test] edges: pass: done fail: { to: error } error: error escalate: escalate error: agent: null edges: default: failed escalate: agent: null edges: default: needs-orchestrator needs-orchestrator: agent: null waiting: true edges: {} terminals: done: implementation_complete failed: implementation_failed YAML def setup super @main = File.join(@dir, "main") @worktree = File.join(@dir, "worktree") [@main, @worktree].each { |root| seed_checkout(root) } TicketFixture.seed(@worktree, tickets: ["442"]) install_map end def test_gate_run_from_the_main_checkout_runs_the_gate_commands_in_the_stored_worktree dispatch_id = open_and_dispatch_in(@worktree) File.write(File.join(@worktree, "worktree-only"), "") out, err, = InProcessDispatchRunner.call(@main, "gate", dispatch_id.to_s) assert_equal "pass", JSON.parse(out)["verdict"], "stderr: #{err}" end private def seed_checkout(root) ProjectScenario.new(root, name: "cc-os").tap(&:git_init) .write_project_yaml_with(path: "lib", test_path: "test", test_command: "test -f worktree-only") %w[lib test].each { |sub| FileUtils.mkdir_p(File.join(root, sub)) } end def install_map maps_dir = File.join(@dir, "maps") FileUtils.mkdir_p(maps_dir) File.write(File.join(maps_dir, "verify.yaml"), MAP) MapCatalog.default = MapCatalog.new(maps_dir, drafts_dir: tmp_drafts_dir) end def open_and_dispatch_in(root) InProcessDispatchRunner.call(root, "implementation-open", "session-1", "442", "verify") out, = InProcessDispatchRunner.call(root, "subagent-start", "session-1", "programmer") JSON.parse(out).fetch("dispatch_id") end end end ``` ### Test 2: `plugins/os-sdlc/tests/pipeline/cli_subagent_stop_worktree_root_test.rb` Red at HEAD: `Expected: ["lib/widget.rb"] Actual: ["README.md"]`, because the landing commit runs `git` in M, so W's HEAD stays at the seed commit. ```ruby # frozen_string_literal: true require_relative "../test_helper" module OsSdlc class CliSubagentStopWorktreeRootTest < TmpdirTestCase def setup super @main = File.join(@dir, "main") @worktree = File.join(@dir, "worktree") [@main, @worktree].each { |root| seed_checkout(root) } TicketFixture.seed(@worktree, tickets: ["442"]) end def test_a_passing_submit_from_the_main_checkout_commits_the_change_in_the_stored_worktree dispatch_id = open_and_dispatch_in_worktree File.write(File.join(@worktree, "lib", "widget.rb"), "# frozen_string_literal: true\n") _out, err, status = submit_from_main(dispatch_id, items: ["lib/widget.rb"]) assert_predicate status, :success?, "expected success, stderr: #{err}" assert_equal ["lib/widget.rb"], worktree_head_paths end private def worktree_head_paths GitRepoFixture.new(@worktree).run_git!("show", "--name-only", "--format=", "HEAD").split("\n") end def seed_checkout(root) %w[lib test].each { |sub| FileUtils.mkdir_p(File.join(root, sub)) } GitRepoFixture.new(root).init_seeded_repo ProjectScenario.new(root, name: "cc-os").write_project_yaml_with(path: "lib", test_path: "test", test_command: "true") end def open_and_dispatch_in_worktree InProcessDispatchRunner.call(@worktree, "implementation-open", "session-1", "442", fixture_map("agent_verdict")) out, = InProcessDispatchRunner.call(@worktree, "subagent-start", "session-1", "reviewer") JSON.parse(out).fetch("dispatch_id") end def submit_from_main(dispatch_id, items:) content = YAML.dump("summary" => "adds the widget", "verdict" => "pass", "items" => items) payload = { dispatch_id: dispatch_id, content: content } InProcessDispatchRunner.call(@main, "subagent-stop", stdin: StringIO.new(JSON.generate(payload))) end end end ``` ## Out of scope - The `worktree_root` column, its migration, `implementation-open` storing it, and brief resolution: #1164. - Backfilling `worktree_root` for closed implementations. - Removing the unread `CompletionIntake::Request#repo` field and `Settlement.build_repo`. This is dead-code cleanup with no behavior change. - The acceptance checks (`required-work-check`, `retired-tests-check`, `unchanged-check`). They run as gate commands, so they run in W after required work item 3. Depends on #1164 (merged as `d2f8a213`). ## Origin - Trigger: decomposition of #1164 into a thin first slice on 2026-10-27; re-scoped from code on 2026-10-27 after #1164 merged - Improvised this session: none - Chain: test-reviewer got `MISSING: test_changes` ← brief read the main checkout ← root taken from caller cwd (`boot/inputs.rb:10`) ← DESIGN: no stored worktree root (#1164 Ruling) - Root candidate: #1164 (parent; slice 1 supplies the stored worktree_root column this ticket reads). Searched worktree_root, stored root, admission, Settlement.build_repo, submit, gate runs. - Where: plugins/os-sdlc/lib/os_sdlc/implementation/targeting.rb, gate/run.rb, implementation/next_step.rb, dispatch/brief.rb - Session: 82e65b81-56fa-4c14-acd0-f225c6f57314 - Transcript: /home/jared/.claude/projects/-home-jared-dev-cc-os/82e65b81-56fa-4c14-acd0-f225c6f57314.jsonl
Author
Owner

Work started on branch ticket-1436 via /os-sdlc:implement (poodr-ticket-implementation).

Work started on branch ticket-1436 via /os-sdlc:implement (poodr-ticket-implementation).
Author
Owner

Resolution

Done: Targeting#project builds the project from the stored worktree root, so Brief.for, Gate::Run, CompletionIntake and Acceptance get the bound worktree. Gate commands run in the worktree. NextStep resolves the stored root once for Transition, Landing, Commit and EscalationPayload, so a submit from the main checkout commits the change in the worktree.

Evidence: main 4c7926be (merged as e652b689). Tests: gate_cli_worktree_root_test, cli_subagent_stop_worktree_root_test. os-sdlc suite 2792 runs, 0 failures. Rubocop clean. Implementation 195 escalated at required-work (target-relative check paths) and was hand-finished per the escalation procedure, with an opus design audit (no must-fix; the should-fix gate-root duplication is fixed).

Follow-ups: #1443 (required-work check paths, skeptic CREATE). RerouteIntake passes the cwd root to Transition: dropped, because a reroute does not reach Commit today; recorded in the commit message.

## Resolution **Done:** Targeting#project builds the project from the stored worktree root, so Brief.for, Gate::Run, CompletionIntake and Acceptance get the bound worktree. Gate commands run in the worktree. NextStep resolves the stored root once for Transition, Landing, Commit and EscalationPayload, so a submit from the main checkout commits the change in the worktree. **Evidence:** main 4c7926be (merged as e652b689). Tests: gate_cli_worktree_root_test, cli_subagent_stop_worktree_root_test. os-sdlc suite 2792 runs, 0 failures. Rubocop clean. Implementation 195 escalated at required-work (target-relative check paths) and was hand-finished per the escalation procedure, with an opus design audit (no must-fix; the should-fix gate-root duplication is fixed). **Follow-ups:** #1443 (required-work check paths, skeptic CREATE). RerouteIntake passes the cwd root to Transition: dropped, because a reroute does not reach Commit today; recorded in the commit message.
jared closed this issue 2026-10-27 18:56:53 +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.

Dependencies

No dependencies set

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