Skip to content

v1.1.0

Choose a tag to compare

@github-actions github-actions released this 03 Sep 08:18
· 74 commits to main since this release
c7f2362

Verifying this release

checksums.txt names every archive by hash, and it is signed, so
checking the signature once covers all of them.

cosign verify-blob \
  --bundle checksums.txt.sigstore.json \
  --certificate-identity "https://github.com/antifailure/antifailure/.github/workflows/release.yml@refs/tags/v1.1.0" \
  --certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
  checksums.txt

sha256sum --check --ignore-missing checksums.txt

There is no public key to fetch. The certificate inside the bundle records which
workflow, in which repository, at which tag produced these files, and
--certificate-identity is what makes the check mean something: without it
you would be verifying that somebody signed this, rather than that we did.

sbom.spdx.json lists what is inside the binaries, read out of the built
artifacts rather than from go.mod. It is signed the same way, substituting
its own bundle.

These archives are reproducible. Building this tag again produces the same
bytes, so you can check the hash above against one you built yourself rather
than taking ours: antifailure.dev/docs/security/releases.

Three things happened in this release and they are worth separating.

The hosted product became something a person can actually use. v1.0.0
shipped a control plane nobody could sign up for, whose operator portal nobody
could sign in to, whose only two writes both answered 403, and whose sign-up
page led to a waitlist that stored an address on a domain with no way to write
back. Every one of those is closed here. Anybody can create an account, the
first operator can be created at all, twenty two operator sections exist and can
write, and the waitlist is gone rather than improved.

Three things v1.0.0 listed as explicitly not stable are stable now, and each
one has a gate behind it rather than a sentence. That is most of what a minor
release can honestly be: 1.0 drew a boundary and said what was outside it, and
the work since has been moving things across that line one at a time, with the
check written before the promise.

And a run of defects that every existing instrument was green about. Two
render gates that reported success over an application they never opened. A
permission held by two roles with no route behind it. A verifier called by
nothing outside its own test. An event stream nothing stopped shrinking. A
deploy path that could set a third of the configuration the product reads. In
each case something was checking, and it was answering a nearby question. The
pattern is common enough in this release that the fixes are described by what
the old check could not see, rather than only by what changed.

What became stable, and what checks it

The event stream's shape. The 1.0 notes listed the set of event types as not
stable, with the reason "types are added as features land". That says what
happens when the catalog grows and nothing at all about what happens when it
shrinks, which is the only direction that breaks anybody. Nothing was stopping
it shrinking: schemas/events.v1.json is generated from the Go type and the
catalog, so deleting a type or an envelope field regenerates cleanly, the diff
is green, every test passes, and the consumer filtering on that type receives
nothing and cannot tell that from a quiet system.
engine/internal/events/stream.register.json now records the fifty five types
and the eight envelope fields version 1 promised, and just eventcheck refuses
a type that is gone, a field that is gone, a field whose type changed, a
required field that became optional, and a closed set that lost a value. The
data object stays outside the promise, and the reference now says so: it is
the type specific payload and its keys move with the code that writes them.

The self-hosting inputs. v1.0.0 listed the Helm values file and the
Terraform variables as free to change in a minor release. The cost of that
sentence fell entirely on the operator, whose values file and tfvars file are
their configuration, kept in their repository and applied by their pipeline: an
upgrade was a hand migration that could not be automated, and nobody outside
this repository could know whether a key still existed in the version they were
about to take. It also failed quietly. Helm accepts a values key no template
reads, and Terraform only warns about a variable nothing declares, so a rename
does not stop an apply, it removes a setting while the apply reports success.
The promise is now the name, the type, and whether an input is required, for
every key in the chart's values and every variable and output in the Terraform.
tools/inputcheck compares against a snapshot taken at v1.0.0 and refuses a
removal, a rename, a changed type, an optional input becoming required, and a
new input arriving without a default. Adding an optional input stays compatible.
Defaults are deliberately excluded, and image_tag is why: it names the release
being cut, and tools/tagsync exists to make sure it moves.

Five variables were renamed first, because freezing a name that is wrong means
living with it until a 2.0. The foundation module's name is
resource_group_name. Its log_analytics is log_analytics_enabled, a switch
that sat one line from an output called log_analytics_id. The alerting
module's connection_percent is database_connection_percent.
golden_replication and golden_soft_delete_days are goldens_replication
and goldens_soft_delete_days. If you are self-hosting from the modules, those
five are the only renames in this release and they are the last ones until 2.0.

The identity of a lint finding. See Added. A finding's LINT-NNN never
moves and is never reused; its rule name, title, explanation and fix all stay
free to change, which is what makes a rule better.

What moves when this tag is pushed

The same things v1.0.0 moved. tools/tagsync holds every version pin in the
tree against this changelog, so the chart's appVersion and the four examples
in the release runbook name this release, and the two Terraform image_tag
defaults keep naming v1.0.0 until this tag has actually published, because those
two are read by an apply from main and naming an unpublished tag produces a
failed apply on the stack that runs the product rather than a stale deployment.

Behaviour you may depend on that changed

  • A path the control plane has no route for answers 404 rather than 500. It
    had been answering 500 with a body that told whoever asked to edit the rate
    limit table, so anything treating a 500 from /v1/ as "the server is
    unhealthy" was reading a routing answer as an outage. GET /health is
    unchanged and was always correct. A route that exists and declares no rate
    limit is still refused, and still refused before its handler runs; what moved
    is where the explanation goes.
  • A HEAD request now resolves its rate limit through the GET it is
    dispatched as. Every HEAD on a bounded endpoint used to be treated as
    unbounded and refused.
  • On a deployment running with the console switched off, a path with no route
    answers JSON rather than plain text. Everything else the API says is JSON, and
    the response somebody finding their way around is most likely to get was the
    one that parsed differently.
  • Five Terraform variables were renamed, and they are the last renames until
    2.0 because the surface is frozen from this release. The foundation module's
    name is resource_group_name. Its log_analytics is
    log_analytics_enabled. The alerting module's connection_percent is
    database_connection_percent. golden_replication and
    golden_soft_delete_days are goldens_replication and
    goldens_soft_delete_days. tools/inputcheck refuses any further rename.
  • af --version prints the version instead of unknown flag: --version. It is
    the same implementation af version runs, including asking the running binary
    for its edition, so the two cannot disagree.

The rest of this release, one entry per change

Every change has its own entry, grouped by what kind of change it is and
searchable: https://antifailure.dev/changelog

Each was written when the change was made, by whoever made it, and is dated by
the commit that landed it. The same entries are in CHANGELOG.md in this
repository.

Security

  • The trust boundary page now says that masking and the scan that checks it are
    one instrument rather than two, and what that means for the data a check
    comment can carry. Both decide what to look at from
    information_schema.columns.data_type and both accept the same six values, so
    a column outside that list is not emptied by the fail-closed default and is
    not read by the verification scan. It is copied. citext, which is the
    ordinary Postgres type for an email address or a username, reports as
    USER-DEFINED and is outside the list. That compounds with the one path on
    which records already crossed the boundary: an invariant that does not hold
    carries up to five rows into the check comment, so such a row can carry a real
    production value. The page had said the scan covers every column; the sentence
    is corrected rather than softened, and the page no longer describes the scan
    as verifying the masking, because a check that shares its subject's blind spot
    verifies nothing about that blind spot. No masking behaviour changes here, and
    the page says why: widening the list is not additive, because a column copied
    today would start being emptied, which changes rules_digest and invalidates
    every existing golden.
  • CodeQL runs on this repository. Nothing was scanning our own code.