Skip to content

Releases: DonaldMurillo/gofastr

v0.8.1 — README rework

19 Jun 20:12

Choose a tag to compare

v0.8.1 — README rework

A docs-only patch. No code changes.

Changed

  • New "Why this exists" section — GoFastr is framed as a personal project first, with the motivations behind it: solidifying web-tech foundations, attacking UI generation from a compiled-language angle (the author's background is Node), working in a compiled language, skipping the convention-vs-configuration choice, building something large with AI, and making a framework that's AI-first on both the authoring and the consuming side.
  • Removed the framework comparison from the README — the PocketBase / Encore / Wasp / Supabase / FastAPI name-drops and the comparison.md link. (The comparison.md file is left on disk for now.)
  • Kiln demoted to a brief experimental mention — the ~60-line section became one paragraph linking to kiln.md, and it's no longer centerstage (dropped from "Built with GoFastr" and the install block; repo-layout line marked (experimental)).
  • Migrated the README-only Kiln detail into kiln.md (plan-gated destructive ops, full tool surface, Claude Code MCP wiring, HTTP tool-call example) so nothing was lost.
  • "dogfooded" → "runs on itself"; fixed the opaque "Walkthrough: the v2 read/write surface" heading → "Walkthrough: the read/write API".

Full details in CHANGELOG.md.

v0.8.0 — native gofastr export (SSG) + site repositioning

18 Jun 13:17

Choose a tag to compare

v0.8.0 — native gofastr export (SSG) + site repositioning

Added

  • gofastr export — native static-site generation. The framework now exports a deploy-ready static site itself, replacing the broken wget --mirror crawl used for the Pages deploy. The crawl baked cache-bust ?v=<hash> queries into on-disk module filenames; the static host stripped the query, every split runtime module 404'd, and all client interactivity silently died (theme toggle, command palette, copy, widgets). App.ExportStatic(ctx, dir, basePath) drives the app in-process, enumerates every declared route, renders each through the SSG path, and dumps all /__gofastr assets with query-free filenames.
  • Subpath base path (--export-base /<repo>) for GitHub Pages project URLs; the builder prefixes every root-absolute URL and bakes the prefix into runtime.js.
  • Runtime static-mode — a data-fui-static marker (stamped only at export time) no-ops every server-backed dispatch so disabled actions read as intentionally inactive, with a dismissible "run locally" ui.Banner. Client-only features (theme, copy, signals) are unaffected.
  • ui.CodeBlock.Scroll + ui.HighlightLines (framework/ui).
  • /examples Meridian row shows the real, full examples/meridian/gofastr.yml blueprint — embedded at build time, drift-guarded by a test, in a scrollable copyable block.
  • New framework/docs/content/static-export.md guide; pages.yml now runs site --export _site --export-base /gofastr.

Fixed

  • Sticky sidebars were silently broken site-wide. body { overflow-x: hidden } forced overflow-y to auto, turning <body> into a scroll container so every position: sticky descendant (docs + components sidebars, TOC, step-rail) anchored to the non-scrolling body and scrolled away. Guard moved to html.
  • Components-page sidebar wouldn't pin even after the above — the framework's sidebar <nav> wrapper is the grid column but didn't pass its height to the SectionMenu widget; it's now a flex column.
  • Header brand read as a doubled version (λ gofastr v0.x dev) — dropped the static tag; the badge shows one version (dev locally, v0.8.0 tagged).

Changed

  • Homepage + getting-started repositioned to lead with screens + blueprints (new "One file, a real app" section).
  • Kiln marked experimental across the site + docs.

Also in this release (post-tag hardening, included)

These landed after the v0.8.0 tag was first cut, so the tag was moved to the commit that includes them before this Release was published:

  • Homepage a11y — the new "real app" section's screen-mock URL failed color-contrast and its Meridian link failed link-in-text-block, in both color schemes; fixed (axe gate green).
  • Island SSE push-drop racecore-ui/island ServeSSE flushed response headers before subscribing, so a client that pushed immediately on connect had its update silently dropped (the TestServeSSE CI timeout). Subscribe now precedes the header flush.
  • Supply-chain hardening — every CI action is now SHA-pinned to an immutable commit (floating tags can no longer swap code into runs), and main is branch-protected with required CI checks.

Full details in CHANGELOG.md.

v0.6.1 — docs-site fixes (version-from-tag, sidebar drawer)

13 Jun 00:55

Choose a tag to compare

Patch release fixing two examples/site (docs site) bugs. No breaking changes, no framework API changes.

Fixed

  • The site version is stamped from the deployment's git tag instead of a hand-bumped constant that had drifted to 0.4.0. Injected at build via -ldflags "-X main.siteVersion=$(git describe --tags --abbrev=0)" (wired into dev-watch.sh, make build-examples, and the Pages workflow). The deployed site now always matches the tag it was built from.
  • The Sidebar showcase's mobile nav drawer now opens. At < 900px, /components/sidebar rendered a hamburger wired to ui-sidebar-drawer, but that drawer was never mounted — the button silently did nothing. It's now mounted (page-scoped, sharing the showcase config), with a contract test (TestE2E_Sidebar_HamburgerOpensDrawer).

Pin a version (go get …@v0.6.1). Full notes: CHANGELOG.md.

v0.6.0 — blueprint is a generator, not a source of truth (BREAKING)

12 Jun 20:50

Choose a tag to compare

Reframes the blueprint as a generator, not a source of truth. gofastr generate now scaffolds owned Go in an idiomatic, module-root layout you read, edit, and commit — no quarantined gen/ directory and no // Code generated … DO NOT EDIT. header. Re-running the generator is add-only and never clobbers your edits.

BREAKING

  • gofastr generate scaffolds into the module root, not gen/. A blueprint scaffolds main.go + entities/ + blueprint/ at the module root, as owned Go with no generated header. Writes are conflict-skip: a re-run adds new files but never overwrites hand-edited code — --force to overwrite. The blueprint is an on-ramp; once scaffolded the generated Go is the source of truth and the running app doesn't need the gofastr.yml.

    • Migration: pass --out=gen (or app.output_dir: gen) to keep the old layout; build/run with go run . instead of go run ./gen. examples/ecommerce scaffolds into an owned app/ subpackage.
  • gofastr migrate diff removed. It applied a blueprint directly onto a live database, treating the blueprint as authoritative over the running schema. Code generation and schema migration are separate concerns.

    • Migration: use gofastr migrate generate <name> to emit a reviewable, versioned migration, then gofastr migrate up; additive columns also converge on boot via AutoMigrate. migrate generate still accepts --from=<blueprint.yml> as an opt-in schema source.

Full notes: see CHANGELOG.md. Pin a version (go get …@v0.6.0).

v0.5.1 — readiness reporting + repository truth patch

11 Jun 14:30

Choose a tag to compare

Patch release with no breaking changes.\n\n- App.Start now reports readiness only after the listener binds.\n- Startup output includes the resolved listening address and correct API-prefixed entity URLs.\n- Bind failures no longer print a false readiness banner.\n- Security, roadmap, Make, coverage, and architecture status surfaces now match v0.5.x.\n- Obsolete worktree scripts were removed.\n- repolint now guards supported-version drift and retired build-script paths.\n\nVerification: make lint and SHORT=1 ./scripts/test-all.sh.

v0.5.0 — first-contact remediation (BREAKING auth)

11 Jun 03:41

Choose a tag to compare

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/auth fails closed on an empty JWTSecret in
    production.
    An AuthManager with DevMode=false and no JWTSecret
    now makes Init return 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, or DevMode: true for local HTTP). The
    blueprint path rejects app.auth with dev_mode: false and no
    jwt_secret at gofastr validate/generate time, so a generated app
    can't be built into the broken state. Migration: set
    AuthConfig.JWTSecret from your secret store (most prod apps already
    do); DevMode is unchanged and still mints a per-process secret.

