v0.5.0 — first-contact remediation (BREAKING auth)
The first-contact release: an adversarially-verified 10-dimension audit
(2026-06-09) found the engine strong but the first-touch surface broken —
the README quickstart failed verbatim, the flagship example shipped
insecure, RBAC was unreachable from the blueprint, and CI was red on every
release tag. Everything below closes those findings. Contains a BREAKING
auth change (see below); pin a version (go get …@v0.5.0).
Changed
- BREAKING:
battery/authfails closed on an emptyJWTSecretin
production. AnAuthManagerwithDevMode=falseand noJWTSecret
now makesInitreturn an error (the app refuses to boot) instead of
warning and continuing — an empty HMAC key yields forgeable,
restart-unstable JWTs. The error names the remedy (set
AuthConfig.JWTSecret, orDevMode: truefor local HTTP). The
blueprint path rejectsapp.authwithdev_mode: falseand no
jwt_secretatgofastr validate/generatetime, so a generated app
can't be built into the broken state. Migration: set
AuthConfig.JWTSecretfrom your secret store (most prod apps already
do);DevModeis unchanged and still mints a per-process secret.
Added
-
Blueprint
access:key — per-operation RBAC fromgofastr.yml. An
optionalaccess:map on an entity (read/create/update/
delete, each a permission string) threads through
EntityDeclaration.Accessinto the generatedregister.goas
framework.AccessControl{…}, where the existing CRUD chokepoint enforces
it fail-closed (403). Fully additive: blueprints without the key produce
byte-identical output. Closes the audit's "one leg of the secure-by-default
triad is unreachable from the primary declaration format". Also re-exports
framework.AccessDeclarationfor symmetry withEntityDeclaration. -
gofastr validate <blueprint.yml>. Parse + full blueprint validation
(including the module/go.mod coherence check, a render pass, and an
entity-name check that rejects names whose generated Go identifier would
not compile, e.g.2fa_tokens) without generating; exit 0/1 with
agent-friendly file:line + remedy diagnostics.--from,--config, and
--outongofastr generatenow also accept the space form
(--from x.yml), which previously silently did nothing. -
unscoped-piilint — CLAUDE.md hard rule #6 in the toolchain. Flags
any auto-exposed entity (CRUD default-on ormcp: true) with PII-shaped
fields and noowner_field/access/multi_tenant. Enabling
app.authalone does not suppress it — the session middleware is
pass-through for anonymous requests (an adversarial review proved the
original auth-suppression wrong by anonymously reading user emails from
a generated example app). Error fromgofastr validate, prominent
warning fromgofastr generate, finding ingofastr audit lint.
Running it against the repo's own examples found and fixed shipped
exposures inblog,lms,real-estate,portfolio, and
project-manager— each now demonstrates a scoping pattern (RBAC-gated
staff rosters, public-catalog reads with gated writes, lead-capture
forms with open create and gated reads). -
Executable-README CI gate.
cmd/gofastr/readme_quickstart_test.go
extracts the README's quickstart blocks and runs them for real: blueprint
→ generate → build → boot →GET /posts200; plus drift gates (no
"unpublished"/replace-directive guidance anywhere in the embedded docs,
README blueprint relations must resolve). The audit found this single
gate would have caught five of its eight confirmed findings. -
App.OnReady(func(addr string))— lifecycle hook that fires after
the listener has actually bound (and is skipped on start failure).
Generated apps now print their startup banner from it, so a migrate
failure can no longer print "Server starting" and then exit 1. -
Trust surface.
SECURITY.md(private vulnerability reporting,
honest v0.x support policy),CONTRIBUTING.md(truthful prereqs: Docker,
Chrome, the test-isolation rules), and low-ceremony issue templates. -
Docs:
comparison.mdandtutorial-blueprint-app.md. An honest
head-to-head (PocketBase / Encore.go / Wasp / hand-rolled Gin+sqlc,
weaknesses included) and the missing thesis tutorial (blueprint →
generate → secure with auth +owner_field+access:→ customize in
plain Go → deploy), every step executed end-to-end before shipping.
Plus a README forexamples/ecommerce. -
In-package Postgres coverage for
framework/crud. The SQL-generation
core had 66 sqlite-only test files; a focused testcontainers suite now
exercises the representative paths (filters, sort, offset+COUNT, cursor
keyset walk, batch rollback, upsert, eager-load include, soft-delete,
owner+tenant fail-closed) against real Postgres. -
First tests for
framework/owner(14): fail-closed verified at the
HTTP gate (anonymous → 401 with no extractor), last-call-wins override
warns, OwnerField-unset is inert, forged clientuser_idoverridden,
race-checked. Two layer-level fail-open contracts (ApplyOwnerScope,
InjectOwnerwithout an extractor) are pinned with tests and comments
documenting thatrequireScopeupstream is the actual boundary. -
Common-mistakes callouts completed and gated. The docs claimed every
topic ends with one; 21 of 60 didn't. Real, code-verified callouts added
to the 15 guide docs (including entity-declarations, "the heart of the
model"); 6 data/index docs exempted with reasons; the claim text now
matches reality andTestGuideDocsEndWithCommonMistakesenforces it. -
Security ledger fully re-verified. All 103 SECURITY_FINDINGS.md rows
re-checked against current code: 102fixed(each with the mitigation
cited AND a named pinning test run and observed passing), 1accepted
(#58, an intentional accepted-risk documented in code), 0 unverified or
open. A guard test pins header-count == row-count and valid status
tokens (fixed/open/needs-verification/accepted). -
Coverage floors are a CI gate.
scripts/coverage-floors.shfails the
blocking job if any claimed package drops ~2 points below its measured
coverage. COVERAGE_NOTES.md now separates own-package numbers from the
full-suite-overlay numbers the old 100% claims were quoting.
Fixed
-
SECURITY:
EntityConfig.MaxListLimitcould be bypassed on two list
paths. The cursor path never consulted the entity cap (asked for ≤3,
served up to 100), and an oversized?limiton the offset/stream path
silently fell back to the default page size (20) — exceeding any cap
below 20. Both were hidden behind the security tests' auto-skip-on-500
heuristic; converting those skips to hard failures (an audit
recommendation) exposed them immediately. Oversized?limitnow clamps
to the effective cap on every path (listLimitCapshared by offset,
stream, and cursor), with regression tests un-skipped and green. -
crud security tests fail instead of skip on server errors. The
skip-on-redacted-500 heuristic (and its "SQLite can't run $N
placeholders" rationale, which was false — the 500s were fixtures
missingTable) is gone across the suite. -
battery/queuetests are deterministic. Thirteen sleep-based
assertions replaced with Close-drain semantics, boundedwaitFor
polling, and an unexported clock seam for lease/visibility expiry —
stable under-race -count=3; no production behavior change. -
Doc staleness found while verifying the new callouts:
widgets.md
described awidget.Mountreturn value and bootstrap route that don't
exist (rewritten aroundMountRuntime/RuntimeTag);
ui-new-components.mdcited three nonexistent drift gates (corrected to
the real ones);ui-getting-started.mdhad a non-compiling
DBFromContextsnippet and listedAPIPrefixas roadmap (it shipped).
cursor-pagination.mdnow documents the per-entity cap. -
Boot-time auto-migrate now adds missing columns.
AutoMigratedid
CREATE TABLE IF NOT EXISTSonly, while deploy.md claimed
"create tables, add columns" — adding a field to an existing entity
broke the next boot. It now reuses the existing schema-diff machinery
and applies the additive changes only (drops/renames/retypes stay
behindmigrate diff's destructive gate); required new columns are
added nullable, column adds run before index DDL, and a racing replica
re-reads live columns on the lock-holding transaction and no-ops.
Also fixes Postgres live-schema readers to case-fold unquoted table
names (mixed-case entities were mis-reported as missing every boot). -
Light color scheme now passes WCAG AA — and the axe gate can no
longer be platform-blind. The "Linux-only" CI axe failures were real:
Linux Chrome defaults toprefers-color-scheme: light, so CI audited
the light palette that Dark-mode dev Macs never did. Light primary/
accent/code-comment tokens retoned (worst offender was 1.71:1, now all
≥4.6:1), the framework'sDefaultThemestatus tones darkened so any
light-theme Badge/Tag chip passes AA, thegofastr theme initscaffold
updated to match, andTestAxe_AllPagesAreCleannow scans every page
under BOTH forced schemes. The browser-e2e CI job is blocking again. -
Generated auth honors
dev_modeand validates it strictly. The
generator hardcodedDevMode: true; the blueprint key now works, with
a deliberate default oftrue(production cookie posture —
__Host-session+Secure— never round-trips on the plain HTTP a
fresh app serves) announced in the generated code, thegofastr generateoutput, and the docs.dev_mode: yesis a hard error, not a
silent coercion to prod mode.auth.CSRFis deliberately not mounted
(it would 403 the JSON/MCP surface); the gap and the
SameSite=Strictmitigation are documented. -
Docs-site copy drift caught by visual review. The site header
advertised "pre-alpha 0.0.4" and the examples pages described blog as
"JSON-declared" — a format removed in v0.4.0. Version now lives in one
siteVersionconstant; blog is correctly described as Go-declared
(users/posts/comments). -
The README quickstart now runs verbatim — and CI enforces it. The
blueprint example was rewritten in block style (the in-house YAML parser
deliberately rejects flow mappings) with every referenced entity
declared; a long-unclosed code fence that inverted every subsequent
block is closed; stale "unpublished / add a replace directive" guidance
is gone (the module resolves on the proxy at v0.4.0); the
go test ./...claim now states its real prerequisites. -
Flow mappings fail with an honest error.
core/yamlnow says
flow mapping "{ ... }" is not supported; use block style …instead of
the misleading "nested mapping must be on an indented line"; flow-list
items that previously silently misparsed now error. -
Relation-typed fields are validated at generate time. A field like
author_id: {type: relation, to: users}pointing at an undeclared
entity now failsgofastr generate/validatewith a remedy, instead of
generating an app that exits 1 at startup. Blueprintmodule:that
contradicts the enclosinggo.moderrors with the exact fix. -
Generated apps with auth actually authenticate. The generator
enabled the auth battery but never mountedauth.SessionMiddleware, so
authorized requests got 401 like anonymous ones (found by dogfooding the
secured flagship). Generatedapp.gonow mounts it after
authMgr.Init; the flagship test asserts the full register → login →
create → owner-isolated list flow across two users. -
examples/ecommerceno longer ships insecure. Auth is enabled and
orders/order_itemsare owner-scoped (owner_field: user_id) —
previously any anonymous caller or MCP agent could read every customer's
PII and mutate orders, violating the repo's own hard rule #6.
BUILD_JOURNAL.mdkeeps the honest history. -
Deterministic CI.
core/staticMIME detection now consults its
canonical extension table before the host mime database, so Content-Type
is identical on macOS and Linux (the 6/6-failingTestDetectFromNameis
fixed at the detection layer, not the test);.js/.mjsserve the
RFC 9239 canonicaltext/javascript.ci.ymlis restructured into a
blocking deterministic job and an isolated, serialized browser-e2e job
(non-blocking until the known Linux-Chrome axe contrast discrepancy in
examples/siteis resolved — condition documented in the workflow). -
Docs drift purged.
overview.mdcore/ package count corrected
(twelve → eighteen);framework/ARCHITECTURE.md's layering map redrawn
to match the real import graph (openapiabovecrud, the
slowquery → dbedge,crud → access);core-ui/ARCHITECTURE.mdnow
states the real runtime size (~7,400 lines across budget-enforced split
modules, ≤12KB-gz core) instead of "a few hundred lines";
examples/README.mdgained the flagship row.
Changed
- Example blueprints declare their real module paths.
blog/lms/portfolio/project-manager/real-estatenow declare
module: github.com/DonaldMurillo/gofastr/examples/<name>(matching the
flagship), sogofastr validatepasses in-place inside the repo. - Repo-wide gofmt sweep + CI gate. 299 tracked files reformatted in one
mechanical pass; the blocking CI job now fails on any gofmt drift in
tracked Go files. - README repositioned around the wedge. Leads with "one blueprint
becomes a server-rendered UI and an API with secure scopes, in plain Go
you own"; MCP/OpenAPI demoted to supporting evidence (schema-derived MCP
became table stakes); validation-status block updated for the secured,
CI-gated flagship.