Skip to content

v0.8.0

Latest

Choose a tag to compare

@github-actions github-actions released this 19 Oct 07:17
· 26 commits to main since this release
7ea62f1

v0.8.0

Manual: action required

  • Breaking. Every write refuses a float that is not finite (+Inf, -Inf, NaN), or beyond a float32 field'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 or sql.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 generic Save of it
    is a 422 on that field until the column is set to a finite value or
    NULL (docs/database.md § Floats).

  • Breaking. upgrade is a framework command, and a reserved management-command name. (reserved-upgrade-command, cli, #341)

    An app that registers its own upgrade command loses it: the
    framework's is added first, and Cobra resolves to it. Rename the
    app's command (and its internal/<pkg>/commands.go registration).

  • Breaking. gombit contract app refuses a framework replaced by a fork, and a go.mod the go command refuses. (contract-app-fork-unresolved, cli, #341)

    A replace of github.com/gombit-dev/gombit by another module used
    to report the fork's version as the framework's; it is now
    unresolved, like a local path. A go.mod requiring 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, a text column
    65,535 bytes (MySQL's TEXT), and an unsized primary-key, indexed,
    unique or defaulted string 191 characters (MySQL's varchar(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.ScopeEdit with
    database.StoredValues judges only what an edit changes); a generic
    Save of it is a 422 on that field until the value is shortened or
    cleaned (docs/database.md § Text).

  • Breaking. database.Search takes a context and returns an error, like FilterEq: 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:
    call database.Search(ctx, q, columns, term) and return its error.
    Run gombit generate to update an app's generated handler.gen.go,
    which also answers a get of an id over MaxInt64 with a 404.

  • A scaffolded internal/product/handler.go answers an id over MaxInt64 with 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 generate does not update it. In
    its get, parse the id with strconv.ParseUint(input.ID, 10, 63)
    instead of 64: 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 the AuthSession / AuthRevokeResult schema names fails at startup: the framework registers them. (auth-session-names-reserved, api, #335)

    With GOMBIT_JWT_SECRET set, registering a route at GET or
    DELETE {prefix}/auth/sessions or DELETE {prefix}/auth/sessions/{id}
    (through Huma or the raw Gin router), an operation
    auth-list-sessions, auth-revoke-session or auth-revoke-sessions,
    or a type Huma names AuthSession, DataListAuthSession,
    AuthRevokeResult or DataAuthRevokeResult now panics when the app
    starts. Drop the app's own session routes, which the framework's
    replace, or rename them. A resource named Session
    (gombit make resource Session) does not collide.

  • Breaking. With auth enabled, a GOMBIT_JWT_REFRESH_TTL shorter than GOMBIT_JWT_ACCESS_TTL fails 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 its exp, expires_in and access cookie still
    promised more. Config validation (config.Load / Validate) and
    auth.NewService refuse that pair; equal TTLs are fine. Raise
    GOMBIT_JWT_REFRESH_TTL to at least GOMBIT_JWT_ACCESS_TTL, or
    lower GOMBIT_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 its exp.

Automatic: applied by the upgrade tooling

  • gombit new records the app's upgrade baseline in a gombit: block of gombit.yaml. (record-upgrade-baseline, scaffold, action record-baseline, #341)

    An app generated before records none, and is detected as scaffold
    version 0. gombit upgrade baseline --write records it.

Informational

  • config.Load reads a .env file 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 .env has 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 a uuid field (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, and has_many
    children were written back. database.ScopeEdit now narrows any
    Select("*") update of the scoped row the same way.

  • On Windows, gombit make resource replaces 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.KeepStoredZeros is deprecated: use database.ScopeEdit with database.StoredValues. (keep-stored-zeros-deprecated, api, #444)

    ScopeEdit judges 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. KeepStoredZeros keeps working.

  • MapPersistError and MapLoadError answer 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 over MaxInt64 is a 422 too.

  • Session management in both auth modes: GET {prefix}/auth/sessions lists the user's active sessions; DELETE {prefix}/auth/sessions/{id} and DELETE {prefix}/auth/sessions?scope=others|all revoke them. (auth-session-management, api, #335)

    auth.Service gains ListSessions (returning []auth.AuthSession),
    RevokeSession, RevokeAllSessions and ErrSessionNotFound. 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/refresh answers 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 UPDATE on PostgreSQL, FOR UPDATE on 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.ts includes the session operations; an existing app's generated client gets them from gombit 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-v2 v1.47.3, github.com/aws/aws-sdk-go-v2/service/s3 v1.114.0 and github.com/aws/smithy-go v1.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