Skip to content

Ruling log

Status: planning state M0. This document is the ruling log according to ADR 0001: decisions with reason and location that do not have their own ADR or that an ADR only summarizes. Load-bearing decisions are ADRs in the ADR index. Every chapter of the specification lists its own decisions with numbers such as R-AR-01 in the section "Decisions of this chapter"; the source is there, the reference is here. Decisions that concern only a particular installation (its accounts, names, hosts, repository paths) are not here but in the private operations repository of that instance (ADR 0002).

Rules for this log

  1. IDs are stable. An ID is never reassigned, never renumbered and never deleted. An outdated row stays in place and names "superseded by" or "Current state:" in the decision, with the new ID or location.
  2. Every row has a decision, a reason and a location. The location is a link to file and section. make linkcheck checks files and anchors; a renamed section breaks the gate.
  3. The chapter wins. If a row deviates from the chapter, the chapter applies (specification first, PLAN 1 principle 3), and the row is adjusted in the same change.
  4. New entries. Build tickets record their rulings in a file of their own, docs/decisions.d/<ticket>.md (section 10). A change to the specification needs an ADR or an entry in this log.
  5. Owner questions are listed with their default in OPEN-QUESTIONS.md, product risks in RISKS.md; this log names only the decision that the plan uses until the answer.

Reading guide: IDs

Prefix Meaning Section
D-B1 to D-B13 Fundamental decisions of the lead; in PLAN and chapters short B1 to B13 1
D-I1 to D-I6 Invariants of the backend policy and the terminal; short I1 to I6 2
D-R-<topic> Rulings of the lead from the research 3
D-L1 to D-L24, D-LS-<n> Rulings of the lead on deviations of the teams and further rulings without an ADR; short L1 to L24 4
D-<abbrev>-<n> Rulings of the planning teams, by chapter; the number counts the rulings of the chapter in the order of the team report and says nothing about the R-ID 5
D-K-<Team><n>, D-K-X<n>, D-KB-<n> Core contract: requests from the teams, alignment of all documents, open items of the core report 6
D-T1 to D-T11 Rulings of the lead on the addendum of 2026-10-10; short T1 to T11. The later rulings of the lead T12 (one session user per person), T13 (no shared locations) and T14 (preview images terminal-* and viz-*) are D-W-57, D-W-62 and D-W-77 in section 9; they are not D-T12 to D-T14 7, 9
D-T12 onward, D-V<n>, D-N<n> Rulings of the writers of the addendum; the column "Writer ID" names their original number 7
D-G-<n> Rulings of the gap integration that are in no chapter; <n> is the number of the gap item 8
D-W-<n> Ruling log of the resumption 9
R-<abbrev>-<n> Decision of a chapter; the abbreviation names the chapter: PR 01, AR 02, DM 03, SY 04, AZ 05, API 06, RE 07, ID 08, AG 09, EX 11, FI 12, PN 13, SU 14, CL 15, OP 16, OB 17, RL 18, SE 19, DP 20, DV 21, IM 22, QA 23, TM 24, VZ 25 8
P-<n>, W<n> Decisions and known contradictions of the plan (PLAN 19, PLAN 7) 8

Chapter references name the number of the chapter and of the section, for example "02 3.3" for 02 3.3. The link leads to the section.

Sections 1 to 9 (the planning state and the resumption: IDs D-B, D-I, D-R, D-L, D-LS, D-<abbrev>, D-K, D-T, D-V, D-N, D-G and D-W) stand in DECISIONS-PLANNING.md, with the same IDs and numbering, so that this log stays below repo.max_file_kb; section 10 and the sections of the build tickets stay here.

10. Rulings of the build milestones

From M1 on, every ticket records what it decides and no chapter specifies: ID D-<Ticket>-<n> (for example D-M1-17-1), date, decision, reason, location. If a ticket changes a row above, the row stays and names the new ID. Each ticket writes its rulings into its own file docs/decisions.d/<ticket>.md, starting with the heading # <ticket> <title> and one table, and adds no section to this file (ruling PL/24, gate fragments-check); the docs site lists the files on the page "Ruling log: tickets". The sections of the tickets up to 2026-10-22 moved there unchanged, one file per section and with the same IDs; an integration section Integration int/m1-wave<n> is int-m1-wave<n>.md.

