What a BigQuery test runs against¶
Status: accepted, and amended repeatedly - the amendments below carry the current state. The rendering half is built and green. The acceptance leg is no longer opt-in-only and no longer absent from CI - see Amendment, 2026-08-30 - and the adapter is no longer unwired: a composition root links it, behind a default-off feature, which is the Second amendment, 2026-08-30 below. Read the amendments before citing anything above them; the reasoning stands, several of its conclusions do not.
The ordinals were renumbered on 2026-09-06, and a citation older than that may name a different
section than it did. Three branches appended here in two days and the sequence drifted into two
Fifth amendments - the second after the Sixth - plus an unnumbered one mid-list. The headings now
run consecutively in file order, and cargo xtask check-guidance holds that rather than a reviewer:
xtask/src/guidance/pages.rs is the rule and states what it does not reach, which is chronology and
every cross-reference outside this page.
docs/implementation-plan-bigquery.md gates its first BigQuery step on one decision, to be made
before the adapter is written: what does a test run against? There is no BigQuery in a container.
The choice decides whether the step can claim acceptance at all or only rendering, and
0007 already says plainly that parse-checked is
narrower than accepted.
This record makes the decision and records what it costs, so it is not implied by whatever the first test happened to do.
The three options, priced¶
An emulator that speaks the API - REFUSED, and not on cost¶
Something that answers the job API without being GoogleSQL would give a green corpus over statements
the real service may reject, which is the exact failure differential.rs exists to prevent: that
test runs one plan through the engine and through a real DuckDB and compares rows, because agreement
between two things that are both wrong is not evidence.
This is the option to refuse first and hardest, because it is the one that produces the most confident-looking green. It is refused on principle rather than on effort: an acceptance test whose subject is not the system whose acceptance is in question is not an acceptance test.
A real project in CI, with a service-account secret - REFUSED for this step¶
It would genuinely vouch for acceptance. What it costs:
- This repository is public. A workflow secret is not available to a pull request from a fork, so the check would be absent on exactly the contributions least likely to have been run locally - the shape of gate that is worse than none, because its green means "nobody could run it".
- Every CI run acquires a dependency on a cloud project, its billing, its IAM and its availability. A gate that fails for a reason outside the diff gets disabled, and this file's own guidance says so.
- It needs a project. Which project, and who pays, is a decision with an owner outside this repository.
Not rejected forever: if an organisation-level credential and an owner appear, this becomes the best option and the acceptance leg below is the thing that gets pointed at it. What is rejected is blocking this step on that.
The Storage Read API against a fixture table - REFUSED, wrong instrument¶
It reads a table without running our SQL. The thing in doubt is whether the statement we generate is accepted and means what we think, and a path that never submits the statement cannot answer it. It would test the transport and call it acceptance.
The decision¶
A real project, reachable from a developer's own machine, with the acceptance leg marked not-in-CI.
What makes it reachable today is that just gcloud-login already lands: it performs both logins that
matter, writes nothing into this repository, and its own documentation states that it serves
SharedServiceUser and cannot become impersonation. That is exactly the posture the adapter step
declares, so the step can be built and demonstrated with nothing more.
Which project, dataset and location a developer works against is not written down here and will not be. A developer names it in their own environment, for the reason the plan page gives: this repository is public, so the value belongs on the machine and only the hook belongs here.
And the fields for two of those are not in this repository for the same reason the value is not:
SourcePlacement::BigQuery declares a billing project and a dataset and nothing else. A dataset's own
project is unrepresentable - an omitted defaultDataset.projectId resolves in the request's (billing)
project, so a dataset owned by a different project than the payer cannot be declared - and neither is
location, which the result-paging call needs for a dataset outside the two multi-regions. Stated
rather than added because no transport consumes them; the change that adds the wire is the one that
decides the fields.
What the corpus therefore claims, exactly¶
Rendering and parse-checking. Not acceptance. In CI, for this dialect:
- the 21 questions render, and their statements and parameters are pinned as goldens reviewed as a diff;
- every statement is parsed with the BigQuery target;
- no value from a question reaches a statement as text, and every identifier is quoted - both asserted with the dialect's own quote character, which is a backtick;
LIMIT 10001is present, which is the row cap's SQL leg.
DuckDB remains the only data system that vouches for acceptance in CI, through the anchors and
through differential.rs. Postgres, ClickHouse and now BigQuery are rendered and parse-checked and
nothing more. The Invariants row in AGENTS.md says this, and now says how much narrower.
The gap the measurement exposed, which is the part worth carrying¶
Writing this dialect turned "parse-checked is narrower than accepted" from a hedge into a measured number, and the finding is sharper than expected: within one target, the parse check cannot see a function's argument order.
BigQuery spells the time bucket DATE_TRUNC(<date>, MONTH) - the date first, the granularity a bare
keyword - where the other three take DATE_TRUNC('month', <date>). Both shapes, rendered for
BigQuery, parse cleanly as BigQuery. So a generator arm that got this wrong would render, parse,
snapshot green, and fail at the service on the first real question.
Three consequences:
sutura_sql::dialect::DateTruncShapeis an exhaustive declaration rather than a check, so a fifth dialect cannot be added without stating its spelling.the_parse_check_cannot_tell_the_two_bucket_shapes_apartkeeps the measurement as a test, so the claim cannot quietly stop being true - and if the parser ever grows a signature check, that test fails and the right response is to re-scope the claim rather than weaken the assertion.- The severity is worth stating precisely, because the two failure modes here are not equal.
Getting the bucket wrong is a rejection at the service: the first argument is the value to
truncate, a string coerces there only if it is a canonical date, and
'month'is not one. Getting the quote character wrong is the wrong-number risk, because a double quote opens a string in GoogleSQL -SELECT "amount"selects the textamount. That one happens to be caught in CI, on a qualified column, and it is caught by luck about the shape we generate rather than by a guarantee.
The second thing measurement found, and it is a wrong number rather than a rejection¶
The bucket's shape is above. Its semantics hid a second defect, on a grain no golden covers, and it is worth its own section because the corpus could not have found it.
BigQuery's WEEK begins on Sunday - its own reference says WEEK is equivalent to
WEEK(SUNDAY). A real DuckDB 1.5.5, asked directly, answers
DATE_TRUNC('week', DATE '2026-08-30') - a Sunday - with 2026-08-24, a Monday, putting that
Sunday in the previous week. So the obvious keyword mapping, Week => "WEEK", would bucket a
Sunday's rows under a different period on BigQuery than on the data system that vouches for
acceptance. No error anywhere; a different number under a certified name.
BigQuery's Monday-based part is ISOWEEK, and that is what the generator asks for. The other four
grains need no such care: DAY, MONTH, QUARTER and YEAR mean the same thing in every dialect
this repository renders for.
Why the corpus could not catch it, and what holds it instead. The example questions ask only day
and month, so no snapshot renders a week bucket at all - which is precisely the kind of gap a golden
suite cannot report, because a missing case looks exactly like a passing one. What holds it is a test
pinning the keyword by value, plus the reasoning written at the match arm.
The limit, because this claim is narrower than it sounds: what was compared is BigQuery's
documented behaviour against a measured DuckDB. Whether ClickHouse's week agrees with DuckDB's is a
pre-existing question that this arm does not touch and that no test in this repository answers.
Flagging it rather than fixing it, because the three-dialect renderings are unchanged here and a
silent change to one of them belongs in its own diff.
The acceptance leg, when somebody runs it¶
It is not built in this change, and the reason is the same decision applied to itself: the adapter
in this repository has no transport that speaks to the endpoint, because nothing here can verify one.
sutura_exec_bigquery::transport::JobTransport is the seam it arrives at, and the change that
implements it is the change that can first run it against a project - which is where the dependency
decision (an HTTP stack and a credential library, in a workspace that cross-builds to musl and holds
an exact licence allowlist) belongs.
When it lands, what it has to assert is what would actually be new information:
- the corpus's statements are accepted and return rows, which is the claim CI cannot make;
- the rows agree with the engine's for the same plan, which is
differential.rs's shape pointed at a second data source; - the bucket is right, which is the one thing above that no local check reaches;
- the result is the endpoint's complete answer and not the first page - a job
jobCompletewithtotalRowsequalling the delivered count, which is what the seam'sJobRows::ofnow forces a transport to vouch for rather than leaving completeness to the shape of a page.
And it stays out of just validate, because a gate that needs a cloud project is a gate that fails
for an environment reason on somebody else's machine.
Consequences¶
- The
data_systems:axis ofcrates/sutura-app/tests/adapters/mod.rsgains no BigQuery entry. That registry's own rule is that an entry is something somebody could deploy, and a cell that cannot execute reads as coverage. The dialect axis gains one, unconditionally. - A developer who wants to try it against their own project needs the transport, which does not exist yet. Until then the honest summary of BigQuery support in this repository is: the statement is right as far as four mechanisms can tell, and nobody has run one. That sentence is superseded - see the amendment above: somebody has now run one.
Amendment, 2026-08-30: acceptance IS in CI now, and the reason this record gave is what changed¶
Status of the amendment: accepted. The decision above stands as reasoning and its conclusion is superseded in one specific way. This section says which, because a merged record that quietly stops being true is worse than one that was wrong from the start.
What this record refused, and on what grounds¶
A real project in CI, with a service-account secret was refused, and the FIRST of its three stated costs was the deciding one:
This repository is public. A workflow secret is not available to a pull request from a fork, so the check would be absent on exactly the contributions least likely to have been run locally - the shape of gate that is worse than none, because its green means "nobody could run it".
That cost is answered by a mechanism this record did not consider: a GitHub environment. An
environment's secrets are not exposed to a pull_request run from a fork at all - the job cannot
start with them - so the failure mode is not "a fork sees the secret" but "a fork's run skips". Which
is the honest outcome for a runner that had no choice, and is this repository's own rule about skipping
versus failing.
The record's own words left this open: "Not rejected forever: if an organisation-level credential and an owner appear, this becomes the best option." A credential and an owner appeared.
What is now in CI¶
.github/workflows/ci.yml's bigquery-acceptance job. Its own job, so a failure is attributable and
the rest of CI does not wait on a cloud call. It names environment bq-test, writes the key from that
environment's secret to a path under $RUNNER_TEMP - outside the checkout - points
GOOGLE_APPLICATION_CREDENTIALS at it, runs nix run .#bigquery-acceptance, and removes it in an
if: always() step.
Three things about the credential's path, because two of them cost real failures:
- It is never echoed, never interpolated into a command line, and never passed as an argument.
printenvwrites it andumask 077precedes the write. A multi-line JSON key is exactly the shape that defeats naive log masking, so nothing relies on masking. - Outside the checkout is not tidiness. The secret sweep scans the WORKING TREE and does not honour
.gitignore, so a key inside the repository failsjust secrets- measured, three leaks - and that blocks the push stage for everybody, not only the person who put it there. - The billing project is read from the key's own
project_id, so no project variable is configured. The dataset and table come from environmentvars; all three fail loudly when unset rather than skipping.
And the three RESOURCE names are masked, which is a second disclosure channel that the credential
handling above does not cover and that this record nearly missed. Workflow logs on a public
repository are public. The wire carries the endpoint's own message on a refusal - bounded,
deliberately, because a 400 invalidQuery with only a reason code proved undiagnosable - and that
message quotes what it refused, as project:dataset.table. So a failing acceptance run would have
printed a project id, a dataset and a table name into a log anybody can read, which is precisely the
class This Repository Is Public says not to disclose. The job therefore emits ::add-mask:: for all
three before the leg runs: the project id read out of the key, and the dataset and table from the
environment's variables. ::add-mask:: is the right mechanism because it does not care where a value
appears - it redacts every later log line in the job, a panicking test's own message included.
The cost is stated rather than hidden: in CI the diagnostic that made carrying the message worth while is redacted away. A maintainer who needs the unredacted text runs the leg locally, where the log is theirs. That is the correct direction for a public repository and it is a real loss.
Where the fixture's location lives, and where it does not. The dataset and the table are GitHub
environment variables on bq-test - scoped to the one environment, not repository-wide - and the
billing project is read from the key. None of the three is written into this repository, which is the
same split .envrc already uses for a developer's machine: the hook is in the repository and the value
is not. A missing variable fails the job with a message naming it, rather than skipping, because
reaching this job at all means somebody configured the environment.
It is an app and not a checks.* output, and that is forced rather than chosen: nix checks run in
a sandbox with no network, so acceptance could not be a check even with a credential. just validate
therefore still does not cover this, and the Consequences below still hold on that point.
What the leg now claims, exactly - and the word that still does not belong¶
A statement this repository generated was accepted by BigQuery on 2026-08-30, answered as one
complete page, and its numbers were the fixture's. That is the first time anything here has had a
statement accepted by that service, and it happened from a developer's machine before the CI job
existed. The three tests are: a dry run accepted, a real run whose two bucketed sums are 42 and 99
against a four-row fixture that discriminates, and a negative control - a table the dataset does not
hold, refused rather than panicking.
The word "corpus" still does not belong near it. This is ONE hand-built SUM over a two-column
table: no join, no COUNT(DISTINCT, no CASE WHEN, no NULLIF ratio, no CAST(... AS FLOAT64) and
no ISOWEEK - and ISOWEEK plus DATE_TRUNC's argument order are precisely the two things the
measurement sections below identified as invisible to a parse check. So the four acceptance bullets
this record wrote are answered as: the statement is accepted (for one statement), the answer is proved
complete, and the rows are NOT compared against the engine's and the bucket is checked only for
MONTH. The corpus-wide leg is #78's importer shape pointed at a dataset, and it is not built.
And identity is untouched. A service-account key is SharedServiceUser - one identity for
everybody who asks - so this establishes accepted, and correct for that identity, and says nothing
whatever about per-subject execution. BigQueryWarehouse::IMPERSONATION still reads
NoPlaceForASubject.
One finding the live run produced that no local check could have¶
The first submission came back 400 invalidQuery: "Cannot access field day on a value with type
INT64". The cause was in the FIXTURE rather than the generator - the plan's metric label was the same
word as the table name, and GoogleSQL resolved the qualifier to the select-list alias instead of the
table. Worth recording twice over:
Definitions::assemblerefuses a dimension named after its metric, and nothing refuses a metric label equal to the TABLE name. On this dialect that produces a statement the service rejects. Flagged rather than fixed here, because it is a domain change and this record is not the place.- It is why the endpoint's
messageis now carried on the refusal, bounded. It had been dropped deliberately - what is not read cannot be logged by accident - and a status plus a reason code that together say your SQL is wrong turned out to be undiagnosable. The bound answers the original concern; dropping the field answered it by removing the diagnostic too.
What has happened since, and the one sentence above that stopped being true¶
The transport exists. 0018 is the dependency
decision this record deferred, and sutura_exec_bigquery::wire is what came of it: jobs.query over
a blocking HTTP client already resolved in Cargo.lock, behind a default-off feature, with the
acceptance leg written as crates/sutura-exec-bigquery/tests/acceptance.rs and reached by
just bigquery-acceptance.
So the last bullet above is corrected rather than left standing: a developer who wants to try it now
has the transport and needs only just gcloud-login and three values in their own environment.
But what they would be running is NOT the acceptance leg this page specifies, and that is worth
being blunt about. The four bullets under The acceptance leg, when somebody runs it ask for the
corpus's statements accepted, the rows agreeing with the engine's, the bucket checked, and the answer
proved complete. tests/acceptance.rs delivers the fourth and a single instance of the first: one
hand-built SUM over a two-column table a developer supplies. It contains no join, no
COUNT(DISTINCT, no CASE WHEN, no NULLIF ratio, no CAST(... AS FLOAT64) and no ISOWEEK - and
ISOWEEK and DATE_TRUNC's argument order are exactly the two things this page MEASURED the parse
check to be blind about, so they are what a live run is worth most for. The leg this page specifies
is #78's importer shape pointed at a dataset - load the fixtures, run the 21 questions, compare rows
with the engine - and it is not built. docs/adr/0018 records that gap, and the smoke leg's own
header opens with it.
This record said the change adding the wire would be the change that could first run it against a
project. It was not - the machine it was written on had no gcloud, no application-default
credential and no project, so the diff that wrote the wire executed nothing. A LATER diff did, and
the two sentences that followed this one here have been deleted rather than hedged: they read
"nothing has been executed against a real dataset, the acceptance leg has never run", and
Amendment, 2026-08-30 above records the run that made both false. What survives unchanged is the
narrowness: the data_systems: axis still gains no entry, because one live statement is not a
registered data system. The summary sentence therefore survives with one word
changed - five mechanisms rather than four, the fifth being the wire's own suite over response
documents that are not the service's - and 0018's What is still not claimed section is where that
is spelled out.
Second amendment, 2026-08-30: the adapter is registered, so the last not wired is spent¶
Status of the amendment: accepted. Nothing above is contradicted by this section - it closes the
one thing every version of this page, and AGENTS.md's Built And Not Wired, kept naming as absent:
no composition root links the crate, and sutura-serve refuses kind: bigquery by name. Both
sentences are now false, and this records what replaced them so a reader does not have to infer it
from a diff.
What is wired¶
sutura-serve opens a kind: bigquery source. open_engine holds the exhaustive match over
sutura_config::SourceKind - it MOVED there from build_engine, because a kind whose answer is a
different registry can only be dispatched by the function that chooses one - and build_bigquery
composes the three layers the acceptance leg composes, in the same order and through the same
constructors: a WireAgent::pinned carrying JobBounds, a Credential read off a declared file, a
BigQueryWire over both, and a BigQueryWarehouse over that.
Two values the source entry did not carry before, both required for that kind and both refused on a
files one:
credential_file, absolute. Required rather than falling back toCredentialFile::well_known, which is what the acceptance leg uses: a test may resolve a credential from whichever of three Google variables happened to be exported, and a service may not - that is an identity nobody declared. It is read at BOOT, for the reason the inbound key set is read before the listener opens.max_bytes_billed, with no default. It is the only bound on bytes SCANNED anywhere in this repository and the only number in the settings tree that spends money, so both safe defaults are wrong in opposite directions: a small one refuses ordinary questions on a large table and a large one is indistinguishable from no bound. Its RANGE stays the adapter's -BytesBilledCeiling::parse- so there is one parse of it and a value outside the range is a startup refusal naming the key.
The query deadline is not a new key: it is filled from server.request_timeout_seconds, which is
what wire::QueryDeadline's own documentation asks a composition root for by name. A job that outlives
the request it is answering is billed for a result nobody is waiting for.
Which binaries link it, and the one that deliberately does not¶
sutura-serve, behind a default-off bigquery feature. sutura-cli, not at all.
(The second half is SUPERSEDED by the seventh amendment below: sutura-cli has a default-off
bigquery feature of its own since telekom/sutura#121. The reason given below is why it took a
feature rather than a plain dependency, and that half still holds.) The reason is
the four cross builds and it is measured rather than assumed:
- the release derivations pass
--package sutura-cli, so they never compilesutura-serveitself; - but
crane.buildDepsOnlyis deliberately unscoped - the flake says so, because the checks share that derivation - so a non-optional dependency insutura-servewould compileureq, rustls andringfor all four cross dependency derivations, two of which are musl, for a binary that links none of it.
So the feature is the honest shape, and the refusal that used to name the KIND now names the FEATURE:
open_bigquery has two definitions of one signature, and the cfg(not(feature = "bigquery")) one
tells an operator to build with --features bigquery rather than to change the kind:. Every gate
here passes --all-features, so the registration is compiled, linted and tested on every run; a bare
cargo build and the four cross builds are unchanged.
What that means for a shipped artifact, stated plainly because a feature is easy to read as
availability: no published binary opens a BigQuery dataset today. The image holds sutura-cli,
which links the engine only; sutura-serve is not a nix package at all and is built from source.
Two limits this registration introduces, both refusals rather than silences¶
- A catalog reading two KINDS of source does not start.
sutura_app::Warehouses<W>is generic in one adapter type, so this process holds two file sources or two datasets and cannot hold one of each; the closed enum over adapter types is an architecture decision that module already defers.one_kindmakes the limit a startup refusal naming both entries and both kinds, because the alternative is a source nothing opened answeringSourceUnavailable- a refusal that reads as nobody configured that about a source the operator configured. refuse_unattachedis files-only now. It compares the served bundle's tables against what the engine holds, and the engine holds them becauseattachput them there. ABigQuerysource has no attach step, so a bundle naming a table the dataset does not hold STARTS, where afilesdeployment in the same state does not. An anchor closes it for a metric that matters, because an anchor re-executes at boot; closing it for the rest is a per-model pre-flight, which is a network call per model rather than a check on a set.
What is still not claimed¶
Everything the amendment above narrows stays narrow. The data_systems: axis of the golden matrix
gains no entry: the registration is a composition, and that registry's rule is that a cell which
cannot execute in the suite reads as coverage. IMPERSONATION still reads NoPlaceForASubject, so a
BigQuery source serves every caller as one service account and an impersonation-at-source
declaration against it is a boot refusal - which is the posture cross-check working, not per-subject
execution arriving. And the corpus-wide leg this page specifies - #78's importer shape
pointed at a dataset - was still not built when this amendment was written. It is now, and the
third amendment below is its record; this sentence is left as the pointer rather than deleted,
because a reader arriving at an amendment wants to know which of its limits a later one spent.
Third amendment, 2026-08-31: the corpus leg is built, and two of the four bullets are answered¶
Status of the amendment: accepted. The first amendment above ended with a sentence that has stopped being true, and it named itself as the thing to watch:
The leg this page specifies is #78's importer shape pointed at a dataset - load the fixtures, run the 21 questions, compare rows with the engine - and it is not built.
It is built. crates/sutura-exec-bigquery/tests/corpus.rs, three #[ignore]d tests behind the same
just bigquery-acceptance the smoke leg uses. And it has RUN, green, in CI on 2026-08-31 - the
bigquery-acceptance job, 8 tests passed, five of them the smoke leg's and three this one's, against
the bq-test environment's real dataset. So the sentence above is superseded by a measurement rather
than by an intention.
Which of the four bullets, exactly¶
| Bullet | Where it stands |
|---|---|
| the corpus's statements are accepted and return rows | Answered, and in two halves for a cost reason stated below: every question that compiles to a plan is put to the endpoint as a dry run, which is free, and separately executed by the row comparison. Measured: 22 accepted, 9 refused by the compiler before a statement existed |
| the rows agree with the engine's for the same plan | Answered for CONTENT - exactly in the sense the eleventh amendment narrows, which is rendered content and not cell type - with one stated exclusion below. For ORDER, answered with one measured divergence the first run found - see the finding below. Measured on this amendment's run: 16 agreed exactly, 5 agreed on content and differed on null placement. The divergence is closed by the fourth amendment, and the leg now compares order exactly |
| the bucket is right | Answered for MONTH, DAY and ISOWEEK, which is every grain the corpus asks. QUARTER and YEAR are still rendered and never executed anywhere |
| the result is the endpoint's complete answer | Already answered by the smoke leg, and answered again here on every question: the seam's Incomplete refusal not firing is the evidence |
| (not one of the four) the anchors hold | Measured: 6 anchors reproduced by the endpoint, the same verdict the engine reaches |
And one claim that is not on the list and is the strongest of the four. The corpus leg also asks
the endpoint to reproduce every ANCHOR the engine reproduces. An anchor is a number somebody
certified, written in the catalog and compared as rendered text at the metric's coarsest grain, so
verify_anchors reaching the same verdict on both sides is a claim about the definition still meaning
what its author said - which is a different and larger thing from two adapters agreeing.
What that reaches that no local check could¶
The corpus renders, for this dialect: 12 LEFT JOINs, 6 COUNT(DISTINCT, 4 CASE WHEN, 4 NULLIF
ratios, one avg, 90 CASTs and 3 ISOWEEK buckets. ISOWEEK and DATE_TRUNC's argument order
are precisely the two constructs the measurement sections above identified as invisible to a parse
check - so this leg is where those two stop resting on a keyword pinned by value plus reasoning at a
match arm.
The section above that said the corpus asks only day and month was true when it was written and
is not now: data-per-subscription-by-week is in the corpus and renders ISOWEEK three times. That
section stays as the record of why the keyword is pinned by value, and this paragraph is the correction
to its factual claim.
The finding, which is what having the leg was for¶
ORDER BY x does not say where a null goes, and the two sides disagree. DataFusion orders nulls
LAST; GoogleSQL orders them FIRST. The example corpus reaches it because
fct_subscription_monthly holds a customer_key with no row in dim_customer, so every question
grouping by a dimension behind that LEFT JOIN comes back with one null-dimension row - and the two
data systems put that row at opposite ends.
Measured, on the leg's first two CI runs. The first went red on
recurring-revenue-by-region-and-family: nineteen rows, identical contents, one of them moved from
last to first. With the divergence pinned, the second run gives the full tally and it is five
questions rather than one - recurring-revenue-by-region-and-family, recurring-revenue-by-region,
recurring-revenue-by-segment, revenue-per-customer-by-segment and
subscription-months-by-region-and-term, the last at 61 rows:
bigquery-corpus: 16 answers agreed exactly, 5 agreed on content and differed on NULL
placement, 9 refusals agreed, 1 excluded, 31 in the corpus
That is a fifth of the corpus, which is worth knowing before deciding the fix is cosmetic.
What it is, precisely. No number is wrong: the row CONTENT is identical on both sides. What differs
is the order of rows in a certified answer, which the plan does claim, because it emits ORDER BY. So
it is the class this record already names as the thing a parse check cannot see - a rendered statement
that is valid SQL with different semantics - and it is invisible to every local check for a sharper
reason than ISOWEEK was: a golden pins the statement TEXT, and the text is identical on both
sides. There is no arm to get wrong. Only two data systems executing it can disagree.
Why it was pinned rather than fixed in the same diff. The fix belongs to sutura-sql: state the
placement, which matches the engine's own default. That rewrites the BigQuery SQL goldens and is a
change with its own review; the branch that found it owns the acceptance leg. So the divergence was
pinned in a way that could not rot: the_corpus_rows_agree_with_the_engine compared content as a SET
and, where the two orders differed, required the orders to be identical once the null-bearing rows
were dropped and required at least one such row to exist. A divergence for any other reason failed; a
content difference failed; and this one failed on the day the generator stated the placement - at which
point this section and the NULL_PLACEMENT constant were to be re-scoped rather than deleted.
That day has come, and the fourth amendment below is the re-scoping. This section stays as the
record of the finding and of the instrument that caught it; what it says about the tolerance is now
history rather than a description of the code. One claim in it is also corrected there: "all four
dialects spell NULLS LAST" is false about the rendered text, and the correction matters because it
is the difference between a claim about behaviour and a claim about bytes.
Whether ClickHouse and Postgres agree with the engine here is not answered by anything, which
is the same shape as this page's existing note about ClickHouse's week. Both are rendered and
parse-checked and neither has ever executed.
The one question excluded from the row comparison, and why it is not a hole¶
revenue-per-churned-subscription-january, excluded from the comparison and not from the run: it
executes, and both sides are required to fail, each against its own expected reason. The metric
declares zero_denominator: fails, so the generator emits a bare / with no NULLIF, and January's
denominator is zero. DataFusion and DuckDB return inf, which the port refuses as a non-finite
value; GoogleSQL RAISES on a zero divisor and the endpoint answers 400. AGENTS.md already stated
that the zero_denominator: fails credit belongs to DuckDB rather than to this arm - this is where
that stops being a claim about documentation.
A cell that cannot execute reads as coverage, which is this repository's rule, and the reason this is
an exclusion with a named literal in the source rather than a continue on a condition: DIVIDES_BY_ZERO
is one constant a reviewer can grep, and the test asserts that exactly one question reaches it.
The importer, and the two decisions in it worth a record¶
78's PostgresWarehouse::load_csv infers a column type per column from the committed bytes and¶
renders CREATE TABLE plus COPY ... FROM STDIN. The first half transfers; the second does not.
-
The rows travel inside the statement. There is no
COPYhere, and handing the endpoint a CSV body beside a statement needs either a load job ortabledata.insertAll- each a second wire surface nobody has run, which is this record's own reasoning about verification applied again. So the load is ONECREATE OR REPLACE TABLE ... AS SELECT * FROM UNNEST([STRUCT ...])per table: atomic in the wayCREATE OR REPLACEis, idempotent against the last run, and with no state where the table exists empty. -
Because the rows are in the statement, the loader REFUSES rather than escapes. A fixture CSV is a document read off disk, which this repository treats as untrusted input. Every cell is re-rendered from a value the importer parsed - an
i64, a finitef64, one of two boolean keywords, a shape-checked ISO date - and the one arm with no parse to hide behind refuses: text outside[A-Za-z0-9 _.-]is a typed refusal naming a position, never a quoted literal. SoO'Brienand'); DROP TABLE x --are unrepresentable in a rendered statement rather than escaped into one. That is prefer unrepresentable to checked at the position where this loader would be the one to break the no-injection property.
transport::JobTransport gains a third method, apply, behind the new fixtures feature - so the
shipped port stays at the two questions this record's own reasoning gave it. It cannot be run:
that method's contract is a COMPLETE result set, and the wire refuses an answer whose totalRows does
not equal the delivered count. A CREATE OR REPLACE TABLE job has no result set for that check to be
about, so routing DDL through run would rest on a response shape nobody here has measured - in the
one place where a wrong guess is a silently half-loaded fixture rather than a visible failure. apply
requires jobComplete and requires nothing else.
fixtures is default-off and, unlike wire, not for a dependency - it adds none. It is that this
feature is the only thing in the crate that can issue CREATE OR REPLACE TABLE, and a build that
serves questions has no business holding one.
What one run costs¶
Under a cent, and the number is set by the count of BILLED JOBS rather than by the size of the
fixtures. On-demand billing has a 10 MiB minimum per table referenced per query and the whole
fixture set is 40 KB, so what a run spends is decided by how many jobs really read data. A full run
submits 12 loads, 22 dry runs, ~22 executions and 12 anchor re-runs; the loads and the dry runs cost
nothing - a CREATE TABLE AS SELECT over a literal array scans nothing, and the endpoint charges
neither slots nor bytes for a dry run - so ~34 jobs are billed, referencing one to three tables each.
That is on the order of half a gibibyte of billed bytes, about a third of a cent at the current
on-demand rate.
The acceptance half being free is why it is a test of its own rather than folded into the row
comparison. The first shape of it went through sutura_app::answer, which dry-runs and executes, so
it doubled the bill for a claim a dry run already makes; asking the compiler for the plan and calling
dry_run directly costs nothing and localises the failure - a red there says not accepted with no
row comparison in the way.
Every job is still capped by maximumBytesBilled at a gibibyte, which is what protects a developer who
points this at a dataset already holding something large under one of the four fixture names.
The grant this needs and the smoke leg does not¶
It creates tables, so reading the dataset is no longer enough. The credential needs
bigquery.tables.create, bigquery.tables.updateData and bigquery.tables.delete in the target
dataset - roles/bigquery.dataEditor on the dataset is the usual way to say that - on top of the
roles/bigquery.jobUser needed to submit a job at all. 0019
records that the acceptance credential's IAM refuses datasets.create; creating a TABLE inside a
dataset it already reads is a different grant. A run whose credential lacks it fails at the first load
with the endpoint's own accessDenied on the chain, which is a diagnosable failure rather than a
mysterious one - and it is a configuration change on the bq-test environment's service account
rather than anything this repository can carry.
What this leg still does not claim, and one new limit it introduces¶
- Identity is untouched. A service-account key and an application-default login are each one
identity for everybody who asks.
BigQueryWarehouse::IMPERSONATIONstill readsNoPlaceForASubject, so a green here is accepted, and correct for that identity. - The
data_systems:axis ofcrates/sutura-app/tests/adapters/mod.rsstill gains no entry, and the Consequences below still hold on that point: a cell in that registry runs insidejust test, and this cannot, because the nix sandbox has no network. The corpus leg is a second#[ignore]d integration target in the adapter's own crate, reached by the same app. - A composition root now links the crate - the second amendment above - so the sentence that used to
stand here, no composition root links the crate and
sutura-serverefuseskind: bigqueryby name, is false. What this leg still does not reach is a SHIPPED one: the registration is behind a default-offbigqueryfeature on a binarynixdoes not package, and the image and all four cross binaries aresutura-cli, which links the engine only. So no published artifact opens a dataset, and a green here says nothing about one that does. - This leg WRITES, and two runs against one dataset will race - and, since #119, no longer.
Four tables named after the example models -
dim_customer,dim_product,fct_subscription_monthlyandfct_usage_daily- used to be replaced on every run under names that were fixed because the generator rendered them unqualified and the job'sdefaultDatasetresolved them. The ninth amendment below is the closure: every table is now suffixed with the run's own token, and a per-run table name stays safe to print because only the dataset and the project are resources. The committed fixture names themselves need no::add-mask::in a public log.
Fourth amendment, 2026-08-31: the finding is fixed, and the leg's tolerance is deleted rather than relaxed¶
Status of the amendment: accepted. The third amendment's finding section ends by naming the
condition under which it would be re-scoped - the day the generator states the placement - and this is
that re-scoping. sutura_sql::generate's ordered_nulls_last wraps every ORDER BY expression in the
dialect layer's own Ordered node with nulls_first: Some(false), from generate and generate_leg
alike, so a plan no longer leaves null placement to whichever data system happens to run it.
The claim, stated as behaviour rather than as bytes¶
The third amendment wrote that all four dialects spell NULLS LAST. That is false about the text,
and the difference is the whole reason this paragraph exists. The layer renders the keyword only where
it is not already the target's default:
| Dialect | Its own default | What renders |
|---|---|---|
BigQuery |
nulls first | ORDER BY x NULLS LAST - the keyword, because it is the one that would otherwise disagree |
DuckDb, Postgres, ClickHouse |
nulls last | a bare ORDER BY x - the layer collapses a keyword that changes nothing |
So what converges is the behaviour and not the statement, and only one dialect's goldens move.
every_order_by_states_nulls_last in crates/sutura-sql/src/generate.rs is the measurement that keeps
that honest: it asserts the keyword for BigQuery and asserts its absence for the other three, so a
future layer version that started spelling it everywhere - or stopped spelling it for BigQuery - fails
by name rather than by golden diff. A test asserting "all four render NULLS LAST" would have been red
the day it was written.
What moved, and what deliberately did not¶
- 30
BigQuerySQL goldens gain the keyword: 22 in the main corpus, five leg shapes, and three qualified-path statements. Reviewed as a diff, per this repository's rule that a golden is regenerated and never typed. - No other dialect's golden changes, which is the table above holding rather than an omission.
- No plan golden changes. Null placement is a rendering decision and the serialized
QueryPlanandLegPlando not carry it, so the definition digest is unmoved and no anchor re-certifies.
The leg's tolerance is deleted, not relaxed¶
the_corpus_rows_agree_with_the_engine now compares CONTENT and ORDER exactly, with no tolerance
for either. Gone with it: the Agreement enum's second variant, the diverged_on_null_placement
counter, the without_nulls instrument, the Row::holds_a_null flag and the NULL_PLACEMENT constant.
The assertion that used to hold the measurement open - at least one question diverges on null
placement - is removed rather than inverted, because a floor of zero is not a measurement of
anything; what replaces it is the exact order comparison, which fails if the placement ever stops being
stated.
The set comparison stays, ahead of the order comparison, and that is deliberate: different rows is a wrong number and same rows, different order is a generator that stopped saying how to sort them. One assertion for both would report the first as the second.
Measured, and the prediction is spent¶
This section was written as a prediction and is now a measurement, which is the only reason it is worth
reading: the bigquery-acceptance job is green on 2026-08-31 with the exact-order comparison in
place, and the tally is the one predicted to the number:
bigquery-corpus: 21 answers agreed exactly on content AND order, 9 refusals agreed, 1 excluded,
31 in the corpus
Read exactly as the eleventh amendment narrows it. The comparison behind this tally was
render-based, so it establishes acceptance, rendered row content and ORDER - and says nothing about
cell type. No number is restated for the typed policy that replaced it.
All five questions the third amendment named now agree exactly - recurring-revenue-by-region-and-family
at 19 rows, recurring-revenue-by-region at 6, recurring-revenue-by-segment at 4,
revenue-per-customer-by-segment at 4 and subscription-months-by-region-and-term at 61 - so the 16
that agreed exactly plus those 5 is the 21 above. 22 statements accepted, 9 refused before rendering and
6 anchors reproduced are unchanged from the third amendment, which is the other half of the claim: this
change moved row ORDER and nothing else.
What it still does not claim, and it is unchanged by the measurement. just validate's sandbox has
no network, so the corpus leg compiles and is skipped there - a developer's terminal is not where this
is decided, and the data_systems: axis of crates/sutura-app/tests/adapters/mod.rs still gains no
entry for the reason the third amendment gives. ClickHouse and Postgres null placement is still
answered by nothing: both are rendered and parse-checked and neither has ever executed, so their arm of
the table above is read off documented behaviour rather than measured. And identity is untouched.
Fifth amendment, 2026-08-31: where each identity claim is proven¶
Status of the amendment: accepted. What every earlier amendment here establishes - and it is worth
saying once rather than scattered - is accepted, and correct for one identity: a service-account key
is SharedServiceUser, so every acceptance run to date proves the product for a single identity and
nothing about who asked. This amendment settles where each identity claim is proven, and it is the
decision issue #81 records; it exists only in that conversation until now.
It amends the first amendment's conclusion in the same way that amendment amended the original decision: CI is no longer the place the two-subject identity claim is refused from. The first amendment made room for a service-account key in CI; this one decides that a second key, and a genuine row-grant mechanism at the source, belong there too. What stays refused from CI is the thing no CI runner can supply - a workforce identity, which is a representation of a person and needs single sign-on. The venue for that is a developer's own machine, one-off, and recorded.
The decision: four venues, four claims¶
| Venue | What runs there | What it is allowed to claim |
|---|---|---|
| CI, every run, every contributor | Fakes at the port (the house rule - ports get fakes, not mocked HTTP) | Every outcome the port can produce, including each refusal |
| CI, in-repo runs only | The acceptance leg, on a service-account key in an environment secret | BigQuery accepts what we generate, and the rows agree with the engine |
| CI, in-repo runs only | Two distinct principals against a row access policy | Two principals, two answers - the mechanism, not the identity class |
| A developer's machine, one-off | Workforce impersonation with a real person | That a human subject's own identity reaches the source |
What each cell required, and which are now true¶
The fakes cell is the oldest and still holds. The wire is exercised against a fake
[transport::JobTransport] and the broker against a fake [StsExchange] - a fake implements the port,
never a documented HTTP layer, so a test can never assert our own request bytes back to us. Nothing in
this amendment changes it; it is here because a decision about venues starts with the one venue already
settled.
The acceptance cell is built. The first amendment's job, the third and fourth amendments' corpus,
all green on 2026-08-31 in the bq-test environment. It is the second row of the table.
The two-principal cell is decided and not built. What it must show is not that two askers get different answers but that each reads what it is entitled to read - a row access policy grants the two principals differently, and each answer is checked against its principal's own entitlement. It needs two things neither this repository nor an agent can provision, because both are live-account work with a human owner:
- a second service-account key in the
bq-testenvironment, kept apart from the first the way the first is kept apart from the tree; and - a row-level grant at the source - a
BigQueryrow access policy - whose per-principal rows are known and written down, so "entitled to" is a check against a documented shape and not a comparison of two numbers.
Until those exist the cell has no subject: an #[ignore]d leg whose credentials no environment holds
would be exactly the scaffolding this record exists not to build, and a row-grant mapping guessed at is
a granted-nothing check. When the two key generations and the policy are in place, the cell lands as a
second identity in the existing bigquery-acceptance leg and needs no new job - the venue is already
CI in-repo only.
The workforce cell is local because it can only be local, and it has never run. A workforce
identity provider requires a client id and single-sign-on configuration, takes subject_token_type:
id_token, and exists to represent a person; no CI runner can supply one at any price, so this is the
one venue that is deliberately not in any gate. What still blocks it is a question this record must
carry rather than assume: whether the identity provider will mint an ID token whose audience is a
third party's provider at all - a claim no code and no repository secret can test, and the deciding
unknown for per-subject BigQuery. Until a run answers it, the whole workforce path is unproven.
The service-account key is accepted, and its expiry is written down¶
Every in-repo acceptance - the corpus and, later, the two-principal cell - runs on a long-lived service-account bearer held as a repository secret. That is accepted as the for now, with the cost named: rotation somebody has to remember, and a credential that leaks if the environment secret does.
The condition that ends it, stated so it is not left to rot: when rotation becomes a chore, or when
a second project needs a credential, move the acceptance leg to Workload Identity Federation - GitHub
mints an OIDC JWT under permissions: id-token: write, Google STS exchanges it for a short-lived token,
and there is no key at all. A workload pool accepts subject_token_type: jwt with an attribute
condition pinning the repository. Until then the key is the cost of the evidence, and this paragraph is
its expiry.
One sentence of that paragraph was wrong when it was merged, and it is corrected here rather than
quietly, because it was the half a reader would have used as the signal to watch. It said neither
id-token nor a Google auth action appears in any workflow, and therefore that the moment either did
was the diff to look for. Measured 2026-09-03: git log -S dates id-token: write in
.github/workflows/release.yml to telekom/sutura#97, merged two hours before this amendment
landed - the release path mints an OIDC token for Fulcio to sign a certificate against, which is a
keyless-signing exchange with a different audience and no Google in it at all. So the permission is not
the signal and never was; a workflow can hold it for years with the key untouched. What is greenfield
is the Google half: no google-github-actions/auth, no workload pool, and no STS exchange anywhere in
this repository. The signal is therefore a Google STS exchange in the acceptance job with no key placed
beside it, and that one is mechanical: cargo xtask check-venues reads the job's own credential state
and holds it to exactly one mechanism - a key and a federated token together is a half-finished migration,
neither is a leg authenticating with nothing, and the keyless state fails asking for this paragraph to be
amended. A gate whose failure is good news is the only shape that reaches a record.
The transferable part, since this record keeps finding the same shape: a limit whose evidence is
nothing in the tree does X is only as good as a search somebody ran. This one was written from
recollection two hours after the tree stopped agreeing with it, and no gate could have known - which is
why the sentence now names the command and the date, per this repository's own rule about a number in
prose. And the correction is registered rather than merely made: both halves of the wrong sentence -
the claim about the tree, and the watch for the permission arriving instruction it justified - are
wordings of one entry in check-guidance's contradicted-claims table, resting on
.github/workflows/release.yml still holding id-token: write, so the rule retires itself the day that
stops being true instead of forbidding a sentence that has become correct again. Two limits of that,
stated rather than left to be discovered. It matches a literal, so a paraphrase escapes - the same
limit the gate records for every row in that table. And this page is excepted from the rule, because
the quotation above would otherwise fail it: that exemption is a blind spot, and what keeps it from
outliving its reason is a_page_a_rule_exempts_holds_a_wording_that_rule_forbids, which fails if this
page stops quoting the sentence it corrects.
The gate named above had the same defect, one round later, and it is fixed here rather than shipped.
check-venues read this job for id-token: write - the very proxy this amendment exists to retire,
narrowed from any workflow to this job. That retires the one false positive and keeps the class: the
permission granted here for some other keyless exchange would report a half-finished Google
migration that does not exist. What it reads now is what the paragraph above actually names - a Google
auth action, a workload pool, or an STS endpoint in this job - and
an_id_token_grant_is_not_the_google_signal_this_record_corrected is the test that keeps the proxy out.
The limit each venue must state¶
- A fork's pull request gets no secret, so the acceptance job is absent on exactly the contributions least likely to have been run locally. That is the first amendment's original objection surviving in reduced form - the environment is the mechanism that makes the fork skip rather than leak, and it cannot do more.
- A key proves
SharedServiceUseronly. Every green here answers does the service accept this and do the rows agree for one identity, and nothing about who asked. Thedata_systems:golden axis gaining an entry, and even a green two-principal cell, do not promote a key to a subject. - A local-only verification has no gate on any path, so the workforce run must leave a record - the date, the versions, and what was run. This is not hypothetical: a claim that some tier made a cell "newly answerable" was wrong precisely because the only measurement was one terminal run and the record did not say so. The workforce cell's record must say enough that a second person can decide whether the claim is repeatable, and it must state the audience answer above as a finding, not as an assumption.
Sixth amendment, 2026-08-31: the two-principal cell's two prerequisites are now provisioned¶
Status of the amendment: accepted. The fifth amendment said the two-principal cell was "decided and not built" because it needed two things "neither this repository nor an agent can provision, because both are live-account work with a human owner": a second (and third) service-account key, and a row-level grant at the source whose per-principal rows are written down. The test-infrastructure PR (#106) now provisions exactly those two, through the pulumi stack, so that paragraph's blocker is spent.
What is provisioned, and the one sentence above it that stopped being true¶
- A second and third service-account key. The stack creates two principals (
sa_a,sa_b) and exports their keys;just infra-setplaces them in thebq-testenvironment asSVC_SUTURUA_BQ_PRINCIPAL_AandSVC_SUTURUA_BQ_PRINCIPAL_B, kept apart from the CI key the same way every key here is kept apart from the tree. - A row access policy whose per-principal rows are written down. Two first-class
RowAccessPolicyresources on the stack'sfact_eventstable grantsa_athe rows where the grouping column equals principal A's value andsa_bthe rows where it equals B's - disjoint by construction, with the predicate spelling the entitlement so "entitled to" is a check against the policy text and not a comparison of two numbers. The exact row values stay config (they are never a committed identifier), which is the same rule the fifth amendment applied to the values.
What the fifth amendment said the cell needed to exist therefore now exists, and the cell is no
longer blocked on provisioning. What is still required is the cell itself: an #[ignore]d leg in the
existing bigquery-acceptance suite that executes the same query as principal A and as principal B
and asserts each reads exactly the rows its policy grants - the second row of the fifth amendment's
table, made real. That leg is the subject of the follow-up PR on top of this one; it needs no new
job, because the venue is already CI in-repo only.
What the workforce cell needs that this does not provide¶
None of the above touches the workforce row of the table. Its blocker is not a missing credential but the audience question the fifth amendment carried: whether the identity provider will mint an ID token whose audience is a third party's provider at all. A service-account key and a workload pool cannot answer that - the subject is a person, not a key - so it stays the one venue with no gate and no run, owned by the enterprise-IdP half (#105). This amendment changes nothing about it; it is recorded here so the two venues are not elided into one "identity is provisioned now".
Seventh amendment: the command-line tool opens a dataset too, behind its own default-off feature¶
This record decided sutura-cli, not at all, and telekom/sutura#121 reverses that half. The
reason it was refused is worth reading before the reversal, because the reason has not gone away: the
release derivations pass --package sutura-cli, so a non-optional dependency there would put ureq,
rustls and ring - which compile C and assembly - into four cross builds, two of them musl. What
changed is not the cost but the shape of the answer: a default-off feature pays that cost only
when somebody asks for it, which is the general rule .agents/skills/sutura/crate-map states for an
adapter with a native or an outbound-TLS dependency, and it is the same shape this record's SECOND amendment already took
for sutura-serve.
What #121 was actually about, and why the reversal is not a change of mind about cost: the only binary a release publishes could answer questions from local files and nothing else, so every claim this record's acceptance legs establish - four dialects, pushdown, a real dataset accepting our SQL - was reachable only from a source checkout. That is a documentation and tutorial problem before it is a capability one.
What is built. crates/sutura-cli/src/sources/bigquery.rs's open composes the same three
layers sutura-serve's build_bigquery composes, through the same public constructors:
BigQueryWarehouse::new over BigQueryWire::new over Credential::read, with one WireAgent
cloned so the token exchange and the job share a pool. It is a copy and not a shared function,
because the two composition roots are separate binaries and neither may depend on the other; what is
genuinely shared is this adapter's own constructors, so a fix to the credential path lands once. The
posture cross-check is called against that adapter's own IMPERSONATION, and an
impersonation-at-source entry is refused by name, before the credential file is read, for the
same reason the server refuses one: no broker that exchanges a subject's credential is attached, so
answering would read every row as the process while the declaration promised otherwise.
The job's deadline comes off server.request_timeout_seconds, through
QueryDeadline::within_request_timeout, even though this binary has no listener a job could outlive.
The alternative was a number invented in the composition root, which is the drifting duplicate the
settings tree exists to prevent.
The limit that buys, stated as a number rather than a caveat, because review measured it:
CALLS_PER_ANSWER is 2 and the connect margin is 5 seconds, so the shipped default of 30 gives a job
10 seconds and RequestTimeout::MAX_SECONDS of 300 caps it at 145 - against a
QueryDeadline::MAX_SECONDS of six hours. On the command-line tool that key therefore bounds nothing
that exists and imposes a ceiling designed to protect an HTTP connection the command does not have: a
twelve-second question is cancelled by jobTimeoutMs with nobody waiting on any request. The
adapter's own QueryDeadline::parse exists for "a deployment stating a budget outright" and is
deliberately not used, because a second key on one binary is the duplicate the first half of this
paragraph refuses. What would lift it is a settings key meaning how long a QUESTION may take rather
than how long a REQUEST may - one number both roots read - and that is a settings decision rather
than this record's.
Three more things no test observes on the command-line root, listed because a composition that is
tested reads as a path that is exercised. OpenedWith::attached is None for a dataset and no test
sees that value, because every one of them stops at the credential read - what it feeds is mcp's
if let Some(attached), and the files arm is what exercises that branch. The mcp command's
Opened::BigQuery arm is instantiated by no test either: all three go through open_engine directly,
and reaching it by hand gives the same credential_file refusal. And the credential refusal prints
the operator's own absolute path out of their own settings - inherited unchanged from
sutura-exec-bigquery and identical on the served path, noted only because Design Principles says
an error is not a place for a path.
And what watches the feature-OFF lane, said at the sharper end than "a behaviour that differs by
build": just test, just mcp-e2e and checks.nextest all pass --all-features, so the
cfg(not(feature = "bigquery")) refusal and the test that provokes it are run by none of them. What
does compile that lane is cargo xtask check-default-features, in just gates: it derives its
package list from nix/shipped.nix's binaries, so this crate joined it the moment the feature
existed, and it runs cargo clippy as well as cargo check because a check cannot see a lint - which
is how a doc_markdown failure lived on such a line in both roots until somebody ran clippy at the
default set. Its limit is CI's: ci.yml reaches every gate as a nix check or a nix app and that
gate is neither, so what CI has for this lane is the four cross link builds for the compile half and
nothing at all for the lint half.
Amended, and the limit is gone rather than narrowed. That gate is now a nix app -
ci.ymlrunsnix run .#default-featureson every pull request that touches Rust - so the lint half of this lane is gated in CI and not only injust gates. An app and not a check for the reason the paragraph above gives for it not being a hygiene gate: it shells out to cargo, which wants a resolvable registry and a writable target directory the sandbox has not got. What did not change: both passes stop at metadata, so the fourcrossbuilds are still the only thing that LINKS the default set, and they are stillneeds: [ci].
The measurement this record's own rule asks for, taken 2026-09-02. cargo check -p sutura-cli
--all-targets on the default feature set touches neither ring nor ureq; the same command with
--features bigquery compiles ring from C and assembly. That is the whole argument for default-off
stated as a build rather than as a prediction, and it is also why the four cross jobs are
unchanged by this branch: they pass no --features, so they compile exactly what they compiled
before. What would price the alternative - the feature ON for four triples - is a build nothing asks
for, so it has not been run.
And what still has not happened, because a feature reads like availability. No published artefact
links the adapter. nix/shipped.nix builds both binaries with cargo's DEFAULT features and its
features paragraph now says so for both, which is telekom/sutura#121's third step decided as its own
recommendation had it: the CLI ships without the feature, and a deployment that needs a dataset runs
a build that carries it. checks.shipped-features is what keeps that from drifting - it reads
ureq's and ring's absence out of each shipped binary's own embedded dependency list rather than
out of a manifest, so a bigquery that stopped being optional on either crate fails a gate.
Two lines elsewhere in this record are dated by that and are corrected here rather than in place,
because each was right when it was written. "The release derivations pass --package sutura-cli, so
they never compile sutura-serve itself" and "sutura-serve is not a nix package at all" were
both spent by telekom/sutura#111, which publishes both binaries. And "the image holds sutura-cli,
which links the engine only" needs the qualifier as published: the crate can now link a second
adapter, and the artefact still does not.
Eighth amendment, 2026-09-03: the build nothing asked for has been run, and it went the other way¶
The sentence this amendment exists to correct is in The measurement this record's own rule asks
for, taken 2026-09-02, above: "What would price the alternative - the feature ON for four triples -
is a build nothing asks for, so it has not been run." It is now false, and it was falsified by
telekom/sutura#121's own step 2 rather than by anything external. nix/shipped.nix grew a
probeFeatures field; a feature named there gets a <bin>-<feature>-<triple>-ci package per
release triple, and the four cross jobs build them beside the shipped set on every pull request.
The record's build-cost reasoning above it is corrected here rather than in place, per this page's
own convention.
First, that the A/B is an A/B, and it was re-taken on the triple that is the risk. The probe
derivation and the shipped -ci derivation for aarch64-unknown-linux-musl were dumped and
diffed. Their input sets are IDENTICAL - 23 input derivations and 4 input sources on both sides,
including the same sutura-deps-aarch64-unknown-linux-musl, so the dependency derivation is shared
rather than rebuilt - and exactly three environment keys differ: the cargo build command, which
gains a trailing --features bigquery; the disabled check command, which gains the same; and the
output path. The job logs confirm the shared half from the other end by never building a second
sutura-deps-<triple>. Nothing else varies between the two columns below.
The cost, as COMPILED UNITS. Seconds drift with whatever else a runner is doing; a unit count
does not. Counted off the Compiling lines of the CRATE derivation's own log and not the job's -
sutura-deps-<triple> is the shared dependency build and is excluded, which is the whole reason
units beat seconds here - for all four published triples of CI runs 33808343712, 33838360913 and
33843494162, feature off and then on, the same in all three:
| triple | OFF | ON |
|---|---|---|
x86_64-unknown-linux-gnu |
100 | 112 |
aarch64-unknown-linux-gnu |
100 | 112 |
x86_64-unknown-linux-musl |
100 | 112 |
aarch64-unknown-linux-musl |
100 | 112 |
Re-taken on 33843494162 with the same answer in all four cells, which is the point of counting units: the seconds moved by 20% across those three runs and this table did not move at all.
+12 units on every one of the four, none dropped, and the same twelve crates every time:
ring, untrusted, rustls, rustls-pki-types, rustls-webpki, webpki-roots, ureq,
ureq-proto, httparse, getrandom 0.2, utf8-zero, sutura-exec-bigquery. That is the adapter
plus the outbound TLS closure and nothing besides. The unanimity is the useful part: a per-triple
figure would leave open which triple the closure is dearer on, and none of them is.
The seconds, for whoever wants them, and they are two different numbers. The FEATURE's delta:
runs 33781193001 and 33792642655 gave +0.5 / -0.1 / +1.2 / +1.3s and +0.4 / -0.3 / +1.3 / +0.9s
over crate derivations of 69-84s - so -0.4% to +1.7% across two runs, which is noise. Cite that
range, never a cell, and prefer the unit counts. The STEP's cost is the other number and it is not
small: the probe is a second crate derivation, and three runs of it read 62-85s per job:
33808343712 gave 69/74/75/85s, 33838360913 gave 72/72/73/80s, 33843494162 gave 62/71/73/73s.
That third run is why the range is cited and not a cell - it put a value BELOW the low end this
record had stated twice, which is what a wall clock on a shared runner does. Feature-off and
feature-on both LINKED on all four triples in every run, with file reporting the right
architecture for each - statically linked on aarch64-musl,
static-pie linked on x86_64-musl, dynamically linked on the gnu pair, exactly as the shipped
builds report on the same triple.
Was default-off necessary? For build cost, no. And the first version of this amendment got the REASON wrong, which is worth recording because the wrong reason was the plausible one. It said the closure was already in the deps derivation with the feature off, so the probe could not have cost much. Half of that is right and half of it is not:
- Right:
craneLib.buildDepsOnlyis called on the unscoped argument set, deliberately, so the checks share one dependency derivation;cargoExtraArgsis set on the final attrset instead. That derivation therefore resolves the WHOLE workspace at cargo's default features - soring,rustlsandureqare compiled insidesutura-deps-<triple>on all four triples with the feature off, visible in all four job logs. The musl C-and-assembly cost this record priced on 2026-09-02 is paid on every pull request either way. - Wrong about WHY, and this is the correction of the correction. The sentence above used to end
"and
sutura-exec-bigqueryis a workspace member takingureqnon-optionally". It does not:crates/sutura-exec-bigquery/Cargo.tomldeclaresureq = { workspace = true, optional = true }behind itswirefeature, so at the default set that crate contributes nothing. The observation was right and the mechanism was invented, which is the harder half of this defect class to notice - a green measurement makes a plausible explanation feel checked. Measured 2026-09-04:
$ cargo tree --workspace --target aarch64-unknown-linux-musl -i ureq -e normal,dev
ureq v3.4.0
[dev-dependencies]
`-- sutura-catalog-datahub <- non-optional, for its provisioned-instance probe
$ cargo tree --workspace -i ureq -e normal,build
ureq v3.4.0
[build-dependencies]
`-- libduckdb-sys -> duckdb -> sutura-exec-duckdb <- host side only
So for a musl target the ONE edge into ureq at the default set is a dev-dependency of the
DataHub catalog crate, and buildDepsOnly builds dev-dependencies - which is what puts the
closure in the shared derivation. libduckdb-sys accounts for the host-side copy, and is why the
gnu host job's log shows ring compiled twice. What that changes for the decision: nothing,
and it is worth knowing anyway, because the cost is paid by a dependency that has nothing to do
with this adapter. Drop that dev-dependency and the musl build-cost argument comes back.
- Wrong: that those units are then REUSED. They are not. The deps build compiles at the
workspace feature union while the probe asks for one package, so the v2 resolver hands it a
narrower feature set, a different -C metadata, and a recompile - sutura> Compiling ring
v0.17.14, in the probe's own derivation, is that recompile. This is the same structural effect
telekom/sutura#223 measured from the other side for the warm-start gate.
- So why is it nearly free? Slack. The twelve units start in the first seconds and finish long
before datafusion, which sits on the critical path ahead of sutura-exec-datafusion and the
crate itself. That is a property of this dependency graph, not of the feature - a shorter
critical path would expose the same twelve units as wall clock.
What default-off IS still necessary for, unchanged and now the whole of the argument: no
published artefact links an outbound TLS stack, which checks.shipped-features asserts out of each
binary's own embedded dependency list rather than out of a manifest. The decision of step 3 stands;
what does not stand is the four cross builds as its reason. Keep the decision, drop the
build-cost justification for it - an argument that a measurement contradicts is worth less than no
argument, because it invites the next reader to trust the rest of the paragraph.
Verifying the lane found a dead gate IN the lane, and that is the part with the longest shelf
life. The first version of the workflow step RECONSTRUCTED which probes to build: it filtered the
flake's attribute names for -<triple>-ci, subtracted the shipped binaries' names, and split what
remained to recover the executable and the feature. So nix/shipped.nix's package-naming rule was a
coupling nothing could check. Measured 2026-09-03 by reordering that name from
<bin>-<feature>-<triple> to <bin>-<triple>-<feature>, probeFeatures untouched: the
reconstruction went empty, the step printed "no binary declares a probeFeatures entry, so no
feature-on build was measured" - which was false, one was declared - and exited zero. This
amendment's whole claim would have reverted to assumed behind a green run and a reassuring
sentence, which is the failure mode this repository cares about most.
The step reads a manifest now: nix build .#feature-probes-<triple> yields a file whose rows carry
the package, the executable and the feature, so nothing in the shell derives anything. Three
directions were then reproduced by hand rather than argued:
| Break | Before | Now |
|---|---|---|
Probe package renamed, probeFeatures intact |
empty set, false notice, exit 0 | probe still found and built, printed sentence still correct |
probeFeatures = [ ] |
notice, exit 0 | ::error:: naming this record, exit 1 |
| Manifest attribute gone from the flake | not expressible | nix build fails, exit 1 |
file cannot carry any of this and is a readout rather than an assertion: it exits zero on a
path that does not exist, measured. What asserts the link is nix build succeeding.
And the same verification caught the step's own printed SENTENCE overstating, which is the
smaller half of the same defect. It printed its elapsed seconds as "That is the price of the
feature ON" - and did so in CI, four times a pull request: 85s, 69s, 75s, 74s in run
33808343712. Those seconds are the whole probe. The feature is the +12 units above, worth under 2%,
and a wall clock in that step cannot separate the two, so the number was off by roughly the width
of the thing it claimed to measure. Corrected to say which number it is and where the other lives.
And it is corrected in prose rather than held, which is the part worth writing down.
check-guidance DOES read .github/** - its file scope is md, nix, yml, yaml, toml and
sh - so this sentence was reachable by the one gate in the repository that fails a false claim,
and nothing had registered it. Registering it now is about 15 lines more than
xtask/src/guidance/claims.rs has: wc -l says 985 on 2026-09-04 against cargo xtask max-lines'
unexemptable 1000, and the split that makes room is in flight on another branch. So the correction
is held by REVIEW until that lands, and the
general lesson is the one the remedy scan already taught from the other side - ask what reads a
mechanism's own prose, and expect the answer to be nothing until somebody has registered it.
And the lane fails closed on the defect it exists to catch, reproduced rather than assumed, and
re-reproduced on the merged tree on 2026-09-04. A type error was planted inside the feature-ON
open in crates/sutura-cli/src/sources/bigquery.rs, and both host derivations were built from
that one tree:
| derivation | result | crate-derivation units |
|---|---|---|
sutura-aarch64-apple-darwin-ci (shipped) |
exit 0 - blind to it | 105 |
sutura-bigquery-aarch64-apple-darwin-ci (probe) |
exit 1, error[E0308]: mismatched types at bigquery.rs:70:25 |
117 |
The 105 and the 117 are the same +12 the four CI triples show, taken from the same pair of builds that produced the asymmetry - so one reproduction answers both questions. That asymmetry is the whole argument for the step existing: before it, every one of those four jobs was green on a tree where the documented source build did not compile.
The limits of this measurement, next to it.
- The PROFILE is not the documented one, and this is the limit that narrows the claim most.
docs/getting-started.mdsayscargo build --release; the probe buildsci.[profile.release]islto = "thin",codegen-units = 16,panic = "abort";[profile.ci]inheritsdev-lto = "off",codegen-units = 256, unwind. Thin LTO and an abort personality acrossring's C and assembly are a different link. So what is proven is the feature's closure cross-compiles and links for each published triple, not the reader's exact command links. Adding a release-profile probe would double the step's cost for a codegen difference nobody has priced; the concession is in the step's printed notice instead, where a reader of a green run will see it. - The probe links and never RUNS, so nothing here says the feature works - only that it builds.
just bigquery-acceptanceis the leg that answers the other question. - Binary size is unmeasured: no step prints it, so the artefact-closure argument above is still qualitative.
sutura-serve'stlsandbigqueryare deliberately unprobed, so none of this is evidence about the server.- The planted-error RED was reproduced on
aarch64-apple-darwinonly, the one triple that host builds natively. The musl link has been exercised green on every pull request and never red, and CI is the only venue that can redden it, because a musl dependency closure is not cached on a developer's machine. - The cost of HAVING the probe is real even though the cost of the feature is not: 62-85s on
each of four
crossjobs, per pull request, across runs 33808343712, 33838360913 and - That is the price of the answer rather than the price of the feature - and the range widened downward on the third run, so quote the range.
- The
filereadout asserts the CPU and not the libc.nix/assert-linked.shmatchesfile's answer against the triple's CPU and refuses a path that is not executable - which is what the barefilehere did not do, since it exits zero on a missing path. What it does not assert isstatically linkedversusdynamically linked, because those strings vary with linker, target andfileversion in ways nobody here has measured. A musl target linked dynamically would pass.
Two couplings this lane rests on. One is held now, and it was not when the lane landed.
WHICH FEATURES ARE PROBED - held, by cargo xtask check-shipped-binaries. The refusal in
.github/workflows/cross-link.yml is the manifest has at least one row, and that was read as a
probe cannot silently disappear. It is not the same claim: zero rows means the CLI probe is gone only because the other binary
declares none, which is a property of today's data rather than of the construction. Declare a probe
for sutura-serve and delete "bigquery" from sutura-cli's, and the manifest is still non-empty
- the four link legs go green and this record's claim reverts to assumed with no signal at all.
The coupling that closes it is the one nothing checked: a page tells a reader to run
cargo build --release -p sutura-cli --features bigquery, and probeFeatures had to contain that
feature. The gate now reconciles the two, in both directions - a documented feature no probe covers
fails, and so does a tree where no page documents such a build at all, because a reconciliation
against nothing passes everything.
WHICH TRIPLES ARE PROBED - still not held. The link matrix in
.github/workflows/cross-link.yml spells the four triples as literals, as release.yml's does, and
check-shipped-binaries reconciles the shipped BINARIES between those files and nix/shipped.nix -
not the TARGETS. So a triple added to crossTargets and to release.yml but not to
cross-link.yml would ship having been linked by nothing, with the feature or without it, and the
only thing saying otherwise is a comment above that matrix. The matrix moved out of ci.yml and
this pointer moved with it - which is the second failure mode of a coupling held by prose: not
only can the comment go, the sentence naming where to read it can be left behind. cross: in
ci.yml is now a three-key caller with no strategy.matrix in it at all, so a reader sent to that
file finds nothing to add a triple to. The reverse direction does fail closed, freely: a matrix
target that is not a release target has no
feature-probes-<triple> attribute, so nix build fails. The gate's own header argues a matrix
cannot be derived because strategy.matrix takes literals, which is exactly the argument for
reconciling this pair too. It is the same shape as the rule above and is a separate change.
Ninth amendment, 2026-09-04: the acceptance leg is re-entrant, and two runs no longer race¶
Status of the amendment: accepted. The third amendment's "NEW" bullet - this leg WRITES, and two runs against one dataset will race - stopped being true in #119, and this is its record. The bullet itself now points here; a merged record that quietly stops being true is worse than one wrong from the start, and the race was the one way this leg could fail for a reason that looked like a product bug.
The mechanism: a per-run table suffix, applied before the plan is compiled¶
Every corpus table is now named with a token unique to the run (GITHUB_RUN_ID in CI, a clock+pid
value locally) and a per-test leg (accept, rows, anchors) - dim_customer_<token>_rows, and so
on. The suffixed names live in the DEFINITIONS: the leg rebuilds a PinnedDefinitions whose models'
tables carry the suffix, and both the engine and BigQuery compile against that bundle, so the plan
and the dataset agree (only the models' tables change; relationships and metrics are cloned verbatim,
and pin recomputes the digest). Two runs, or this leg's own three tests under nextest's default
parallelism, each create, read and drop only their own tables.
The two mechanisms that keep it clean, and why both are there¶
- Each
CREATEsets a 24-hour table expiration. This is the GUARANTEE half, and it exists for a reason specific to this crate: the shipped profiles runpanic = "abort", so a cancelled runner never unwinds - aDropguard that would clean up is skipped before it runs. Table expiration is the mechanism that survives that: the table self-deletes after the interval whether or not anybody drops it. - Each run DROPs its own tables when it finishes. This is the TIDY half - a complete run leaves nothing behind even for the 24-hour interval. It is not the guarantee, because an abort skips it; the expiration is.
The per-run table name is safe to print¶
The suffixed names are committed-fixture-names-plus-tokens, not resources: only the dataset and the project are resources. A run prints its table names as they load, which is what lets a log say which run wrote them; the dataset and project are still masked in CI, and a table name never carries them.
The belt: a CI concurrency group shared across refs¶
ci.yml's bigquery-acceptance job now carries a job-level concurrency group keyed on the
dataset (vars.SUTURA_BQ_DATASET), not on the ref, with cancel-in-progress: false. Two in-repo
runs against the one bq-test dataset therefore QUEUE rather than interleave. It is the belt, not
the braces: the per-run suffix is what makes them safe, and the developer's own local run is a
concurrent writer the group cannot serialise - its per-run table names announce it on the shared
dataset, and the just bigquery-acceptance comment says that in one sentence.
What becomes provable, and how¶
just bigquery-acceptance twice concurrently, locally, both green - the table names differ per run,
and each run drops its own. The CI job is green on a branch, its printed table names carrying the
run suffix.
Not in scope¶
Cross-dataset fixtures (#118) and the conformance packs (#116) are still future work; this change makes the first safe to add rather than doing it.
Tenth amendment, 2026-09-04: the two-principal cell exists, and what it is not¶
Status of the amendment: accepted; the cell is written and has not been run. The sixth amendment said the cell's two prerequisites were provisioned and that "what is still required is the cell itself". This is that cell - issue #123 - and this amendment records three decisions taken while writing it, one measurement that closed a question the wrong way, and the two things a human still has to do.
What the cell is, and the one sentence that keeps it honest¶
crates/sutura-exec-bigquery/tests/two_principals.rs, reached by just bigquery-two-principals and
by nix run .#bigquery-two-principals. One QueryPlan value, borrowed twice, so the statement is
the same statement and not two that resemble each other - and each leg presents a bearer minted
from that principal's own service-account key through this crate's own Credential, which is issue
123's second bullet answered by reusing the credential path a deployment runs rather than by¶
hand-rolling a token exchange.
It is leg 2's SOURCE half and not leg 2. The two principals are service accounts whose private
keys the leg holds, so nothing was exchanged and nobody asked. What a green run would establish is
that a real data system applies the row grant of the principal whose bearer this adapter presented
rather than the identity the transport holds; what it says about a caller is nothing.
docs/where-identity-is-proven.md carries that as a venue of its own with five exclusions, and it
deliberately does not move the two subjects read two different row sets row up to it: two
principals is not two subjects, and eliding those is the overstatement that page exists to prevent.
Three decisions, and the reason each went the way it did¶
It has its own task and its own nix app rather than being a third leg inside
just bigquery-acceptance. It needs five values and two key documents the other two legs do not,
and one task demanding all of them would make the legs a developer holding one credential can run
unreachable. Both apps filter on the test BINARY and not on a test list, so the property the
acceptance app's comment states survives: a test added to either target is reached without a count
anywhere being edited.
It does not seed the table, though issue #123 asked it to. BigQueryWarehouse::load_fixture
renders CREATE OR REPLACE TABLE, and replacing a table drops its row access policies - so the one
loader this crate has would disarm the grant the cell asserts on, and the run after it would compare
two identical row sets. There is no arbitrary-SQL entry point to reach for instead, deliberately.
So the rows belong to whoever owns the policies: the predicate and the rows it selects are two halves
of one grant, and splitting them across the stack and a test file is how they drift. The stack seeds
one row per principal beside the two RowAccessPolicy resources, and the cell's non-emptiness
assertion is what refuses to read an unseeded table as a pass.
The control leg accepts two outcomes and prints which it got. What the endpoint answers a principal no policy grants decides how strong that control is, and it is unmeasured: documented behaviour is no rows, the observable alternative is a refusal, and both are not reading either principal's rows. The first green run narrows it to one sentence; until then the test says so at the assertion rather than guessing.
Two outcomes is what the sentence said and fourteen is what the code accepted, which review
found. let Ok(rows) = ... else { return; } took every BigQueryError variant as evidence of a
refusal - and the sharpest of them are not refusals at all: UnmappedType, NotAnInteger and their
siblings mean the endpoint answered and rows came back, so a deployment that had just read the
policied table printed the deployment's own identity was refused and passed the control without
looking at what it read. Render and PresentedDisagreesWithPosture are defects in the test file
and were green; one Endpoint timeout left the cell with no control while both subject legs passed.
It is an exhaustive match now, in wire::tables::was_refused's shape and for its reason - a new
variant is a compile error at that line rather than a new way to pass - accepting only a 403 that
is not rateLimitExceeded/quotaExceeded. 401 is deliberately refused too: a credential that
cannot authenticate leaves the control unable to distinguish anything while reading as though it
had. The limit that survives is on the page beside the claim: a 403 does not say WHICH grant was
missing, so a missing row grant and a missing bigquery.jobUser are indistinguishable here.
The measurement that closed a question the wrong way¶
The three behaviours the cell rests on were probed from a developer machine against the acceptance
project, with the credential that machine holds. Creating a table, INSERTing into it and reading it
back all work. Creating a row access policy does not: the acceptance credential is refused
bigquery.rowAccessPolicies.create - Access Denied ... Permission bigquery.rowAccessPolicies.create
denied on table - so a policy cannot be created, replaced or inspected from a developer machine at
all. Two consequences, and both are limits rather than defects:
- whether
INSERTworks on a table that HAS row access policies was not measured, because no policy could be created to try it against. The seeding decision above makes it the stack's problem rather than the test leg's - but it does not close the question, and an earlier wording of this bullet read as though it had. Review found the three resources unordered: the seed job and the two policies each depended on the table alone and the policies were bound to no name, so pulumi created all three concurrently and which pathuptook was the scheduler's choice. The ordering is declared now - the job first, both policiesdepends_onit, and the principals' read grant waits for both policies so a freshupnever leaves a principal able to read an unpolicied table. What remains, stated rather than moved: the policies persist, so from the second apply onwards a changed grouping value re-digests the job against a table that already carries them, and theINSERTruns on a policied table deterministically.just infra-upafter a config change is the first place anyone can observe it. Whoever gets there: record the answer here. If it is refused,upfails on the resource the whole fixture depends on; if it is accepted but filtered, the cell reports principal A read no rows at all and points two steps from the cause; - the policied dataset is not listable from a developer machine either, so the cell's own configuration cannot be discovered locally and no local run of it is possible. That is why the venue page says the claim is not answered yet rather than that the run failed.
What is left for a human, and neither is code¶
- The
bq-testenvironment has to carry five more values - the policied dataset and table, the grouping column, and the grouping value each policy grants.sync-bq-test-env.shexports them from the stack now, sojust infra-setis the mechanism; it needs the pulumi state, which lives with whoever ranjust infra-up. - The CI job has to run the leg, and that change is WRITTEN and must not merge yet. It is a
separate pull request stacked on this one rather than a future one - the sentence this item first
carried said it had not happened, and it had - and it stays in draft for the reason it was kept
separate: wiring a job to five variables that do not exist yet turns the
bigquery-acceptancejob red on every push until somebody sets them, and a job that is red for a configuration reason is a job people learn to ignore. The order is the environment first, the job second.
The cap decision this item used to carry has evaporated, and the sequence is worth recording
because it is the ordinary case rather than an accident. An earlier wording said
.github/workflows/ci.yml was over the 1000-line cap
#285 is about, in the present tense, and paired
it with 987 → 1042. Neither was true of any tree this record shipped in: measured with
wc -l, the file was 999 on main and at this record's own head, and this change touches no
workflow at all. Then #289 landed and took it to
856 - wc -l .github/workflows/ci.yml on main at fd5959ea, and on this branch after
carrying that merge in. So there is 143 lines of headroom and no cap decision for the job
change to make: devco/max-lines-ignore needs no [warn] entry for this workflow, and one added
before #289 landed should come out. That file is for generated and vendored output, where length
is a function of what is described; a hand-written workflow in it is a promise to split, and a
promise nobody needs is worse than none.
The transferable half: a line count written in the present tense is a measurement with no date, and nothing in this repository derives it. Write the command and the commit beside the number, or the number is a claim that rots on somebody else's merge.
What review of the cell found, recorded because each was a claim rather than a bug¶
Two rounds, and no count in this heading on purpose - it was three and then it was not. The second round's other findings are recorded where they belong rather than listed again here: the control leg accepting fourteen error variants is under Three decisions above, the seed job and the two policies being unordered is under The measurement that closed a question the wrong way, and four citations pointing at the ninth amendment when this one is the tenth were simply wrong and are corrected.
A verdict was carrying two states. The venue page's can meant the venue is capable and the
standing test lives somewhere else, and this cell needed the standing test lives here and nothing
has run it. Those are not the same and only the first is evidence, so cargo xtask check-venues
grew a sixth verdict - unrun - which it refuses from a venue nothing reaches and which the venue's
own section has to use in that word.
And the first version of those rules held a spelling rather than the transition, which review of
this cell found. All three read the page: nothing read whether a run had happened, so the sentence
the change that carries the first green run moves that cell was still a sentence nothing read -
which is what it had just been criticised for being. The closure is a fourth rule with a mechanism:
the venue's Reached by task is resolved against every just <task> and nix run .#<app> the
workflows, the local composite actions and the shared nix/ shell invoke - one walk, shared with
check-workflows, because a step moving out of a workflow is the recorded way a reference leaves a
gate's sight - and an unrun cell whose venue is among them is refused. Measured by wiring
nix run .#bigquery-two-principals into ci.yml and re-running the gate: it fails naming this
venue, and passes again with the line removed.
The rule is one-sided, and the side it does not hold is stated on the page beside the claim. An
invocation is not a green run: a wired job that always skips reddens the cell, and a hand-run is
invisible to it. The authority for did this pass is the GitHub API, unreachable from
checks.hygiene for the same reason nix eval is unreachable from check-workflows. So moving the
cell to yes stays review's, with the run named beside it; what stopped being possible is leaving
it at unrun while a job runs it.
One assertion in the cell could not fail. With each answer asserted to be exactly its own principal's grouping value, and the fixture control already refusing a pair whose values are equal, disjointness followed. It is deleted rather than kept: an assertion that cannot go red reads as an independent check and is not one, and it made the page and this record advertise three where there are two.
The step this amendment asks for nearly broke the venue that HAS a green run. The two policied
resource names were added to the masking step's emptiness guard, which exits non-zero and runs
before the shared-key leg - so an unset value belonging to a venue with no run would have stopped
the only BigQuery venue with evidence. Masked there, guarded in the cell's own step. check-venues
was satisfied either way, which is why review and not the gate found it, and it is the sharpest
example of this record's own warning about a job red for a configuration reason.
And two limits this cell does not close, named so they are not read as closed¶
The single-credential limit is superseded by the twelfth amendment.
acceptance/properties.rs now derives recognised $RUNNER_TEMP destinations from lines naming
configured secrets and requires each file in an rm argument list. Indirect copies, cd-relative
writes and whether a conditional cleanup actually executes remain outside that scan.
Nothing checks that the two nextest filters are complements. binary(two_principals) and
not binary(two_principals) appear in two just recipes and two flake apps, and no gate reads a
nextest filter expression - so renaming the target would silently un-filter the acceptance app.
Two nextest profiles carrying the pair once, next to the tests, is the shape that would close it.
Eleventh amendment, 2026-09-05: what exactly meant on the measured run, and what it did not¶
Status of the amendment: accepted; it corrects a published claim and carries no live run. The
fourth amendment above records 21 answers agreed exactly on content AND order, and the third
amendment's status cell read Answered for CONTENT, exactly. Both are narrowed here, because the
comparison that produced them was not exact in the sense either sentence invites.
The comparator of that day compared cells through Value::render, which is a display form.
Value::Null and Value::Text("null") both render null; Value::Integer(1) and Value::Text("1")
both render 1. A cell the endpoint answered as text where the engine answered a number - or as the
word null where the engine answered nothing at all - was therefore counted as agreement on that
run. The 21 are evidence about acceptance, about rendered row content and about ORDER,
and about nothing else. The status cell's exactly has to be read that way, which is why it now says
so at the cell rather than here alone.
Both legs share one typed policy now. sutura_domain::warehouse::agreement, behind a default-off
feature and cfg(test): the variant is part of the comparison key, and the one approximation is
named and scoped - RealTolerance::DIFFERENTIAL, thirteen significant digits, reaching Value::Real
and no other variant.
No number is restated for it, deliberately. Nothing has run the corpus leg against the real
dataset since the policy changed, so the tally above stays as the transcript of the run that made it
and this amendment corrects the reading rather than replacing the measurement. What is measured for
the typed policy is that module's own suite plus two cells per leg asserting that a null and the word
null, and an integer and its own text, are refused by the content comparison - the diagnosis
named in the expected panic, not merely that some panic fired. The next bigquery-acceptance run is
what would restate the number with exactly meaning what it says.
Twelfth amendment, 2026-09-07: the first of the tenth amendment's two limits is closed¶
Status of the amendment: accepted; it closes a limit this record named, and carries no new run.
The tenth amendment states that check-venues reads ONE credential path, so written under
$RUNNER_TEMP and removed and this file is a second copy of the secret are held for the CI key
and by review for the two principal keys placed beside it. That sentence was true when it was
written and is not now: telekom/sutura#389 derives the credential set from the job, which is the fix
this record named. properties::credential_placement reads recognised redirects into $RUNNER_TEMP
from a line that spells a secret, and each such file has to be deleted by name; shape::removes reads an
rm's ARGUMENT LIST, because one rm over three paths contains the whole-string form of none of
them but the first - so the form it replaces would also have failed a job that removes three
correctly.
Measured on this job, tree restored after each: the two principals' writes added under
$RUNNER_TEMP and left out of the cleanup, cargo xtask check-venues names both files and
just hygiene exits 1; with one rm naming all three, the leg's own path LAST, it exits 0. The
pre-change pair that both exited 0 was the additional writes with and without separate ordinary
cleanup commands, while retaining the original primary cleanup. The combined cleanup with the
primary path last instead exited 1 before this change: it was a false refusal of a correct job.
Review narrowed the shell spelling claim. Plain and braced RUNNER_TEMP expansions, with the
whole word or directory double-quoted, now resolve to the same path in both the primary-write and
additional-copy checks. A single-quoted variable is literal and earns no placement or cleanup.
Cleanup arguments end at the command boundary: a trailing comment or later echo removes nothing,
while an rm behind a compound-command keyword is still read. This recognises commands; it does
not establish that a condition lets them execute.
What is still review's, narrower than the sentence it replaces: a copy made by a command that
does not spell the secret's name - a cp of the key file, a base64 -d of it - a cd-relative write
or other shell-built path, and a removal whose condition never fires. The second limit that
amendment names, the two nextest filters, is
untouched and is telekom/sutura#430.
Thirteenth amendment, 2026-09-10: the cross matrix is reduced, and this record's probe claim narrows with it¶
Status of the amendment: accepted; it withdraws a claim this record makes about a venue, and carries no new run.
The eighth amendment says that a feature named in probeFeatures gets a package per release triple
"and the four cross jobs build them beside the shipped set on every pull request", and prices
having the probe at "62-85s on each of four cross jobs, per pull request". The eighth
amendment's cell list also states that "the musl link has been exercised green on every pull
request and never red". None of the three survives unqualified, and the first two had already
stopped being true before this change: the pull-request leg was reduced to the two aarch64 triples
earlier, so four, on every pull request was describing a venue that had stopped running.
What runs now. cross-link.yml builds ONE reduced set on every event it is called for -
aarch64-unknown-linux-gnu for the architecture axis and x86_64-unknown-linux-musl for the
static-allocator C axis, one cell each so a red leg says which axis broke - and the full four
survive only in release.yml's build, whose publish job asserts that exactly four artefacts
arrived. cargo xtask check-workflows holds all three literals, the release four included; that
last one is what makes the reduction a later signal rather than an absent one.
What it costs this record's claim, stated rather than smoothed over. The
feature-probes-<triple> step rides that matrix and NOTHING else runs it - the release path runs
neither it nor the embedded-dependency-list assertion - so the --features bigquery link is proven
on two triples and in no venue at all for x86_64-unknown-linux-gnu and
aarch64-unknown-linux-musl. nix/shipped.nix's probeFeatures declarations are untouched and
cargo xtask check-shipped-binaries still reconciles them against the documented builds, so the
DECLARATION is intact; what shrank is how many triples exercise it.
And the half that improves, because it is the reason the pair is this pair.
x86_64-unknown-linux-musl is back on the pull-request leg, where it had not run since that leg
became the aarch64 pair - so the musl-link sentence above is true again for the host architecture on
every pull request, and false for aarch64-unknown-linux-musl in ordinary CI. Priced against what
it replaces: the reduction runs two cells on a pull request where four ran before this record's
period and two ran after it, and two on main where four ran.