system-model step 3: journey tracer with use_case-tagged Minitest and LikeC4 dynamic views #1327

Closed
opened 2026-10-25 17:36:26 +00:00 by jared · 5 comments
Owner

Problem

The system-model generator draws objects and collaborators but not journeys. The design (docs/system-design-format/README.md section 6, step 3) calls for traced journeys from tagged tests.

Reproduce

n/a: feature.

Approach

A TracePoint recorder and a use_case tag in Minitest run as a subprocess of bin/system-model. The generator never loads the project it measures. Each tagged test emits one Journey: a use-case name and the ordered messages between objects under the target. The Writer emits each Journey as a LikeC4 dynamic view. The os-me journey tests and the operations.md proof are #1392 (split 2026-10-26: one implementation run covers one target, see #1109).

Required work:

  1. Add SystemModel::Tracer in plugins/os-sdlc/lib/os_sdlc/system_model/tracer.rb. It takes a runner and runs the tests in a subprocess.
  2. Add the Journey Data class in plugins/os-sdlc/lib/os_sdlc/system_model/journey.rb.
  3. Add a Minitest plugin with the use_case tag and the TracePoint recorder under plugins/os-sdlc/lib/os_sdlc/system_model/recorder/.
  4. Make the Writer emit Journeys as LikeC4 dynamic views in plugins/os-sdlc/lib/os_sdlc/system_model/.

Settled by ADR-0300 and the 2026-10-26 rulings

  • A test names its journey with the block helper use_case("route a judgment row") { ... }. The recorder traces only inside the block. Each adopting project adds this shim to test_helper.rb; the recorder replaces it in the subprocess:
    module UseCase
      def use_case(_name) = yield
    end
    Minitest::Test.include(UseCase)
    
  • The view id is the use-case name, lower-cased, each run of non-word characters replaced by _. Two use cases with the same view id are an error.
  • The Writer writes each Journey to design/generated/journeys/<view_id>.c4 as a dynamic view. Writer#clear also deletes generated/journeys/*.c4.
  • Journey steps use the full <dir>.<id> element id, for example lib_os_me_cli.OsMe__CLI__RouteRequest -> lib_os_me.OsMe__RowLabels 'label'.
  • The step label is the method name only.
  • The Tracer runs only the test files that contain use_case. It injects the recorder with RUBYOPT=-r<recorder.rb>. The recorder writes one JSON line per Journey to a file named by an env var, not to stdout: {"use_case": "...", "test": "File#method", "messages": [{"from": "OsMe::X", "to": "OsMe::Y", "method": "call"}]}.
  • A message is a :call event where the receiver class and the caller self class are both under the target root and differ. Self-calls and calls into gems or stdlib are dropped. Consecutive repeats are kept.
  • Calls in child processes (Open3 to bin/) are out of scope for this ticket.

Which tests carry a use_case block

See #1392.

Acceptance criteria

  1. Given a test tagged use_case, when the Tracer runs it, then it returns one Journey with the use-case name and the ordered messages.
  2. Given an untagged test, when the Tracer runs it, then it returns no Journey.
  3. Given a Journey, when the Writer runs, then design/generated/journeys/<view_id>.c4 holds one LikeC4 dynamic view for it, and likec4 validate accepts the model.
  4. Given the generator process, when the Tracer runs, then the target project loads only in the subprocess.

Owner sketch

module SystemModel
  Journey = Data.define(:use_case, :messages)

  class Tracer
    def initialize(runner:)
      @runner = runner
    end

    def journeys(target) = @runner.call(target).map { |h| Journey.new(**h) }
  end
end

Origin

  • Trigger: the build of plugins/os-sdlc/bin/system-model on branch system-model-generator, 2026-10-25
  • Improvised this session: none
  • Chain: feature ← DESIGN docs/system-design-format/README.md section 6 step 3
  • Root candidate: none found; searched journey, use_case, dynamic view, system-model, system_model, tracepoint, likec4 across all open tickets
  • Where: plugins/os-sdlc/lib/os_sdlc/system_model/
  • Session: 2ab68513-6af4-4fac-bedf-dcc0983e7b88
  • Transcript: /home/jared/.claude/projects/-home-jared-dev-cc-os/2ab68513-6af4-4fac-bedf-dcc0983e7b88.jsonl

VERDICT: CREATE

