Skip to content

v0.86 TWOSOURCE3 (#1183): every conformance slot states which tree it read, per slot - #1527

Closed
avrabe wants to merge 2 commits into
mainfrom
v086-twosource3
Closed

avrabe wants to merge 2 commits into
mainfrom
v086-twosource3

Conversation

@avrabe

@avrabe avrabe commented Oct 14, 2026

Copy link
Copy Markdown
Contributor

The refuting command found the pair it asked for

In step 3, on a single invocation:

status-evidence gate      DERIVED-PASS  "present and wired in ci.yml at ref"
status-evidence live run  DERIVED-FAIL  "exit 1"

One subject, opposite verdicts. The failing slot's entire detail was the exit code, so nothing told the reader that the PASS describes the committed ref while the FAIL describes the uncommitted working tree.

Paired control — a population of zero would have been a refusal, not a pass:

run live-run slot ref-reading slot
clean checkout DERIVED-PASS exit 0 DERIVED-PASS … at ref
one uncommitted edit DERIVED-FAIL exit 1 DERIVED-PASS … at ref (unchanged)

The comparison does detect a disagreement, and the only thing that moved was the working tree. Restored after; tree clean.

What ships

add() now takes a required source naming which tree the slot read, from a closed six-name vocabulary, rendered beside the status in both the printed line and the JSON. All 62 call sites declare one.

step 3  status-evidence gate      DERIVED-PASS [release-ref ] present and wired in ci.yml at ref
step 3  status-evidence live run  DERIVED-PASS [worktree    ] exit 0

The split really is per slot, not per step — step 3 alone draws on the release ref, the working tree and the GitHub API, and step 7's slots read the record from the ref while taking the ancestry from the commit graph. So a source is a tuple, not one token, and there is no per-step default.

Not collapsed onto one convention, deliberately. The pre-tag slots are meant to be statements about the working tree — that is what check_release_identity's own docstring says — so forcing every slot onto the ref would break a deliberate design rather than fix a defect. The four worktree readers are labelled, not moved.

Only the author knows which tree a slot read — the same argument R11 already makes about disposition: — so every call says, and a reader-level check fails when one stops saying. It is static because an output-only check would see only the slots that fired in one mode, and most of the population is rarely-taken FAIL branches.

Negative controls, all executed

  • strip source= from one live call site → FAILED, naming line 1056
  • plant an undefined tree name → FAILED, on the vocabulary diff
  • omit it in a call → TypeError (the six existing synthetic-slot tests raised exactly that before being updated)
  • a refused slot is not recorded (asserted), so it cannot default silently

Behaviour is unchanged, measured

The (step, label, status, detail) tuples are identical to the pre-change baseline in both modes — 18 findings retro at v0.85.0, 19 pretag at v0.86.0. Suite goes 65 → 69 tests, green. The one consumer that unpacked a 4-tuple is widened; tag_release.sh greps the status token, which is untouched. Already CI-wired at ci.yml:817.

Refs #1183

🤖 Generated with Claude Code

https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L

avrabe and others added 2 commits October 14, 2026 07:26
… read, per slot

The refuting command ran first and FOUND the pair it asked for. In step 3, on a
single invocation:

  status-evidence gate      DERIVED-PASS  "present and wired in ci.yml at ref"
  status-evidence live run  DERIVED-FAIL  "exit 1"

One subject, opposite verdicts. The failing slot's ENTIRE detail was the exit
code, so nothing told the reader that the PASS describes the committed ref while
the FAIL describes the uncommitted working tree.

PAIRED CONTROL, because a population of zero would have been a refusal rather
than a pass: with a clean checkout the live-run slot reads DERIVED-PASS "exit 0";
with ONE uncommitted edit it reads DERIVED-FAIL "exit 1"; and the ref-reading
slot is UNCHANGED across both runs. So the comparison does detect a
disagreement, and the only thing that moved was the working tree. Restored
after, tree clean.

WHAT SHIPS. `add()` now takes a required `source` naming WHICH TREE the slot
read, from a closed six-name vocabulary, and the verdict renders it beside the
status in both the printed line and the JSON. All 62 call sites declare one.

The split really is PER SLOT and not per step, which is why there is no
per-step default: step 3 alone draws on the release ref, the working tree and
the GitHub API, and step 7's slots read the record from the ref while taking the
ancestry from the commit graph - so a source is a TUPLE, not one token.

NOT collapsed onto one convention, deliberately. The pre-tag slots are MEANT to
be statements about the working tree - that is what check_release_identity's own
docstring says - so forcing every slot onto the ref would break a deliberate
design rather than fix a defect. The four worktree readers are LABELLED, not
moved.

Only the author knows which tree a slot read, the same argument R11 already
makes about `disposition:`, so every call SAYS and a reader-level check fails
when one stops saying. It is static because an output-only check would see only
the slots that FIRED in one mode, and most of the population is rarely-taken
FAIL branches.

NEGATIVE CONTROLS, both executed:
  * strip `source=` from one live call site -> FAILED, naming line 1056
  * plant an undefined tree name           -> FAILED, on the vocabulary diff
  * omit it in a call                      -> TypeError, and the six existing
    synthetic-slot tests raised exactly that before being updated
  * a refused slot is NOT recorded (asserted), so it cannot default silently

BEHAVIOUR IS UNCHANGED, measured rather than asserted: the
(step, label, status, detail) tuples are IDENTICAL to the pre-change baseline in
BOTH modes - 18 findings retro at v0.85.0, 19 pretag at v0.86.0 - and the suite
goes 65 -> 69 tests, green. The one consumer that unpacked a 4-tuple is widened;
tag_release.sh greps the status token, which is untouched.

Refs #1183

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L
…ried for three releases

The lane DELIVERED its done-when, so the status is claiming and the verdict
leads DELIVERED.

Recorded alongside it, because reading the artifact's own issue field rather
than trusting it is what surfaced it: the issue this lane names is about a
delivery-floor constant with too much slack. It was fixed and released many
releases ago, and neither its body nor its single comment mentions the
worktree-versus-ref split this lane is about - so it does not carry this lane's
analysis, which is the one thing a closed citation is legitimately for here.

v0.84's own verified-by already said the named issue was closed before its
measurement. Three releases then carried the field forward unchanged. The number
is left in place rather than swapped for an invented one; correcting it is owed
to v0.87 planning.

Refs #1183

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L
@avrabe

avrabe commented Oct 14, 2026

Copy link
Copy Markdown
Contributor Author

Superseded by #1537, which carries this branch's content in full.

Why: a dependabot auto-merge moved main and put every lane PR behind=1. The merge gate's currency check (rev-list --count head..base must be zero) means merging any one lane PR re-stales the rest, so the six lanes were forced sequential — six rebases and six full CI cycles, with three more auto-merge-enabled dependabot PRs still queued to move main again.

The consolidation was verified by simulation before being chosen: the whole wave applied to a detached worktree off origin/main, the single ci.yml conflict resolved with both steps kept, and gated there at no CI cost — status_evidence 0 failures, claim_check 75/75, verdict_prose clean, and the window anti-vacuity guard untripped.

Nothing was dropped. The artifact records are byte-identical apart from the PR number they name, plus one correction the simulation surfaced: SPELLSCAN now carries issue-scope: outlives, because #1515's own subject is the SELFTEST2 vacuity and that lane is partial.

@avrabe avrabe closed this Oct 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant