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
1 change: 1 addition & 0 deletions CHANGELOG.md

Large diffs are not rendered by default.

Large diffs are not rendered by default.

137 changes: 102 additions & 35 deletions docs/engineering/ai-delivery/tasks/BE-03.md
Original file line number Diff line number Diff line change
Expand Up @@ -372,7 +372,10 @@ Re-derived from the merged code at the authoring base:
`ThothError::DistributionPlatformNotAssignable` and
`ThothError::EntityNotFound` already exist.
11. **Generated contract** is `thoth-client/assets/schema.graphql`, written by
`thoth-client/build.rs` from `thoth_api::graphql::create_schema()`.
`thoth-client/build.rs` from `thoth_api::graphql::create_schema()`. It is a
build product, ignored through `thoth-client/.gitignore`, so it is
regenerated rather than tracked; section 14.3 item 3 states how BE-03
evidences it. The tracked client contract file is `assets/queries.graphql`.

## 3. Explicit scope

Expand Down Expand Up @@ -821,11 +824,17 @@ one connection:
`ThothError::DistributionPlatformNotAssignable` before the first lifecycle
call, so a rejected request never depends on rollback to leave the
configuration untouched.
8. Update `subscription_package` only if it differs. Record whether it changed.
The coordinator writes the column directly inside its own transaction; it does
**not** route the change through the shared `Crud::update` macro, so the
mutation writes no `publisher_history` row and the configuration audit of
section 8 remains the only history BE-03 writes (section 6.4).
8. Compare the requested `subscriptionPackage` with the package read under the
lock in step 3 and record whether it differs as `package_changed`. **Do not
update the publisher row here.** The package write is deferred to the single
publisher `UPDATE` of step 10, so a change to both the package and the
platform state still updates the publisher row exactly once and therefore
still fires the existing `AFTER UPDATE` work-freshness trigger exactly once.
When that update happens, the coordinator writes the column directly inside
its own transaction; it does **not** route the change through the shared
`Crud::update` macro, so the mutation writes no `publisher_history` row and
the configuration audit of section 8 remains the only history BE-03 writes
(section 6.4).
9. Apply the desired platform state **through BE-02's connection-scoped
lifecycle primitives**, per section 7.7, never by writing
`publisher_distribution_platform` rows directly, as follows:
Expand Down Expand Up @@ -855,16 +864,34 @@ one connection:
the report, not this mutation.

That bound covers the statements BE-03 issues. It does **not** describe
everything the transaction writes: step 10's publisher `UPDATE` fires the
existing `AFTER UPDATE` trigger, which issues one further set-based statement
updating every work of that publisher (sections 2.1 item 8 and 6.4). The
transaction's total row-write footprint is therefore one publisher row, plus
the bounded configuration and audit rows, plus **N related work rows**, where
N is the number of works currently belonging to that publisher through its
imprints. N is a database-side effect of one set-based trigger statement, not
a per-work application loop, and BE-03 must not introduce one.
10. If and only if step 8 or step 9 changed something, set
`service_configuration_updated_at` per section 6.2.
everything the transaction writes: step 10's **single** publisher `UPDATE`
fires the existing `AFTER UPDATE` trigger, which issues one further set-based
statement updating every work of that publisher (sections 2.1 item 8 and
6.4). The transaction's total row-write footprint for a committed change is
therefore one publisher row, plus the bounded configuration and audit rows,
plus **N related work rows**, where N is the number of works currently
belonging to that publisher through its imprints — and it is that same
footprint whether the change was package-only, platform-only, a linked
repair, or package and platform combined, because all four commit through one
publisher `UPDATE`. N is a database-side effect of one set-based trigger
statement, not a per-work application loop, and BE-03 must not introduce one.
10. If and only if step 8 or step 9 changed something, issue **exactly one**
publisher `UPDATE`, which in a single statement:
1. writes `subscription_package` to the requested value when
`package_changed`, and leaves the column untouched otherwise;
2. sets `service_configuration_updated_at` per section 6.2;
3. returns the resulting publisher row, so the `after_state` of step 11 is
read from what was actually persisted.

If neither step 8 nor step 9 changed anything, the request is the true no-op
of section 7.4 and issues **zero** publisher `UPDATE` statements.

One publisher `UPDATE` per committed change is a required property, not an
incidental one: the publisher row carries the shared `AFTER UPDATE`
work-freshness trigger, so a second `UPDATE` in the same transaction would
run that trigger's set-based cascade a second time over the same N work rows
for no additional effect. A combined package-and-platform change must
therefore cost the same one cascade as a platform-only change.
11. If and only if step 8 or step 9 changed something, insert exactly one
`publisher_service_configuration_history` row for the whole committed
change, with the `source` and `actor` supplied by the caller's write
Expand Down Expand Up @@ -1227,7 +1254,7 @@ CREATE TABLE public.publisher_service_configuration_history (
FOREIGN KEY (publisher_id)
REFERENCES public.publisher(publisher_id) ON DELETE CASCADE,
CONSTRAINT publisher_service_configuration_history_actor_check
CHECK (btrim(actor) <> '')
CHECK (actor ~ '[^[:space:]]')
);

CREATE INDEX publisher_service_configuration_history_publisher_created_idx
Expand All @@ -1249,11 +1276,19 @@ Notes:
non-ZITADEL migration control identity, and a column named `user_id` would then
assert something untrue. **No new identifier format, credential, secret or
local account table is introduced.**
- the `CHECK (btrim(actor) <> '')` constraint makes "explicit, stable actor
- the `CHECK (actor ~ '[^[:space:]]')` constraint makes "explicit, stable actor
identity" a database property rather than a convention: no write path,
present or future, can record an anonymous or blank actor. Section 18.7
requires catalog verification of the constraint and a test proving a blank
actor is rejected.
present or future, can record an anonymous or blank actor. **The authoritative
invariant is that an audit actor must contain at least one non-whitespace
character**, and the predicate above is the exact expression of it: the POSIX
`[[:space:]]` class covers space, tab, newline, carriage return, vertical tab
and form feed, so `actor ~ '[^[:space:]]'` rejects the empty string and every
whitespace-only string built from those classes, while accepting any actor that
carries a real identifier even when it is surrounded by whitespace. A narrower
`btrim(actor) <> ''` predicate would satisfy only part of that invariant — it
trims spaces alone, so a tab-only or newline-only actor would pass — and is
therefore not used. Section 18.7 requires catalog verification of the
constraint and tests proving the whole whitespace matrix is rejected.
- actor and source are explicit columns, not fields buried in free-form JSON,
so they are indexable, typed and greppable.
- `created_at timestamptz` follows BE-02. The legacy
Expand Down Expand Up @@ -1363,8 +1398,9 @@ incoherent reserved value to avoid:
coherently in one place rather than inheriting the misleading `user_id` name
(section 8.1);
3. `source` disambiguates the namespace, so a `MIGRATION_BACKFILL` actor can
never be mistaken for a ZITADEL user id, and the `CHECK (btrim(actor) <> '')`
constraint makes an anonymous actor impossible for any write path;
never be mistaken for a ZITADEL user id, and the
`CHECK (actor ~ '[^[:space:]]')` constraint of section 8.1 makes an anonymous
actor impossible for any write path;
4. section 9.3 binds MIG-01 to the same coordinator and the same persistence
invariants, so the value's semantics are fixed by BE-03 rather than left for a
later task to improvise;
Expand Down Expand Up @@ -1878,13 +1914,29 @@ protected by section 11.1.
[]` as the merged enum-list precedent. If the generated lines render as
non-null lists, the implementation — not this specification — is wrong and
must be corrected before the diff is accepted;
3. commit the regenerated `thoth-client/assets/schema.graphql`;
3. **do not hand-edit the generated schema, and do not commit it.**
`thoth-client/assets/schema.graphql` is build-generated by
`thoth-client/build.rs` and is ignored through `thoth-client/.gitignore`
under existing repository authority — the root `AGENTS.md` section 12.2 and
`thoth-client/AGENTS.md` section 1 both govern it. BE-03 must not hand-edit
it, must not newly commit it, must not force-add it and must not modify
`thoth-client/.gitignore` to make committing it possible. The evidence
obligation is discharged instead by recording, in the implementation report:
the exact generation command used through the repository's normal build path;
the exact additive/removal SDL diff against the authorized base, produced by
regenerating the base artifact the same way; and a reproducible identity for
the generated artifact — its SHA-256, or the repository-equivalent checksum —
at the exact head being reviewed;
4. update `thoth-client/assets/queries.graphql` and the client's generated
types only if the client actually consumes a changed surface. BE-03 adds
protected operations the internal export client does not use, so the
expected outcome is **no query change**; if that holds it must be stated as a
reviewed conclusion, not an omission;
5. build and test `thoth-client`;
reviewed conclusion, not an omission. `assets/queries.graphql` **is** tracked
and is committed when it changes; only the generated schema artifact is
ignored;
5. verify `thoth-client` compatibility by building and testing it through the
repository-supported workspace path, recording the exact commands and
results;
6. assess `thoth-app` codegen compatibility as an additive-only change and
record the conclusion. **BE-03 does not modify `thoth-app`**;
7. record the exact backend commit SHA of the reviewed BE-03 head in the
Expand All @@ -1908,9 +1960,9 @@ Schema changes:
- `CREATE TYPE public.publisher_service_configuration_source` with the two closed
values of section 9.1;
- `CREATE TABLE public.publisher_service_configuration_history` with its
`actor text NOT NULL` column, its named non-blank actor check constraint, its
primary key, its `ON DELETE CASCADE` foreign key and its composite index, per
section 8.1.
`actor text NOT NULL` column, its named non-whitespace actor check constraint
`CHECK (actor ~ '[^[:space:]]')`, its primary key, its `ON DELETE CASCADE`
foreign key and its composite index, per section 8.1.

`down.sql` drops the table, the type and the column.

Expand Down Expand Up @@ -2179,7 +2231,12 @@ Also required:
`PolicyContext::user_id()`;
- `source` is `SUPERUSER_API` for **every** row BE-03 writes, and no BE-03 path
can write `MIGRATION_BACKFILL`;
- the non-blank actor check constraint rejects a blank or whitespace-only actor.
- the section 8.1 actor check constraint enforces the invariant that **an audit
actor must contain at least one non-whitespace character**. It must be proven
to reject the empty string and every whitespace-only actor, covering at least
spaces, tabs, newlines, carriage returns, vertical tabs, form feeds and a mixed
whitespace string, and to accept an actor carrying a real non-whitespace
identifier surrounded by whitespace.

**Timestamp-movement evidence (section 6.4).** All six cases must be reproduced
against a **real disposable PostgreSQL database** with the migration applied, so
Expand Down Expand Up @@ -2241,7 +2298,12 @@ Also required:

- a test asserting that a committed configuration change writes **no**
`publisher_history` row, confirming the coordinator does not route the package
update through `Crud::update` (section 7.3 step 8);
update through `Crud::update` (section 7.3 step 10);
- tests asserting the committed write footprint of section 7.3 step 10: exactly
**one** publisher `UPDATE` for a package-only change, a platform-only change, a
linked repair and a combined package-and-platform change, and **zero** for a
true no-op and for a stale request, with no per-work application loop in any
case;
- the implementation report must explicitly record the
`publisher_history.data` consequence of section 6.4: because
`service_configuration_updated_at` is appended to the `Publisher` struct that
Expand Down Expand Up @@ -2369,8 +2431,9 @@ Also required:
- `pg_class.relfilenode` for `publisher` compared before and after, with the
observed lock and duration recorded;
- catalog verification of the new type and its exact two labels in order, the
table, its primary key, its foreign key, the named non-blank actor check
constraint, the composite index and the new `publisher` column;
table, its primary key, its foreign key, the named non-whitespace actor check
constraint — whose catalog definition must be the section 8.1 predicate, not a
narrower `btrim` form — the composite index and the new `publisher` column;
- `thoth-api/src/schema.rs` matches the migrated database contract.

### 18.8 Query-count evidence
Expand Down Expand Up @@ -2555,8 +2618,12 @@ must additionally record:
- the exact SDL diff, including the new `ThothPackage` and `PublisherCapability`
enum blocks, and the three list-argument lines quoted **verbatim** and
compared explicitly against the merged siblings named in section 14.3 item 2;
- the exact backend commit SHA for APP-01 contract pinning, and the schema
artifact that SHA pins (section 14.3 item 7);
- the exact backend commit SHA for APP-01 contract pinning, and the reproducible
identity (SHA-256 or repository-equivalent checksum) of the generated schema
artifact that SHA pins, regenerated through the normal build path at that exact
head rather than committed (section 14.3 items 3 and 7);
- the `thoth-client` compatibility result, with the exact
repository-supported workspace commands used (section 14.3 item 5);
- the effective-capability evidence: the field's final name and type, that its
value is `ThothPackage::capabilities()` for every package with the asserted
canonical ordering, that no capability state is persisted anywhere, that a
Expand Down
Loading
Loading