## Problem The system-model generator draws objects and collaborators but not journeys. The design (docs/system-design-format/README.md section 6, step 3) calls for traced journeys from tagged tests. ## Reproduce n/a: feature. ## Approach A TracePoint recorder and a `use_case` tag in Minitest run as a subprocess of `bin/system-model`. The generator never loads the project it measures. Each tagged test emits one Journey: a use-case name and the ordered messages between objects under the target. The Writer emits each Journey as a LikeC4 dynamic view. The os-me journey tests and the operations.md proof are #1392 (split 2026-10-26: one implementation run covers one target, see #1109). Required work: 1. Add SystemModel::Tracer in plugins/os-sdlc/lib/os_sdlc/system_model/tracer.rb. It takes a runner and runs the tests in a subprocess. 2. Add the Journey Data class in plugins/os-sdlc/lib/os_sdlc/system_model/journey.rb. 3. Add a Minitest plugin with the `use_case` tag and the TracePoint recorder under plugins/os-sdlc/lib/os_sdlc/system_model/recorder/. 4. Make the Writer emit Journeys as LikeC4 dynamic views in plugins/os-sdlc/lib/os_sdlc/system_model/. ### Settled by ADR-0300 and the 2026-10-26 rulings - A test names its journey with the block helper `use_case("route a judgment row") { ... }`. The recorder traces only inside the block. Each adopting project adds this shim to `test_helper.rb`; the recorder replaces it in the subprocess: ```ruby module UseCase def use_case(_name) = yield end Minitest::Test.include(UseCase) ``` - The view id is the use-case name, lower-cased, each run of non-word characters replaced by `_`. Two use cases with the same view id are an error. - The Writer writes each Journey to `design/generated/journeys/<view_id>.c4` as a `dynamic view`. `Writer#clear` also deletes `generated/journeys/*.c4`. - Journey steps use the full `<dir>.<id>` element id, for example `lib_os_me_cli.OsMe__CLI__RouteRequest -> lib_os_me.OsMe__RowLabels 'label'`. - The step label is the method name only. - The Tracer runs only the test files that contain `use_case`. It injects the recorder with `RUBYOPT=-r<recorder.rb>`. The recorder writes one JSON line per Journey to a file named by an env var, not to stdout: `{"use_case": "...", "test": "File#method", "messages": [{"from": "OsMe::X", "to": "OsMe::Y", "method": "call"}]}`. - A message is a `:call` event where the receiver class and the caller `self` class are both under the target root and differ. Self-calls and calls into gems or stdlib are dropped. Consecutive repeats are kept. - Calls in child processes (`Open3` to `bin/`) are out of scope for this ticket. ### Which tests carry a `use_case` block See #1392. ## Acceptance criteria 1. Given a test tagged `use_case`, when the Tracer runs it, then it returns one Journey with the use-case name and the ordered messages. 2. Given an untagged test, when the Tracer runs it, then it returns no Journey. 3. Given a Journey, when the Writer runs, then `design/generated/journeys/<view_id>.c4` holds one LikeC4 dynamic view for it, and `likec4 validate` accepts the model. 4. Given the generator process, when the Tracer runs, then the target project loads only in the subprocess. ## Owner sketch ```ruby module SystemModel Journey = Data.define(:use_case, :messages) class Tracer def initialize(runner:) @runner = runner end def journeys(target) = @runner.call(target).map { |h| Journey.new(**h) } end end ``` ## Origin - Trigger: the build of `plugins/os-sdlc/bin/system-model` on branch system-model-generator, 2026-10-25 - Improvised this session: none - Chain: feature ← DESIGN docs/system-design-format/README.md section 6 step 3 - Root candidate: none found; searched journey, use_case, dynamic view, system-model, system_model, tracepoint, likec4 across all open tickets - Where: plugins/os-sdlc/lib/os_sdlc/system_model/ - Session: 2ab68513-6af4-4fac-bedf-dcc0983e7b88 - Transcript: /home/jared/.claude/projects/-home-jared-dev-cc-os/2ab68513-6af4-4fac-bedf-dcc0983e7b88.jsonl VERDICT: CREATE
Author
Owner

#1327 journey tracer: two decision memos

Written 2026-10-25. Read-only research. No ADR is created. Both items are one-way doors and go to Jared.

