Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 43 additions & 1 deletion docs/adr/0004-migration-from-cloak.md
Original file line number Diff line number Diff line change
Expand Up @@ -1082,7 +1082,7 @@ Provenance: bead ece-60gg.

## Amendment (2026-09-29): a `from:` of this package's own type that declares `legacy:` is not silent

Status: proposed (2026-09-29).
Status: accepted (2026-09-30).

Decided: ruled by the operator, 2026-09-29.

Expand Down Expand Up @@ -1154,3 +1154,45 @@ This Note decides nothing: no decision, no amendment's rule and no status
word above changes.

Provenance: bead ece-qlc.

## Note (2026-09-30): the Amendment of 2026-09-29 on a `from:` that declares `legacy:` is accepted

The Status line of the Amendment above, "a `from:` of this package's own
type that declares `legacy:` is not silent", now reads `accepted
(2026-09-30)`. The record's own Status line and its index row, `accepted
(2026-08-27, with amendments)`, do not change. The Note of 2026-09-29 on the
release `eval` lines decides nothing and has no status word to flip.

The code half shipped in `encryptor_ecto` 0.7.0, the commit tagged `v0.7.0`
(`1fcb204`) and published on Hex; its changelog's `[0.7.0]` section carries
the `**Breaking**` entry for a plan field whose `from:` declares `legacy:`.
Every claim the Amendment makes was re-verified at that commit before the
flip:

- What was open. `Encryptor.Ecto.Migrator.Source.vault_backed?/2` answers by
the params `Encryptor.Ecto.Binary.init/2` freezes, and `:legacy` is one of
the keys `frozen_params?/1` requires (`@our_params`,
`lib/encryptor/ecto/migrator/source.ex`), so a declaration with `legacy:`
set still answers `true`. The exit guide carries the section "If you must
go back: the reverse plan" (`docs/guides/migrate-from-cloak.md`), and
`Encryptor.Ecto.Binary`'s moduledoc carries "The migration window:
`:legacy`".
- The decision and where it lives. A declared `true` or `false` is returned
as declared (`Encryptor.Ecto.Migration`'s `source_authenticated!/4`); a
field that declared nothing reaches `undeclared_source!/3`, which raises a
`CompileError` (`raise_at!/2`) when `vault_backed?/2` answers `true` and
the frozen params name a `legacy:` module (`legacy_reader/2`). The message
names the schema and the field (`field_at/1`), the `from:` type and the
`legacy:` module (`undeclared_legacy_message/3`). A vault-backed `from:`
whose declaration names no `legacy:` module still compiles silent, to
`true` (the last branch of `undeclared_source!/3`).
- What it does not change. Between `v0.6.0` and `v0.7.0`, `vault_backed?/2`
changes in its doc only, and `@field_options` is unchanged
(`lib/encryptor/ecto/migration.ex`), so its answer is the same and no
field option is added.
- The tests. `test/encryptor/ecto/migration_test.exs` has "refuses a from:
of this package's own type that declares legacy:", "a from: that declares
legacy: compiles once the field declares" and "one of this package's own
types needs no declaration".

Provenance: bead ece-vvez.
53 changes: 52 additions & 1 deletion docs/adr/0005-wrapped-key-row-shape.md
Original file line number Diff line number Diff line change
Expand Up @@ -1114,7 +1114,7 @@ Provenance: bead ece-60gg.

## Amendment B (2026-09-29): `:root_vault` is required only when `:gcp_kms` is absent

Status: proposed. Ruled by the operator, 2026-09-29. No text above this
Status: accepted (2026-09-30). Ruled by the operator, 2026-09-29. No text above this
Amendment is edited in place, and no status word above flips.

Cites into this package name the function they point at, as it stands in
Expand Down Expand Up @@ -1297,3 +1297,54 @@ read, destroy the version, delete the row", assert the new term as the
commit this Note ships in leaves them.

Provenance: bead ece-h9qm.

## Note (2026-09-30): Amendment B is accepted

Amendment B's Status line now reads `accepted (2026-09-30)`. The record's own
Status line, `accepted (2026-09-13)`, and its index row, `accepted
(2026-09-13, with amendments)`, do not change. Amendment B's "no status word
above flips" was true of it and stays true: the status word that flips is
its own, and it flips here. The two Notes after it decide nothing and have
no status word to flip.

The code half shipped in `encryptor_ecto` 0.7.0, the commit tagged `v0.7.0`
(`1fcb204`) and published on Hex; its changelog's `[0.7.0]` section carries
the `Added` entry for a store started without `:root_vault`. Every claim was
re-verified at that commit before the flip, and against `encryptor` `v0.6.0`
(`91e9643`), the release 0.7.0 pins (`{:encryptor, "== 0.6.0"}` in
`mix.exs`, `deps/0`):

- B1. `Encryptor.Ecto.KeyStore`'s `root_vault/1` answers `{:ok, nil}` when
`:root_vault` is absent and `:gcp_kms` is present, and otherwise defers to
`module_option/2`, which refuses an absent option as `{:missing_config,
[:provider, :root_vault]}` and a value that is not a module as
`{:invalid_config, :root_vault, :not_a_module}`. `root_vault/1` reads only
whether `:gcp_kms` is present; `init/1` then runs `gcp_kms/2`, which
refuses a malformed value in its own terms. The `state/0` type reads
`root_vault: module() | nil`.
- B2. The `%{root_vault: nil}` clause of `unwrap_row/4` matches an
`:engine_message` row whose `key_id` is `nil` and answers
`{:invalid_key_descriptor, {:no_root_vault, "engine_message"}}`; an
engine-message row carrying a `key_id` falls through to the clause that
answers `{:invalid_key_descriptor, :unexpected_key_id}`, as the
`:missing_key_id` clause answers before the GCP delegation. In `encryptor`
0.6.0, `t:Encryptor.Provider.reason/0` carries `{:invalid_key_descriptor,
term()}` (`lib/encryptor/provider.ex`), so the term widens nothing.
`encryption_key/2` unwraps the newest row only, and the repo test "costs
only the engine-message rows of a mixed scope" asserts the read keeping
the GCP version and the write answering the refusal.
- B3. Once `init/1` has placed it in the state, the root vault is read by
`unwrap_row/4` alone: the engine-message clause unwraps under it, and the
clause B2 adds matches its absence.
- B4. The commit that shipped the code half changes no `wrapping_shape`
value, column or `tenant_ref` derivation in
`lib/encryptor/ecto/key_store.ex`: beside its moduledoc and doc text, its
code changes are the `state/0` type, `init/1`'s call to `root_vault/1`,
`root_vault/1` itself and the new `unwrap_row/4` clause.
- The tests. `test/encryptor/ecto/key_store_test.exs` has the describe block
"init/1's :root_vault, with :gcp_kms set" beside "refuses a missing root
vault", and `test/encryptor/ecto/key_store_repo_test.exs` has the describe
block "a store configured with :gcp_kms and no root vault", over the fake
in `test/support/test_gcp_kms.ex`.

Provenance: bead ece-vvez.
5 changes: 5 additions & 0 deletions docs/adr/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ ADR-0004's Note of 2026-09-24 (Q1, the folded index) and ADR-0005's
Amendment A of 2026-09-24 were accepted on 2026-09-24, and ADR-0006 and
ADR-0007 with them; each record's foot Note says what was verified.

ADR-0004's Amendment of 2026-09-29 (a `from:` that declares `legacy:`) and
ADR-0005's Amendment B of 2026-09-29 (`:root_vault` optional with `:gcp_kms`)
were accepted on 2026-09-30, once `encryptor_ecto` 0.7.0 shipped them; each
record's foot Note says what was verified.

New ADRs: next number, same three-section format (Context, Decision,
Consequences), plus the typespecs and worked-example sections this family's
records carry. Pick the number against a freshly fetched remote.
Expand Down
Loading