Recorded from #618 phase 4 so the tradeoff is written down rather than inferred from a filter.
The gap
WorktreeFlow._carried_artifact_rels builds clean_incoming_collisions' protected operand from paths.sprint_status and paths.deferred_work, restricted to paths git already tracks. An untracked, non-ignored artifact is therefore not protected, and the post-merge carry commits it whole — including anything the operator put in it.
Measured, sandbox story run, board written but never committed, operator marker appended mid-run:
dirty at merge : {'.../sprint-status.yaml': '??'} (a stray: not in the incoming set)
summary : done=1 paused=False escalated=0
git log : f8279f01 chore(sprint-status): carry 1-1-a to done
marker in that commit = True
Same shape reproduced on an untracked ledger via _carry_harvested_deferrals (chore(deferred-work): carry harvested findings from ...).
Why it was left out
Protecting untracked artifacts was measured and rejected: with the board protected unconditionally, an untracked non-ignored board with no operator dirt anywhere ends every isolated run done=0 paused=True escalated=1. Every run, of every project that has yet to commit its board, halted at its first story — the unattended-halt class #460 and #618 exist to remove.
It also buys less than the tracked case does. The #618 hazard is committing a divergence from a baseline somebody else authored; an untracked artifact has no baseline, the orchestrator has been reading that exact file as its own all along, and committing it whole is how a non-ignored board first reaches git at all (#350's carry). The bytes are committed, not overwritten — nothing is lost, unlike the tracked case.
For completeness, the third shape: a gitignored artifact never reaches the question. dirty_paths does not report ignored files and git add refuses an ignored pathspec, so the carry degrades (board-advance-carry-uncommitted / harvest-carry-uncommitted) instead of committing.
What a fix would need
A discriminator between "the run's own artifact, first time into git" and "content the operator authored in that file" — which the pre-flight cannot see from dirty_paths alone, since both read ??. Options worth weighing:
- Have the carry commit only when the run's own write actually changed the file, comparing against the pre-carry bytes rather than staging by pathspec.
- Refuse only when the untracked artifact is dirty and the run has no pending write of its own for it.
- Accept the current behavior and document it (the file is the orchestrator's; committing it is the point).
Recorded from #618 phase 4 so the tradeoff is written down rather than inferred from a filter.
The gap
WorktreeFlow._carried_artifact_relsbuildsclean_incoming_collisions'protectedoperand frompaths.sprint_statusandpaths.deferred_work, restricted to paths git already tracks. An untracked, non-ignored artifact is therefore not protected, and the post-merge carry commits it whole — including anything the operator put in it.Measured, sandbox story run, board written but never committed, operator marker appended mid-run:
Same shape reproduced on an untracked ledger via
_carry_harvested_deferrals(chore(deferred-work): carry harvested findings from ...).Why it was left out
Protecting untracked artifacts was measured and rejected: with the board protected unconditionally, an untracked non-ignored board with no operator dirt anywhere ends every isolated run
done=0 paused=True escalated=1. Every run, of every project that has yet to commit its board, halted at its first story — the unattended-halt class #460 and #618 exist to remove.It also buys less than the tracked case does. The #618 hazard is committing a divergence from a baseline somebody else authored; an untracked artifact has no baseline, the orchestrator has been reading that exact file as its own all along, and committing it whole is how a non-ignored board first reaches git at all (#350's carry). The bytes are committed, not overwritten — nothing is lost, unlike the tracked case.
For completeness, the third shape: a gitignored artifact never reaches the question.
dirty_pathsdoes not report ignored files andgit addrefuses an ignored pathspec, so the carry degrades (board-advance-carry-uncommitted/harvest-carry-uncommitted) instead of committing.What a fix would need
A discriminator between "the run's own artifact, first time into git" and "content the operator authored in that file" — which the pre-flight cannot see from
dirty_pathsalone, since both read??. Options worth weighing: