Repository navigation
Promote edge to stable: Polis 2.43.1 - #3342
Merged
Merged
Conversation
…ly when testConnection is routed (#3322) The deploy's health check expected no handler switches unless the version route was selected, so a box serving testConnection from Rust, which needs POLIS_API_CONNECTION_READS=1, failed validation. The check now expects that switch exactly when testConnection is in RUST_API_ROUTES, alongside the existing version rule. Every other check is unchanged. New cases cover the switch enabled, missing, unexpected and combined with other routes. Claude-Session: https://claude.ai/code/session_01Ss9NfEMZS9Qm4FZ2npXDrx Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…ame checks run on our own machines (#3324) Each workflow job's commands move into a make target (with shell entrypoints where a job needs one), keeping its service setup, pinned runtimes, test selections and artifact handling as they were. The coordinator workflow stays inline because its required-job control checks the literal commands. The pinned Node runtime is set up before the TLS server checks, the final Delphi replay keeps the PostgreSQL backend, and the API job resolves its source SHA before any test or build. A README section documents the prerequisites for running the same checks locally. Claude-Session: https://claude.ai/code/session_01Ss9NfEMZS9Qm4FZ2npXDrx Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…tedb never meets the image's temporary server (#3327) The PostgreSQL image initialises with a temporary server that listens on the Unix socket only, then restarts. pg_isready on the socket could succeed against that temporary server, and the createdb that followed failed with a missing socket while it restarted. Waiting for TCP readiness waits for the real server. Claude-Session: https://claude.ai/code/session_01Ss9NfEMZS9Qm4FZ2npXDrx Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
… regains two routes (#3336) The identity, Treevite and POST-comments recorders matched each case's route hits by the route's global registration ordinal, so mounting one more Express app before the routes shifted every id and made live Node disagree with every committed fixture. The recorders now discover the mounted apps and resolve a hit by its method and path, so a new app leaves the fixtures untouched; the re-recorded fixtures differ from the old ones only in route identity (every old id was one too high), with the comparators, expected differences, cases and policies byte-identical. The route census regains GET /api/v3/version, which was registered but missing, and names the participation route's middleware as registered; the census check now passes on the source it describes. The legacy census test is updated for the repaired census; two tests that walked all registrations select them explicitly. Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
…running server (#3340) * recordings: cases name their route by method and path, and the census regains two routes The identity, Treevite and POST-comments recorders matched each case's route hits by the route's global registration ordinal, so mounting one more Express app before the routes shifted every id and made live Node disagree with every committed fixture. The recorders now discover the mounted apps and resolve a hit by its method and path, so a new app leaves the fixtures untouched; the re-recorded fixtures differ from the old ones only in route identity (every old id was one too high), with the comparators, expected differences, cases and policies byte-identical. The route census regains GET /api/v3/version, which was registered but missing, and names the participation route's middleware as registered; the census check now passes on the source it describes. The legacy census test is updated for the repaired census; two tests that walked all registrations select them explicitly. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * ci: the committed route census is checked against the source and the running server Nothing compared the committed route census with the routes the server registers, which is how one route went missing from the census and a middleware name drifted without anyone noticing. A new local check, make check-route-census (also called verbatim by a hosted workflow), regenerates the census from source with the unchanged extractor and fails on any difference, naming each changed route and printing the regeneration command before the diff, then starts the characterization app and runs the runtime coverage check against it. The check is strict: any change to the route file requires a regenerated, committed census. Unchanged edge fails both halves; with the repaired census it passes; a route added without a census row, a stale middleware name, a runtime-only route and a route removed but left in the census each fail it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
…3328) Production's server log group already retains 365 days, but the stack still declared one month. The next CDK deploy that touched the group would have cut retention to 30 days and deleted eleven months of request logs. The stack now declares the retention production has. Deploying it changes nothing. Claude-Session: https://claude.ai/code/session_01Ss9NfEMZS9Qm4FZ2npXDrx Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
… the runner (#3329) Two database tests in post_treevite_waves_tests.rs failed intermittently with PostgreSQL timeouts, on hosted runners and on a quiet x86 box: - allocator_lock_and_real_fk_locks_do_not_cycle timed out (57014) in fixture DDL, before either lock probe ran: setup used the serving connection with its 500 ms statement budget. - concurrent_creates_and_owner_budget_are_serialized raced: the second create could reach the owner lock after the first had committed, or time out waiting for it, so the test sometimes observed no serialization at all. Setup and lock probes now use a test-only connection with 30 s / 10 s statement and lock timeouts; connect() and the real writer pool keep the serving settings. The concurrent test holds the owner advisory lock in an observer, sees both creates waiting on it before releasing, and joins both workers before asserting. Test-only; no handler, schema or serving change. Under 8-process load plus 8 CPU workers, 600 pairs: concurrent creates passed 348 before and 600 after. Handler mutations still fail the tests: a real lock cycle or a member-row FOR UPDATE fails the allocator test; removing the owner lock, or taking it after the cap check, fails the concurrent test. Claude-Session: https://claude.ai/code/session_01Ss9NfEMZS9Qm4FZ2npXDrx Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…d the route's own deadline (#3331) next_comment::tests::slower_reads_cannot_occupy_next_statement_slots failed on a quiet x86 box at 75.9 ms against its 75 ms bound, with an HTTP 200 that met the route's own deadline: the test compared end-to-end wall time, including client and scheduling time, against the route deadline. The test now proves the property directly. While every slower pool is held full, it asserts each slower pool's slots are all occupied and its own next-statement pool is untouched, and requires an HTTP 200 (completion inside the route's deadline, no Node fallback) on at least one of up to four loaded requests. Slot counts are derived from the pools instead of being hard-coded. The CPU-probe sampling and the unused libc dev-dependency are removed. Wall time is printed for diagnosis only. Test-only; no route, pool, deadline or serving change. Under 8 CPU workers and 4 concurrent test processes: 200/200 pass; quiet: 10/10. Mutations still fail it: a 100 ms slowdown in read() fails 10/10 quiet and 20/20 loaded at the deadline check; one permit budget shared by every pool fails at the occupancy check. Claude-Session: https://claude.ai/code/session_01Ss9NfEMZS9Qm4FZ2npXDrx Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…instead of sleeping (#3339) The test for the whole-generations cache expected a refresh after a sleep and unwrapped the result, so a slow or timed-out database read (a PostgreSQL statement timeout on the math bundle read, as one hosted run showed) surfaced as an unwrap on None with no explanation. The test now times each read with the clock the cache itself uses and asserts that a cached generation is never served after it was stored plus three seconds and never replaced before, with no sleep; a read that returns nothing is retried only when the pool's own counters show a database failure, and unexpected data fails at once. Lifetimes of 1000, 2800, 3200 and 8000 ms each fail it; a wrong environment key, a dropped generation without a database failure and a cache that never reuses fail too. Test module only. Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
…ch's state (#3332) * polis-api: the startup line names every route it serves and each switch's state Until now the bind-success event said only the address and the math label, so nobody could tell from the logs which routes this process serves or which typed routes are switched on. The event now carries a catalog: every route family with its method and path patterns, and for each switched route the switch's environment variable and whether it reads on or off. Only on/off states are logged, never a value. The catalog is logging-only metadata beside dispatch; dispatch itself is byte-identical. Tests keep the two honest: every catalog entry's method and path must reach its own dispatch family (not the hand-back), every dispatch key must have a catalog entry whose family is its module, Treevite paths are derived from the contract's operation table, and every switch name in the catalog must be one its route module actually reads. Removing an entry, adding a dispatch arm without one, naming a switch nothing reads, or swapping two routes' switches each fails a test. The scanner's limits are written at the top of the test file. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * polis-api: the startup catalog tests check each entry's switch and match family names exactly The switch-to-reader check worked per family, so the two math-membership entries could swap their switches without a failure; and the reader match used substrings, so one family name matched inside another. Each entry is now checked against its own startup state field, names have identifier boundaries, and a regression covers the comments, moderation-comments and participant-comments readers. Test module only; the catalog, the line and dispatch are unchanged. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * polis-api: the startup catalog scanner checks name boundaries on the original source The reader scanner checked identifier boundaries on whitespace-stripped source, so a keyword merged with the name after it and valid reader forms (if state.x, match m::, &mut state.x, return m::) were rejected, a false failure only. The structural lookup keeps the compact form; byte offsets let the exact-name check inspect the original whitespace. Sixteen keyword cases across spaces, tabs, LF and CRLF; the earlier mutations still fail. Test module only. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
…ent route owns its pool (#3335) * polis-api: a test over the real startup wiring proves the next-statement route owns its pool The next-statement isolation tests exercise a pool they build themselves, so nothing failed if production wiring handed that route another route's pool, either where the pools are built or in a handler's choice. Startup construction (config, the completed App and the refresh task) now lives in one function that main calls; its statements moved verbatim and eight startup cases give identical output and exit status before and after. A new test runs that constructor in a child process against the real configuration, checks the next-statement pool is a distinct object from every other configured pool (97 of them) with its exact limits (read-only, two slots, 1 ms acquire, 50 ms statement, 300 s lifetime), and drives the real next-statement and comments dispatch to check which pool each used. Handing the route the common pool, the comments pool or the comments limits, reassigning a pool later, or making a handler read another route's pool each fails it. Handlers are unchanged; the participation path gains no per-request work. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * polis-api: the wiring test derives its pool list from the App, so a new pool must be classified The startup wiring test listed the configured pools by hand, so a pool added later went unchecked until someone added it. One macro now derives the pool references and an exhaustive classification for every App field, with no rest pattern, nested owners included; an unclassified field fails to compile, a pool classified as disabled or as sharing the next-statement pool fails the intended assertion, and the earlier mutations still fail. Helpers compile only under test with database tests; production source is unchanged and eight startup cases give byte-identical output. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
* ci: local make targets for the five hosted suites that had none Delphi Python Tests, the polis-cli format, clippy and test job, the PostgreSQL Delphi writers, the Delphi result reads and the production importer each get a check-* target and a ci/local entry point. Each runs the commands its workflow runs, so a Docker host can run these suites the same way it runs the others. - check-delphi-python-tests runs the workflow's eight jobs in order. It builds the postgres and delphi test images once, runs the six shards from those images, then runs the combine's own tests and the combine. DELPHI_PYTHON_LEG runs one of those jobs. COMPOSE_PROJECT_NAME names the images, the shard projects and the results directory. - check-polis-cli requires Python 3.12 and runs scripts/test-polis-cli.sh. - check-dynamo-writers and check-production-import take the project name and PostgreSQL port their workflows set, refuse a project that is already in use, and run their scripts. - check-delphi-result-reads requires Node 24.22.1 and runs scripts/test-delphi-result-reads.sh --ci. The hosted workflows are unchanged. docs/local-checks.md lists the new targets. On an arm64 Docker host each target passed with the same test counts as its latest hosted run. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ss9NfEMZS9Qm4FZ2npXDrx * ci: tests for the local entry points that fail on their known defects ci/tests/test_local_entry_points.py runs the real ci/local entry points in a throwaway Git checkout with stub docker, cargo, node and Python executables first on PATH and stub suites. No Docker is needed. Against the current scripts, 21 of its checks fail, one for each known defect: - The Delphi target's full run can pass on the previous run's results when those cannot be replaced. - A failed image build, a refused or failed shard rerun, a changed source or rebuilt images still leave the previous run's results to be combined. - A shard leg runs with no images from its run. - Its results directory can be pointed outside .ci-out or through a symlinked .ci-out. - check_project in runtime.sh accepts a project when any of its three Docker queries fails. - The polis-cli target checks python while its launcher runs python3, and it passes on an inherited CARGO_TARGET_DIR, as the result reads target does. - The writers target lets its launcher remove raw containers it did not create, accepts an existing cargo volume, never removes the one it creates, and writes proof logs outside .ci-out. The import target also writes its proof outside .ci-out. The commits that follow fix each one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ss9NfEMZS9Qm4FZ2npXDrx * ci: local project checks fail closed, and the CLI target checks python3 check_project in ci/local/runtime.sh tested only the output of its three Docker queries. When a query failed with no output, the project counted as free. This came in with the earlier change that added the local entry points (PR #3324). Each query's exit status is now checked, and a failed query refuses the project with a message, as an occupied one already did. check_python now takes the interpreter to check (default python, as before). The polis-cli target checks python3, which its launcher runs, and unsets an inherited CARGO_TARGET_DIR so the crates build where the hosted job builds them. Two helpers for the entry points that follow: - check_absent refuses containers or volumes, by exact name, that a launcher creates with plain docker run, and fails closed the same way. - check_out_dir creates .ci-out/<name> and refuses a name or symlink that would put results outside .ci-out. The ProjectCheckTests and PolisCliTests cases in ci/tests/test_local_entry_points.py now pass. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ss9NfEMZS9Qm4FZ2npXDrx * ci: Delphi target results are bound to their run and never read stale The Delphi Python tests target recorded each shard's exit status in a file. Its full run called the shard step where the shell ignores errors, and then trusted those files. When an earlier run's files could not be replaced, the target combined the earlier run's results and passed, whatever the current shards did. A failed image build, a refused or failed shard rerun, a changed source or rebuilt images also left the previous run's results to be combined. Each job now runs in its own process with strict error handling, and the results are bound to the run by receipts in .ci-out/<project>: - The images job is the start of a run. It first removes every receipt, then writes images.ok after the build succeeds. images.ok holds a new run id, the source identity (HEAD, uncommitted changes, untracked files) and the two image ids. - A shard job first removes its own receipt, before the project check. It then runs only against this run's images and an unchanged source, and writes shard-I.ok only after it exits 0. A shard can therefore be retried within a run. - The combine accepts only six shard receipts of this run, source and images. - The full run takes the shard results from the shard processes' own exit statuses, runs every shard, and combines only when all six exited 0. - Receipts are written atomically, and any failure to remove or write one fails the job. - The results directory is always .ci-out/<project>. The DELPHI_PYTHON_OUT override is refused. The DelphiPythonTests cases in ci/tests/test_local_entry_points.py now pass. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ss9NfEMZS9Qm4FZ2npXDrx * ci: writers, result reads and import targets own what they remove and write The Delphi writers launcher creates its proof, dynamo and build containers and a cargo registry volume with plain docker run. When it exits it removes the two containers by name, whoever created them, and it never removes the volume. Its entry point checked only the Compose project, which those resources are not part of. - The writers target now refuses to start while any of those containers or the volume already exists. Everything its launcher removes is then its own, and the target removes the cargo volume the run created. - The result reads target refuses the same way for the two containers its launcher removes. - These checks fail closed when Docker cannot list containers or volumes. - The writers and import proof logs now go to .ci-out/<project>, through a directory check that refuses a symlink out of .ci-out. - The writers and result reads targets unset an inherited CARGO_TARGET_DIR. Their launchers run queue-rs/target/debug/polis-migrate, so a shared target directory would run an old binary or none. The launcher scripts are unchanged. All 30 cases in ci/tests/test_local_entry_points.py now pass. docs/local-checks.md describes the run receipts, the results directory and the test command. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ss9NfEMZS9Qm4FZ2npXDrx * ci: tests for the remaining identity, run and results-directory defects Ten new cases in ci/tests/test_local_entry_points.py fail against the current entry points: - Identity: - The Delphi source identity hashes git's partial output when git cannot read a changed tracked file, so it matches the clean source and the combine passes. - Image inspection that prints plausible ids and then fails is accepted by both the images job and a shard. - Runs: when a new run cannot remove the previous run's receipts, a combine run on its own still accepts them, because nothing names the run the caller expects. A job run on its own may also omit the run. - Results directories: - A combined directory replaced by a symlink, or a file link inside it, makes the combine write outside .ci-out. - A link planted in the writers results directory makes the launcher overwrite a file outside it. - The import target accepts a results directory that already exists. - Cleanup: when Docker cannot answer during cleanup, the writers target reports success while its cargo volume may remain. The 30 earlier cases still pass. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ss9NfEMZS9Qm4FZ2npXDrx * ci: the Delphi target's source and image identities fail closed The source identity hashed the combined output of several git commands. When one of them failed (git diff cannot hash a changed tracked file it cannot read), the hash covered the partial output and could equal the clean source's. The comparisons ran inside command substitutions that ignore errors. Image inspection kept only its formatted output, so a docker call that printed two ids and then failed was accepted. - source_id hashes HEAD, the diff against HEAD and the untracked files' names and contents separately. Each git command's exit status is checked, and nothing is printed unless all of them succeeded. The diff is taken with no external diff, textconv or colour. - image_ids requires docker to succeed and to print exactly two sha256 ids. - Every use reads these values through helpers that fail the job when they cannot be computed, and only then compares them. Three of the new cases in ci/tests/test_local_entry_points.py now pass: an unreadable changed source, and image inspection failing after printing ids, in the images job and in a shard. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ss9NfEMZS9Qm4FZ2npXDrx * ci: Delphi jobs run on their own name the run they belong to The run id the images job made up proved only that the receipts agreed with one another. When a new run could not remove the previous run's receipts (for example, a read-only results directory), its images job failed. A combine run on its own then still accepted the previous run, because it had no way to know which run the caller meant. DELPHI_PYTHON_RUN now names the run: a value the caller chooses, new for every run. - Each job run on its own (DELPHI_PYTHON_LEG set) requires it. - The images job records it in images.ok, and shards record it in their receipts. - Shards and the combine refuse an images.ok of any other run, and the combine refuses shard receipts of any other run. - The full run uses the given value or makes a new one, prints it, and passes it to its jobs. - Receipts the new run could not remove still name the earlier run, so they are refused. Two more of the new cases in ci/tests/test_local_entry_points.py now pass: a combine asked for a new run refuses the previous run's surviving receipts, and a job on its own without a run is refused. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ss9NfEMZS9Qm4FZ2npXDrx * ci: no write under a local results directory can follow a planted link The entry points checked only .ci-out and the project directory under it. A link already waiting further down was followed: a rust.log link in the writers results directory made the launcher's tee overwrite a file outside it, and a combined directory or file link in the Delphi results directory made the combine write outside .ci-out. - check_out_dir takes "fresh": the directory must not exist yet and is created empty, so nothing can be waiting in it. The writers and import targets use it for their proof logs. Their launchers already refuse to reuse a project's proof, and a rerun now needs a new project or the old directory removed. Every step in check_out_dir is checked explicitly, since it runs inside a command substitution. - The Delphi combine removes its combined directory before each combine, and fails if it cannot. - Receipts are written to a file mktemp creates, then renamed into place. A link at a receipt's name is replaced, not followed. - Shard directories were already replaced by the shard script. Four more of the new cases in ci/tests/test_local_entry_points.py now pass: a planted combined directory, a planted file link inside it, and existing writers and import results directories. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ss9NfEMZS9Qm4FZ2npXDrx * ci: the writers target fails when it cannot confirm its cargo volume is gone The writers entry point removes the cargo registry volume its run created. It treated any failed docker volume inspect as "no such volume". So when Docker could not answer during cleanup, the target passed and the volume could remain unreported. The cleanup now lists volumes and checks that Docker answered. It removes the volume when the listing shows it, and fails the run when the listing or the removal fails, saying the volume may remain. The last new case in ci/tests/test_local_entry_points.py now passes: all 40 pass. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ss9NfEMZS9Qm4FZ2npXDrx * docs: local checks name the run, the fresh results and what stays outside docs/local-checks.md now describes: - DELPHI_PYTHON_RUN, which a Delphi job run on its own requires, and how the combine refuses receipts of any other run. - The new, empty results directories the writers and import targets require. - The cleanup check on the writers target's cargo volume. - That these ownership checks belong to the make targets, not to the launcher scripts called directly. - What the suites behind these targets still write outside .ci-out: crate targets, server/node_modules, the importer's launcher-mount test resources and host caches. - That no hosted job runs the entry-point tests yet. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ss9NfEMZS9Qm4FZ2npXDrx --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
… and refuses ephemeral ports (#3338) On a macOS work box the ephemeral port counter (49152-65535) is shared by the whole machine and IPv6 sockets are given ports already held on IPv4. The compose PostgreSQL port is published on IPv4 only, and a localhost address tries [::1] first, so a test suite's own listener could take the PostgreSQL port mid-run ("Connection terminated unexpectedly") or a pg client could connect to itself and wait forever. Three intermittent integration failures matched the counter passing the port to the second. The harness now uses 127.0.0.1 in both default database URLs, and refuses before any container starts when the PostgreSQL port or any published compose port is 49152 or above, dynamic, or crosses that range, saying why. The three clients in the Treevite accept-lock test get a five-second connect timeout so a wrong connection reports instead of hanging. Test logic, deadlines and lock budgets are unchanged; hosted CI, on Linux with port 5432, is unaffected. With a competing dual-stack listener held for the whole run, unchanged edge fails at migration and the fixed harness passes (2429 tests). Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
…oute (#3337) * recordings: cases name their route by method and path, and the census regains two routes The identity, Treevite and POST-comments recorders matched each case's route hits by the route's global registration ordinal, so mounting one more Express app before the routes shifted every id and made live Node disagree with every committed fixture. The recorders now discover the mounted apps and resolve a hit by its method and path, so a new app leaves the fixtures untouched; the re-recorded fixtures differ from the old ones only in route identity (every old id was one too high), with the comparators, expected differences, cases and policies byte-identical. The route census regains GET /api/v3/version, which was registered but missing, and names the participation route's middleware as registered; the census check now passes on the source it describes. The legacy census test is updated for the repaired census; two tests that walked all registrations select them explicitly. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * clients report what paints red in the browser console to one server route Until now no Polis client reported errors anywhere and no server route accepted such reports, so a broken response or a broken page was visible only in a participant's browser console. Every page of the admin console, the report, the legacy participation client (including vis2 and the embed) and the alpha participation client now carries a dependency-free reporter that catches uncaught errors and unhandled rejections, script, style, image and font load failures, console.error and console.warn, failed fetch and XMLHttpRequest calls (status 0 or 400 and above; 304 is success) and CSP violations. Each distinct position is its own report, carrying a stable signature (client, kind, status or message category, and the location with the bundle's content hash removed) and a count; repeats batch after one second or on page hide; a page sends at most 50 signatures, 20 sends a minute and 200 per tab session, by sendBeacon or a credential-less keepalive fetch to the client's own origin only. The server gains POST /api/v3/clientErrors, handled before the body parser and authentication. It reads no cookie, identity or database, touches no pool or lock, caps the body, admits a report only when both a per-address limit (burst 10, then 1 a minute; IPv4 address or IPv6 /64, taken from the X-Forwarded-For entry before a recognized Cloudflare edge) and a per-process limit (burst 100, then 1 a second) admit, always answers 204, and writes one structured warn line, client_error, with no address in it. The participation path shares nothing with it. Triage is deliberate: a Rust binary, render-client-errors, turns an exported Logs Insights result into a table of signatures with their message, count and pages; precise rows by default, an opt-in cross-build grouping. Nothing feeds an automated fixer. The route census gains the new route; the recorders already name routes by method and path, so no fixture changes. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
…runs (#3333) * ci: every nginx upstream must be a container the production web role runs The 2.43.0 deploy answered 502 for every static asset at the origin because nginx sent those paths to a file-server container that production web boxes never start; the hotfix sent them back through Node. Nothing would have caught the same mistake again. A new check, `make check-nginx-upstreams` (also called verbatim by a hosted workflow), parses every upstream host from the nginx templates and the rendered Rust route locations, reads the services the web role starts from the deploy hook and the Compose file, and fails naming the upstream, the config file and the hook line when an upstream names a service the web role does not run. An empty, commented exception list is the only way around it. The 2.43.0 shape fails it; edge passes. The hook reader joins continued lines, tokenizes only the container-start lines, finishes one-line helpers, and names a line in every error. The README states what it does not read: includes of files that are not .conf or .template, and shell constructs it cannot follow. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * ci: the nginx upstream check's hook reader follows compose commands, continued lines and one-line helpers The reader matched compose only as a bare docker-compose or docker compose followed by config, so the hook's own /usr/local/bin/docker-compose start was neither refused nor counted, while plugin-style forms (a COMPOSE array, a hook moved to docker compose, command -v) were refused. It now matches the executable's basename, skips the global options before the subcommand, and accepts config or no subcommand. It also stops a comment's trailing backslash from swallowing the next line, closes one-line helper functions (including those ending in "& }"), refuses direct docker compose starts, reviews docker by absolute path, parses spaced and function-keyword declarations, and names the line in every error, including the ROLE_IMAGES one. Six fixture groups cover these; the 2.43.0 shape still fails naming file-server and its template. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Contributor
Author
Delphi Coverage Report
|
colinmegill
marked this pull request as ready for review
November 5, 2026 00:33
This branch was successfully deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Release plan
Version: 2.43.1
Reason: no new selected migrations
Previous stable:
f46391b539997689e01d1c0c40f5dcd38d494dcbCandidate:
c3dadc86fe467bd8a70e1eac61cf2017789a4381release.txt additions:
Merge the reviewed version preparation into edge, then refresh this plan for the resulting edge SHA before merging the promotion with a merge commit.