Release notes
The current release is v1.11.0.
Nucleus is on the stable v1.x line (v1.0.0 tagged 2026-07-10): stable
surfaces are frozen by contract tests, and every v1.x upgrade is designed
to be drop-in for code that uses them — see
Support & compatibility and the
upgrade guide. Commit-level detail for every
release, including the pre-1.0 history, lives on
GitHub Releases.
v1.10.0 (2026-08-21)
A minor release: the vertical-slice module arc. A mounted module can now carry everything its feature needs, and a generator emits that shape.
- Modules declare their own policy rows and CSRF exemptions.
Module.Policiescontributes RBAC rows (same shape as arbac_policy.csvrow, objects relative to the modulePrefix) to the default-deny enforcer, andModule.CSRFExemptrides the same pre-startup window as the automatic webhook-prefix exemption. Rows join the live in-memory ruleset only — the host's policy file is never written, and adenyrow in the host's CSV overrides any moduleallow. Malformed declarations fail boot withErrInvalidModulePolicynaming the module and entry. - Embedded module migrations are applicable. One deliberate call —
rt.ApplyModuleMigrations(), typically inOnStart— appliesModule.Migrationsthrough the real pipeline: the module-scoped ledger (<module>/<id>) with checksum tracking, idempotent across restarts. Application boot still never mutates the schema on its own; the boot warning about declared-but-unapplied migrations now names the call and goes quiet once the module uses it. Under the hood:db.NewModuleFSMigrator, anfs.FS-backed migrator usable directly. - Embedded module templates.
Module.Templatesregisters a module's.htmlfiles under the<module-name>/namespace;app.WithTemplatesFS(prefix, fsys)is the general accumulating extension point behind it. On a name collision the host'stemplates_dirparses last and wins. nucleus generate module <name>. One self-contained package underinternal/<name>/— model + storage for the configured dialect, controller, and a module carrying its policy rows, CSRF exemption, embedded migrations and page template. Mounting it is the whole integration: norbac_policy.csvornucleus.ymledits, no manual migrate step, pinned by an executable-scaffold test that asserts the policy file stays byte-for-byte untouched.
v1.9.2 (2026-08-19)
A patch release that corrects documentation which promised more than the code delivered. No runtime behavior changes.
EnsureBucket's documented scope now matches what the code can do. The godoc suggested it could provision "the" bucket after construction, butNewS3Storeverifies the configured bucket(s) up front and refuses to construct when one is missing — so a store whose own bucket is absent can never exist to callEnsureBucketon. The contract now states the real scope: provisioning buckets other than the store's own (exports, per-tenant spaces, scratch areas); self-provisioning the configured bucket is exactly whatstorage.s3.create_bucket_if_missingis for, at construction time. The storage guide says the same, and a live MinIO contract test (TestS3Live_EnsureBucketProvisionsAnotherBucket) pins it.
v1.9.1 (2026-08-18)
A patch release from the SSR arc's re-verification.
- The outbox dispatcher starts after extensions attach. The dispatcher's
first pass is immediate, and
Extension.Attach— which ran later — is the supported way to register bridges: a message already durable in the table could be leased with an empty route registry and fail with "no bridge route matched", consuming a retry and dirtyingattempts/last_error. The dispatcher now starts only after every extension has attached; a pre-existing pending message delivers on attempt 1. Durability semantics are unchanged. - The template extension point is reachable from the documented
builder.
nucleus.New()gainsWithTemplateFuncs,WithTemplatesandWithOpenAuthz(plus package-level re-exports) — v1.9.0's template options only existed onapp.New, which the builder wraps. A parity test now enforces that every public application option has a builder counterpart, so this class of gap fails the suite instead of the next release. - API additions must regenerate the frozen baseline in the same change. The baseline only failed on removals, so v1.9.0's new symbols shipped unlisted and external coverage denominators undercounted the public surface. Regenerated, and a new check fails on unlisted additions.
v1.9.0 (2026-08-17)
A feature minor: the server-side render layer works the way the documentation prescribes. Found by the external coverage demo's first real MVC application (session login, CSRF forms, full CRUD).
- Modules with a
Prefixreceive the template engine and the session manager. The prefix sub-router was built from scratch and never inherited either — so the documented way of declaring a module answered every render with "template engine is not configured" and every session helper with "session manager is not configured". The three composition paths (Group,With,Route) now share a single derivation function, so a future Mux-level dependency cannot be forgotten by one of them. Auditing this also surfaced that nothing ever handed the session manager to the router tree at all:app.Newnow does, and theContextsession helpers (SessionPutString& co.) work everywhere. - Template functions and prebuilt bases.
app.WithTemplateFuncsregisters atemplate.FuncMapavailable to every template the startup loader parses, andapp.WithTemplatesinjects a prebuilt*template.Templateas the parse base — presentation logic (date formats, percentages, pagination URLs) belongs in templates, not precomputed in Go. Order: registered functions → recursive parse oftemplates_dir→ the engine is wired into the router. See the routing guide. csrf_insecure_cookie(development-only, defaultfalse) disables the Secure attribute on the CSRF cookies, mirroringsession_cookie_secure: false— without it the double-submit flow was unreachable for plain-HTTP non-browser clients such as Go's cookie jar againsthttp://127.0.0.1.- An SSR conformance suite now runs in CI: a module WITH a prefix, served over real HTTP, must render a loaded template by name, keep session state (write → read → destroy), enforce CSRF (419 without the token, 200 with it), serve module-mounted statics, and apply registered template functions — the five things any server-rendered application needs on day one, none of which had a test before.
v1.8.2 (2026-08-17)
A patch release: three findings from the external coverage demo's first server-rendered MVC application.
- The scaffold renders with the framework's own engine.
app.Newused to load templates with a flat glob whilenucleus startappscaffolds its template into a subdirectory — on a fresh project no template loaded and every render answered "template engine is not configured", with no startup warning. The loader now walkstemplates_dirrecursively; each template registers under its path relative to that directory (fieldservice/index.html), root files keep their flat name (base.html), and{{define}}blocks keep their declared names — flat layouts keep resolving unchanged. Startup logstemplates loadedwith the count, a present-but-empty directory logs a WARN, and the render-without-engine error now says what to check. A new executable-scaffold test boots a scaffolded project in CI and demands the generated page renders over HTTP — the class of generator/runtime disagreement that produced this finding (and the SQLite-DDL one before it) now fails the suite instead of the first user. - Outbox: per-instance lease owner and a routing-policy knob. Every
process used to write lease rows as the same literal
nucleus-app, so a co-tenant process could lease — and fail — messages another instance was able to deliver, untraceably. The default owner is now derived per instance (nucleus-<hostname>-<pid>);outbox.lease_ownersets a stable identity andoutbox.missing_route_policy(error, the previous behavior, orignore) controls what happens when a leased topic has no registered bridge. Startup logs both. SessionCache.Flushno longer panics. Flushing sliced every session key to a fixed length, panicking on unrelated keys of 8–13 bytes — ordinary session data. It now uses prefix checks and touches only cache-prefixed entries.
v1.8.1 (2026-08-17)
A patch release closing the loop on S3 bucket provisioning.
storage.s3.create_bucket_if_missingis reachable fromnucleus.yml. The missing-bucket startup error recommends that exact key, but the strict configuration validator rejected it: the key existed inpkg/storageand not in the application config schema — a circular repro with no operator exit (the environment form did not work either). The key now loads from the file and fromNUCLEUS_STORAGE__S3__CREATE_BUCKET_IF_MISSING, and reaches the storage constructor. Opt-in with defaultfalse: a missing bucket without the flag still fails startup loudly, exactly as before.- A parity test keeps the two config surfaces in sync. The application
schema mirrors
pkg/storage's own config structs; every storage key must now be mirrored or carry an explicit, reasoned exclusion — so a future divergence fails the suite instead of becoming another advertised-but-rejected key.
v1.8.0 (2026-08-16)
The developer-experience minor: the scaffolding, testing and configuration gaps measured by the 2026-08-16 developer-experience audit, on top of the fixes already landed in earlier patches.
-
generate resource(andstartapp) emit a mountable module wired to a real repository. The scaffold now produces anucleus.Contextcontroller implementing the REST Resource sub-interfaces, a repository running real SQL against the framework-managed*sql.DB(statements rendered for the configured dialect — RETURNING on PostgreSQL, OUTPUT INSERTED on SQL Server, OUT bind on Oracle, LastInsertId elsewhere), andinternal/modules/<name>_module.go:nucleus.New().Mount(modules.<Name>Module())is the whole integration. The*router.Muxhandler that forced a hand-written adapter and the in-memory map repository are gone;startappshares the same artifacts and its module also serves the scaffolded page. -
In-process test kit —
pkg/nucleustest(experimental).nucleus.RunContext(ctx, app)gives the run loop a caller-owned lifetime (cancel = graceful shutdown), and the kit wraps it:nucleustest.Start(t, builder)boots the app on a free loopback port, waits for/healthz, stops viat.Cleanup, andMintTokenissues bearer tokens against the app's ownjwt_secret. E2E suites no longer needgo build+exec.Command+ hand-rolled polling. -
profile: devboots a realistic config with zero backing services. A productionnucleus.ymlnaming PostgreSQL (+ replica), two Redis endpoints, S3 and SMTP boots unchanged withprofile: dev(orNUCLEUS_PROFILE=dev): in-memory sessions and jobs, local filesystem storage, the no-op mailer, and SQLite (an already-SQLite URL is kept; extra database aliases are dropped). Unknown profile values fail config load. Identical onapp.LoadConfigand the fluent loader.
v1.7.0 (2026-08-16)
A feature minor: the security composition the docs describe now works end to end, and object storage can provision itself.
- The global default-deny authorization layer sees JWT claims. When
JWT signing material is configured, a bearer is decoded ahead of the
global enforcement and the request's subjects are tried in order — the
token's user id, its role, then
anonymous. Role-based CSV policies (p, admin, /api/admin/*, read, allow) finally work at the global layer without re-implementing RBAC per module. Strictly non-restrictive: requests that passed before still pass (the anonymous fallback preserves bootstrap grants for authenticated callers); requests that were wrongly denied now succeed. - S3 bucket bootstrap.
storage.s3.create_bucket_if_missing: trueprovisions the configured bucket(s) at startup;S3Store.EnsureBucketis the programmatic, idempotent form. Behaviour change: without the opt-in, a missing bucket now fails the constructor loudly (with an actionable message) instead of booting green and failing on the first upload. ServiceRegistration.Healthis wired into/healthzas the checkservice:<name>; a failing service flips the endpoint to 503. New building blocks:health.FuncProbeandapp.RegisterHealthProbefor application-owned checks.
v1.6.2 (2026-08-16)
A patch release: dumpdata/loaddata round-trip on schemas with foreign
keys, plus documentation honesty fixes.
loaddatainserts in FK-dependency order. The load plan is now ordered topologically by the foreign-key graph introspected from the target database, so a fixture produced bydumpdata(which lists tables alphabetically) restores in a single invocation instead of failing on the first child table. The caller's order — the file's, or an explicit--tables— is the stable tie-break: a valid explicit order passes through unchanged (it used to be silently re-sorted alphabetically), an FK-invalid one is repaired. Self-references are skipped and FK cycles fall back to the given order rather than failing the load; the dry-run plan shows the real order.- Documentation: the health-probe comment no longer denies the mail probe
the code performs; the storage guide's import path is current; the auth
guide's policy examples use the CRUD action vocabulary the middleware
actually enforces (
read/create/update/delete), with the mapping spelled out;ServiceRegistration.Healthnow states loudly that it is accepted but not yet wired into/healthz. - The showcase example re-pins the current sibling tags (nucleus v1.6.1 at cut time, agent v0.5.7, server v0.9.2, quarkbridge v0.3.7, quarkdatasource v0.2.8, quark v1.4.1).
v1.6.1 (2026-08-15)
A patch release: the resource scaffolder targets your real database.
generate resource/startappemit migration DDL for the configured database instead of unconditional SQLite. A project configured against PostgreSQL used to receive"id" INTEGER PRIMARY KEY AUTOINCREMENTandDATETIMEcolumns, andnucleus migratethen failed with a syntax error on the very migration the CLI had produced. The scaffold now resolves the dialect from the project's config (the same confignucleus migratereads,NUCLEUS_*overrides included), and both commands accept--dialect/--config/--databaseto override it. A fresh project with no config keeps the sqlite default.- New helpers for the same decision in your own tooling:
db.SystemFromURL(URL → SQL system, no connection) andmodel.BuildMigrationScaffoldForSystem(dialect-dispatched scaffold). - The generated repository is an in-memory placeholder and now says so — a note in the generated file and in the command output replaces the previous silence about the migration's table going unused.
- Security: Go 1.26.6 (stdlib CVEs),
grpcv1.82.1,otelv1.44.0; thenucleus newscaffold inherits the same directives, and the showcase example re-pins quark v1.4.1.
v1.6.0 (2026-07-22)
Defense-in-depth hardening of the webhook surface, from the first directed security review of the continuous-audit regime. No behaviour changes for correctly-configured apps; the wire is unchanged.
Added
- Webhook registration rejects non-canonical mounts. A module webhook
registered with
path == "/"(which would mount a catch-all subtree) or a module name containing..//(which would shift the mount point) now fails boot instead of mounting something surprising. Canonical paths and names are unaffected. - Outbox payload-encoding header is informational by design. The bridge
signs the body only — byte-for-byte the module-webhook scheme, so one
verifier covers both surfaces — and
X-Outbox-Payload-Encodingis now documented as unsigned/informational. New consumer helperoutbox.CheckPayloadEncodingdecodes by the encoding a consumer expects and rejects a mismatch (ErrPayloadEncodingMismatch), rather than trusting the request header. The signed wire is unchanged from v1.5.0.
Upgrade notes
Drop-in. If a module registered a webhook at / or with a slash in its
name, that was already broken (unreachable or mis-mounted) and now fails
loudly at boot — give it a real path. Outbox consumers should verify the
body-only signature with the module-webhook verifier and check the payload
encoding against their own config (see CheckPayloadEncoding), not the
request header.
v1.5.0 (2026-07-22)
Signs and versions the outbox webhook contract, hardens module webhooks (canonical paths, opt-in anti-replay), and fixes Oracle pagination and S3/GCS not-found detection. Drop-in: the outbox wire is unchanged by default and the new webhook behaviour is opt-in.
Added
- The outbox bridge webhook has a signed, versioned contract. With
outbox.bridges.<n>.config.secret, every delivery carries an HMAC-SHA256 signature over the body inX-Nucleus-Signature(sha256=<hex>) — the same scheme module webhooks verify, so one verifier covers both. Every delivery also declares its payload shape inX-Outbox-Payload-Encoding: json|base64, so a consumer never guesses. The wire is byte-for-byte the v1.4.0 default (base64);payload_encoding: jsonopts into embedding the payload as JSON. A body-level contract test compares the emitted webhook byte for byte per variant — the gap the symbol-only freeze cannot see. Without a secret, deliveries are unsigned and a boot WARN says so. See Storage & background tasks. - Webhook anti-replay (opt-in).
WebhookSpec.TimestampTolerance > 0requires anX-Nucleus-Timestampheader inside the signed material (SignWebhookBodyWithTimestamp), rejecting stale or tampered timestamps. The default (tolerance 0) keeps the body-only scheme unchanged. The absence of anti-replay in the default scheme is now documented as a limit, with event-ID dedup as the recommended pattern.
Fixed
- Oracle pagination emits valid SQL.
FindAllandFindByIDinpkg/modelusedLIMITon Oracle (ORA-00933); they now useOFFSET … FETCH NEXT … ROWS ONLY/FETCH FIRST 1 ROWS ONLY, the twin of the earlier MSSQL fix. The admin-user CLI lookup is fixed the same way. Exercised against a real Oracle in CI. - Webhook paths must be canonical. A module webhook registered with a
non-canonical path (
..,., doubled or trailing slash) now fails boot instead of mounting an unreachable route. - S3/GCS not-found by SDK type, not error text.
Get/Existsof a missing key now map tostorage.ErrNotFoundagainst real endpoints (previously matched on the error string, which a real S3 endpoint does not produce). A real-MinIO CI lane covers it. - Security:
golang.org/x/textbumped to v0.39.0 (GO-2026-5970).
Upgrade notes
Nothing to change. If a bridge was relying on the base64 payload wire, it is
unchanged; opt into payload_encoding: json when your consumer is ready.
Configure outbox.bridges.<n>.config.secret to start signing deliveries.
v1.4.0 (2026-07-20)
Module jobs and webhooks are now executed, not just declared: the Jobs
and Webhooks closures a module registers run for real, backed by the
existing task runtime and the application router. Also fixes a stop-path
bug in the Asynq task provider and rejects, on request, primary keys
assigned by HTTP clients. Drop-in upgrade; the new surfaces are opt-in.
Added
- Module jobs run on a real scheduler.
JobRegistry.Register(name, spec)schedules background work declared by a module:Everyfor fixed intervals orCronfor 5-field cron expressions and descriptors (@hourly,@every 90s), validated at boot and identical on every provider; per-runTimeout; andSingletonto skip a tick while the previous run is still executing. Thejobs_providerkey selects the runtime —memory(default, in-process) orasynq(Redis-backed, durable, withjobs_redis_urlandjobs_concurrency). A broken registration (duplicate name, invalid cron, missing handler) fails boot instead of silently never running. See Module jobs and webhooks. - Module webhooks mount real routes.
WebhookRegistry.Register(path, spec)mounts an inbound receiver at<webhooks_prefix>/<module><path>behind a method allow-list (405), a body cap (413, default 1 MiB) and — whenSecretis set — constant-time HMAC-SHA256 verification of theX-Nucleus-Signatureheader, rejecting unsigned or mis-signed requests with 401 before your handler runs.nucleus.SignWebhookBodyproduces the signature for senders and tests. Withcsrf_enabled: truethe webhook prefix is exempted automatically — webhooks authenticate by signature, not CSRF token. A webhook registered without aSecretis flagged at boot. RejectClientPK. A per-model opt-in that rejects entities arriving throughCreatewith a client-assigned primary key (model.ErrClientAssignedPK), for apps that bind request bodies straight into models. The check runs before hooks, so server-side key assignment inBeforeCreatekeeps working.
Fixed
- The Asynq task worker stops when you stop it.
Manager.Runwaited on OS signals internally, so cancelling its context (or callingClose) shut the server down but never unblockedRun— an embedded worker could not be stopped through the API.Runnow returns promptly on context cancellation and onClose. - Boot no longer warns about declared jobs and webhooks. The "background execution is not yet wired" readiness warning is gone — both surfaces execute. The warning for embedded migrations stays: Nucleus is SQL-first and never auto-applies them.
Upgrade notes
Nothing to change in existing apps. If a module already declared Jobs
or Webhooks closures (previously inert), they now execute on the next
boot: review those closures before upgrading, set jobs_provider if you
want durability over the in-process default, and note that invalid
registrations that were silently ignored before now fail startup — which
is the point.
v1.3.3 (2026-07-19)
A correctness patch: client-assigned primary keys work through Create,
unsupported engines fail at startup instead of at runtime, and two more
surfaces emit valid T-SQL. Drop-in for most apps — read the upgrade notes
if you point the sql session store or the outbox at SQL Server or Oracle.
Fixed
- A pre-assigned primary key now travels in the
INSERT. Client-generated keys (UUIDs, natural keys) were silently dropped from the insert: SQLite stored a row with aNULLprimary key without any error, and PostgreSQL/SQL Server failed with aNOT NULLviolation. A non-zero key is now included in the statement, and the read-back / back-fill machinery is skipped, so the entity keeps exactly the key you set. A zero-value key keeps the previous behavior: the column stays out of theINSERTand the database generates the key. See Models & database — including the security note on accepting keys from HTTP clients. - The SQL session store and the outbox refuse unsupported engines at startup. Both subsystems speak SQLite, PostgreSQL and MySQL only, but an MSSQL or Oracle database URL used to be silently treated as SQLite — the failure surfaced later, mid-request, as invalid SQL. Construction now fails at startup with an error naming the supported engines.
not nullis matched exactly indb:tags.db:"not null unique"(a space where a;was intended) used to mark the field required and silently lose theunique; the malformed directive now falls through to the startupWARNintroduced in v1.3.2 instead of half-applying.- By-id operations reject models without a primary key.
FindByID,UpdateandDeleteon a model that declares no primary key return an explicit "model has no primary key" error (check witherrors.Is) instead of guessing a phantomidcolumn, and the default list ordering falls back to a real column of the model. nucleus createuserandnucleus changepasswordemit valid T-SQL. Their admin-user lookups used aLIMITclause SQL Server does not accept; on MSSQL they now useSELECT TOP 1.
Upgrade notes
If your configuration points the sql session store or the outbox at an
MSSQL or Oracle database, the app now stops at startup with a clear error
instead of failing later with invalid SQL. That configuration never worked
— it silently ran SQLite-flavored SQL against the wrong engine — but a
deployment that "started fine" before the upgrade will now refuse to boot
until those subsystems point at a supported engine (SQLite, PostgreSQL,
MySQL).
v1.3.2 (2026-07-19)
A correctness patch focused on the model layer's db: tags and on
Create across database engines. Drop-in.
Fixed
- Unknown
db:tag directives now warn at startup. A directive the parser does not recognize was — and still is — applied as nothing; the difference is that the app now logs one startupWARNper affected field, naming the unrecognized tokens and the supported syntax, instead of leaving you trusting a constraint that never existed.db:"-"now excludes a field from persistence. Createonly reads back the generated key when it actually can. TheRETURNING/OUTPUT INSERTEDread-back is now emitted only for models that declare a real, integer primary-key field. Models with string/UUID keys or without a declared primary key previously got a read-back query that could fail (for example against tables with noidcolumn); they now take the plain insert path, matching SQLite/MySQL behavior.- List pagination on SQL Server emits valid T-SQL. Paginated list
queries used a
LIMITclause SQL Server does not accept; they now use theOFFSET … FETCHform. The whole CRUD surface is exercised against a real SQL Server (and Oracle) in release validation. - The version pinned by
nucleus newcan no longer go stale. The framework version written into generatedgo.modfiles is maintained by the release tooling and cross-checked in CI on every build.
Upgrade notes
Nothing to change. If your startup logs show new WARN lines about db:
tags, those tags were already being ignored — fix the tag syntax, don't
silence the log. See
the FAQ
for the supported directives.
v1.3.1 (2026-07-15)
A one-fix patch. Upgrade if Create should hand you the generated primary
key on PostgreSQL or SQL Server.
Fixed
Createbackfills the generated primary key on PostgreSQL and SQL Server. Those drivers do not implementLastInsertId, so the entity's ID field silently stayed at zero after a successful insert.Createnow usesRETURNING(PostgreSQL) /OUTPUT INSERTED(SQL Server) to populate it. Oracle remains a declared gap — see Support & compatibility.
v1.3.0 (2026-07-13)
A minor release that completes the v1.2.0 security hardening pass and rounds out observability.
New
- Opt-in driver-level SQL instrumentation (
sql_driver_instrumentation). Off by default (zero hot-path cost); when enabled, directQueryContext/ExecContextstatements that bypass the model layer — session stores, outbox dispatch, migrations, raw SQL — also reach the observability live SQL feed, without double-recording CRUD statements. - The observability package and its hooks are now stable, covered by the same compatibility promise as the rest of the framework.
Security
- CSRF protection as a config switch.
csrf_enabled: truemounts origin verification (Sec-Fetch-Site) with a double-submit token fallback;csrf_exempt_pathsexcludes Bearer-only subtrees. Themvcscaffold enables it by default. metrics_public: falsetakes/metricsout of the anonymous allow-list and puts it behind the default-deny RBAC enforcer.
Upgrade notes
Both new switches default to the previous behavior (csrf_enabled: false,
metrics_public: true); nothing changes until you opt in.
v1.2.0 (2026-07-12)
A security-hardening minor. Existing deployments can notice these changes at upgrade time — read the notes below.
Security
jwt_secretmust be at least 32 bytes. Any non-empty value used to be accepted; a shorter secret is now a boot error. Generate a proper one (openssl rand -base64 32) or move tojwt_keys[].- Proxy headers are no longer trusted by default.
X-Forwarded-For/X-Real-IPare ignored unless the immediate peer is listed in the newtrusted_proxieskey; otherwise the TCP peer address is the client IP for rate limiting and logs. - HSTS is emitted only over TLS or when explicitly forced
(
env: production) — plain-HTTP development runs are no longer pinned to HTTPS by a stray header.
Upgrade notes
- Short
jwt_secretvalues fail the boot — rotate the secret before upgrading. - If Nucleus runs behind a load balancer, set
trusted_proxiesto its address ranges or rate limiting will see every request as coming from the balancer.
v1.1.0 (2026-07-11)
New
- SQL events report rows affected. The observability feed's SQL events
carry the driver-reported
RowsAffected. Additive; drop-in.
v1.0.0 (2026-07-10)
The first stable release. The compatibility promise starts here: stable surfaces are pinned by contract freeze tests and change only through the documented deprecation policy.
Breaking
- Cross-origin requests are denied by default. The implicit allow-all
CORS default is gone: an empty
cors_originsnow emits no CORS headers at all. Deployments that relied on allow-all must opt in explicitly — a real origin allow-list, orcors_origins: ["*"]to keep the old behavior.
Upgrade notes
If browsers suddenly report CORS errors after this upgrade, set
cors_origins to the exact origins your frontend uses. Everything else in
v1.0.0 is the certification of surfaces that already existed in v0.12.x.