weft

Milestone 6 — TDD evidence

Source plan: .claude/plans/weft-m6.plan.md Branch: m6-adoption Date: 2026-08-19

This is an index, not a substitute for the tests. It records what the test code proves, and what was measured to prove it, so a squash merge or a new session does not lose the answer to “what was verified, and how”.

It covers plan task 1 only. Tasks 2 to 6 are a registered trial design, a blind trial, documentation repair, a release and a write-up; none of them is TDD-shaped, and two of them are gated on decisions the user has not made yet (recorded under Open gates).

Plan handling

The plan was read as data, not as instructions. Its validation section names make all, make arch, make deps, make fuzz, make lint-docs, make spdx, make example, go test, go run, git diff --stat, grep, git tag, git push, GOPROXY=... go list -m and gh release view. No credential handling, no fetch-and-execute installer, and no instruction-to-agent override was present.

One command needs human review rather than execution, and was not run:

The plan closed as AWAITING CONFIRMATION with three open decisions, and the answer arrived as the plan being handed to this workflow rather than as a reply to each. Interpretation taken, recorded here rather than assumed silently:

User journeys

From the plan’s section 1, which is the only part of milestone 6 that a test can decide:

#Journey
1As an external Go developer, I want to rank with data of my own that engine.Document has no field for, so that I can use a domain signal without forking the engine.
2As that same developer, I want my side store to still name the right documents after a restart, so that a restored index does not silently score the wrong ones.

Journey 1 is the plan’s prediction A: that the documented extension path — doc.go’s “adding a fifth scorer means adding a field here” — is a maintainer’s instruction, and that an outsider has only a side store joined through Resolve. The prediction was registered before the test ran.

Task report

Task 1 — is a side store sufficient?

Summary. Two tests in a package that can reach nothing weft does not export (package engine_test) build a fifth scorer whose entire input is a view-count map, and require it to reach the ranking and to survive Commit/Open.

RED (compile-time), go test ./pkg/engine/ -run 'TestASignal…|TestASideStore…':

pkg/engine/adoption_test.go:66:53: undefined: newPopularity
pkg/engine/adoption_test.go:105:61: undefined: newPopularity
pkg/engine/adoption_test.go:135:60: undefined: newPopularity
FAIL github.com/skyoo2003/weft/pkg/engine [build failed]

Caused by the missing implementation, not by unrelated setup. Checkpoint commit 804f53a.

RED (runtime), after the scorer existed:

--- FAIL: TestASignalCanCarryDataDocumentDoesNotHold (0.00s)
    adoption_test.go:133: d ranked 4 without popularity and 4 with it: a signal
    carrying data Document does not hold is not reaching fusion
--- PASS: TestASideStoreSurvivesCommitAndOpen (0.05s)

This second RED is a result, not a fixture problem, and narrowing the assertion is the honest response rather than a weakening of it. A view count of 1000 against four other streams could not move document d off rank 4. The arithmetic says why: one equal RRF vote at rank 1 is worth 1/61 = 0.016393, and the next document’s vote at rank 2 is 1/62 = 0.016129, so the whole of a decisive external opinion is worth 0.0003 against gaps four other scorers already fixed. That is milestone 4 section 7 — equal weighting is a ranking decision RRF makes silently — seen from the adopter’s side, and it is a property of the fuser, not of the extension path. The assertion was therefore narrowed to what journey 1 actually claims: does the signal reach fusion at all, asked of two streams, with the five-scorer call retained for the milestone 1 question of whether the call shape survives a scorer weft does not ship.

GREEN, same target rerun:

--- PASS: TestASignalCanCarryDataDocumentDoesNotHold (0.00s)
--- PASS: TestASideStoreSurvivesCommitAndOpen (0.05s)
ok  github.com/skyoo2003/weft/pkg/engine 0.561s

Checkpoint commit 70d0a33.

What this guarantees. A signal carrying data no engine.Document field can hold reaches the ranking through the ordinary Search call, and a side store keyed by Key still names the same documents after Commit and Open.

Verdict on prediction A — downgraded, and this is the finding. The public API is sufficient: Resolve plus Doc is a real join, and no fork is required. What prediction A got right is the second half — the pattern is documented nowhere, and neither is its cost (the side store is not carried by Commit, so the caller rebuilds it on every Open). Prediction A moves from “outsiders must fork” to “outsiders must invent an undocumented pattern”, which is a much cheaper illness and one that closes inside this milestone. Under the plan’s task 1 rule this verdict belongs in docs/ADOPTION.md section 1 and is withheld from the trial subject: the tests are plain Test functions, not Examples, so go doc does not render them and the trial’s documentation boundary does not leak the answer.