Added

  • Blueprint access: key — per-operation RBAC from gofastr.yml. An
    optional access: map on an entity (read / create / update /
    delete, each a permission string) threads through
    EntityDeclaration.Access into the generated register.go as
    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.AccessDeclaration for symmetry with EntityDeclaration.

  • 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
    --out on gofastr generate now also accept the space form
    (--from x.yml), which previously silently did nothing.

  • unscoped-pii lint — CLAUDE.md hard rule #6 in the toolchain. Flags
    any auto-exposed entity (CRUD default-on or mcp: true) with PII-shaped
    fields and no owner_field / access / multi_tenant. Enabling
    app.auth alone 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 from gofastr validate, prominent
    warning from gofastr generate, finding in gofastr audit lint.
    Running it against the repo's own examples found and fixed shipped
    exposures in blog, 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 /posts 200; 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.md and tutorial-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 for examples/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 client user_id overridden,
    race-checked. Two layer-level fail-open contracts (ApplyOwnerScope,
    InjectOwner without an extractor) are pinned with tests and comments
    documenting that requireScope upstream 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 and TestGuideDocsEndWithCommonMistakes enforces it.

  • Security ledger fully re-verified. All 103 SECURITY_FINDINGS.md rows
    re-checked against current code: 102 fixed (each with the mitigation
    cited AND a named pinning test run and observed passing), 1 accepted
    (#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.sh fails 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.MaxListLimit could be bypassed on two list
    paths.
    The cursor path never consulted the entity cap (asked for ≤3,
    served up to 100), and an oversized ?limit on 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 ?limit now clamps
    to the effective cap on every path (listLimitCap shared 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
    missing Table) is gone across the suite.

  • battery/queue tests are deterministic. Thirteen sleep-based
    assertions replaced with Close-drain semantics, bounded waitFor
    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 a widget.Mount return value and bootstrap route that don't
    exist (rewritten around MountRuntime/RuntimeTag);
    ui-new-components.md cited three nonexistent drift gates (corrected to
    the real ones); ui-getting-started.md had a non-compiling
    DBFromContext snippet and listed APIPrefix as roadmap (it shipped).
    cursor-pagination.md now documents the per-entity cap.

  • Boot-time auto-migrate now adds missing columns. AutoMigrate did
    CREATE TABLE IF NOT EXISTS only, 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
    behind migrate 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 to prefers-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's DefaultTheme status tones darkened so any
    light-theme Badge/Tag chip passes AA, the gofastr theme init scaffold
    updated to match, and TestAxe_AllPagesAreClean now scans every page
    under BOTH forced schemes. The browser-e2e CI job is blocking again.

  • Generated auth honors dev_mode and validates it strictly. The
    generator hardcoded DevMode: true; the blueprint key now works, with
    a deliberate default of true (production cookie posture —
    __Host-session + Secure — never round-trips on the plain HTTP a
    fresh app serves) announced in the generated code, the gofastr generate output, and the docs. dev_mode: yes is a hard error, not a
    silent coercion to prod mode. auth.CSRF is deliberately not mounted
    (it would 403 the JSON/MCP surface); the gap and the
    SameSite=Strict mitigation are documented.

  • **Docs-site copy drift caught by visual review.*...

Read more

v0.4.0 — blueprint-only declarations + ecommerce flagship (BREAKING)

10 Jun 05:20

Choose a tag to compare

The blueprint becomes GoFastr's single declaration format: the legacy
entities/*.json path is removed (BREAKING), and a declaration-driven
flagship (examples/ecommerce) proves one gofastr.yml → SQL + REST + OpenAPI

  • MCP + UI end to end.

Added

  • Declaration-driven flagship example — examples/ecommerce. A complete
    storefront (five related entities, screens, nav, custom endpoints, seed data,
    and a theme) declared once in gofastr.yml and emitted as runnable Go by
    gofastr generate --from=gofastr.yml (the generated gen/ is gitignored).
    flagship_test.go regenerates, builds, and runs it to prove every surface —
    SQL schema, REST CRUD, OpenAPI, the 25-tool MCP surface, and the
    server-rendered UI — is live with zero hand-written application code. See
    examples/ecommerce/BUILD_JOURNAL.md.

Fixed

  • gofastr generate now gofmt's its generated Go. Blueprint output is run
    through go/format before being written, so the emitted package is clean and
    stable across regenerations (no more spurious diffs on re-generate).
  • gofastr generate --from re-run no longer refuses to clean main.go. The
    output-dir cleaner now owns main.go (the blueprint emits gen/main.go), so
    regenerating over an existing gen/ succeeds instead of erroring with
    "refusing to clean — contains unknown entry".

Removed

  • BREAKING: the legacy entities/*.json declaration format is gone. The
    gofastr.yml blueprint is now the single declaration format — it decodes into
    the same EntityDeclaration shape and additionally emits main.go, screens,
    and stubs, so the JSON-file path was a strict subset. Removed:

    • Framework API: App.EntityFromFile, App.EntitiesFromDir,
      App.GroupEntitiesFromDir, framework.LoadEntityDeclaration,
      framework.LoadEntityDeclarations. (The EntityDeclaration /
      FieldDeclaration types and .Config() remain — they are the in-memory
      shape the blueprint loader decodes entities into.)
    • CLI: gofastr generate entity <name> and gofastr new entity <name> (both
      now print a removal notice and exit non-zero); the --entities=<dir> flag
      on gofastr generate, gofastr migrate generate, and gofastr migrate diff.
    • gofastr generate no longer defaults to "scan entities/ and generate." It
      requires --from=<blueprint.yml> (or a gofastr.codegen.yml extension
      config). Auto-discovery of gofastr.yml is intentionally not done — that
      filename is also the gofastr init isolation config.

    Migration: declare entities in a gofastr.yml blueprint and run
    gofastr generate --from=gofastr.yml, or declare them in Go via
    app.Entity(name, framework.EntityConfig{…}) (unchanged). gofastr migrate generate <name> --from=<blueprint.yml> and gofastr migrate diff --from=<blueprint.yml> replace the old --entities=<dir> form. The
    gofastr.codegen.yml extension protocol and codegen package are unchanged.

    Follow-up (kiln is experimental): kiln freeze still writes
    entities/*.json as its own snapshot artifact; emitting a gofastr.yml
    blueprint directly is tracked for a later pass.

v0.3.3 — assessment backlog C

10 Jun 05:20

Choose a tag to compare

The four larger features held back from v0.3.2, each additive and
backward-compatible. The OAuth token store passed the mandatory dual-model
security audit (see AI_TEST_AUDIT.md).

Added

  • Typed schemas for custom entity.Endpoint. New optional
    InputSchema/OutputSchema ([]schema.Field) fields. When set, the OpenAPI
    spec emits a typed requestBody/200 response and the generated MCP tool
    advertises a typed input schema, instead of a shapeless {type:object}. A
    single helper (openapi.EndpointInputSchema) feeds both the OpenAPI and MCP
    paths. Endpoints with no schema render exactly as before.
  • OAuth2 token store + transparent refresh (battery/auth). A new
    OAuthTokenStore interface + AES-GCM-sealed SQLOAuthTokenStore persists
    {access, refresh, expiry} per (user, provider); RefreshOAuthToken /
    ValidOAuthToken refresh transparently on/near expiry via the provider's
    refresh grant (Google + GitHub). OAuth login now persists the refresh token
    (previously discarded) when a store is wired. Opt-in — login is unchanged
    with no store configured. EncryptionKey is required (fails closed); the
    userID passed to refresh/valid must be the authenticated principal.
  • Cron-expression scheduling in the queue Scheduler. Scheduler.Cron(spec)
    fires on a standard 5-field cron expression (plus @daily/@hourly/… shortcuts),
    alongside the existing Every(interval). Reuses framework/cron (now exposing
    Parse/Schedule.Next) — no second cron parser. Interval schedules are unchanged.
  • Request context in i18n-rendering framework/ui components. RepeaterConfig,
    LightboxConfig, StepWizardConfig, PasswordInputConfig gain an optional
    Ctx field so their localizable strings resolve the request's locale instead
    of always rendering the default. Nil Ctx preserves today's behavior.

v0.3.2 — assessment backlog B

10 Jun 05:20

Choose a tag to compare

A developer-experience patch from the same whole-framework assessment that
drove v0.3.1 — twenty DX improvements and small features, all with tests and
shipped docs. No BREAKING changes; everything is additive.

Added

  • App.WithSeed(func(ctx) error) — register seed funcs that run AFTER
    auto-migration (tables exist) and before the listener binds, fixing the
    first-run "no such table" footgun.
  • framework.DBFromContext(ctx) / WithDBContext + an auto-wired
    DBContextMiddleware — screens reach the app's *sql.DB from the request
    context instead of a package-level global handle.
  • access.GetRoles(ctx) (and the framework.GetRoles facade) — the reader
    half of the role-context seam, for role-based UI branching.
  • PluginGetAs[T] — typed plugin lookup mirroring the existing GetAs[T].
  • Typed interactive effectsConfirm/AfterText/AfterDisable/ScrollTo/
    PushState builders in core-ui/interactive, replacing hand-written
    data-fui-* attribute strings.
  • ListOptions.NestedFilters — in-process ListAll/CountAll now apply the
    same ?author.name=alice EXISTS-subquery nested filters the HTTP path does.
  • RedisQueue.Start(ctx, interval) — background reclaim ticker recovers jobs
    stranded by a crashed worker, matching DBQueue.
  • Battery wrappers for cache/search/storage (NewBattery) with clean
    lifecycle shutdown of background goroutines.
  • gofastr harness creds add|list|delete — store credentials in the
    encrypted credstore; gofastr --help now lists the harness/agents subcommands.
  • audit_log.tenant_id — a nullable column (idempotent ADD COLUMN) stamped
    from the request tenant, so multi-tenant audit trails are scopeable.

Changed

  • The OpenAPI spec advertises ?fields= (projection) and ?trashed= query
    parameters so SDK generators and agents can see them.
  • Auto-CRUD registration pre-flights entity/screen path collisions with an
    actionable diagnostic (names the entity, the colliding path, and the fixes)
    instead of the opaque ServeMux /foods/llm.md conflicts panic.
  • Queue Queue/Browsable/Replayable interface assertions moved into source
    files (fail at build, not test-link).
  • The agents.md snippet validator now understands interface methods and
    non-New constructors (e.g. embed.OpenIndex), so it stops
    false-flagging correct interface APIs while still catching fictional methods.

Documentation

  • New queue.md and testkit.md reference pages; battery/embed now
    ships agents.go/agents.md so semantic search is discoverable to agents.
  • Documented the typed list/get hooks (OnBeforeList/OnAfterList/OnBeforeGet/
    OnAfterGet) and a consolidated hook-skip matrix.
  • Security docstrings on the unscoped softdelete.Restore/ForceDelete/WithTrashed
    helpers; "Common mistakes" sections (form-module, api-versioning); deeper
    observability docs; GetRoles/PluginGetAs docs; and stale-claim fixes.

v0.3.1 — assessment backlog A

10 Jun 05:20

Choose a tag to compare

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 except OwnerField, and the blueprint YAML allow-list
    rejected owner_field outright — 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 AfterList redaction. ?stream=true
    skipped include resolution and the AfterList hook; an AfterList redactor would
    have been silently bypassed, leaking the fields it exists to hide. An explicit
    stream with ?include= or a registered AfterList hook is now refused with 400;
    an auto-stream (very large limit) falls back to the buffered path so redaction
    always runs.
  • GOFASTR_HARNESS_MACHINE_KEY no 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/403 on RBAC-gated, batch, and SSE operations.
    EntityConfig.Access is folded into the gated flag and 403 is added alongside
    401, so generated SDKs/agents see the real auth contract instead of treating
    RBAC-gated routes (and _batch/_events) as public.
  • The UpsertOne DO-NOTHING fallback SELECT now applies tenant/owner/soft-delete
    scope (defense-in-depth; upsertPreflight already guarded the row).

Fixed

  • updated_at is 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 COLUMN for a required field with no default no longer emits NOT 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.InTx joins 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 into BuildDSLQuery. 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-live is no longer applied to
    attribute- or html-mode signal nodes (invalid ARIA + live-region spam on island swaps).
  • Carousel timers and the toc IntersectionObserver are torn down on SPA navigation
    instead of leaking for the session.
  • RedisQueue implements Browsable (ListJobs/Stats over the dead-letter list),
    so the admin queue page works on the most common non-DB production backend.
  • Scheduler enqueue failures log via slog instead of fmt.Printf, surfacing
    otherwise-invisible job loss to the log battery/observability.
  • MemoryQueue handler timeout is configurable via WithHandlerTimeout (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 sitewide WithOpenGraph tags, so first-match crawlers honour the page override.
  • gofastr new entity and generate entity agree 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 --framework works for an installed binary outside the source tree.

Documentation

  • Corrected the access.Policy interface in the docs from a non-existent 3-arg
    Can(ctx, permission, resource) to the real 2-arg Can(ctx, permission) (custom
    policies following the docs failed to compile), and documented that per-record
    decisions are made via OwnerField scoping or Before* hooks. A compile-time
    assertion now pins the doc to the interface. The Go interface is unchanged.
  • Documented the streaming/AfterList exclusivity, App.InTx ambient-tx joining,
    the ADD COLUMN NOT NULL deferral, and corrected the stale updated_at hook
    comment in migrate.go.