AppendTaskExecutionLog discarded the error from its read of the buffer, so a
transient cache failure looked like an empty buffer and the very next write
replaced the whole accumulated log with just the newest line. Flush already
distinguished miss from failure; append now does the same.
GetExecution returned (nil, nil) under the in-process driver where the asynq
driver returns an error, so the same contract call meant 'empty' in one
deployment mode and 'failed' in the other.
Authenticate, CreateAuthSource, UpdateAuthSource and ToggleAuthSource claimed
success with a nil record, so any test that reached them surfaced a nil
pointer dereference instead of the actual cause. Full suite confirms no test
relied on the silent behaviour.
RunPushTest flattened channel validation failures with %v, telegram's
fallback path discarded the original send error, and the config loader's
type assertion on viper.ConfigFileNotFoundError would miss a wrapped form
and fatally abort over a merely missing file. errorlint now reports zero.
loadTaskExecutionLog and loadTaskExecutionLogs could never fail, yet four
call sites branched on their error as if they could, presenting unreachable
code as error handling.
bodyContent's fallback ranged over the body map, and Go randomizes map
iteration, so the same notification rendered its fields in a different order
on every send. Observed failing before the fix: the second call already
reordered the output. Iterate sorted keys instead.
email, telegram and lark each re-implemented the title/content/level lookup
with only their markup differing, and each carried a dead content := ""
initialization that every branch overwrote. Three small helpers in template.go
now own that logic.
parsePositiveInt reported invalidity through a bool that both call sites
discarded, and returned (false, nil) whenever Atoi succeeded on a negative
number. GetLogs therefore accepted ?cursor=-5 and served it as cursor 0
('latest') instead of the documented 400. Validity now travels through the
error result, which no caller can ignore.
Four long-running goroutines were launched with a bare go statement, so a
panic in any of them took down the whole process: the RAM cache's expired-key
eviction, the batch writer's flush worker, the disk cache cleanup worker and
the PoW memory store sweeper. Route them through util.Go.
The architecture gate only grepped for 'go func(', which is why the named-call
form went unnoticed; widen it to cover both launch styles.
EnsureCompressedImageCache passed the arriving caller's request context into
the singleflight body, which runs once for every concurrent requester of that
cache key. If the first client disconnected, gin canceled the context, the
shared generation aborted, and every follower received that failure and fell
back to the uncompressed original. Detach cancellation with
context.WithoutCancel so trace values still propagate but the shared work
outlives any single requester.
ServeFileByID and DownloadFile duplicated the lookup failure mapping and
each used an unchecked *strconv.NumError assertion that cannot match a
wrapped error. One helper now classifies 404 vs 400 via errors.As; each
endpoint keeps its own fallback for unclassified failures. ErrInvalidUploadID
became unused once both sites report ErrInvalidFileID for a malformed ID.
Five sites used == against sentinels (redis.Nil, ingest.ErrForbidden,
errs.ErrDatabaseUninitialized). The neighbouring not-found checks already
went through errors.Is helpers, so a wrapped error would silently downgrade
a 403 to a 400 and a 500 to a 400.
Using2/Using3 flattened per-dependency injection failures with %v while
Using1 wrapped with %w, so errors.Is could not see ErrServiceNotFound
through a multi-dependency resolution failure.
The committed golangci gate now reports 0 issues, so the previous
lint_issues metric was saturated and could no longer measure progress.
Measure debt against an immutable .auto/lint.ref.yaml snapshot that adds
analyzers for genuine defects (panics, error unwrapping, dead stores,
missing enum cases, method ordering, suppression hygiene) while excluding
cosmetic churn (tagliatelle, wrapcheck). Guard enforces build, vet, tests,
the Cordis architecture gate, and anti-cheat floors: the yardstick cannot
be edited, the project gate may only be strengthened, nolint directives may
only shrink, and no test may disappear.
The Dockerfiles and the release workflow still assumed the pre-refactor tree:
go.mod, go.sum and main.go now live under backend/, so `COPY go.mod go.sum`
failed outright, and the Go module is the bare `Wavelet`, so the buildinfo
ldflags pointed at github.com/Rain-kl/Wavelet and stamped nothing. The
frontend export was also overlaid onto ./plugins/... rather than the
driver_http path that `//go:embed all:dist` actually reads.
Repoint every packaging path at backend/, correct the ldflags module prefix,
and assert dist/index.html in both docker stages, in build-embedded and in the
release workflow, so a silently empty static export can no longer ship an
API-only binary. Also carry pnpm-workspace.yaml into the cached install layer.