Repository navigation
v0.8.0
Manual: action required
-
Breaking. Every write refuses a float that is not finite (
+Inf,-Inf,NaN), or beyond afloat32field's range, with a 422 on that field. (floats-must-be-finite, behavior, #449)It was stored on PostgreSQL and SQLite (and a 500 on MySQL). Code
that stored a non-finite float on purpose in a column it never
returns as JSON now gets a*database.ValidationError: store NULL
(a pointer orsql.NullFloat64) instead. A row that already stores
one keeps working for partial updates of other columns and for an
admin edit that leaves the column alone, but a genericSaveof it
is a 422 on that field until the column is set to a finite value or
NULL (docs/database.md § Floats). -
Breaking.
upgradeis a framework command, and a reserved management-command name. (reserved-upgrade-command, cli, #341)An app that registers its own
upgradecommand loses it: the
framework's is added first, and Cobra resolves to it. Rename the
app's command (and itsinternal/<pkg>/commands.goregistration). -
Breaking.
gombit contract apprefuses a framework replaced by a fork, and ago.modthe go command refuses. (contract-app-fork-unresolved, cli, #341)A
replaceofgithub.com/gombit-dev/gombitby another module used
to report the fork's version as the framework's; it is now
unresolved, like a local path. Ago.modrequiring the framework
twice, or replacing it twice with different targets, is an error.
The replace that counts is the one the go command applies to the
required version (an exact-version replace over an every-version
one; a replace of another version does not apply), so a go.mod
carrying several replaces can report a different version, or
resolve where it used to fail. Build the contract against a
framework release. -
Breaking. Every write refuses text a column cannot store the same way on every driver (a NUL byte, invalid UTF-8, more than the column holds), with a 422 on that field. (
text-storable-on-every-driver, behavior, #444)It was a 500 on PostgreSQL or MySQL and stored on SQLite. A
size:n/varchar(n)column holds n characters, atextcolumn
65,535 bytes (MySQL'sTEXT), and an unsized primary-key, indexed,
unique or defaulted string 191 characters (MySQL'svarchar(191)),
now on every driver: PostgreSQL and SQLite stored more. A row that
already stores such text stays editable through the admin, its form
included, and partial updates (database.ScopeEditwith
database.StoredValuesjudges only what an edit changes); a generic
Saveof it is a 422 on that field until the value is shortened or
cleaned (docs/database.md § Text). -
Breaking.
database.Searchtakes a context and returns an error, likeFilterEq: a search term with a NUL byte or invalid UTF-8 is a 422. (database-search-takes-context, api, #444)Code calling
database.Search(q, columns, term)no longer builds:
calldatabase.Search(ctx, q, columns, term)and return its error.
Rungombit generateto update an app's generatedhandler.gen.go,
which also answers agetof an id overMaxInt64with a 404. -
A scaffolded
internal/product/handler.goanswers an id overMaxInt64with a 404; an app scaffolded before answers a 500 on PostgreSQL. (scaffolded-get-id-over-maxint64, scaffold, #444)The handler is app-owned, so
gombit generatedoes not update it. In
itsget, parse the id withstrconv.ParseUint(input.ID, 10, 63)
instead of64: PostgreSQL's bigint cannot bind a larger id, and no
row holds one. Apply the same to any hand-written handler that
parses a uint id. -
Breaking. With auth enabled, an app that registers the session routes, the
auth-*session operation IDs or theAuthSession/AuthRevokeResultschema names fails at startup: the framework registers them. (auth-session-names-reserved, api, #335)With
GOMBIT_JWT_SECRETset, registering a route atGETor
DELETE {prefix}/auth/sessionsorDELETE {prefix}/auth/sessions/{id}
(through Huma or the raw Gin router), an operation
auth-list-sessions,auth-revoke-sessionorauth-revoke-sessions,
or a type Huma namesAuthSession,DataListAuthSession,
AuthRevokeResultorDataAuthRevokeResultnow panics when the app
starts. Drop the app's own session routes, which the framework's
replace, or rename them. A resource namedSession
(gombit make resource Session) does not collide. -
Breaking. With auth enabled, a
GOMBIT_JWT_REFRESH_TTLshorter thanGOMBIT_JWT_ACCESS_TTLfails config validation, so the app does not start. (refresh-ttl-at-least-access-ttl, config, #335)- Configuration:
GOMBIT_JWT_REFRESH_TTL: some values are now refused. - Configuration:
GOMBIT_JWT_ACCESS_TTL: some values are now refused.
The refresh token's lifetime is the session's, and an access token
must not outlive its session: it would stop authenticating when the
session ends while itsexp,expires_inand access cookie still
promised more. Config validation (config.Load/Validate) and
auth.NewServicerefuse that pair; equal TTLs are fine. Raise
GOMBIT_JWT_REFRESH_TTLto at leastGOMBIT_JWT_ACCESS_TTL, or
lowerGOMBIT_JWT_ACCESS_TTL. An access token also ends with its
session: it stops authenticating when the session expires, not only
when it is revoked, so a token issued before the upgrade under the
old TTLs stops at its session's expiry, before itsexp. - Configuration:
Automatic: applied by the upgrade tooling
-
gombit newrecords the app's upgrade baseline in agombit:block ofgombit.yaml. (record-upgrade-baseline, scaffold, actionrecord-baseline, #341)An app generated before records none, and is detected as scaffold
version 0.gombit upgrade baseline --writerecords it.
Informational
-
config.Loadreads a.envfile whole: a line longer than 64 KiB no longer silently stops it, so the assignments after one now apply. (dotenv-long-lines, config)Before, the first line over 64 KiB ended the read without an
error, and every variable after it kept its process-environment
or default value. If a.envhas such a line, check the values
set after it. -
An admin relation field on a string key stores the string sent, byte for byte; a JSON number for it is a 422. (
admin-relation-string-keys, behavior, #452)A numeric-looking key (
"123","007") was stored corrupted. A
client that sends a string key as a JSON number now gets a 422 on the
field, as for any string field: send it as a string. A uuid key must
be the canonical form, as auuidfield (no braces, with hyphens).
An integer key still accepts a number or its digits as a string. -
An admin PATCH writes only the columns it (or a model hook) changed, never associations, and answers 404 for a row deleted meanwhile. (
admin-patch-writes-changes-only, behavior, #450)It used to save the whole loaded row: a concurrent change to another
column was reverted, a deleted row came back, andhas_many
children were written back.database.ScopeEditnow narrows any
Select("*")update of the scoped row the same way. -
On Windows,
gombit make resourcereplaces files with a POSIX-semantics rename, so a file open with delete sharing no longer blocks it. (make-resource-windows-posix-rename, cli, #341)On a volume that refuses that rename (FAT or exFAT, a network share
or mapped drive, a\\wsl$path, or Windows before 10 1709) it keeps
its previous plain rename and its rollback. -
database.KeepStoredZerosis deprecated: usedatabase.ScopeEditwithdatabase.StoredValues. (keep-stored-zeros-deprecated, api, #444)ScopeEditjudges an edit of a loaded row on what it changes,
compared after the model's hooks, for the text check as well as
stored zero instants.KeepStoredZeroskeeps working. -
MapPersistErrorandMapLoadErroranswer a database data exception (a value too long or out of range for its column) with a 422 instead of a 500. (data-exceptions-are-422, behavior, #444)PostgreSQL 22001, 22003, 22007, 22008, 22021, 22P05 and MySQL 1264,
1265, 1292, 1366, 1406 (database.IsDataException). A uint list
filter overMaxInt64is a 422 too. -
Session management in both auth modes:
GET {prefix}/auth/sessionslists the user's active sessions;DELETE {prefix}/auth/sessions/{id}andDELETE {prefix}/auth/sessions?scope=others|allrevoke them. (auth-session-management, api, #335)auth.ServicegainsListSessions(returning[]auth.AuthSession),
RevokeSession,RevokeAllSessionsandErrSessionNotFound. Cookie
mode clears the session cookies when a request ends its own session
(docs/auth.md § Sessions). -
A refresh token revoked by logout or session revocation is a 401 that no longer revokes the user's other sessions; only an already-rotated token counts as reuse. (
refresh-reuse-only-rotated-tokens, security, #335) -
POST /auth/refreshanswers 500, not 401, when the database fails while the rotation locks the user's row (a deadlock, a lock timeout, a lost connection). (refresh-lock-failure-is-500, behavior, #335)Only a user that no longer exists is a 401 there, so a client can
retry instead of treating the failure as a dead credential. -
Refresh-token rotation and the session revocations lock the user's row for their transaction (
FOR NO KEY UPDATEon PostgreSQL,FOR UPDATEon MySQL), so they run one at a time per user. (auth-user-row-lock, behavior, #335)Application code that holds a lock on a user's row in a long
transaction now delays that user's refreshes and revocations. Code
that also writes a user's refresh tokens should lock the user's row
first, as the framework does. -
The refresh-token reuse cascade takes the user's row lock, like rotation, so it also revokes the token a concurrent rotation of another of the user's sessions issues. (
refresh-reuse-cascade-takes-user-lock, security, #335)On PostgreSQL that token could survive the cascade.
-
A new app's placeholder
schema.tsincludes the session operations; an existing app's generated client gets them fromgombit client generate. (client-schema-session-operations, client, #335) -
The framework requires newer AWS SDK for Go v2 modules (the S3 storage driver):
github.com/aws/aws-sdk-go-v2v1.47.3,github.com/aws/aws-sdk-go-v2/service/s3v1.114.0 andgithub.com/aws/smithy-gov1.28.5. (aws-sdk-updates, dependency, #570, #590)Go's minimal version selection moves an app's build to them with the
framework upgrade.
What's Changed
- [UPGRADE-1] Application/framework baseline metadata by @leo-aa88 in #552
- [UPGRADE-2] Machine-readable compatibility manifest by @leo-aa88 in #554
- fix(resourcegen): explain name:type,modifier instead of reporting an unknown type by @leo-aa88 in #567
- fix(database): refuse text no driver stores as given; map data exceptions to 422 by @leo-aa88 in #564
- fix(database): refuse non-finite floats on every write by @leo-aa88 in #569
- build(deps): bump github.com/aws/aws-sdk-go-v2/service/s3 from 1.113.4 to 1.114.0 by @dependabot[bot] in #570
- fix(admin): PATCH writes only the columns it changed, never associations by @leo-aa88 in #574
- ci: fail when the committed admin SPA build is not its source's by @leo-aa88 in #575
- [UPGRADE-3] gombit upgrade --dry-run planner by @leo-aa88 in #566
- fix(admin): coerce a relation value as its key column's type by @leo-aa88 in #576
- [UPGRADE-4] Dependency upgrade execution by @leo-aa88 in #582
- [UPGRADE-5] Deprecated API scanner by @leo-aa88 in #583
- [UPGRADE-6] Reviewable AST-aware Go codemods by @leo-aa88 in #584
- build(deps): bump github.com/aws/aws-sdk-go-v2 from 1.47.1 to 1.47.3 by @dependabot[bot] in #590
- chore(upgrade): tell contributors how to satisfy the changelog gate by @leo-aa88 in #596
- [AUTH-5] Sessions and device management by @Brunotlps in #581
- [UPGRADE-7] Configuration upgrade analysis by @leo-aa88 in #585
- fix(faulttest): make Injector.Reached nil-safe by @peda-cos in #586
- fix(faulttest): forward half-closes through TCPProxy by @peda-cos in #587
- [UPGRADE-8] Generated client / contract regeneration integration by @leo-aa88 in #599
- [UPGRADE-9] Database / framework-schema upgrade hooks by @leo-aa88 in #601
- [UPGRADE-10] Compatibility / deprecation policy by @leo-aa88 in #600
- chore(release): prepare v0.8.0 by @leo-aa88 in #595
New Contributors
Full Changelog: v0.7.1...v0.8.0