Skip to content

Bump uuid, gate publishes per-registry, and stop Go caching the corpus - #194

Merged
brentrager merged 3 commits into
mainfrom
fix/stream-and-cache
Aug 20, 2026
Merged

Bump uuid, gate publishes per-registry, and stop Go caching the corpus#194
brentrager merged 3 commits into
mainfrom
fix/stream-and-cache

Conversation

@brentrager

Copy link
Copy Markdown
Contributor

Three small fixes from a cross-repo sweep, plus a verified negative on the stream question (below).

1. uuid 9.0.1 → 11.1.1

Advisory: moderate, missing buffer bounds check in v3/v5/v6 when buf is provided, patched in >=11.1.1.

Not reachable here. Logger's only call sites are uuidv4() with no arguments (src/Logger.ts:189, :573); v3/v5/v6 are never imported and no buf is ever passed. But the advisory propagates to every consumer — @smooai/config traced it through three separate paths into its tree and had to add a pnpm.overrides pin. Fixing it at the source is cheaper than N overrides. @types/uuid dropped; uuid 11 ships its own types.

2. Publish steps gate on the registry, not on npm's success

if: steps.changesets.outputs.published == 'true'   # ← on PyPI, crates.io, Go tag, NuGet

That output is true only when the publish command shipped something in that run. So a run that dies after npm leaves the other four behind — and the next run, with no changesets left, reports published=false, skips all four, and goes green having published nothing.

This happened here today. 4.5.1 and 4.5.2 reached npm and PyPI while crates.io, NuGet and the Go tag sat at 4.5.0, across runs that reported success. It only got caught because I compared the live registries by hand.

Each step now asks its own registry whether the version is already there:

PYPI=$(behind "$(curl -sfL https://pypi.org/pypi/smooai-logger/json || true)" '.releases | has($v)')
CRATES=$(behind "$(curl -sfL -H '...' https://crates.io/api/v1/crates/smooai-logger/versions || true)" '[.versions[].num] | index($v)')
NUGET=$(behind "$(curl -sfL https://api.nuget.org/v3-flatcontainer/smooai.logger/index.json || true)" '.versions | index($v)')
git ls-remote --exit-code --tags origin "refs/tags/go/v${VERSION}"

Side benefit: a re-run now heals a partial release instead of skipping it.

The lookup fails toward publishing — a garbled or failed response counts as "behind". Every path below is duplicate-tolerant (--skip-duplicate on NuGet) or version-guarded (the Go tag), so a redundant attempt is cheap while a skipped one loses a release.

Verified against the live registries:

version pypi crates nuget go-tag
4.5.3 (real) not behind not behind behind ¹ not behind
9.9.9 (fake) behind behind behind behind
(empty response) behind

¹ NuGet accepted the 4.5.3 push (HTTP 201, both .nupkg and .snupkg) but its flat-container index still 404s ~50 min later — nuget.org validation latency, not a failure. This is precisely the case --skip-duplicate plus fail-toward-publish handles gracefully.

3. go:test gains -count=1

Go's test cache does not invalidate on fixtures read from outside the package dir, and go/parity_corpus_test.go reads ../parity-corpus.json.

I checked before changing anything, and Go is currently safe — it refuses to cache that test at all. Positive control:

TestLevelString    (reads nothing outside the pkg)  → ok  (cached)   ← on re-run
TestParityCorpus   (reads ../parity-corpus.json)    → ok  0.296s
                                                    → ok  0.284s     ← never cached

I also re-ran the #183 corpus positive control: corrupting the placeholder still fails Go. So the Go half of that corpus proved what it claimed. -count=1 costs nothing and means a five-language parity guarantee no longer rests on that behaviour.


Verified negative: no single-read() and no hand-rolled stream mocks

