v0.3.1 — assessment backlog A
A correctness and developer-experience patch from a whole-framework
assessment. No BREAKING changes. Twenty fixes, all with regression tests;
the recurring theme was converting silent wrong answers into correct
behavior or loud errors.
Security
- Codegen and blueprints no longer drop
OwnerField.renderEntityRegistration
emitted every scope flag exceptOwnerField, and the blueprint YAML allow-list
rejectedowner_fieldoutright — so generated/blueprinted apps silently lost the
per-user row scoping the docs hard-warn about. Both paths now preserve it. - Streaming list can no longer bypass
AfterListredaction.?stream=true
skipped include resolution and theAfterListhook; anAfterListredactor would
have been silently bypassed, leaking the fields it exists to hide. An explicit
stream with?include=or a registeredAfterListhook is now refused with400;
an auto-stream (very largelimit) falls back to the buffered path so redaction
always runs. GOFASTR_HARNESS_MACHINE_KEYno longer silently downgrades. Only a raw 32-byte
value was accepted; a hex or base64 key failed the length check and fell through to
the default passphrase with no warning. The env var now decodes raw-32/hex-64/base64
and errors loudly on an unparseable or wrong-length value.- The OpenAPI spec advertises
401/403on RBAC-gated, batch, and SSE operations.
EntityConfig.Accessis folded into the gated flag and403is added alongside
401, so generated SDKs/agents see the real auth contract instead of treating
RBAC-gated routes (and_batch/_events) as public. - The
UpsertOneDO-NOTHING fallbackSELECTnow applies tenant/owner/soft-delete
scope (defense-in-depth;upsertPreflightalready guarded the row).
Fixed
updated_atis restamped on every UPDATE and bulk update. It previously froze
at its creation value because the field loop skips all auto-generate columns; cache
invalidation and change detection silently saw stale timestamps. Clients still
cannot forge it.ADD COLUMNfor a required field with no default no longer emitsNOT NULL.
That DDL fails on a populated table (Postgres and old SQLite); the column is now
added nullable with the deferral noted in the change summary (matches the kiln path).App.InTxjoins an ambient transaction already in the context (e.g. when called
from a CRUD hook) instead of silently opening a second independent transaction and
breaking atomicity.- DSL
after(cursor)is wired intoBuildDSLQuery. It was parsed and discarded,
so DSL pagination always returned page 1. Composite-cursor/unknown-field entities now
return a clear error instead of no-oping. - LiveSearch debounce works. The emitted attribute (
data-fui-rpc-debounce) did not
match what the runtime reads (…-ms); debounce was silently ignored. - Widget dismiss closes its
EventSources instead of leaking a live server SSE
connection on every modal open/close. - Signal ARIA is text-mode only.
role=status/aria-liveis no longer applied to
attribute- or html-mode signal nodes (invalid ARIA + live-region spam on island swaps). - Carousel timers and the toc
IntersectionObserverare torn down on SPA navigation
instead of leaking for the session. RedisQueueimplementsBrowsable(ListJobs/Statsover the dead-letter list),
so the admin queue page works on the most common non-DB production backend.- Scheduler enqueue failures log via
sloginstead offmt.Printf, surfacing
otherwise-invisible job loss to the log battery/observability. MemoryQueuehandler timeout is configurable viaWithHandlerTimeout(default
unchanged at 30s) so long jobs aren't silently cancelled and dead-lettered.- Per-page Open Graph/meta beats the global default. Per-screen SEO is emitted before
the sitewideWithOpenGraphtags, so first-match crawlers honour the page override. gofastr new entityandgenerate entityagree on table naming (singular
snake_case, matching the framework default) so migrations target one table.- Built-in harness profiles are embedded (
go:embed, on-disk-wins fallback), so
gofastr harness --frameworkworks for an installed binary outside the source tree.
Documentation
- Corrected the
access.Policyinterface in the docs from a non-existent 3-arg
Can(ctx, permission, resource)to the real 2-argCan(ctx, permission)(custom
policies following the docs failed to compile), and documented that per-record
decisions are made viaOwnerFieldscoping orBefore*hooks. A compile-time
assertion now pins the doc to the interface. The Go interface is unchanged. - Documented the streaming/
AfterListexclusivity,App.InTxambient-tx joining,
theADD COLUMNNOT NULLdeferral, and corrected the staleupdated_athook
comment inmigrate.go.