ID Date Decision Reason Location
D-DEPS-1 2026-10-11 The development tools pin katex through overrides in tools/node/package.json to 0.18.10, the newest 0.18 release that is not deprecated (0.18.11 is deprecated by its authors: https://registry.npmjs.org/katex/0.18.11, retrieved 2026-10-11). The pin goes when mermaid and mermaid-cli accept a fixed katex themselves; Renovate proposes newer pins in the group "dev tools (Node)". GHSA-238p-pmpm-9mq7 is fixed from 0.18.2 (https://github.com/advisories/GHSA-238p-pmpm-9mq7, retrieved 2026-10-11), but mermaid-cli 11.17.0 asks for katex@^0.16.25 and mermaid (also the newest 12.1.0) for ^0.16.47 (https://registry.npmjs.org/@mermaid-js/mermaid-cli/11.17.0, https://registry.npmjs.org/mermaid/12.1.0, retrieved 2026-10-11), so the Dependabot security update failed with "conflicting dependencies" and no tool release resolves to a fixed version. mermaid-cli uses the installed package only for the katex style sheet; mermaid's browser bundle carries its own katex code, which the override does not change, and the repository's diagrams contain no math. make diagrams renders every diagram with the pin. toolchain.md, package.json
D-DEPS-2 2026-10-11 GHSA-86w9-cpqp-85rv (node-forge up to 1.4.0, RSA PKCS#1 v1.5 signature verification) is dismissed on GitHub as "vulnerable code not used" and stays an accepted OSV finding with an expiry date (D-QA-19). No override and no removal: no node-forge release has the fix (https://github.com/advisories/GHSA-86w9-cpqp-85rv, retrieved 2026-10-11), and the newest @sonar/scan (5.0.1) pins node-forge 1.4.0 exactly (https://registry.npmjs.org/@sonar/scan/5.0.1, retrieved 2026-10-11). @sonar/scan imports node-forge only in src/request.js (source of 5.0.1: https://github.com/SonarSource/sonar-scanner-npm/blob/bfa5f4128eb69f902e09190f7ad1a41dc65b1912/src/request.ts, default truststore in https://github.com/SonarSource/sonar-scanner-npm/blob/bfa5f4128eb69f902e09190f7ad1a41dc65b1912/src/properties.ts, retrieved 2026-10-11), to read a PKCS#12 truststore when sonar.scanner.truststorePath is set or <sonar.userHome>/ssl/truststore.p12 exists; it calls util.decode64, asn1.fromDer, pkcs12.pkcs12FromAsn1, getBags and certificateToPem, none of which verifies an RSA signature (PKCS#12 integrity is an HMAC), and the certificates go to Node's TLS stack, which does the verification. The repository configures no truststore, and even with one the path verifies no signature. A probe on 2026-10-11 ran exactly these calls on a truststore with an RSA certificate (exponent 3) while every RSA verify entry point threw: zero calls. Replacing the scanner would need Java or Docker for the scan, which the toolchain avoids on purpose. osv-scanner.toml, toolchain.md
D-DEPS-3 2026-10-13 Pull requests of the dependency bots dependabot[bot] and renovate[bot] pass CI like fork pull requests. tools/ci/pr_mode.py (job plan of ci.yml) sets private_checks=false when the pull request author and the actor of the run are both one of these logins: the deny list then runs with the generic rules (denylist (generic rules, fork)), sonar is skipped, and the run shows the notice "Reduced checks". make commitlint gets the pull request author as PR_AUTHOR and exempts from the DCO sign-off only the commits whose author e-mail is exactly that bot's noreply address (BOT_AUTHORS in tools/hooks/commit_msg.py); Conventional Commits still apply to them and to the title. Before the merge the maintainer rewrites the body with their own Signed-off-by, which runs every check again with the secrets and signs the squash commit on main. GitHub gives a run that Dependabot starts no Actions secrets and treats it like a fork run (https://docs.github.com/en/code-security/dependabot/troubleshooting-dependabot/troubleshooting-dependabot-on-github-actions, retrieved 2026-10-13), so every Dependabot pull request failed on the missing private list, the missing SonarQube token and the missing sign-off (the bot signs with an address other than its author address). The rule depends on the pull request author, which only GitHub sets, and on the actor, which decides about the secrets: a bot address written into a commit of a fork or of a person's pull request exempts nothing, and a person's run on a bot pull request keeps every check and fails closed without a secret. Renovate gets the same rule so both bots follow one path. Dependabot takes the build(deps) prefix from the history of main; a title it writes outside the rules fails commitlint, and the maintainer fixes the title instead of the gate relaxing. push-discipline.md, gates.md, ci.yml, pr_mode.py

The first sections of this section, from "M1-01 Server gates" to "M1-16 Event log with commit-ordered sequence", stand in DECISIONS-FOUNDATION.md, unchanged and with the same IDs (ruling PL/12).

PL Rulings of the PR lane

No Date Ruling Reason
PL/1 2026-10-14 make test runs the Go tests with -coverpkg=./...: a statement counts as covered when any test of its module runs it, also a test in another package. The gate coverage (quality.coverage.go_min_percent) and the new-code coverage of SonarQube read this profile. Many use cases are tested through their HTTP adapter against PostgreSQL (the coming sessions, refresh and step-up flows among them); with per-package coverage the code those tests run counted as uncovered, and the new-code gate failed for code that the suite executes.
PL/2 2026-10-15 make test gives each Go test binary quality.test.go_timeout (20m, GO_TEST_TIMEOUT) instead of the default ten minutes of go test. Current state: 40m, with a time budget of half of it in CI (PL/39). With -coverpkg=./... (PL/1) every package instruments the whole module; internal/identity/adapters/httpapi needed 568 s in the sonar job of #84 and passed ten minutes in the sonar job of a later pull request twice, so the job failed although no test failed.
PL/3 2026-10-16 The handler test harness (httpservertest.New) takes the request validator of the chain from httpservertest.SharedValidator, built once per process, contract and operation list, like the response validator (M1-31/17). Every harness built its own validator over the whole contract: with the tests of M1-36 the HTTP test package of identity needed 699 s and 9.5 GB under -race and module-wide coverage, and both CI jobs that run make test lost their runner; with the shared validator it needs 42 s and 1.8 GB on the same host. httpserver.Validator is safe for concurrent use.
PL/4 2026-10-16 Sections 1 to 9 of this log move unchanged to docs/DECISIONS-PLANNING.md; section 10 and the sections of the build tickets stay here, and new rulings are still recorded here. The log grew by about 10 KiB per ported ticket and would pass repo.max_file_kb (1024 KiB) within the M1 tickets still to land; the briefs forbid raising the limit or excluding a file from the hygiene hooks, and the IDs stay stable (rule 1).
PL/5 2026-10-17 Members with design-system goldens install DsGoldenFileComparator (package:design_system/testing.dart) in test/flutter_test_config.dart and pass no diffThreshold to alchemist. In a CI golden a pixel counts as different only when one channel differs by more than dsCiGoldenChannelTolerance, and the golden passes while at most dsCiGoldenDiffThreshold of its pixels differ (D-M4-04-6); a platform golden (macOS only) must match exactly; a different size fails. The bound of D-M4-04-6 counted every changed pixel: CI goldens recorded on macOS differed on the Linux runner in up to 0.8 % of the pixels of the core component sheets (M4-07) and in 88 pixels of every mark golden of bothy_viz (M6-11), nearly all by a few steps of one channel at anti-aliased edges, so CI was red without a visible change. The tolerance stays below the closest dark token pair that marks an edge (a subtle border on a raised surface, held by golden_comparator_test.dart), and closer surfaces still fail the exact platform golden in every local make check. sonar.coverage.exclusions leaves packages/*/lib/testing.dart out of the coverage measure, like the test helpers of M3-01/20: bothy_noise and design_system both have that file, so SonarQube could not resolve the relative path lib/testing.dart of their lcov reports and counted the new comparator as uncovered (0 %), although golden_comparator_test.dart runs every line of it.
PL/6 2026-10-18 The CI jobs gates and sonar get timeout-minutes: 90 (before 60 and 45), and ci-ok gets 120 (before 90), so that ci-ok, which starts with the run and polls the other jobs, outlives the longest job it waits for plus plan and the runner queue. Both jobs run the whole make test, sonar with coverage before the scanner. On the pull requests of 2026-10-17 and 2026-10-18 sonar took between 17 and 36 minutes and was twice (pull requests 128 and 132) cancelled at its 45 minute limit while the scanner was still starting, and gates took up to 59 of its 60 minutes; a cancelled job leaves the required check ci-ok red without a finding. A limit at about one and a half to two times the usual duration keeps a hung run bounded and stops cancelling healthy ones.
PL/7 2026-10-18 The serve tests of internal/wire wait up to startupWait (three minutes, in serve_test.go) for the listener lines, the start line and /readyz, instead of 30 seconds each. Under -race on a host with a load around 30 the start of a test server (migrations, key check, warming the validators) took longer than 30 seconds, and the pre-push make check failed with /readyz = 503 startup in tests the change did not touch; integration and port runs since wave 35 saw the same. The shared harness already waits three minutes (M1-71/10); a server that never gets ready still fails. The test stores of files/adapters/s3 get the same treatment: testTransferTimeout (five minutes) replaces the 30 second transfer and egress timeouts, because a Put of several megabytes to the SeaweedFS container failed in CI with transport: deadline exceeded, the third time across pull requests.
PL/8 2026-10-19 Tests wait in one way: internal/platform/testkit holds Patience (named value quality.test.patience, three minutes) and the waits Until, Receive, Context and Deadline; every wait of a Go test for something that must happen uses them instead of its own number of seconds, and a check that something stays fast makes the slow alternative take longer than Patience (a drain delay of 30 minutes, a server that holds its connections until the test ends, an egress timeout of twice Patience) and compares with Patience. The Semgrep rule bothy-go-test-wall-clock-wait refuses a deadline variable, a connection deadline, time.After, time.NewTimer, context.WithTimeout, an http.Client timeout or an elapsed-time bound with a literal number of seconds or minutes in a _test.go file; a bounded give-up or a latency bound under test carries a nosemgrep with its reason. It does not see a wait bounded by a constant of the test or by time.Sleep, and Dart tests are not covered. testkit.Integration marks every test that needs a container runtime (pgtest.New, the SeaweedFS helpers, the container tests of pgtest); go test -short skips them and pgtest.Main then starts no container, while make test and CI never pass -short. Three tests read a clock where they raced: the backupd SIGTERM test waits for the line of the ready stanza instead of the attempt counter of the fake, the serve role test waits for the ready line after /readyz answers, and TestInsertTx_ScheduledAt compares the schedule with the clock of the database. startupWait (PL/7), waitLong, the start timeouts of the SeaweedFS helpers and of pgtest become Patience. Test servers get startup.dependency_wait of Patience where the database is reachable, both pgtest containers run with max_connections 400, because slower tests overlap more and the default of 100 ran out under load, and the acquire, statement and lock timeouts of pgtest.Config are Patience. The poll tests of the event log tail and of the consumers keep their wait for the polled delivery and log its time instead of bounding it. Repeated reruns of CI and of the pre-push make check on the loaded build host came from the same class, fixed one instance at a time before (PL/7, M1-41/15, #97, #117): a test that bounded a correct but slow step by a few seconds (/readyz, a SeaweedFS transfer or container start, a consumer round, a SIGTERM of the fake pgBackRest). The failed attempts of the last 150 CI runs that passed on a rerun name, among others, TestS3RequestsAreCoreS3, TestServeStopsChildOnSIGTERM, TestServeFailsWhenTheServerExits, TestServeRolesStartReadyAndStop, TestOwnerRace, TestConsumerWakesOnNotify and TestListScalesWithManyCheckpoints; the lane reports add TestInsertTx_ScheduledAt, the egress and migrate tests and the wire /readyz waits. A condition that never holds still fails, only after Patience, well below quality.test.go_timeout.
PL/9 2026-10-19 The REST contract and the files generated from it as one file each (docs/api/openapi.yaml, its byte copy services/server/api/rest/openapi.gen.yaml and sdk/typescript/src/generated/openapi.ts) have their own size bound, the named value api.contract_max_file_kb (2048 KiB), in a second check-added-large-files hook; every other file keeps repo.max_file_kb, and the general hook leaves out exactly these three files (a test holds both lists and both values). The contract grows with every API ticket (about 12 KiB for M2-24) and reached repo.max_file_kb: with M2-24 it is 1027 KiB, so the next API change could not be committed. Splitting the contract into several files would need every generator, check and test that reads it to read a bundle instead, a change across all lanes; YAML anchors would shrink it but the pinned Dart generator refuses more than 50 aliases. A separate, still bounded limit keeps the growth visible; a split is due before the contract nears the new bound.
PL/10 2026-10-19 The pre-push hook runs tools/hooks/push_plan.py: it maps the files between the pushed commit and its merge base with origin/main to make check COMPONENTS=<components> AREAS=<areas> LINT_FROM_REF=<base> LINT_TO_REF=<head>. COMPONENTS comes from the component map of CI (tools/ci/components.py changed), so the hook selects the component gates as the CI job gates does; a path the map sends to all (the Makefile, tools/, .github/, .semgrep/, quality/, root files other than Markdown, unknown paths) runs the full make check, as do PUSH_FULL=1, a tag push and a branch without a merge base. AREAS=tools (format, lint, types, tests and coverage of the Python tools) is on unless every changed file is a source file of a component, because the tools tests read workflows, manifests, docs and configuration of the whole repository; without it make test still runs the tools tests that read component sources (TOOLS_SOURCE_TESTS). A renamed file counts with its old and its new path (git diff --no-renames). LINT_FROM_REF and LINT_TO_REF run the file hygiene hooks of make lint on the changed files. Every other gate of make check runs on every push; CI never sets AREAS or the lint refs and runs as before. Every push ran the full make check on a build host shared by up to ten sessions: the Go suite under -race, the tools suite, the Dart suite and the JVM tests for a change of one component, which made a push take most of an hour and turned host load into failed pushes. The component selection of CI was already the agreed exception of rule 1 of chapter 23; the hook now uses the same map, and the tools gates, which the map does not cover, get their own conservative switch. A gate the hook left out that fails in CI is a gap of the map, fixed in the map with a test (docs/quality/push-discipline.md).
PL/11 2026-10-20 ci.yml runs make gates in seven parallel jobs instead of one: gates (format, codegen, lint, types, structure, commitlint and the workflow policy), gates (docs and contracts), gates (security and supply chain), test (tools), and test (server), test (apps) and test (SDKs and plugins) for the planned components with AREAS=none. Each job installs every toolchain with the Go, npm, uv and pub caches. tools/ci/tests/test_ci_parallel_gates.py holds that the jobs together run exactly the targets of make gates, each once except the tests split by component; ci-ok requires every job, and a component test job may be skipped by the plan as the single job skipped those tests before. The test jobs hand their coverage reports to sonar as sonar-coverage-<part>; sonar needs them and is skipped when one fails. One gates job ran every gate in a row, and a green run of a pull request took 45 to 65 minutes, nearly all of it in that job (tools suite about 15 minutes, Go suite under -race about 16, Dart, JVM and SDK tests, then docs and scans). The parts are independent, the build-verify workflow already runs them in parallel for build branches, and with AREAS (PL/10) no test job repeats the tools suite.
PL/12 2026-10-20 The first sections of section 10, from "M1-01 Server gates" to "M1-16 Event log with commit-ordered sequence" (written up to 2026-10-14), move unchanged to docs/DECISIONS-FOUNDATION.md; new rulings are still recorded here. When the log again comes near repo.max_file_kb, the oldest sections of tickets that are on main move the same way to that file. With the open M1 stack the log grew past repo.max_file_kb, which the hook check-added-large-files rejects on every push; PL/4 moved sections 1 to 9 for the same reason. Moving finished sections keeps every ID and text, and the briefs forbid an exception from the size bound.
PL/13 2026-10-20 The unpack step of the sonar job reads coverage.tar both straight in the download folder and in one folder per artifact. actions/download-artifact puts a single artifact that matches the pattern straight into its path; a pull request of one component (only test (server) or only test (apps)) had its archive there, the step looked only in sub-folders, and SonarQube saw 0 % coverage on new code (PR #168). Test: test_unpack_step_reads_one_or_several_artifacts.
PL/14 2026-10-20 One plan decides the conditional CI jobs of ci.yml and build-verify.yml: tools/ci/components.py ci-plan writes components and jobs; the component test jobs follow the component map, the platform jobs need apps/, packages/, the root pubspec.yaml or pubspec.lock, the image jobs the build context and test of bothy-postgres, the migrations and the module files of the server; a shared input (a path of no component, docs/api/), an empty diff, a push to main and a manual run need every job. ci-ok runs the same plan step itself and accepts a skip only of a job the plan did not list; build-verify-ok checks its needs the same way. The three gates jobs, test (tools), the deny list and sonar run on every change; the tools tests also cross build component sources (release builds), so they cannot follow the component map. The plan diffs without rename detection. Shared inputs stay as the component map defines them, tools/ included. Fewer runners per pull request without letting a skipped job hide a failure: before, ci-ok accepted a skip of these jobs whatever the cause. The repository-wide gates read every kind of file, so they cannot be narrowed by component.
PL/15 2026-10-20 ci-ok is the last job of ci.yml, like build-verify-ok of build-verify.yml: it needs every other job, runs with always() and tools/ci/ci_ok.py needs checks the results of its needs against the plan (a conditional job may be skipped only when the plan left it out, sonar may be skipped, exactly one deny-list job must pass, every other job must succeed, a missing plan fails). The first step of the job plan creates a check run ci-ok in progress for the head commit of a pull request (checks: write, not for forks and dependency bot runs), and ci-ok completes it with its own conclusion; release.yml grants the call checks: write instead of actions: read. Without needs, ci-ok started with the run and polled the others, so it held a runner for the whole run, about a quarter of the runner minutes of ci.yml, inside the shared limit of concurrent jobs. A job with needs gets its check run only when those jobs are done (seen again in the job list of a build-verify run: build-verify-ok was created when the last job ended), so the mark of the plan job keeps an older green ci-ok of the same commit from standing after an edit of title or body (LG/3).
PL/16 2026-10-20 The self-contained tool folders under tools/ are components of the map (TOOL_COMPONENTS in tools/ci/components.py, longest key wins): api-checks, catalog, configdoc, denylist, doccheck, docs, opscheck, scancode, sonarqube, testing and tools/README.md select no component, tools/api-checks/breaking.py and tools/sonarqube/compose.yaml select server, tools/design-tokens/ selects apps and server (the server's designkeys generator and tests read its output design/build/tokens.resolved.json, which now selects server too). This replaces the tools/ part of the shared inputs of PL/14. tools/bin/, tools/ci/, tools/hooks/, tools/node/ (SHARED_TOOL_FOLDERS), the root files of the tools project and every folder the map does not name stay shared inputs. tools/ was a shared input as a whole, so most pull requests ran every job, often only for one data file such as tools/configdoc/planned.yaml. These tools are read only by the repository-wide gates and the tools tests, which run on every change; the files that a component gate or test reads (the server-only make api-breaking, the image pin that the pgtest test compares) select that component. Recorded in gates.md, "Jobs a change needs".
PL/18 2026-10-21 The Android emulator jobs (apps-android-26, apps-android-36 of ci.yml and build-verify.yml) install the emulator, the platform tools, the platform and the system image of their level with tools/ci/android_sdk.py before the emulator action: up to ATTEMPTS tries with PAUSE_SECONDS between them, and the files of the emulator and of the image decide whether a try worked, not the exit code of sdkmanager. The action then finds the packages and downloads nothing. On 2026-10-20 the jobs failed several times with the same class: a broken download ("Error on ZipFile unknown archive" for the emulator or the image), after which the action could not reach the emulator ("could not connect to TCP port 5554"); sdkmanager reports the broken archive only as a warning, and the action does not retry.
PL/17 2026-10-20 A build team may push build-base/<ticket> right before build/<ticket>, each in a push of its own, pointing at the integration head the ticket branch was built on; the pre-push hook gives that push the fast gates of a build branch. The plan of build-verify.yml measures the diff from the merge base of the pushed commit with that ref (tools/ci/build_base.py); without the ref or without common history it measures from the merge base with origin/main as before, and without any base every job runs. No workflow runs on build-base/**. A ticket branch on an integration base differs from its old merge base with main in thousands of files and always touches shared inputs, so every build run ran every job. The ticket's own change is the diff from the head it was built on, as its later pull request on that head would show it. build-verify is no required check; ci-ok of the pull request still runs the plan from the merge base with main. Recorded in push-discipline.md, "Build branches".
PL/19 2026-10-21 The two catch-up race tests of the gateway (TestGateway_OperatorNewChannelWhileCatchingUp, TestGateway_TokenRevokedDuringCatchUpCloses4012) run their world with sync.heartbeat_miss_limit 9, so the connection that sends no heartbeats on purpose has a heartbeat deadline of 300s, beyond testkit.Patience. Both tests read a backlog of about 2000 events (plus a cursor frame per event in the first) on a connection without heartbeats; since gate G9 (M1-67) validates every frame, the drain on a hosted runner outlasted the default deadline of 90s and the first test closed with 4009 heartbeat_timeout after 1950 dispatches in CI of pull request 197. The heartbeat deadline is not what these tests check.
PL/20 2026-10-21 TestMain of internal/wire builds the contract bundle of the gateway harness (specschema.Default()) once before the tests, next to the request validator of M1-31/18. The bundle is built on the first wstest.Dial behind a sync.OnceValues, which every parallel gateway test then waits for; under -race on a hosted runner this ate into the 6s token lifetime of TestGatewayTokenExpiry (TestClose4011ExpiredWithoutReauth closed with 4011 at identify in CI of pull request 209). Same class as PL/19: the G9 validation of M1-67 must not land inside a timing window a test checks.
PL/21 2026-10-20 A timing comparison of a test (the allowed difference of two medians) uses testkit.TimingBound: the bound itself without the race detector, five times the bound under it (testkit.RaceEnabled). ID-T21 and AZ-P4 use it; the local factor of ID-T21 goes. Both compare medians against authz.not_found_timing_tolerance (20 ms) and failed in CI under -race with a difference of about 23 ms (ID-T21 three times, AZ-P4 in the build of the M1 stack): the race detector makes Go code 2 to 20 times slower, so a write of about a millisecond on one path becomes tens of milliseconds. One place holds the rule for every such test; the comparison without the race detector still measures the real code.
PL/22 2026-10-21 Gateway tests do not depend on how fast the host runs. testkit.PatientGateway(interval) gives the gateway windows of a test that does not check them: sync.heartbeat_interval interval, sync.heartbeat_miss_limit testkit.HeartbeatMissLimit(interval) (a limit whose deadline is at least one interval beyond Patience) and sync.identify_timeout Patience. The gateway world of internal/wire (newGatewayWorld, so every delivery, typing and conformance world) and the test server of gateway/adapters/ws start with them (test/harness keeps the production windows, which its performance tests measure); the write timeout of the wstest client is Patience. Each deadline test of internal/wire (TestGatewayDeadlines: 4003, 4009, 4006 after the resume window, 4011 of an agent token) runs in a world of its own where only that window is short and nothing in its setup races it, so a slow host only makes the close come later (the stopwatch of a lower bound starts before the dial or identify, and the resume comes ten seconds after the window of one second); the 4011 test moves an injected clock of the process past the token's expiry instead of issuing a token of a few seconds. TestGatewayTokenExpiry runs on the same injected clock in the process and in the side identity that issues the tokens and proofs: it moves the clock to the instant of reauth_required and past the expiry, with the default lifetime of 10 minutes, instead of a lifetime of 20s (pull request 211) that identify on a loaded host still outlasted (19s locally); its clients beat by hand under a heartbeat deadline beyond every move. The deadlines at the exact instant, and the first heartbeat after more than one interval (formerly TestFirstHeartbeatJitterTolerated with real seconds), are tested with an injected clock: in gateway/app and over a real WebSocket in TestDeadlinesFollowTheInjectedClock of gateway/adapters/ws, which waits for a tick of the handler at the moved time before each check. The Semgrep rule bothy-go-test-gateway-window refuses a heartbeat interval, identify timeout or resume window of its own, a miss limit below 5, and a literal HeartbeatMissLimit or IdentifyTimeout of the use case configuration in a _test.go file (environment entries and struct fields, not the dotted keys of test/harness) outside gateway/app and gateway/domain (which run on an injected clock); a deadline test carries a nosemgrep with its reason. It does not see a world built without the helpers above. Recorded in testkit README, "Usage". Four gateway flakes in one day came from one class (pull requests 205, 206 and 207, then TestGateway_OperatorNewChannelWhileCatchingUp closed with 4009 heartbeat_timeout after 128s on main): the worlds ran the production windows (90s heartbeat deadline) or windows of one to six seconds while the tests checked something else, and the drain, identify or heartbeat of a loaded runner with -race and the G9 validation outlasted them. Like PL/8 for waits, the windows now follow Patience, and only the tests of a deadline keep a short window, in a direction a slow host cannot fail.
PL/23 2026-10-21 CI reruns a failed Go test at most twice, alone as its top-level test (gotestsum --rerun-fails=2 --rerun-fails-run-root-test through tools/ci/flakes.py), not its whole package; a package that failed without a failed test reruns whole. A test or package whose last attempt failed, a data race, a panic, a timeout, a build error and more than ten failures still fail the job; coverage is the merge of the attempt profiles. Each rerun that passed is a flake: job summary, artifact flakes-<job>, and the issue labelled flake that flakes.yml keeps every hour (the runs of a closed issue stay reported). A failure that only shows after the other tests of its package ran passes CI as a flake of every run, which puts it at the head of the list; local runs without CI=true stay strict. Recorded in quality gates, "Flakes". The owner (2026-10-21): flakes must stop blocking landings, rerun for the failed tests only, a test that fails in every attempt stays red, coverage unchanged, flakes go to a list. A whole-package rerun would catch the order dependency but repeats packages of up to 15 minutes for every flake; the red team found the gap, and this ruling keeps the owner's rule and makes the gap visible on the list (test_an_order_dependent_failure_is_a_flake_of_every_run).
PL/24 2026-10-22 Changelog entries and ticket rulings are fragments, one file per ticket: <component>/changelog.d/<ticket>.md next to the component's CHANGELOG.md (changelog.d/ for the repository as a whole) with only Keep a Changelog ### headings and list items, and docs/decisions.d/<ticket>.md with the rulings. make release-prep puts a component's fragments under "Unreleased" in file name order and deletes them (make changelog-preview COMPONENT=<name> shows the result without writing); the docs site lists the ruling files on a generated page. The gate fragments-check (tools/ci/fragments.py, CI job gates) refuses a malformed or misplaced fragment, a ticket or integration section in this file (also one a rebase kept), and in a pull request a line added under "Unreleased" of a CHANGELOG.md against the merge base. The ticket and integration sections of this file moved unchanged into docs/decisions.d/ (relative links one level up); the entries already under "Unreleased" stay where they are, because they are the assembled form. Parallel pull requests all appended to the same two files, so every landing made the next ones conflict: a rebase and a full CI run each. GitHub's merge ignores git merge drivers such as merge=union, so only separate files avoid the conflict. Moving the existing entries of CHANGELOG.md into fragments would change no text and give no fewer conflicts.
PL/25 2026-10-22 In a pull request the advisory gate osv fails only for vulnerable dependencies the pull request adds or changes: ci.yml sets OSV_BASE to the base commit, and tools/ci/advisories.py drops the findings of OSV-Scanner that the merge base has in the same lock file at the same package version, and the called govulncheck findings (OSV entry, module, version) that the merge base has too; those print as warnings. A scanner error, an unreadable failing report and a failed base scan still fail. A push to main and the daily workflow advisories.yml scan in full; the keeper opens the update pull request when they fail. Recorded in quality gates, "Advisories". Five times in a week a new public advisory (kin-openapi, certifi, Go, virtualenv) turned the gate red for main and every open pull request at once and stalled all landings for one to two hours, although no pull request had changed the dependency. Comparing findings with the merge base in the same run keeps the scanners' own verdict and database; a new call into a vulnerable Go dependency still fails (test_govulncheck_fails_for_a_call_the_pull_request_adds), and test_a_pull_request_that_adds_a_vulnerable_dependency_fails proves an added vulnerable dependency still fails.
PL/26 2026-10-22 In a pull request a Go test that failed in a package the pull request changes fails the job even when its rerun passed: ci.yml sets FLAKE_BASE to the base commit, and tools/ci/flakes.py counts a package as changed when a file of its directory, or of a testdata folder below it, differs between the merge base and HEAD. Such a failure is not recorded as a flake. Packages outside the change keep the reruns of PL/23, a push to main reruns every package, and a checkout without the base commit fails before the tests. Recorded in quality gates, "Flakes". The owner (2026-10-21): rerun flaky Go tests only outside the changed packages. A flake elsewhere is not the pull request's doing and must not stop its landing; a test of a package the pull request touches may have been made flaky by it, and a rerun that passes would hide that.
PL/27 2026-10-22 The append-only registries that stay shared, every CHANGELOG.md, docs/DECISIONS.md, every migrations.sha256 and tools/docs/language-allow.txt, merge as a union of lines (merge=union in .gitattributes); code, JSON, YAML and other documents keep the normal merge. tools/ci/tests/test_merge_union.py pins the list against the tracked files and checks that a cherry-pick onto a moved main keeps both appended lines. See quality gates, "Fragments". The steering (2026-10-21): merge=union for CHANGELOG and the append-only registries. Fragments (PL/24) removed most shared lines; the rest (PL rows, migration sums, language exceptions, release sections) still conflicted on every local rebuild of a branch. A union never fails, so it is limited to files whose lines stand alone. migrations-lint fails a doubled or unsorted sum line and two migrations with the same number, and fragments-check a ticket section brought back into docs/DECISIONS.md; two PL rows with the same number or both versions of a line that both sides changed pass unnoticed, so read the merged file. GitHub's merge ignores the driver, so it helps the local rebase and cherry-pick only.
PL/28 2026-10-22 internal/wire wires each module of architecture/modules.yaml in its own file module_<name>.go: a function returns its moduleWiring (state embedded in server, start parts with an explicit order key and step name, REST handlers, route bindings, mounts after the REST routes, job registry fields and the job inserter), moduleWirings in wiring.go lists each module once, and serve.go only iterates them around the process steps of processWiring. Parts of one step with adjacent keys form one start component, so the step names and the start order stay as before. wiring_internal_test.go checks that every module of the register is wired exactly once (or listed in notWiredYet), that order keys are unique and the parts of a step adjacent, and pins the start components of each role. The steering (2026-10-22): serve.go was touched by almost every server ticket (12 merges in a day), so parallel pull requests conflicted there and each conflict cost a rebase and a full CI run. With a file per module two tickets of different modules no longer edit the same lines. The public Deps struct, the doc comment of Serve, JobModules and Handlers stay shared; a new start step still extends the pinned sequence of the test.
PL/29 2026-10-23 A timing comparison of a Go test (testkit.TimingSame, AZ-P4) runs only in a top-level test that does not call t.Parallel; Go starts the parallel tests of the package only after every such test has finished. The Semgrep rule bothy-go-test-timing-parallel refuses TimingSame in a test or subtest that calls t.Parallel. Other packages of the same go test run are not excluded. A gateway world of internal/wire gives its people access tokens for a day (auth.access_token_ttl), longer than any run of the package; a test of token expiry sets the key itself. TestCommandAutocomplete failed on main and on every pull request that changed internal/wire: next to the parallel tests of the package its AZ-P4 comparison differed beyond the bound in every round, because load lengthens a real, allowed difference (the invisible channel decides and audits, the unknown one does not) by the same factor as the requests, and the suite outlived the default ten-minute token of its Owner (401 token_expired). The contract model of the schema checks is now built once per test binary instead of for every check.
PL/30 2026-10-23 TestCountersOnlyUsedByPhaseHooks type-checks the packages of the server module (test variants included) from source, one after the other in import order, with the checked packages shared; only packages outside the module come from export data, loaded with the -race and -tags settings of the running test binary. The scan never asks go list -export for a package of the module (TestCountersScanBuildsNothingOfTheModule). The scan ran 577 to 850 s in green main runs and was the test still running when internal/wire hit its 22 minute timeout (shard 1 of the M2-15 run, the M8-03 pull request). go list -export compiled every package of the module and its tests again without -race and coverage, because the test build caches only the instrumented objects, and every package was checked with its own importer, which read the export data of all its dependencies again.
PL/31 2026-10-23 Every serve process of the tests of internal/wire (serveEnv) waits testkit.Patience for a pool connection, a statement and a lock (testkit.PatientDatabase: database.acquire_timeout, database.statement_timeout, database.lock_timeout); a test of one of these deadlines passes its own value. A test that fails while its serve process runs or has ended prints the logs and the exit code of the process. The production defaults (2s, 5s, 2s) failed start steps on loaded CI runners, the start ended the process and /readyz refused connections for the whole wait (TestServeWorkerRolesRunConsumers/all, TestWebhookConsumerRegisteredInWorkerRoles/all, TestServeLogTailInAPIRoles/worker), and a request of a gateway world answered 503 unavailable after the acquire timeout (TestSlashCommands/TestAppCommandsNotInvocableBeforeM2_25).
PL/32 2026-10-24 The dependency license step of make licenses fails only for license violations, never for a vulnerability: tools/ci/dep_licenses.py runs osv-scanner scan source --format json --licenses=<allowlist> over the lock files of the shipped components and fails for a package with license_violations, for an exit code other than 0 or 1, for a report that is not JSON and for a failing scan whose report names neither a violation nor a vulnerability. Advisories stay with make osv alone (PL/25). OSV-Scanner v2.6.0 has no license-only mode: with --licenses it still matches every package against the advisory database and exits 1 for a vulnerability. So after PL/25 every new public advisory still turned the security gate red on every pull request (PR #299: osv let the merge base's findings pass, then make licenses failed on fast-uri and serialize-javascript). Tests: tools/ci/tests/test_dep_licenses.py, test_licenses_judges_licenses_not_advisories in tools/ci/tests/test_make_dart_workspace.py.
PL/33 2026-10-24 The curl and go mod download calls of the tracked Dockerfiles are retried: curl with --retry 5 --retry-all-errors (with -f an HTTP error is a curl error that a plain --retry does not repeat), go mod download in a bounded retry loop that leaves on success and fails after its last attempt. apt is left as it is: it retries a failed fetch itself (Acquire::Retries). Not covered and not checked: the Flutter SDK clone and its downloads (git clone, flutter --disable-analytics, flutter pub get) in the console stage of services/server/Dockerfile, ADD <url> in tools/scancode/Dockerfile, and curl or go mod download written in another form than a simple command (exec form, heredoc, inside $(...) or if). A transient error of a download host turned the image jobs of main red although nothing in the change was wrong: go mod download on a stream error of the Go module proxy (postgres-image (amd64), run 40012832148) and the restic download on an HTTP 403 of the GitHub release host (postgres-image (arm64), run 40065171991). Test: tools/ci/tests/test_dockerfile_fetch_retry.py holds every tracked Dockerfile to the rule.
PL/34 2026-10-24 Every 404 not_found problem of the HTTP API has the same body: the problem writer (httpserver.WriteProblem) leaves out detail, reason, hint, current and the extension members for not_found, as it already did for internal_error. A use case may still give its miss a detail; it never reaches the answer. R-AZ-01 and 06 "Errors": a resource that does not exist and one that is not visible answer the same. A visible forum without a project binding answered DELETE /channels/{channel_id}/project-binding with the detail the forum has no project binding, an unknown forum without one, so the contract test G7 (404 equality) failed whenever Schemathesis picked such a forum (flake list #238, eight runs, last 40062118285). Fixing the writer covers every use case at once (the runs repository has six more not_found errors with a detail). Test: TestProblem_NotFoundIsUniform.
PL/35 2026-10-24 The authorizer resolves a resource that names a thread (a thread itself, or a message, reaction or other resource on the channel path with its thread) by the thread's current channel and that channel's space alone; a channel or space hint of such a resource is not compared with it. The hints are still checked where they name a link that does not change: a channel resource whose id and hint differ, a channel with a foreign space hint, a thread resource whose id and thread hint differ stay an internal error. This narrows the rule "a hint that contradicts the source is an internal error" of brief M1-28 "Resolution". thread.move changes the channel of a thread at any time, and the callers build the hints from a read of the thread before the check (the send path in messaging, the push consumer in notify). A move that committed between that read and the check gave internal_error "contradictory channel hint": a 500 to a sender (flake TestMoveThread_ConcurrentSendWithAttachment, flake list #238, last run 40096824007) and a failed round of the push consumer. The current place is the one the decision must use (rights of this moment), so dropping the stale hint is the correct answer, and fixing it in the resolver covers every caller at once. Tests: TestMovedThread_StaleHintsFollowTheStructure, TestMovedThread_ChannelHintsStillChecked.
PL/36 2026-10-25 A lone side of an opening (never paired, its counterpart absent on its node) that ends by itself counts once in bothy.terminal.relay.unpaired with its side when the session ended open_failed with close_detail = open_timeout: the relay reads the end of the session after the side closed (RelaySessionEnd) and counts then. The side was removed from its slot under the lock first, so neither its own lone timer nor the end from the log tail counts it again; a side that ends while the opening still runs, or after another end, counts nothing. This completes the rule of 24 9 item 4 and D-M2-47-8 for the second path of the end. With two nodes and the edge without hash routing, the end of an opening committed by the lone timer of the device's node reached the host service through session.close from the node of its control connection, and the host service closed its data connection with 1000 before the log tail of the data connection's node delivered the end; the relay took that as a host closing by itself and counted nothing, so TM-T15 saw the client side rise and the host side stay (flake TestScenarios/tm-t15/without_hash_routing, flake list #238, seven runs, last 40126148784; the logs of api-2 show the data connection closed with 1000 and no unpaired record). The domain row for the log tail (sessionEnded) and the new check share domain.OpeningTimedOut. Test: TestRelay_LoneSideEndsAfterTheOpeningTimedOut (red before the fix for both sides).
PL/37 2026-10-25 Every server of harness.Start (and its restarts) waits testkit.Patience for a pool connection, a statement and a lock (testkit.PatientDatabase), as the serve processes of internal/wire do (PL/31); a test of one of these deadlines sets it through Options.Config. StartProcess keeps the production deadlines: its chaos and perf tests run the real binary as an operator would. Sixteen concurrent createGrant calls of the search world seeding (perf/internal/searchworld) are rights changes that queue on the permission version; on a loaded runner one statement passed the production database.statement_timeout of 5s, SQLSTATE 57014 reached the client as 500 internal_error and every TestSmallCorpus_* test failed in its first run (flake list #238, up to seven runs, last 40133132295). Locally a 40ms statement timeout gives the same 500 on POST /grants, a short lock timeout gives 503 instead. Test: TestStartDatabaseDeadlinesArePatient (red before the fix).
PL/38 2026-10-25 The two catch-up race tests of the gateway (PL/19) wait per frame (nextFrame, testkit.Patience for each frame) instead of one Patience for the whole drain, and their world (slowReader) sets sync.heartbeat_miss_limit 100 and agent.ack_timeout 1h, so no deadline of the connection can fire before the -timeout of the test binary; this replaces the miss limit 9 of PL/19. TestGateway_OperatorNewChannelWhileCatchingUp failed four times on the flake list #238 with after 1180 dispatches: context deadline exceeded (and 1545) in CI. The agent acknowledges every 200th dispatch; when 200 dispatches took longer than the default ack timeout of 30s (about 6.5 frames per second in the failing runs, each frame validated by gate G9 under -race), the window delivered again from the acknowledgement, and the reader, which counts redeliveries too, never reached the end. Reproduced locally with an ack timeout of 1s and a reader that takes 10ms per frame: the seqs cycle from the acknowledgement for minutes. A drain at that pace also outlasts one Patience. Test: TestSlowReaderDeadlinesOutlastTheTestBinary (red with the old world settings).
PL/39 2026-10-25 The Go test shards keep every test binary well below its time limit, and CI enforces it: quality.test.go_timeout rises from 20m (PL/2) to 40m, and in the CI shards of make test (tools/ci/flakes.py --budget, passed by go_shards.py) a test binary (a package, a part of a split package, a rerun) that runs longer than half of it fails with its name and time (the time budget); internal/wire runs in four parts instead of two, test/agents and test/terminal in two (SPLITS of tools/ci/go_shards.py), its one TestConformance that held all scenarios split into TestConformance1 to TestConformance4 (round-robin), because parts divide top-level tests; tools/ci/go_test_timings.json holds the durations of six green main runs (2026-10-24/25), overridden by those of the first three CI runs of the new layout (parts and their tests measured), the per-test durations of the two-part wire layout removed; the image prebuild is bounded (PREBUILD_TIMEOUT); a shard that runs an e2e package of the terminal suite first builds the suite's server and host images once (PREBUILD, test/terminal/cmd/termsuite-images, the same builds as stack.Start), so the builds in their TestMain are BuildKit cache hits. internal/wire hit the package timeout three times (#288, #298, PR #324 run 40154193414: part 1 of 2 killed after 1320s, the go command's limit of 22m). Cause: the timings were from #220 and lacked the heavy packages added since (test/agents, test/leak, test/integration/runcontext, test/integration/costs, test/terminal/e2e/twonode and others); weighing the median they all went to shard 1 with the first wire part, and the part ran 260s to 1320s in the same assignment depending on that load. Each of the six e2e packages built the server image in its TestMain (5m to 9m41s), competing with the other packages of its shard for the CPUs. A binary that ran past the limit was killed with a goroutine dump that blamed whichever test ran last (TestCountersOnlyUsedByPhaseHooks, 47s alone). The budget (20m) ends a slow binary before the former effective limit (22m) and names the package; the higher hard limit only catches hangs. Not 30m (a budget of 15m): on the first CI runs of this change the same assignment ran test/terminal in 597s on one runner and 1043s on a slower one, so a 900s budget would turn green work red by runner speed alone.
PL/40 2026-10-25 The relay holds a sender back instead of closing the receiver at once when the queue toward the receiver is full: forward waits for room up to terminal.relay_stall_timeout (new, 10s) and stops reading from the sending connection meanwhile, so the transport slows the sender; a receiver that takes nothing within that time, or a message larger than terminal.relay_buffer_max_bytes, is still closed with 4014 (24 6.3). A reattach replays the host's scrollback (host_service.scrollback_bytes, 1 MiB) at once, four times the relay buffer (256 KiB). The console terminal suite failed on main (runs 40146763038 and 40159931690): the device was closed with 4014 about 30 ms after the reattach, because the device link lagged behind the host link for a moment; the same made a session with a full scrollback unattachable from any slower client. A larger buffer would only move the limit.
PL/41 2026-10-25 tools/ci/apps_platform.py runs a platform test command once more when its output reports Building native assets failed and it failed, and prints the end of the non-empty stdout.txt and stderr.txt of every hook run under .dart_tool/hooks_runner before; any other failure, and a second native assets failure, fails the job as before. The script passes the output of each command through as bytes: on the Windows runner its stdout is a pipe in the ANSI code page, which cannot encode the marks flutter prints. Main run 40169660725 failed apps (Windows) in the build hooks of flutter_assemble before the first test file loaded; flutter only points to logs that the job did not keep. The hooks of sqlite3 and sodium download their libraries, the build runs before any test, so one more build hides no test failure.
PL/42 2026-10-25 The counters scans (TestCountersOnlyUsedByPhaseHooks and its fixture test, PL/30) move from internal/wire to their own test package test/countersscan with their fixtures; they skip in a binary with the race detector and run in make arch (CI job gates) with go test -tags contract_validate and without it. The scan rule itself is unchanged. The type check of the whole module grows with every merged package and ran under -race in the loaded CI shards 265 s (main run 40175958999), 679 s (40178949367) and 829 s (PR #361 run 40187445124, internal/wire 1241 s, over the time budget of PL/39); without the race detector it takes about 6 s. A static rule needs no race detector; a fourth split of the package would only move the limit.
PL/43 2026-10-25 Every path of the relay that counts a side as unpaired (its lone timer, the end of the opening at open_timeout that the log tail brings first, the side's own close after that end) logs the one record terminal relay side unpaired with the side and its close code, through one helper that logs and then counts; the relay decision ActCountUnpaired carries the close code of the counted side. D-M2-47-11 asks for the record with every count, but only the lone timer logged it: with two nodes the end committed by node A reached node B through its log tail before B's own timer ran, so B counted the host side without a record (flake TestTMT15_WithoutHashRouting, "node B records []", issue #238, runs 40195251326 and 40197489896).
PL/44 2026-10-25 The start of a platform test on a device fails or stalls now and then; tools/ci/apps_platform.py runs such a command once more, at one place: (1) when it failed and its output reports a test file that failed to load (Failed to load "<file>": ...); (2) when it printed nothing for SILENCE_SECONDS (15 minutes) after its output began: it is stopped with its process tree (its own process group) and runs once more when its last line was the build or the install of the app (Xcode build done., <check mark> Built, Installing), so it stalled before its test file started; a stall after any other line, a test result among them, fails as before. A second failure fails the job as before. Every flutter test on a device runs exactly one test file, so a load failure means no test of the command ran; tools/ci/tests/test_flutter_device_tests_one_file.py reads every tracked platform-tests.json and shell script and fails for a flutter test ... -d with more or less than one test file. The iOS simulator entries move to the new system ios of platform-tests.json (no host: it runs on macOS with --system ios, or on a Mac without --system after macos), run by the new CI job apps-ios (apps (iOS)); apps-macos runs --system macos. Main run 40209199826 failed apps (Android API 36) with Failed to load ".../phone_suite_test.dart": Failed to start Dart Development Service about 3 s after the app was installed: the VM service of the app accepted the connection of the Dart Development Service and closed it during its start; the log does not show why, and the direct connection of --no-dds does not retry after an accepted connection either. Main run 40213460068 ended apps (macOS) at its 60 minute limit: after the iOS app was built, flutter waited for the VM service of the app on the simulator, which it does without a limit (ios/simulators.dart), and printed nothing for 18 minutes; the same step took about one minute in the run before. The job took 48 to 52 minutes in the passing main runs before, so a stall and one more run would not fit in it; the two jobs run in parallel.
PL/45 2026-10-25 G7 drops every generated request header that net/http judges itself before any handler, in any spelling, at one place (auth.TRANSPORT_HEADERS, auth.without_transport_headers): Host, Content-Length, Transfer-Encoding and Expect; the transport sets them. This replaces the Host-only filter of M1-71/13. The negative mode of Schemathesis adds arbitrary headers; after Host (M1-71/13) it now sends CONTENT-LENGTH with an empty value on body-less operations (logout, createStepUpChallenge, endViewAs, cancelImport and others), and net/http answers that with its own plain text 400, which G7 reports as an undocumented content type (flake TestG7_SchemathesisAllEnabledOperations, issue #238, the only failure of runs 40139189329, 40140996052, 40161471847, 40164666016 and 40208660005). A probe against net/http answered a bad Content-Length with a plain text 400, an unknown Transfer-Encoding with 501 and an Expect other than 100-continue with 417 before the handler ran; these answers say nothing about the contract. Test: test_without_transport_headers_drops_what_net_http_judges_itself.
PL/46 2026-10-26 A server of test/harness (harness.Start) runs no round of the job status.probe by default: River runs a no-op in its place, after the test's own JobModules hook has seen the real use case. Options.StatusProbe runs the real job as in production. A test of the status page takes the use case through JobModules and runs the rounds itself, as test/status does. Since M7-09 (#355) a round writes status_components and appends status.updated whenever the agents or the checks change (for example one component per new agent), at the start of the worker and every status.check_interval, at any moment of a test. Tests that read the event log after a seq saw that event (flake TestImportNoNotification, issue #238, runs 40195368253, 40206016112, 40213304017, 40223819388 and 40230065261, always status.updated:1 beside the expected events); no other harness test needs the job behind its back. Test: TestStatusProbe_OffByDefault, TestStatusProbe_OptIn.