Facts that the memos use (checked 2026-10-25):

  • os-me tests are plain class XTest < Minitest::Test with def test_... methods (77 test files, 0 spec-style). test_helper.rb does require 'minitest/autorun'.
  • Minitest is 6.0.6. It has no tag feature. It has two native hooks that matter here:
    • Minitest::Runnable#metadata, a plain Hash that is copied to the Result that reporters get (minitest.rb lines 539-563, 683).
    • --include PATTERN / --exclude PATTERN filter on the method name.
  • Minitest 6 does not find plugins automatically from minitest/autorun; a plugin is loaded with Minitest.load :name, Minitest.register_plugin, or a plain require. So the Tracer can inject the recorder with RUBYOPT=-r<recorder.rb> in the subprocess. The project does not need to know the recorder file.
  • Many os-me tests exercise the code through Open3.capture3(RbConfig.ruby, BIN, ...) (for example route_cli_test.rb, row_labels_run_test.rb). TracePoint in the test process does not see those calls. RUBYOPT is inherited by the child, so a recorder can follow them only if it writes one file per process.
  • No os-me test calls RowRouter or CLI::RouteRequest in-process today.
  • Prior art (from memory, not verified against a primary source this session): RSpec metadata it "...", use_case: "x"; minitest-tagz tag :x before def test_; Cucumber @tag lines. None is a dependency here.
  • Hand-written design/views/lever_check.c4 already writes include lib_os_me.OsMe__RowRouter. So the <dir>. prefix is parsed by hand files today, but ADR-0299 does not record it.
  • OverlayCheck::GENERATED_ID matches only the bare OsMe__X part. A wrong prefix (lib_os_me_cli.OsMe__AnswerSource) passes OverlayCheck. likec4 validate rejects it: "Could not resolve reference to Referenceable named 'OsMe__AnswerSource'" (probe in scratchpad c4probe/, 2026-10-25).
  • likec4 validate reads .c4 files in subdirectories: a dynamic view in generated/journeys/route_a_judgment_row.c4 validated with the os-me model (18 files, valid).
  • Writer#clear deletes only generated/*.c4. It does not delete files in a subdirectory.
  • ElementId.directory maps / and - to _. lib/os_me and lib/os-me give the same id. Not seen in os-me today.

Decision 1: how a Minitest test names its use case

Headline: Pick the use_case tag shape that every adopting project writes in its tests and that the recorder reads.

Options

A. Block helper inside a test (the README section 2 form)

class RouteJourneyTest < Minitest::Test
  def test_a_judgment_row_routes_to_predict
    row = Fixtures.judgment_row
    decision = use_case("route a judgment row") do
      OsMe::CLI::RouteRequest.new(row, tier: :judgment).call
    end

    assert_equal :predict, decision.route
  end
end

Shim in the project's test_helper.rb (3 lines; the recorder prepends over it):

module UseCase
  def use_case(_name) = yield
end
Minitest::Test.include(UseCase)

Recorder reads: the name argument at run time; it traces only while the block runs.

B. Class macro before the method (minitest-tagz style)

class RouteJourneyTest < Minitest::Test
  use_case "route a judgment row"
  def test_a_judgment_row_routes_to_predict
    decision = OsMe::CLI::RouteRequest.new(Fixtures.judgment_row, tier: :judgment).call

    assert_equal :predict, decision.route
  end
end

Recorder reads: a class-level table filled by method_added; it traces the whole test, setup and assertions included.

C. Method-name prefix

def test_use_case__route_a_judgment_row
  OsMe::CLI::RouteRequest.new(Fixtures.judgment_row, tier: :judgment).call
end

Recorder reads: the method name, by regex; the name is lossy (no spaces, no capitals). Native filter: --include /test_use_case__/.

D. Native Minitest 6 metadata

def test_a_judgment_row_routes_to_predict
  metadata[:use_case] = "route a judgment row"
  decision = OsMe::CLI::RouteRequest.new(Fixtures.judgment_row, tier: :judgment).call

  assert_equal :predict, decision.route
end

Recorder reads: result.metadata[:use_case] in a reporter, after the test ran; it must trace every test and drop the untagged ones.

E. Separate manifest

# design/journeys.yml
route a judgment row: test/route_journey_test.rb#test_a_judgment_row_routes_to_predict

Recorder reads: the YAML, then runs each listed test with --include. A rename of a test breaks the link without a failure in the test suite.

Side by side

A block B macro C prefix D metadata E manifest
Trace window the block only whole test whole test whole test whole test
Name with spaces yes yes no yes yes
Shim in project 3 lines about 10 lines none none none
Untagged tests traced no no no yes (cost) no
Drift risk none low none none high

Recommendation

A, block helper. Confidence 75%.

Deciding reason: the block marks the name and the trace window in one place, so a Journey holds the entry message and what it causes, and not fixture setup or assertion calls. Options B to E need a second rule to cut setup out of the trace.

Second reason: it is the form already written in README section 2, so no design document changes.

What breaks if reversed later

Every tagged test in every adopting project, the shim in each test_helper.rb, and the recorder. The use-case name becomes the dynamic view id (see Decision 2), so a hand view that links to a journey also breaks.

Sub-point inside the same door

The view id is derived from the name: lower case, each run of non-word characters becomes _ ("route a judgment row" becomes route_a_judgment_row). Two tests with the same name are an error (the Tracer refuses), not a merge.

Ask

Rule A, B, C, D, or E. Default if no ruling: A, with the derived view id rule above.


Decision 2: where generated dynamic views live, and how ADR-0299 changes

Headline: ADR-0299 says the generator writes only design/generated/<dir>.c4. Journeys are not per directory. Pick their file layout, and record the <dir>. prefix that hand files already parse.

A generated journey view looks like this (validated with likec4 validate on the os-me model):

views {
  dynamic view route_a_judgment_row {
    title 'route a judgment row'
    lib_os_me_cli.OsMe__CLI__RouteRequest -> lib_os_me.OsMe__RowLabels 'label(row)'
    lib_os_me.OsMe__RowLabels -> lib_os_me.OsMe__AnswerSource 'classify(row)'
    lib_os_me_cli.OsMe__CLI__RouteRequest -> lib_os_me.OsMe__RowRouter 'route(labels, row_id:, tier:, flow:)'
  }
}

Each step must use the full <dir>.<id> form. The prefix is part of the contract.

Options

A. One file for all journeys

design/generated/
  lib.c4
  lib_os_me.c4
  lib_os_me_cli.c4
  journeys.c4          # every dynamic view

Risk: a directory named journeys/ in the target gives journeys.c4 too, and the two files collide.

B. One file per journey in a subdirectory

design/generated/
  lib.c4
  lib_os_me.c4
  lib_os_me_cli.c4
  journeys/
    route_a_judgment_row.c4
    offer_a_reversible_move.c4

No collision with directory files. A changed trace shows as a one-file diff. Writer#clear must also delete generated/journeys/*.c4.

C. Per-directory journey files, by the directory of the entry object

design/generated/
  lib_os_me_cli.c4
  lib_os_me_cli.journeys.c4

A journey crosses directories, so the owner is arbitrary.

Recommendation

B, design/generated/journeys/<view_id>.c4. Confidence 80%.

Deciding reason: it cannot collide with a <dir>.c4 name, and one journey per file makes "a changed trace is a review signal" (README section 2) a one-file diff.

ADR action: supersede ADR-0299, do not amend it. The project rule says the task is not complete until a superseding ADR exists when one-way-door content changes, and "the generator writes only design/generated/<dir>.c4" is that content. The new ADR carries all of ADR-0299 and adds three lines:

  1. The generator writes design/generated/<dir>.c4 and design/generated/journeys/<view_id>.c4, and rewrites both whole on every run.
  2. A generated element's full id is <dir>.<id>, where <dir> is the source directory with / and - replaced by _ (ElementId.directory), and <id> is the ADR-0299 __ form. Hand files and journey steps use the full id.
  3. A journey view id is the use-case name derived as in Decision 1. Hand views can link to it.

Fix that follows from line 2 (not a ruling): OverlayCheck checks only the bare id, so a wrong prefix passes it and fails only at likec4 build. It should compare the full <dir>.<id>.

What breaks if reversed later

Hand views that link to journey view ids; any project tool that reads generated/journeys/; every hand file that uses the <dir>. prefix (views/lever_check.c4 today).

Ask

Rule A, B, or C, and confirm "supersede ADR-0299" (not amend). Default if no ruling: B, and a superseding ADR as above.


Judgment calls inside #1327 for /os-me:predict

Call Predicted answer Confidence
Tracer report format (subprocess to generator) One JSON line per Journey on a file given by an env var: {"use_case": "...", "test": "File#method", "messages": [{"from": "OsMe::X", "to": "OsMe::Y", "method": "call"}]}. Constant paths, not ids; the Writer encodes ids. Not stdout, because tests print. 80%
What is a "message" A :call event where the receiver class and the caller's self class are both under the target root, and the two differ. Self-calls and calls into gems or stdlib are dropped. Consecutive duplicates are kept (a loop shows as repeats). 70%
Subprocess calls (Open3 to bin/) Out of scope for the first cut. The five journeys use in-process tests. A ticket is captured to follow RUBYOPT into children with per-pid files. 70%
Which five journeys 1. route a judgment row (operations.md sequence 1, Ruby part: CLI::RouteRequest to RowLabels to RowRouter to RouteLog). 2. offer a reversible move for a high-cost row (operations.md sequence 2: StakeReducer#options). 3. climb the evidence ladder until the stop rule (operations.md sequence 1 loop: EvidenceLadder, StopRule). 4. one extraction run (extraction.md: ExtractCursor, ExchangeFinder, MomentLog). 5. report calibration by band (learning-loops.md calibration loop: CalibrationReport). Journey 1 needs a new in-process test, because no current test calls RowRouter in-process. 65%
Where the proof file goes docs/system-design-format/proof/journeys-vs-operations.md, one table per journey: drawn step, traced step, verdict (match, drawing wrong, code wrong). Agent-side steps (fast-batch orchestrator, predict agent) are listed as "not traceable", not as a difference. 75%
Dynamic view step label The method name only (route), not the argument list. Arguments are not known from TracePoint without cost. 70%
# #1327 journey tracer: two decision memos _Written 2026-10-25. Read-only research. No ADR is created. Both items are one-way doors and go to Jared._ Facts that the memos use (checked 2026-10-25): - os-me tests are plain `class XTest < Minitest::Test` with `def test_...` methods (77 test files, 0 spec-style). `test_helper.rb` does `require 'minitest/autorun'`. - Minitest is 6.0.6. It has no tag feature. It has two native hooks that matter here: - `Minitest::Runnable#metadata`, a plain Hash that is copied to the `Result` that reporters get (`minitest.rb` lines 539-563, 683). - `--include PATTERN` / `--exclude PATTERN` filter on the method name. - Minitest 6 does not find plugins automatically from `minitest/autorun`; a plugin is loaded with `Minitest.load :name`, `Minitest.register_plugin`, or a plain `require`. So the Tracer can inject the recorder with `RUBYOPT=-r<recorder.rb>` in the subprocess. The project does not need to know the recorder file. - Many os-me tests exercise the code through `Open3.capture3(RbConfig.ruby, BIN, ...)` (for example `route_cli_test.rb`, `row_labels_run_test.rb`). TracePoint in the test process does not see those calls. `RUBYOPT` is inherited by the child, so a recorder can follow them only if it writes one file per process. - No os-me test calls `RowRouter` or `CLI::RouteRequest` in-process today. - Prior art (from memory, not verified against a primary source this session): RSpec metadata `it "...", use_case: "x"`; minitest-tagz `tag :x` before `def test_`; Cucumber `@tag` lines. None is a dependency here. - Hand-written `design/views/lever_check.c4` already writes `include lib_os_me.OsMe__RowRouter`. So the `<dir>.` prefix is parsed by hand files today, but ADR-0299 does not record it. - `OverlayCheck::GENERATED_ID` matches only the bare `OsMe__X` part. A wrong prefix (`lib_os_me_cli.OsMe__AnswerSource`) passes OverlayCheck. `likec4 validate` rejects it: "Could not resolve reference to Referenceable named 'OsMe__AnswerSource'" (probe in scratchpad `c4probe/`, 2026-10-25). - `likec4 validate` reads `.c4` files in subdirectories: a `dynamic view` in `generated/journeys/route_a_judgment_row.c4` validated with the os-me model (18 files, valid). - `Writer#clear` deletes only `generated/*.c4`. It does not delete files in a subdirectory. - `ElementId.directory` maps `/` and `-` to `_`. `lib/os_me` and `lib/os-me` give the same id. Not seen in os-me today. --- ## Decision 1: how a Minitest test names its use case **Headline:** Pick the `use_case` tag shape that every adopting project writes in its tests and that the recorder reads. ### Options **A. Block helper inside a test (the README section 2 form)** ```ruby class RouteJourneyTest < Minitest::Test def test_a_judgment_row_routes_to_predict row = Fixtures.judgment_row decision = use_case("route a judgment row") do OsMe::CLI::RouteRequest.new(row, tier: :judgment).call end assert_equal :predict, decision.route end end ``` Shim in the project's `test_helper.rb` (3 lines; the recorder prepends over it): ```ruby module UseCase def use_case(_name) = yield end Minitest::Test.include(UseCase) ``` Recorder reads: the name argument at run time; it traces only while the block runs. **B. Class macro before the method (minitest-tagz style)** ```ruby class RouteJourneyTest < Minitest::Test use_case "route a judgment row" def test_a_judgment_row_routes_to_predict decision = OsMe::CLI::RouteRequest.new(Fixtures.judgment_row, tier: :judgment).call assert_equal :predict, decision.route end end ``` Recorder reads: a class-level table filled by `method_added`; it traces the whole test, setup and assertions included. **C. Method-name prefix** ```ruby def test_use_case__route_a_judgment_row OsMe::CLI::RouteRequest.new(Fixtures.judgment_row, tier: :judgment).call end ``` Recorder reads: the method name, by regex; the name is lossy (no spaces, no capitals). Native filter: `--include /test_use_case__/`. **D. Native Minitest 6 metadata** ```ruby def test_a_judgment_row_routes_to_predict metadata[:use_case] = "route a judgment row" decision = OsMe::CLI::RouteRequest.new(Fixtures.judgment_row, tier: :judgment).call assert_equal :predict, decision.route end ``` Recorder reads: `result.metadata[:use_case]` in a reporter, after the test ran; it must trace every test and drop the untagged ones. **E. Separate manifest** ```yaml # design/journeys.yml route a judgment row: test/route_journey_test.rb#test_a_judgment_row_routes_to_predict ``` Recorder reads: the YAML, then runs each listed test with `--include`. A rename of a test breaks the link without a failure in the test suite. ### Side by side | | A block | B macro | C prefix | D metadata | E manifest | |---|---|---|---|---|---| | Trace window | the block only | whole test | whole test | whole test | whole test | | Name with spaces | yes | yes | no | yes | yes | | Shim in project | 3 lines | about 10 lines | none | none | none | | Untagged tests traced | no | no | no | yes (cost) | no | | Drift risk | none | low | none | none | high | ### Recommendation **A, block helper. Confidence 75%.** Deciding reason: the block marks the name and the trace window in one place, so a Journey holds the entry message and what it causes, and not fixture setup or assertion calls. Options B to E need a second rule to cut setup out of the trace. Second reason: it is the form already written in README section 2, so no design document changes. ### What breaks if reversed later Every tagged test in every adopting project, the shim in each `test_helper.rb`, and the recorder. The use-case name becomes the dynamic view id (see Decision 2), so a hand view that links to a journey also breaks. ### Sub-point inside the same door The view id is derived from the name: lower case, each run of non-word characters becomes `_` (`"route a judgment row"` becomes `route_a_judgment_row`). Two tests with the same name are an error (the Tracer refuses), not a merge. ### Ask Rule A, B, C, D, or E. **Default if no ruling: A**, with the derived view id rule above. --- ## Decision 2: where generated dynamic views live, and how ADR-0299 changes **Headline:** ADR-0299 says the generator writes only `design/generated/<dir>.c4`. Journeys are not per directory. Pick their file layout, and record the `<dir>.` prefix that hand files already parse. A generated journey view looks like this (validated with `likec4 validate` on the os-me model): ``` views { dynamic view route_a_judgment_row { title 'route a judgment row' lib_os_me_cli.OsMe__CLI__RouteRequest -> lib_os_me.OsMe__RowLabels 'label(row)' lib_os_me.OsMe__RowLabels -> lib_os_me.OsMe__AnswerSource 'classify(row)' lib_os_me_cli.OsMe__CLI__RouteRequest -> lib_os_me.OsMe__RowRouter 'route(labels, row_id:, tier:, flow:)' } } ``` Each step must use the full `<dir>.<id>` form. The prefix is part of the contract. ### Options **A. One file for all journeys** ``` design/generated/ lib.c4 lib_os_me.c4 lib_os_me_cli.c4 journeys.c4 # every dynamic view ``` Risk: a directory named `journeys/` in the target gives `journeys.c4` too, and the two files collide. **B. One file per journey in a subdirectory** ``` design/generated/ lib.c4 lib_os_me.c4 lib_os_me_cli.c4 journeys/ route_a_judgment_row.c4 offer_a_reversible_move.c4 ``` No collision with directory files. A changed trace shows as a one-file diff. `Writer#clear` must also delete `generated/journeys/*.c4`. **C. Per-directory journey files, by the directory of the entry object** ``` design/generated/ lib_os_me_cli.c4 lib_os_me_cli.journeys.c4 ``` A journey crosses directories, so the owner is arbitrary. ### Recommendation **B, `design/generated/journeys/<view_id>.c4`. Confidence 80%.** Deciding reason: it cannot collide with a `<dir>.c4` name, and one journey per file makes "a changed trace is a review signal" (README section 2) a one-file diff. **ADR action: supersede ADR-0299, do not amend it.** The project rule says the task is not complete until a superseding ADR exists when one-way-door content changes, and "the generator writes only `design/generated/<dir>.c4`" is that content. The new ADR carries all of ADR-0299 and adds three lines: 1. The generator writes `design/generated/<dir>.c4` and `design/generated/journeys/<view_id>.c4`, and rewrites both whole on every run. 2. A generated element's full id is `<dir>.<id>`, where `<dir>` is the source directory with `/` and `-` replaced by `_` (`ElementId.directory`), and `<id>` is the ADR-0299 `__` form. Hand files and journey steps use the full id. 3. A journey view id is the use-case name derived as in Decision 1. Hand views can link to it. Fix that follows from line 2 (not a ruling): `OverlayCheck` checks only the bare id, so a wrong prefix passes it and fails only at `likec4 build`. It should compare the full `<dir>.<id>`. ### What breaks if reversed later Hand views that link to journey view ids; any project tool that reads `generated/journeys/`; every hand file that uses the `<dir>.` prefix (`views/lever_check.c4` today). ### Ask Rule A, B, or C, and confirm "supersede ADR-0299" (not amend). **Default if no ruling: B, and a superseding ADR as above.** --- ## Judgment calls inside #1327 for /os-me:predict | Call | Predicted answer | Confidence | |---|---|---| | Tracer report format (subprocess to generator) | One JSON line per Journey on a file given by an env var: `{"use_case": "...", "test": "File#method", "messages": [{"from": "OsMe::X", "to": "OsMe::Y", "method": "call"}]}`. Constant paths, not ids; the Writer encodes ids. Not stdout, because tests print. | 80% | | What is a "message" | A `:call` event where the receiver class and the caller's `self` class are both under the target root, and the two differ. Self-calls and calls into gems or stdlib are dropped. Consecutive duplicates are kept (a loop shows as repeats). | 70% | | Subprocess calls (`Open3` to `bin/`) | Out of scope for the first cut. The five journeys use in-process tests. A ticket is captured to follow `RUBYOPT` into children with per-pid files. | 70% | | Which five journeys | 1. `route a judgment row` (operations.md sequence 1, Ruby part: `CLI::RouteRequest` to `RowLabels` to `RowRouter` to `RouteLog`). 2. `offer a reversible move for a high-cost row` (operations.md sequence 2: `StakeReducer#options`). 3. `climb the evidence ladder until the stop rule` (operations.md sequence 1 loop: `EvidenceLadder`, `StopRule`). 4. `one extraction run` (extraction.md: `ExtractCursor`, `ExchangeFinder`, `MomentLog`). 5. `report calibration by band` (learning-loops.md calibration loop: `CalibrationReport`). Journey 1 needs a new in-process test, because no current test calls `RowRouter` in-process. | 65% | | Where the proof file goes | `docs/system-design-format/proof/journeys-vs-operations.md`, one table per journey: drawn step, traced step, verdict (match, drawing wrong, code wrong). Agent-side steps (fast-batch orchestrator, predict agent) are listed as "not traceable", not as a difference. | 75% | | Dynamic view step label | The method name only (`route`), not the argument list. Arguments are not known from TracePoint without cost. | 70% |
Author
Owner

Rulings (Jared, 2026-10-26)

  • Decision 1: A, block helper use_case("name") { ... }. The view id is derived from the name; a duplicate name is an error.
  • Decision 2: B, design/generated/journeys/<view_id>.c4, with a new ADR that supersedes ADR-0299.

Which tests carry a use_case block

A journey test meets all three conditions:

  1. It enters where a user or agent enters: the CLI controller or entry object that bin/ calls (for example OsMe::CLI::RouteRequest), not an inner object such as RowRouter.
  2. Inside the block, it uses real collaborators. A test double cuts the trace.
  3. It maps to one named use case in the design docs (docs/decision-system/operations.md sequences), so the proof file can compare drawn and traced steps.

One tagged test per user goal. Do not tag every integration test. The tracer runs only the files that contain use_case; untagged tests are not traced.

Note: the fast-batch-15 branch converts os-me Open3 CLI tests to in-process calls. After it merges, those tests can become journey tests.

## Rulings (Jared, 2026-10-26) - **Decision 1: A**, block helper `use_case("name") { ... }`. The view id is derived from the name; a duplicate name is an error. - **Decision 2: B**, `design/generated/journeys/<view_id>.c4`, with a new ADR that supersedes ADR-0299. ## Which tests carry a `use_case` block A journey test meets all three conditions: 1. It enters where a user or agent enters: the CLI controller or entry object that `bin/` calls (for example `OsMe::CLI::RouteRequest`), not an inner object such as `RowRouter`. 2. Inside the block, it uses real collaborators. A test double cuts the trace. 3. It maps to one named use case in the design docs (`docs/decision-system/operations.md` sequences), so the proof file can compare drawn and traced steps. One tagged test per user goal. Do not tag every integration test. The tracer runs only the files that contain `use_case`; untagged tests are not traced. Note: the fast-batch-15 branch converts os-me `Open3` CLI tests to in-process calls. After it merges, those tests can become journey tests.
Author
Owner

Work started on branch ticket-1327 through /os-sdlc:implement (map poodr-ticket-implementation). The ticket body now carries the ADR-0300 rulings.

Work started on branch `ticket-1327` through `/os-sdlc:implement` (map poodr-ticket-implementation). The ticket body now carries the ADR-0300 rulings.
Author
Owner

Implementation 165 failed with verdict:error: the test-reviewer brief omits test edits outside the target plugin (occurrence posted on #1109). The tests were sound. Split: Required work 5 and 6 and AC 5 moved to #1392. The os-sdlc tests from run 165 stay in the worktree and are red for the intended reason. Opening a fresh run on the os-sdlc scope.

Implementation 165 failed with verdict:error: the test-reviewer brief omits test edits outside the target plugin (occurrence posted on #1109). The tests were sound. Split: Required work 5 and 6 and AC 5 moved to #1392. The os-sdlc tests from run 165 stay in the worktree and are red for the intended reason. Opening a fresh run on the os-sdlc scope.
Author
Owner

Resolution

Done: Journey tracer built in os-sdlc: Tracer runs use_case-tagged tests in a subprocess with the Recorder injected via RUBYOPT; Journey and Journey::Message hold the traced messages; Writer::JourneyFile writes design/generated/journeys/<view_id>.c4 as a LikeC4 dynamic view. ADR-0300 (supersedes ADR-0299) records the rulings.

Evidence: Implementation 166 implementation_complete, 20 dispatches, full suite green three times (dispatches 1443, 1446, 1449); commits b04998ec (ADR-0300), 2e9b35a0 (code + tests plugins/os-sdlc/tests/system_model_journeys_test.rb), cf8284d9 (status leaf History row, README section 6). Run 165 failed on a pipeline defect, recorded as an occurrence on #1109.

Follow-ups: #1392 (five os-me journey tests and the operations.md proof, split from this ticket); #1109 occurrence (test-reviewer brief omits test edits outside the target plugin); child-process tracing (Open3) stays out of scope, no ticket until a journey needs it; lens proposals: none survived the challenge.

## Resolution **Done:** Journey tracer built in os-sdlc: Tracer runs use_case-tagged tests in a subprocess with the Recorder injected via RUBYOPT; Journey and Journey::Message hold the traced messages; Writer::JourneyFile writes design/generated/journeys/<view_id>.c4 as a LikeC4 dynamic view. ADR-0300 (supersedes ADR-0299) records the rulings. **Evidence:** Implementation 166 implementation_complete, 20 dispatches, full suite green three times (dispatches 1443, 1446, 1449); commits b04998ec (ADR-0300), 2e9b35a0 (code + tests plugins/os-sdlc/tests/system_model_journeys_test.rb), cf8284d9 (status leaf History row, README section 6). Run 165 failed on a pipeline defect, recorded as an occurrence on #1109. **Follow-ups:** #1392 (five os-me journey tests and the operations.md proof, split from this ticket); #1109 occurrence (test-reviewer brief omits test edits outside the target plugin); child-process tracing (Open3) stays out of scope, no ticket until a journey needs it; lens proposals: none survived the challenge.
jared closed this issue 2026-10-26 13:57:08 +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#1327
No description provided.