system-model step 3: journey tracer with use_case-tagged Minitest and LikeC4 dynamic views #1327
Labels
No labels
P0
P1
P2
P3
bug
category/code-change
category/cross-repo
category/eval-run
category/investigation
category/no-test-target
category/owner-ruling
category/prose-artifact
category/recurring-chore
category/structure
category/umbrella
crc-refactor
create
delete
enhancement
filed-by/agent
filed-by/user
frozen
lint-rule
needs-info
needs-triage
next
plugin/cc-architect
plugin/os
plugin/os-adr
plugin/os-aidd-lint
plugin/os-backlog
plugin/os-context
plugin/os-doc-hygiene
plugin/os-sdlc
plugin/os-vault
project/cc-os
ready-for-agent
ready-for-human
recurring
review
rule-proposal
update
waiting
wayfinder:grilling
wayfinder:map
wayfinder:map
wayfinder:research
wayfinder:task
wayfinder:task
wontfix
worklist/deviations
worklist/lint-rule
worklist/new-implement-build
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
jared/cc-os#1327
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
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_casetag in Minitest run as a subprocess ofbin/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:
use_casetag and the TracePoint recorder under plugins/os-sdlc/lib/os_sdlc/system_model/recorder/.Settled by ADR-0300 and the 2026-10-26 rulings
use_case("route a judgment row") { ... }. The recorder traces only inside the block. Each adopting project adds this shim totest_helper.rb; the recorder replaces it in the subprocess:_. Two use cases with the same view id are an error.design/generated/journeys/<view_id>.c4as adynamic view.Writer#clearalso deletesgenerated/journeys/*.c4.<dir>.<id>element id, for examplelib_os_me_cli.OsMe__CLI__RouteRequest -> lib_os_me.OsMe__RowLabels 'label'.use_case. It injects the recorder withRUBYOPT=-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"}]}.:callevent where the receiver class and the callerselfclass are both under the target root and differ. Self-calls and calls into gems or stdlib are dropped. Consecutive repeats are kept.Open3tobin/) are out of scope for this ticket.Which tests carry a
use_caseblockSee #1392.
Acceptance criteria
use_case, when the Tracer runs it, then it returns one Journey with the use-case name and the ordered messages.design/generated/journeys/<view_id>.c4holds one LikeC4 dynamic view for it, andlikec4 validateaccepts the model.Owner sketch
Origin
plugins/os-sdlc/bin/system-modelon branch system-model-generator, 2026-10-25VERDICT: CREATE
#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):
class XTest < Minitest::Testwithdef test_...methods (77 test files, 0 spec-style).test_helper.rbdoesrequire 'minitest/autorun'.Minitest::Runnable#metadata, a plain Hash that is copied to theResultthat reporters get (minitest.rblines 539-563, 683).--include PATTERN/--exclude PATTERNfilter on the method name.minitest/autorun; a plugin is loaded withMinitest.load :name,Minitest.register_plugin, or a plainrequire. So the Tracer can inject the recorder withRUBYOPT=-r<recorder.rb>in the subprocess. The project does not need to know the recorder file.Open3.capture3(RbConfig.ruby, BIN, ...)(for exampleroute_cli_test.rb,row_labels_run_test.rb). TracePoint in the test process does not see those calls.RUBYOPTis inherited by the child, so a recorder can follow them only if it writes one file per process.RowRouterorCLI::RouteRequestin-process today.it "...", use_case: "x"; minitest-tagztag :xbeforedef test_; Cucumber@taglines. None is a dependency here.design/views/lever_check.c4already writesinclude lib_os_me.OsMe__RowRouter. So the<dir>.prefix is parsed by hand files today, but ADR-0299 does not record it.OverlayCheck::GENERATED_IDmatches only the bareOsMe__Xpart. A wrong prefix (lib_os_me_cli.OsMe__AnswerSource) passes OverlayCheck.likec4 validaterejects it: "Could not resolve reference to Referenceable named 'OsMe__AnswerSource'" (probe in scratchpadc4probe/, 2026-10-25).likec4 validatereads.c4files in subdirectories: adynamic viewingenerated/journeys/route_a_judgment_row.c4validated with the os-me model (18 files, valid).Writer#cleardeletes onlygenerated/*.c4. It does not delete files in a subdirectory.ElementId.directorymaps/and-to_.lib/os_meandlib/os-megive the same id. Not seen in os-me today.Decision 1: how a Minitest test names its use case
Headline: Pick the
use_casetag 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)
Shim in the project's
test_helper.rb(3 lines; the recorder prepends over it):Recorder reads: the name argument at run time; it traces only while the block runs.
B. Class macro before the method (minitest-tagz style)
Recorder reads: a class-level table filled by
method_added; it traces the whole test, setup and assertions included.C. Method-name prefix
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
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
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
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"becomesroute_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 validateon the os-me model):Each step must use the full
<dir>.<id>form. The prefix is part of the contract.Options
A. One file for all journeys
Risk: a directory named
journeys/in the target givesjourneys.c4too, and the two files collide.B. One file per journey in a subdirectory
No collision with directory files. A changed trace shows as a one-file diff.
Writer#clearmust also deletegenerated/journeys/*.c4.C. Per-directory journey files, by the directory of the entry object
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>.c4name, 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:design/generated/<dir>.c4anddesign/generated/journeys/<view_id>.c4, and rewrites both whole on every run.<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.Fix that follows from line 2 (not a ruling):
OverlayCheckchecks only the bare id, so a wrong prefix passes it and fails only atlikec4 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.c4today).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
{"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.:callevent where the receiver class and the caller'sselfclass 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).Open3tobin/)RUBYOPTinto children with per-pid files.route a judgment row(operations.md sequence 1, Ruby part:CLI::RouteRequesttoRowLabelstoRowRoutertoRouteLog). 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 callsRowRouterin-process.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.route), not the argument list. Arguments are not known from TracePoint without cost.Rulings (Jared, 2026-10-26)
use_case("name") { ... }. The view id is derived from the name; a duplicate name is an error.design/generated/journeys/<view_id>.c4, with a new ADR that supersedes ADR-0299.Which tests carry a
use_caseblockA journey test meets all three conditions:
bin/calls (for exampleOsMe::CLI::RouteRequest), not an inner object such asRowRouter.docs/decision-system/operations.mdsequences), 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
Open3CLI tests to in-process calls. After it merges, those tests can become journey tests.Work started on branch
ticket-1327through/os-sdlc:implement(map poodr-ticket-implementation). The ticket body now carries the ADR-0300 rulings.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.
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.