Give each takedown run its own directory when two start in the same second #964
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#964
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
Two takedown runs of one case that start in the same second get the same run stamp. They then share one run directory and one scratch checkout, and each run changes the other's files. The run scripts avoid this only by starting the runs of one case in series.
Observed
plugins/os-sdlc/eval/ticket-takedown/lib/takedown/command/run_assembly.rb:14:The prism-architect eval discarded one parallel launch for this reason (
results/prism-architect/discarded-parallel-launch/). The handoffdocs/handoffs/2026-10-06-prism-architect.mdlists it as a harness defect.Reproduce
Start two
bin/takedown-runruns of one case in the same second.Approach
Required work:
require "tempfile"tolib/takedown.rb. — check: presentrequire "tempfile"inplugins/os-sdlc/eval/ticket-takedown/lib/takedown.rbadapter/metrics_log.rb#appendto one locked append: open with WRONLY, APPEND, CREAT, takeflock(LOCK_EX), write the header only when the file is empty, then the row. — check: presentLOCK_EXinplugins/os-sdlc/eval/ticket-takedown/lib/takedown/adapter/metrics_log.rbadapter/problem_cache.rb#writeto write a Tempfile in the cache dir and publish it withFile.link;EEXISTkeeps the first reply. — check: presentFile.linkinplugins/os-sdlc/eval/ticket-takedown/lib/takedown/adapter/problem_cache.rbcontroller/report_cli.rb#newestto list run directories only and order them byRunStamp. — check: presentRunStamp.parseinplugins/os-sdlc/eval/ticket-takedown/lib/takedown/controller/report_cli.rbRunContext.open(request, paths:, clock:)inmodel/run_context.rb; add theviewsmember; remove#redactionsand#extra_redactions. — check: presentdef self.openinplugins/os-sdlc/eval/ticket-takedown/lib/takedown/model/run_context.rbTicketViews.readinmodel/ticket_views.rb. — check: presentdef self.readinplugins/os-sdlc/eval/ticket-takedown/lib/takedown/model/ticket_views.rbRunRequest#extra_redaction_sentencesinmodel/run_request.rb. — check: presentdef extra_redaction_sentencesinplugins/os-sdlc/eval/ticket-takedown/lib/takedown/model/run_request.rbcommand/run_builder.rbto use@context.viewsand delete the privateviewsmethod. — check: present@context.viewsinplugins/os-sdlc/eval/ticket-takedown/lib/takedown/command/run_builder.rbcommand/run_assembly.rb#pipelineto build its context withRunContext.open. — check: presentRunContext.openinplugins/os-sdlc/eval/ticket-takedown/lib/takedown/command/run_assembly.rbcommand/probe_assembly.rb#pipelinethe same way. — check: presentRunContext.openinplugins/os-sdlc/eval/ticket-takedown/lib/takedown/command/probe_assembly.rbmodel/run_stamp.rb:RunStampwithfirst,parse,succ,to_sand ordering by time, number, ticket. — check: presentclass RunStampinplugins/os-sdlc/eval/ticket-takedown/lib/takedown/model/run_stamp.rbadapter/stamp_claim.rb:StampClaimclaims a stamp withDir.mkdirin every root and takes the next suffix when any root holds it. — check: presentDir.mkdirinplugins/os-sdlc/eval/ticket-takedown/lib/takedown/adapter/stamp_claim.rbtest/model/run_stamp_test.rbwith the tests named in the owner sketch. — check: presentclass RunStampTestinplugins/os-sdlc/eval/ticket-takedown/test/model/run_stamp_test.rbtest/adapter/stamp_claim_test.rbwith the tests named in the owner sketch. — check: presentclass StampClaimTestinplugins/os-sdlc/eval/ticket-takedown/test/adapter/stamp_claim_test.rbtest/adapter/metrics_log_test.rbwith the fork test named in the owner sketch. — check: presentclass MetricsLogTestinplugins/os-sdlc/eval/ticket-takedown/test/adapter/metrics_log_test.rbtest/adapter/problem_cache_test.rb. — check: presenttest_parallel_writes_leave_one_whole_explanationinplugins/os-sdlc/eval/ticket-takedown/test/adapter/problem_cache_test.rbtest/controller/report_cli_test.rb, replacetest_latest_picks_newest_dirswith the two--latesttests named in the owner sketch. — check: presenttest_latest_picks_the_newest_runs_by_stampinplugins/os-sdlc/eval/ticket-takedown/test/controller/report_cli_test.rbtest/controller/run_cli_test.rb, addbuild_cliand thecheckouthelper, makescratch_rootoverridable, and add the three claim tests named in the owner sketch. — check: presenttest_two_runs_of_one_case_in_the_same_second_get_their_own_run_dir_and_checkoutinplugins/os-sdlc/eval/ticket-takedown/test/controller/run_cli_test.rbtest/controller/probe_cli_test.rb, name the runs root once and add the probe acceptance test. — check: presenttest_two_probes_of_one_case_in_the_same_second_get_their_own_run_dirinplugins/os-sdlc/eval/ticket-takedown/test/controller/probe_cli_test.rbresults/prism-architect/scripts/run-oracle.sh, replace the stale collision comment with the new two-sentence comment. — check: presentCases run in parallel. The runs of one case run in series.inplugins/os-sdlc/eval/ticket-takedown/results/prism-architect/scripts/run-oracle.shresults/prism-architect/scripts/run-variant.sh, replace the stale collision comment the same way. — check: presentCases run in parallel. The runs of one case run in series.inplugins/os-sdlc/eval/ticket-takedown/results/prism-architect/scripts/run-variant.shAcceptance criteria
Owner sketch
Responsibilities:
RunStamp(new,model/run_stamp.rb)StampClaim(new,adapter/stamp_claim.rb)RunContext.open(new; one path for both CLIs)TicketViews.read(moved from privateRunBuilder#views)--extra-redactionsfileRunRequest#extra_redaction_sentences(moved fromRunContext#extra_redactions)MetricsLog(changed: locked append)ProblemCache(changed: temp file + link)ReportCLI#newest(changed: run dirs only, ordered byRunStamp)Code sketch (changed classes only):
Script comments (prose):
results/prism-architect/scripts/run-oracle.sh:5-7andrun-variant.sh:3-4lose the stale reason ("the checkout stamp is case + second ... collide"); they read# Cases run in parallel. The runs of one case run in series.The scripts stay serial.Tests to write, with these names so the test-writer copies them:
test/model/run_stamp_test.rb: test_the_first_stamp_of_a_second_is_case_and_time, test_a_tenth_claim_in_one_second_is_newer_than_the_second, test_order_is_by_time_across_cases, test_a_name_that_is_not_a_stamp_failstest/adapter/stamp_claim_test.rb: test_the_first_claim_gets_the_plain_stamp_and_a_directory_in_each_root, test_a_stamp_taken_in_any_root_gets_the_next_suffix_and_is_left_alone, test_one_directory_serves_as_both_roots, test_concurrent_claims_sharing_only_the_scratch_root_get_distinct_stamps, test_concurrent_claims_sharing_only_the_runs_root_get_distinct_stampstest/adapter/metrics_log_test.rb: test_parallel_runs_appending_to_one_new_file_keep_one_header_and_every_rowtest/adapter/problem_cache_test.rb: test_a_second_write_keeps_the_first_explanation, test_parallel_writes_leave_one_whole_explanationtest/controller/report_cli_test.rb: test_latest_picks_the_newest_runs_by_stamp, test_latest_fails_on_a_directory_that_is_not_a_run_stamptest/controller/run_cli_test.rb: test_scratch_checkout_lands_under_the_scratch_root (assertions use checkout(STAMP)), test_two_runs_of_one_case_in_the_same_second_get_their_own_run_dir_and_checkout, test_two_runs_with_their_own_scratch_roots_in_one_runs_root_get_their_own_run_dir, test_a_run_that_fails_on_its_ticket_inputs_makes_no_directorytest/controller/probe_cli_test.rb: test_two_probes_of_one_case_in_the_same_second_get_their_own_run_dirFull test bodies: docs/takedown/964/final-design.md, section Tests.
Retired tests
plugins/os-sdlc/eval/ticket-takedown/test/controller/report_cli_test.rb#test_latest_picks_newest_dirs
Origin
scripts/run-oracle.sh)DESIGN: stamp from case and clock second (run_assembly.rb:14)Resolution
Done: Each takedown run claims its own stamp in both roots with an atomic mkdir; metrics-all.tsv appends under flock; problems/.json publishes by hard link; --latest orders by time then suffix and fails loudly on a non-stamp directory; all run inputs and variant cards are read before the claim. Design: docs/takedown/964/final-design.md, revised after owner review. Built test-first outside /os-sdlc:implement on branch prism-architect, because no os-sdlc target covers the eval tests (see #963).
Evidence: Commit
1db8388con prism-architect; eval suite 353 runs, 742 assertions, 0 failures (run by the orchestrator); new tests red before the change; History row1616aa75.Follow-ups: none filed. Dropped with reason: scratch-root cleanup and tools that write outside the claimed directories (graphify, claude CLI) are listed as known risks in the design and are not the same class; rubocop does not cover eval/ by config.