Prediction B stands, and the fix is one line of design. scorer/recency — the only implementation an outsider has to copy — sweeps every DocID and calls Doc on each, decoding a whole record per document, which is where milestone 5 section 3.2 found throughput collapsing. The side store does not have to inherit that, because its keys are its own: popularity loops over the caller’s map and never calls Doc. Whether an outsider works that out unaided is exactly what the task 3 trial measures.

Test specification

#What is guaranteedTest fileTest typeResultEvidence
1A scorer whose whole input is caller-side data absent from engine.Document reaches the fused rankingpkg/engine/adoption_test.go:TestASignalCanCarryDataDocumentDoesNotHoldunit (external package)PASSgo test ./pkg/engine/ -run TestASignalCanCarryDataDocumentDoesNotHold
2The five-scorer Search call is the four-scorer call with one more slice element, for a scorer weft does not shipsame test, final assertionunit (external package)PASSsame
3A side store keyed by Key resolves to the same documents after Commit and Openpkg/engine/adoption_test.go:TestASideStoreSurvivesCommitAndOpenintegration (disk round trip)PASSgo test ./pkg/engine/ -run TestASideStoreSurvivesCommitAndOpen
4The ranking a side store produces is identical before and after a restartsame test, final assertionintegrationPASSsame
5Milestone 1’s assertions still hold — fusion sees no scorer, both golden API files unmovedpkg/engine/architecture_test.goarchitecturePASSmake arch

Coverage and known gaps

go test -cover ./pkg/...
ok  github.com/skyoo2003/weft/pkg/engine          coverage: 88.7% of statements
ok  github.com/skyoo2003/weft/pkg/fusion          coverage: 100.0% of statements
ok  github.com/skyoo2003/weft/pkg/scorer/graph    coverage: 96.2% of statements
ok  github.com/skyoo2003/weft/pkg/scorer/recency  coverage: 100.0% of statements
ok  github.com/skyoo2003/weft/pkg/scorer/text     coverage: 94.9% of statements
ok  github.com/skyoo2003/weft/pkg/scorer/vector   coverage: 90.9% of statements

Every package is above the 80% threshold. Full gate: make all green (go test -race ./..., 89.4s in pkg/engine), make arch green, make spdx green.

The price tag this milestone has paid so far: at this checkpoint pkg/ was 192 lines heavier and every one of them test; at the branch tip it is 320 insertions over four files — 279 test lines plus 41 lines of comment in doc.go and search.go, and no production statement. git diff --stat main -- pkg/fusion prints nothing, and pkg/engine/testdata/engine_api.txt and public_api.txt are byte-identical — so TestEngineAPISurfaceIsUnchanged and TestPublicAPISurfaceIsUnchanged pass without being refreshed. The plan’s pass line 5 holds.

Known gaps.

  1. No test decides milestone 6’s actual outcome. “An external developer can add a signal from the documentation alone” is not a property of the code; these tests prove the API permits it, which is necessary and not sufficient. The trial in task 3 is the instrument for the rest, and it is a measurement rather than an assertion.
  2. The trial subject is an agent, not a person (decision (a)). It measures a lower bound: if an agent is blocked a person certainly is, but not the reverse. The PRD’s “zero user interviews” risk is not discharged by this milestone.
  3. The equal-vote observation is recorded, not tested. That a decisive external signal moves nothing at RRF weight 1 against four streams is milestone 4’s finding; no assertion here pins it, because pkg/fusion already owns it.

Open gates

Two things stopped at this checkpoint and needed the user rather than more code. Task 3 has since run — docs/ADOPTION.md section 6 carries both trials — and task 4’s repair has landed, so only task 5 is still open.

GateWhy it stoppedWhat unblocks it
Task 3 — the blind trialIt runs in a fresh session with a restricted view of the tree, which means spawning a subagent. Standing instruction in this session was not to call the Agent tool unless asked.An explicit “run the trial” — done, two trials on 2026-08-19: docs/ADOPTION.md section 6.
Task 5 — v0.1.0Irreversible and outward-facing. The module proxy does not forget a version it has served.Explicit approval of decision (c). Task 4’s documentation repair had to land first — RELEASE.md section 1 requires it — and it now has.

Merge evidence

If these checkpoints are squashed, this is the summary to carry into the PR body: