Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 96 additions & 0 deletions .github/reviews/speed-up-local-test-runner.receipt.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
{
"schema_version": 1,
"instance": "speed-up-local-test-runner",
"program": {
"id": "boatstack-reviewer",
"version": "1",
"fingerprint": "76680abd47f9e920ebfe807d3b3f226cbb48bd1a531b0a0a068cd27fd3e6f6e6"
},
"policy": {
"prompt_path": ".github/codex/review-prompt.md",
"prompt_sha256": "66c4c7111f200de489f4fed0852b261e36cbd1e962c61320c1e4ea2dd2641986",
"schema_path": ".github/codex/review-output-schema.json",
"schema_sha256": "113b02c5cca93156692031c3dedd038e5c2b80cbc4a56337ce06e536110e3e6a",
"max_rounds": 16,
"stall_window": 3,
"weights": [
1000,
100,
10,
1
]
},
"base_ref": "origin/main",
"merge_base": "1cb20b6862d34831898037792c1eea06cfdd0122",
"head_commit": "b27992fb3c827ad22041e70f667082295ba36220",
"reviewed_tree": "d0705c1c64d482191bec8c9d5a957dec98979569",
"generation": 1,
"rounds": [
{
"generation": 1,
"index": 1,
"candidate_fingerprint": "32fc0219bbe4164255b3fa328a515883b1377db4f032d9d27a015c9fdb2718e3",
"reviewed_tree": "d0705c1c64d482191bec8c9d5a957dec98979569",
"head_commit": "b27992fb3c827ad22041e70f667082295ba36220",
"merge_base": "1cb20b6862d34831898037792c1eea06cfdd0122",
"verdict": "patch is correct",
"measure": 0,
"finding_count": 0,
"priorities": [
0,
0,
0,
0
],
"transition": "review.converge"
}
],
"final_review": {
"findings": [],
"overall_correctness": "patch is correct",
"overall_explanation": "The patch changes only the local test runner (.github/scripts/run_go_tests.py, ci_shard.py), its unit tests, and adds a release note; no Boatstack kernel, control-program, or CI-workflow behavior changes. Reviewed to closure: (1) package attribution in read_test_packages depends on go test printing each package's -list block contiguously, which the go tool guarantees via per-package output buffering; a ? [no test files] block cannot split another package's names from its ok line. (2) Tests with the same name in multiple packages keep every owner and the shard scope is the union, so scoping cannot drop an owner that ./... would have run. (3) verified_package_scope fails closed: an enumerated test with no owning package or outside its shard scope refuses the run before any worker spawns, and both the rejecting and accepting directions have unit tests. (4) LPT weights are strictly positive (load_timings filters v\u003e0, default weight 1.0) and shard count is capped at len(names), so no shard can be empty; an empty regex additionally refuses. (5) Timings persistence goes to the user cache with atomic os.replace, degrades to count-balancing on missing or malformed data, and the runner is local-only so CI never touches it; the Windows CI path through read_test_names is byte-identical. (6) -count=1, the jobs cap, and process isolation are unchanged, so fresh execution and seam isolation invariants hold. Questions for model-level verification (not defects): none newly reachable; the runner state machine gains only a refusing pre-spawn gate, which cannot introduce a blocking state because refusal exits the run. Model-level verification before merge is not required.",
"overall_confidence_score": 0.85
},
"control_state": {
"mode": "converged",
"revision": 3
},
"kernel_receipts": [
{
"schema_version": 3,
"id": "rcp-a2307a2aed0e2372749a7022830a74b324476865ce91018533379e28d56cbe6e",
"instance_id": "speed-up-local-test-runner",
"prescription_id": "prx-76a2977343c8aee6ee7bf0368087461caf9c2b457bfe8a55588b62d8fb58df57",
"program": {
"id": "boatstack-reviewer",
"version": "1",
"fingerprint": "76680abd47f9e920ebfe807d3b3f226cbb48bd1a531b0a0a068cd27fd3e6f6e6"
},
"transition_id": "review.converge",
"prior_state_revision": 1,
"attempt_state_revision": 2,
"result_state_revision": 3,
"authority_fingerprint": "38a1c3289b7f87426b560c99da0f3734b2a379475db8a915ab55d82f9bbd3846",
"capabilities": [
"review.submit"
],
"effects": [
{
"facet": "review.round",
"operation": "review.converge",
"fingerprint": "32fc0219bbe4164255b3fa328a515883b1377db4f032d9d27a015c9fdb2718e3"
}
],
"prior_observation": "5611ccc070fff3754879649c3522b8280ad0243ba86b9b00ea2c0739e7fd4a41",
"result_observation": "6a7c27ba3f797fcb50d94824e5fe8068d2763b960dbdccdbbbf671db3e2ff487",
"verification": "satisfied",
"committed_at": "2026-08-21T04:25:28.501588Z"
}
],
"honesty": {
"semantic_correctness": "not-evaluated",
"origin_authenticity": "not-proven"
},
"sealed_at": "2026-08-21T04:26:27.391146Z",
"fingerprint": "d857c5f2ec72e78388ec491979890ee82f34ad5a7d627d7dbd6d270c506662e6"
}
30 changes: 30 additions & 0 deletions .github/scripts/ci_shard.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@
# beginning with "Test"; keep only those.
TEST_NAME = re.compile(r"^Test[A-Za-z0-9_]*$")