Checked all five languages for the @smooai/file defect class. Logger is clean, and structurally so — it is a log producer; it never consumes a stream. There is no read(), ReadAll, read_to_end, or .Read( anywhere in any port (the .read() grep hits are iterator .next() and RwLock::read()).

Every write path uses a loop-until-done primitive:

port write path
TypeScript process.stdout.write + the rotating-file-stream library — Node buffers, never truncates
Python stdlib RotatingFileHandler / TimedRotatingFileHandler, sys.stdout.write
Rust write_all in both places (logger.rs:449, rotation.rs:87) — never bare write
Go io.WriteString; rotation.go:100 uses os.File.Write, whose contract returns a non-nil error when n != len(b), and the code checks it
.NET FileStream.Write(bytes, 0, bytes.Length) — writes all or throws

And no hand-rolled stream mocks. Every test writer is the real runtime type: Go bytes.Buffer (io.Writer), .NET StringWriter (TextWriter), Python io.StringIO, Rust tempfile::tempdir (real files). There is no vi.mock in the TS suite at all — CapturingLogger overrides the logger's own declared logFunc hook, which is not a stream. The only MagicMock is a Lambda context object.

🤖 Generated with Claude Code

https://claude.ai/code/session_0152bbE1veqfG1SVJdyLCBxC

Three unrelated-but-small fixes from a cross-repo sweep.

uuid 9.0.1 -> 11.1.1. The advisory (missing buffer bounds check in v3/v5/v6
when `buf` is provided) is NOT reachable: logger calls `v4()` with no arguments
and never v3/v5/v6. But every consumer inherits the advisory and has to carry
its own pnpm.overrides pin — config already did — so fixing it at the source is
cheaper than N overrides. @types/uuid removed; uuid 11 ships its own types.

Publish steps no longer gate on `steps.changesets.outputs.published`. That
output is true only when the publish command shipped something IN THAT RUN, so
a run that died after npm left the other four registries behind — and the next
run, with no changesets left, reported published=false, skipped all four, and
went GREEN having published nothing. Not theoretical: 4.5.1 and 4.5.2 stranded
crates.io, NuGet and the Go tag at 4.5.0 exactly this way today, across runs
that looked fine. Each step now asks its own registry whether the version is
already there, so a partial release heals on re-run instead of being reported
as a success.

The lookup fails TOWARD publishing: a garbled or failed response counts as
"behind". Every path below it is duplicate-tolerant (NuGet `--skip-duplicate`)
or version-guarded (the Go tag), so a redundant attempt is cheap while a skipped
one loses a release. Verified against the live registries at 4.5.3 (pypi/crates/
go-tag correctly not-behind), at a fake 9.9.9 (all behind), and on an empty
response (behind).

go:test gains -count=1. Go currently declines to cache parity_corpus_test.go
because it reads ../parity-corpus.json from outside the package dir — confirmed
with a positive control: an in-package test reports "(cached)" on re-run while
the corpus test never does. So the #183 corpus positive control was valid. But
a five-language parity guarantee should not rest on that behaviour.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0152bbE1veqfG1SVJdyLCBxC
@changeset-bot

changeset-bot Bot commented Aug 20, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 40b3a97

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@smooai/logger Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

brentrager and others added 2 commits August 20, 2026 15:10
Caught by the format:check this PR's sibling added — my local run predated
writing the file. Working as intended.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0152bbE1veqfG1SVJdyLCBxC
Caught before merge: the changesets action leaves the workspace checked out on
the version branch, so in a run that only OPENS the version PR, package.json is
already bumped to a version nothing has published. The lag check would have read
that bumped version, found every registry "behind", and shipped PyPI/crates.io/
NuGet/Go-tag ahead of npm from an unmerged branch. The old
`published == 'true'` gate happened to prevent that; my replacement did not.

npm is now the explicit source of truth: if npm does not have this version, there
is nothing to backfill and all four steps skip. If npm does have it — either
because ci:publish just shipped it, or because an earlier run did — each
registry is asked individually. That keeps the original fix (a partial release
heals on re-run) without ever publishing ahead of npm.

Verified both directions: 4.5.3 -> npm has it, proceed; 9.9.9 -> npm lacks it,
skip all four.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0152bbE1veqfG1SVJdyLCBxC
@brentrager
brentrager merged commit 96efa6b into main Aug 20, 2026
1 check passed
@brentrager
brentrager deleted the fix/stream-and-cache branch August 20, 2026 19:14
brentrager added a commit that referenced this pull request Aug 20, 2026
)

#194 made each registry publish gate on "does npm already serve this version",
to stop a mid-run failure silently stranding four registries. It reintroduced
the same failure through a different door: npm registry READS are eventually
consistent, so `npm view @smooai/logger@4.5.4` moments after a successful
publish still 404s. The gate concluded "not released", skipped PyPI, crates.io,
the Go tag and NuGet, and the run went green — leaving 4.5.4 on npm alone.

Two independent signals now count as released, because neither alone is enough:

  - changesets published it in THIS run (steps.changesets.outputs.published).
    Authoritative the instant it happens, no registry read involved. This is the
    normal path and the one the race broke.
  - npm already serves it. The backfill path, which is what lets a re-run catch
    up registries an earlier run left behind — the whole point of #194.

Only when neither holds do the four steps skip, which is correct: that is a run
that merely opened the version PR, where package.json is bumped to something
nothing has published and the workspace sits on an unmerged branch.

Deliberately NO changeset. With package.json staying at 4.5.4, merging this
takes the backfill path and ships 4.5.4 to the four registries it is missing
from. A changeset would bump to 4.5.5 and strand 4.5.4 permanently.

Verified all three states by hand: published-this-run -> proceed even while npm
404s; npm-serves-it -> proceed; unreleased 9.9.9 -> skip all four.


Claude-Session: https://claude.ai/code/session_0152bbE1veqfG1SVJdyLCBxC

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
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