From 56d05eef78875fe3fdbe0b5cd02558e665243c4d Mon Sep 17 00:00:00 2001 From: Antoine Toussaint <312536060+antoinetoussaint-byte@users.noreply.github.com> Date: Sun, 4 Oct 2026 11:13:02 -0400 Subject: [PATCH] docs(workcontext)!: the pseudo-version is this module's own now, and the sweep's base is the PR's target MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two follow-ups from #48's review, on a fresh branch off main after the merge. 1. THE README'S PSEUDO-VERSION SECTION WAS FALSE. It argued at length that this module merges on a core pseudo-version because no core tag existed to move to, with a measured table of what `v0.7.1` does and does not carry. All of that is void: core#692 merged and `v0.9.0` is tagged, and `workcontext/go.mod` pins it. Rewritten to what a consumer actually needs, which is a different thing in two ways: - `v0.8.0` and `v0.8.1` are a TRAP, named rather than left for somebody to find. Core's main cut both from its own release commits while #692 sat unmerged, so neither carries it — and they are not missing `workcontext`, which would be obvious. The package is there at its pre-#692 revision, so pinning `v0.8.0` fails on Seal, SealSource, OperationBinding and ErrNotACoreToken, and reads like a bad pin rather than a stale one. v0.9.0 is the floor for anyone bisecting core under this module. - THE PSEUDO-VERSION IS NOW THIS MODULE'S OWN. `sdk-go/workcontext` has never been tagged, so a consumer resolves a pseudo-version of a main commit: `go get -u` will not move them, and a stable release on top of an unreleased module is a choice to make deliberately. The retraction in `workcontext/go.mod` waits on the same thing — a `retract` takes effect only once a version carrying it is released, so until this module is tagged it is intent in the file rather than something `go get` enforces. Whether a `workcontext/vX.Y.Z` tag is cut is a release-process decision for whoever owns releases, and the section says so instead of implying an answer. Nothing here tags anything. (The section is in workcontext/README.md, not AGENTS.md as relayed — AGENTS.md's stream and gate sections were already corrected before the merge.) 2. THE SWEEP'S BASE IS THE PULL REQUEST'S OWN TARGET. `SWEEP_BASE` read `github.event.repository.default_branch` unconditionally — right for every PR this repository has had, wrong for one targeting anything else. A PR into a release branch would have had that branch SWEPT, failing circularly on the content the PR is changing, while the default branch was excluded and went unchecked. Both halves are wrong and they conceal each other: the red looks like a finding and the unswept branch looks clean. `github.base_ref` is set on a pull_request and empty on a push, which is exactly the needed distinction — a push build has no base, and for it the default branch is right. That ordering is the rule, and the test asserts the order rather than only the presence of both, because reversed it would make every PR sweep its own target. What the test can and cannot do is stated in it: a GitHub expression is evaluated by GitHub, so this asserts the expression's SHAPE, and the script's behaviour given a base is covered by the existing both-event-shapes test. Neither runs the expression; a reviewer executed the old and new forms externally. Round twelve's minor — the base-ref test inheriting os.Environ() and so an ambient SWEEP_BASE — was fixed before the merge and is on main already; verified rather than redone. Verified: both suites clean, golangci-lint 0 issues in both, go mod tidy clean, working-tree sweep ok (65 Go files). Three mutations on the new rule, three killed. Refs #41 Refs #47 Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/go.yml | 27 +++++++++---- sweep_bypasses_test.go | 48 +++++++++++++++++++++++ workcontext/README.md | 85 +++++++++++++++++----------------------- 3 files changed, 104 insertions(+), 56 deletions(-) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 743aa9e..6882868 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -114,15 +114,26 @@ jobs: # The script lists by prefix and asserts SWEPT AGAINST WHAT THE REMOTE # PUBLISHES, because a gate that sweeps a subset and reports success is # worse than no gate — the green is the evidence somebody acts on. - # SWEEP_BASE is passed EXPLICITLY, from the repository's own default - # branch, so the sweep does not have to guess which ref this change - # merges into. Guessing is what made this check answer differently on a - # push build than on a pull_request build: GITHUB_BASE_REF is empty on - # a push, the base fell through to the pushed branch, and the default - # branch — which carries the implementation until this merges — was - # swept and failed. + # SWEEP_BASE is passed EXPLICITLY, so the sweep does not guess which + # ref this change merges into. Guessing is what made this check answer + # differently on a push build than on a pull_request build: + # GITHUB_BASE_REF is empty on a push, the base fell through to the + # pushed branch, and the default branch — which carried the deleted + # implementation until that change merged — was swept and failed. + # + # THE PR'S OWN TARGET FIRST, the default branch only as the fallback. + # This read the default branch unconditionally, which is right for + # every pull request this repository has had and wrong for one + # targeting anything else: a PR into a release branch would have had + # that branch SWEPT — failing circularly on content the PR is changing + # — while the default branch was excluded and went unchecked. Both + # halves of that are wrong and they hide each other. + # + # `github.base_ref` is set on a pull_request and empty on a push, which + # is exactly the distinction needed: a push build has no base, and for + # it the default branch is the right answer. env: - SWEEP_BASE: ${{ github.event.repository.default_branch }} + SWEEP_BASE: ${{ github.base_ref || github.event.repository.default_branch }} SWEEP_HEAD: ${{ github.head_ref || github.ref_name }} run: | git fetch --no-tags --prune --quiet origin \ diff --git a/sweep_bypasses_test.go b/sweep_bypasses_test.go index 5c9e1ae..89f84d0 100644 --- a/sweep_bypasses_test.go +++ b/sweep_bypasses_test.go @@ -799,3 +799,51 @@ func TestTheBaseRefIsTheDefaultBranchWhicheverEventRan(t *testing.T) { require.Contains(t, explicit, "origin/main", "with badges named as the base, main is an ordinary published ref again") } + +// THE WORKFLOW MUST SWEEP THE PR'S OWN TARGET, not whatever the default branch +// happens to be. +// +// `SWEEP_BASE` read `github.event.repository.default_branch` unconditionally. +// That is right for every pull request this repository has had and wrong for +// one targeting anything else — a release branch, say. Then the release branch +// would be SWEPT, failing circularly on the very content the PR is changing, +// while the default branch was excluded and went unchecked. Both halves are +// wrong and they conceal each other: the red looks like a finding and the +// unchecked branch looks clean. +// +// `github.base_ref` is set on a pull_request and empty on a push, which is the +// distinction needed — a push build has no base, and the default branch is the +// right answer for it. +// +// WHAT THIS TEST CAN AND CANNOT DO, stated because the gap is the point: a +// GitHub expression is evaluated by GitHub, not here, so this asserts the +// expression's SHAPE — that the PR's target is consulted before the default +// branch. The script's behaviour given a base is covered by +// TestTheBaseRefIsTheDefaultBranchWhicheverEventRan, which drives both event +// shapes. Neither test runs the expression; a reviewer executed the old and new +// forms externally to establish that. +func TestTheSweepsBaseIsThePullRequestsOwnTarget(t *testing.T) { + workflow, err := os.ReadFile(filepath.Join(".github", "workflows", "go.yml")) + require.NoError(t, err) + + var base string + for _, line := range strings.Split(string(workflow), "\n") { + if key, value, found := strings.Cut(strings.TrimSpace(line), "SWEEP_BASE:"); found && key == "" { + base = strings.TrimSpace(value) + break + } + } + require.NotEmpty(t, base, "the sweep step passes no SWEEP_BASE, so it is guessing again") + + require.Contains(t, base, "github.base_ref", + "SWEEP_BASE must consult the pull request's own target. Reading only the default "+ + "branch sweeps the real target of a PR aimed anywhere else — failing circularly "+ + "on the content that PR is changing — and excludes the default branch, which then "+ + "goes unchecked.") + require.Contains(t, base, "default_branch", + "and must fall back to the default branch, because a push build has no base at all — "+ + "which is the bug this replaced") + require.Less(t, strings.Index(base, "github.base_ref"), strings.Index(base, "default_branch"), + "the order is the whole rule: the PR's target FIRST, the default branch only as the "+ + "fallback. Reversed, every pull request would sweep its own target.") +} diff --git a/workcontext/README.md b/workcontext/README.md index 2a3ba25..54b01ba 100644 --- a/workcontext/README.md +++ b/workcontext/README.md @@ -853,54 +853,43 @@ root: ./scripts/check-one-implementation.sh ``` -### This module merges on a pseudo-version, deliberately - -`workcontext/go.mod` pins **`v0.7.2-0.20261003161926-4051e4383635`** — core at -`4051e438` — and not a release tag, because there is no tag to move to and will -not be one before this merges. Core's `version/info.codefly.yaml` already says -`0.8.0`, but its `version-tag.yml` cuts the tag from the **merge commit** of -core#692 and core never tags by hand; core#692 is still open on two owner -decisions. The latest published core tag is `v0.7.1`, dated 2026-09-30. - -**What `v0.7.1` does and does not carry**, because the imprecise version of this -("no released core has the Work Context") is both wrong and misleading — it -invites the answer "then nothing is worse off by waiting". Measured against the -tag, not inferred: - -| At `v0.7.1` | | -| --- | --- | -| present | `Authority.Start`, `Verifier.Verify`, `Verified`, `Grant`, `replay.go`, `scope.go` — an **unsealed** mint and verify | -| absent | `Seal` and `SealSource`, so no installation, epoch or build binding and no revocation through the seal | -| absent | `Inspect` — the structural entrypoint this module reads every capability through | -| absent | `Verifier.Recheck` — the non-consuming re-check the stream guard is built on | -| absent | `Authenticator` — the verify-only entrypoint | -| absent | `MaxTTLCeiling` — `v0.7.1` has the unbounded-lifetime behaviour this module measured and refused to trust | -| absent | **the whole `workcontext/conformance` directory** — the kit this module's suite runs, and the import gate that proves it did not reimplement core | - -So the accurate statement is: **there is no released core carrying the sealed -capability, the conformance kit, or either verification entrypoint as they now -stand.** A consumer that needs only unsealed mint-and-verify has `v0.7.1` and is -not blocked. A consumer that owes an import gate — which is every consumer of -this contract — has nothing tagged to run it against. - -A pseudo-version is a *pin*, not a workaround: it is reproducible for anyone who -checks the branch out, and `go.sum` records the hashes. What it is not is a -release line, which matters for a consumer in two ways: - -- **`go get -u` will not move you off it**, and nothing will warn you. The pin - is the version. -- **Do not cut a consumer release whose only Work Context dependency is this - pseudo-version** unless you accept the same unreleased dependency. Pinning - this module from a branch is fine for integration; publishing it as stable is - the thing to hold. - -Core will say when `v0.8.0` lands. The follow-up is a one-line `go get` and a -`go mod tidy` in this module, and it is **not** a merge precondition for this -pull request: holding a finished change behind somebody else's release is how a -branch rots, and the pin names an exact commit either way. - -What the tag will add is nothing functional — `67ee7220` is the content — so -re-pinning is bookkeeping, not an upgrade. +### This module pins core `v0.9.0`, and is itself consumed at a main commit + +`workcontext/go.mod` pins **`v0.9.0`** — a release tag, cut from the merge commit +of core#692, which is the change that made the capability sealed and gave this +module `Inspect`, `Verifier.Recheck`, `SealSource` and the conformance kit. + +**Two earlier tags are a trap worth naming.** Core's `main` cut `v0.8.0` and +`v0.8.1` from its own release commits while core#692 sat unmerged, so neither +carries it. They are not missing `workcontext` — the package is there at its +pre-#692 revision, which is worse than absence: pinning `v0.8.0` compiles +nothing this module uses and fails on `Seal`, `SealSource`, `OperationBinding` +and `ErrNotACoreToken`, so the failure reads like a bad pin rather than a stale +one. If you are bisecting core versions under this module, `v0.9.0` is the +floor. + +**This module itself has no tags**, and that is the pseudo-version that matters +to a consumer now. `github.com/codefly-dev/sdk-go/workcontext` has never been +released, so `go get` resolves a pseudo-version of a **main commit** — a pin: +reproducible, hash-recorded in `go.sum`, and not a release line. Two +consequences: + +- **`go get -u` will not move you off it**, and nothing warns you. The pin is + the version. +- **Think before cutting a stable consumer release on it.** Pinning this module + at a main commit is fine for integration; publishing something stable on top + of an unreleased module means owning that choice deliberately. + +**The retraction in `workcontext/go.mod` waits on the same thing.** It retracts +every pseudo-version of this module published before the second implementation +was deleted, and a `retract` directive takes effect only once a version carrying +it is itself released. Until this module is tagged, that retraction is a +statement of intent in the file rather than something `go get` enforces. + +Whether a `workcontext/vX.Y.Z` tag is cut, and when, is a **release-process +decision for whoever owns releases here** — not something this module does by +hand, and not something to infer from this file. What is recorded here is only +what a consumer needs in order to know what they are pinning. ## The wire