# The per-package summary line that follows that package's test names.
# Packages without test files print "? <pkg> [no test files]" instead.
PACKAGE_SUMMARY = re.compile(r"^ok\s+(\S+)")


def read_test_names(stream) -> list[str]:
"""Parse `go test -list` output from a stream into a sorted, de-duped list."""
Expand All @@ -62,6 +66,32 @@ def read_test_names(stream) -> list[str]:
return sorted(names)


def read_test_packages(stream) -> dict[str, tuple[str, ...]]:
"""Parse `go test -list` output into a test-name -> owning-packages mapping.

`go test -list ./...` interleaves each package's test names with that
package's trailing "ok <pkg> <t>" summary line, so names are attributed
to the next summary line seen. A name can legitimately exist in more than
one package; every owner is kept (sorted, de-duped). Names never followed
by a package summary are dropped — callers that need completeness must
verify the mapping covers their enumeration and fail closed on a gap.
"""
owners: dict[str, set[str]] = {}
pending: list[str] = []
for line in stream:
stripped = line.strip()
if TEST_NAME.match(stripped):
pending.append(stripped)
continue
summary = PACKAGE_SUMMARY.match(stripped)
if summary:
package = summary.group(1)
for name in pending:
owners.setdefault(name, set()).add(package)
pending = []
Comment on lines +86 to +91

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Do not derive package ownership from unstructured test output

Invariant: a successful scoped run must execute every enumerated test in every owning package. A valid package p can define TestAlpha and a TestMain that calls m.Run() and then emits ok example.com/mod/q 0.01s; enumeration then produces TestAlpha, that line, and Go's real ok .../p summary. .github/scripts/ci_shard.py assigns TestAlpha to q, clears it before the real summary, and the coverage check accepts the mapping. If q exists without TestAlpha, the scoped worker reports success with no tests run, while p/TestAlpha is skipped. This is introduced by replacing the previous ./... sweep with ownership inferred from ambiguous stdout. The runner can therefore falsely report the complete suite passed. Use package-labelled output or per-package enumeration. The smallest regression test is an integration fixture with packages p and q and the above TestMain, asserting that p/TestAlpha executes or enumeration refuses.

Confidence: 0.98

return {name: tuple(sorted(packages)) for name, packages in owners.items()}


def assign_shards(names: list[str], total: int, timings: dict[str, float]) -> list[list[str]]:
"""Partition `names` into `total` shards via LPT greedy on estimated cost.

Expand Down
Loading
Loading