Skip to content

Fail closed on the release this run publishes, and name every release it skipped - #106

Merged
iderex merged 1 commit into
mainfrom
posture/name-every-release-the-run-skipped
Aug 12, 2026
Merged

Fail closed on the release this run publishes, and name every release it skipped#106
iderex merged 1 commit into
mainfrom
posture/name-every-release-the-run-skipped

Conversation

@iderex

@iderex iderex commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

decisions/failure-posture.md draws the line between what stops a run and what
is skipped by name, and nothing applied it at the release level. This does.
It is the body of #28 and does not close it; the clause that is left is below.

The line, and why it is where it is

A published release is immutable. A release from years ago that shipped no
archive, or a descriptor nothing can parse, will be missing it forever, so a
run that stops over it freezes the catalogue permanently. A defect in the
release this run is trying to publish is this project's own mistake from today
and is worth stopping for.

So the classification turns on one question, which release is the newest for its
plugin and channel, and the answer has to exist before any descriptor is read.
It is the publication time. The tag cannot answer it, because
decisions/manifest-schema.md refuses the tag as a version string.

The two clauses of the Done-when this holds

The first, with the defect in a release nobody can repair:

go test ./internal/posture -run TestAnUnusableHistoricalReleaseIsSkippedByNameAndTheRestArePublished -count=1 -v
=== RUN   TestAnUnusableHistoricalReleaseIsSkippedByNameAndTheRestArePublished
--- PASS: TestAnUnusableHistoricalReleaseIsSkippedByNameAndTheRestArePublished (0.00s)
PASS
ok  	flowfin.dev/hub/internal/posture	0.342s

The second, the same missing file moved to the release the run exists to
publish:

go test ./internal/posture -run TestADefectInTheNewestReleaseStopsTheRun -count=1 -v
=== RUN   TestADefectInTheNewestReleaseStopsTheRun
--- PASS: TestADefectInTheNewestReleaseStopsTheRun (0.00s)
PASS
ok  	flowfin.dev/hub/internal/posture	0.352s

Both fixtures plant their defect by taking away a file a release would have
shipped rather than by inventing a condition, and the asymmetry itself is held
in one test that runs three defects through both positions, so a change
collapsing the two sides cannot pass by moving a fixture.

The third clause, a run that resolves zero plugins, was already held at the
layer above by sources.Judge and is pinned by
TestARunThatResolvesNothingIsFatal in internal/sources. Nothing here
changes it.

The four guards, each watched refusing something

Every defect treated as a skip, which is the version of this that never stops
anything:

-		newest := i == 0
+		newest := false

go test ./internal/posture -run TestADefectInTheNewestReleaseStopsTheRun -count=1
--- FAIL: TestADefectInTheNewestReleaseStopsTheRun (0.00s)
    posture_test.go:128: a defect in the newest release produced 0 stops: []
FAIL	flowfin.dev/hub/internal/posture	0.379s

The newest taken as the first element the API answered with, which is what a
run that skipped the ordering would do:

-		return newerFirst(ordered[i], ordered[j])
+		return false && newerFirst(ordered[i], ordered[j])

go test ./internal/posture -run TestTheNewestIsThePublicationTimeRatherThanTheOrderTheApiAnswered -count=1
--- FAIL: TestTheNewestIsThePublicationTimeRatherThanTheOrderTheApiAnswered (0.00s)
    posture_test.go:177: the newest release was decided as something else: [] [a-plugin 1.0.0-stable: no-usable-sidecar: no asset's contents name a-plugin_3.0.0.0.zip]
FAIL	flowfin.dev/hub/internal/posture	0.540s

A read that did not happen treated as a release that cannot be published:

-		case err != nil:
+		case false && err != nil:

go test ./internal/posture -run TestAReadThatDidNotHappenStopsTheRunWhereverItWas -count=1
--- FAIL: TestAReadThatDidNotHappenStopsTheRunWhereverItWas (0.00s)
    posture_test.go:215: a read that failed produced 0 stops: [] []
FAIL	flowfin.dev/hub/internal/posture	0.484s

The cap applied across the list rather than per target, which is the shape
decisions/version-cap.md exists against:

-	kept := manifest.CapPerTarget(versions, manifest.Cap)
+	kept := versions
+	if len(kept) > manifest.Cap {
+		kept = kept[:manifest.Cap]
+	}

go test ./internal/posture -run TestTheCapIsPerTargetSoASlowLineIsNotPushedOff -count=1
--- FAIL: TestTheCapIsPerTargetSoASlowLineIsNotPushedOff (0.00s)
    posture_test.go:295: the older target line kept 0 entries: [...]
FAIL	flowfin.dev/hub/internal/posture	0.361s

All four run 2026-08-12 and reverted afterwards. The last output is trimmed
where the entry list starts; the full line prints all five kept entries.

What the suite covers, and what it reaches

go test ./internal/posture -count=1 -cover
ok  	flowfin.dev/hub/internal/posture	0.365s	coverage: 89.6% of statements

Every fixture is invented, the only host in one is the domain reserved for
documentation, and the bytes arrive through the Fetch a caller supplies, so
nothing in this suite leaves the runner.

The gate

go clean -testcache
go run . gate
gate examined 14 of 14 legs.
  build                           passed
  test                            passed
  format                          passed
  editorconfig                    passed
  tests-reach-nothing             passed
  no-hardcoded-names              passed
  site-fetches-nothing-outside    passed
  site-links-resolve              passed
  coverage                        passed
  site-declares-its-language      passed
  install-address-is-answered     passed
  focus-stands-off-every-surface  passed
  page-matches-the-token-file     passed
  pr-hygiene                      passed

Run 2026-08-12 at the commit this branch carries.

The means

Go, the tree's own, for the reasons in decisions/means.md. No dependency is
added.

What is not in this change

There is no verb that runs any of it, so the report this produces is text no
run prints yet. The pipeline that would call it, place the bytes and exit
non-zero is #31, and until that exists the first clause of #28's Done-when is
met as a classification and not as a published file. That is why this does not
close the issue.

The plugin-level assembly is also not here. Reading a plugin's identity is
internal/identity and ordering plugins into the file is manifest.Select;
this package answers for one plugin's releases and returns the entries.

Nobody has read this but me. The commands and the red output above stand in
place of a second reader rather than claiming one.

… it skipped

decisions/failure-posture.md draws a line and nothing applied it at the release
level. A defect in the newest release for a plugin and channel is a build
published wrong today and stops the run. The same defect in a release nobody
can repair is skipped, named, and does not stop anything, because a published
release is immutable and stopping over one converts somebody else's old mistake
into this project's standing outage.

This applies that line. It orders a plugin's releases by publication time,
because the answer has to exist before any descriptor is read and
decisions/manifest-schema.md refuses the tag as an order. It attempts every
release rather than stopping at the first defect, so three broken releases take
one run to find rather than three. It reports what became of every release,
including a plugin that published nothing, because a manifest that is short
because releases were skipped and one that is short because there was nothing
to add are the same file.

A read that did not happen is not a release that cannot be published. The two
are told apart by the type of the error and a transport failure stops the run
wherever it happened, which is the one case the asymmetry above does not apply
to: its symptom is a short list, and a short list looks exactly like success.

A release trimmed by the per-target cap is named as trimmed rather than as
defective, so a run that reports a capped release the same way it reports a
broken one does not teach everybody to ignore both.

Part of #28, which also asks for a run to publish from.

Signed-off-by: Nils Lehnen <[email protected]>
@iderex
iderex merged commit 65766c9 into main Aug 12, 2026
36 checks passed
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