diff --git a/ARCHITECTURE.toml b/ARCHITECTURE.toml index 42f64d0b04..da3bd7064e 100644 --- a/ARCHITECTURE.toml +++ b/ARCHITECTURE.toml @@ -181,6 +181,87 @@ mode = "required" [rules.pattern_ratchet.substrate_literals.stale_entries] # entries with fewer matches, none, or gone mode = "required" +# Writes of the control-plane Postgres `public.sandboxes` row move into its +# store, `pplx-space-sandbox-store`; this ratchet only shrinks. It counts each +# production line that opens a statement writing the row (`UPDATE`, +# `INSERT INTO`, `DELETE FROM`, `MERGE INTO` or `TRUNCATE` naming `sandboxes`, +# in any case, schema-qualified or quoted, before any alias, including right +# after a `\n`, `\r` or `\t` escape in a one-line string), and each call to +# the sandbox-portability renderers that compose part of its SET list +# (`sandbox_assignments()`, as a method or a path call, and the +# `*_current_local_artifact_marker_assignments` functions, after punctuation, +# a line start or `return`, `match`, `if`, `in`, `else` or `while`), so a file +# with a composed `UPDATE sandboxes s SET {...}` counts the head and each +# renderer call. +# Not caught: a statement head split across lines (`UPDATE` and `sandboxes` on +# different lines) or built at run time (`format!("UPDATE {table} ...")`); a +# `COPY sandboxes FROM ...`; a statement in a non-`.rs` file, such as the +# placement state machine's `include_str!` claim statements under +# `postgres/sql/`; a head right after an escape other than `\n`, `\r` or +# `\t` (`\0`, `\x0a`); a renderer called through an alias or a `use ... as` +# rename, passed as a function value +# (`.map(SuspendedK8sFinalizationSql::sandbox_assignments)`), or called +# right after `break` or `yield`; database triggers; scripts and smoke +# harnesses outside workspace members' `src/`. +# False positives: the same text in a trailing `//` or `/* */` comment, in +# `#[doc = "..."]`, in a SQL `--` comment or in an ordinary string such as an +# error message ("failed to update sandboxes"); a statement on another +# database's table of the same name. +# sbx-mgr's node-local SQLite database also has a `sandboxes` table, a +# different database on a different machine with its own store and fences +# (`sbxmgr_sqlite` and `dependency_fence.sbxmgr_*` below), so its crates are +# allowed whole: those rules guard `crates/libs/sbx/mgr/**` and +# `crates/apps/sbx/**`, which today are all of `crates/libs/sbx` and +# `crates/apps/sbx`, and a narrower glob would make this required rule fire +# on moves within sbx-mgr. Test files are +# filename-based, so `allow` adds the `#[cfg(test)]` support modules and +# fixtures, which write rows to set up their cases; `#[cfg(test)]` code inline +# in a production file counts, so test-only writes go in a `*_tests` file. +# Exception paths, each a reviewed baseline edit: a new writer outside the +# store is a new entry (`no_new_entries`); another write in a listed file is a +# raised cap (`no_raised_caps`); splitting a listed file into modules needs +# entries for the new files and a lower cap on the old one, since `--renames` +# pairs only whole-file renames. Portability calls its private +# `sandbox_assignments` helper bare; a path-qualified call to it there +# (`Self::...`, `super::...`) matches and needs a reviewed entry. +[rules.pattern_ratchet.sandbox_writes] +patterns = [ + # A statement head on the row's table + '''(?i)(?:\\[nrt]|\b)(?:UPDATE|DELETE\s+FROM|INSERT\s+INTO|MERGE\s+INTO|TRUNCATE(?:\s+TABLE)?)\s+(?:ONLY\s+)?(?:\\?"?public\\?"?\s*\.\s*)?\\?"?sandboxes\b''', + # Calls to the portability renderers of the row's assignments + '''(?:\.|::)\s*sandbox_assignments\s*\(''', + '''(?:^|[^\w\s]|\b(?:return|match|if|in|else|while)\s)\s*(?:\w+\s*::\s*)*(?:clear|write|publish_clear_or_preserve)_current_local_artifact_marker_assignments\s*\(''', +] +allow = [ + "crates/libs/sandbox/store/**", + # node-local sbx-mgr SQLite `sandboxes`: a different database + "crates/libs/sbx/**", + "crates/apps/sbx/**", + "**/*_tests/**", + "**/test_support.rs", + "**/test_support/**", + "**/*_test_support.rs", + "**/test_fixtures.rs", + "**/*_test_fixture.rs", + "**/scripted_test_doubles.rs", + "crates/libs/db-test-fixtures/**", +] + +[rules.pattern_ratchet.sandbox_writes.no_crossing] # only allowed and listed files may match +mode = "required" + +[rules.pattern_ratchet.sandbox_writes.no_new_entries] # a pull request may not add entries to the baseline +mode = "advisory" + +[rules.pattern_ratchet.sandbox_writes.no_raised_caps] # a pull request may not raise a cap in the baseline +mode = "advisory" + +[rules.pattern_ratchet.sandbox_writes.listed_no_growth] # listed files may not gain matching lines +mode = "required" + +[rules.pattern_ratchet.sandbox_writes.stale_entries] # entries with fewer matches, none, or gone +mode = "required" + # Rootfs copies must preserve NoCoW and sparse extents through common-io's # reflink helper. A line matcher cannot infer a path variable's contents, so # conservatively fence all new plain filesystem copies and cp launches. diff --git a/architecture-baseline.toml b/architecture-baseline.toml index 66566b0ae5..833e8a38d7 100644 --- a/architecture-baseline.toml +++ b/architecture-baseline.toml @@ -144,6 +144,39 @@ "crates/libs/node/mgr/server/src/service/artifact_fs/receive.rs" = 4 "crates/libs/node/mgr/server/src/net/fallback.rs" = 1 +# Writers of the control-plane Postgres `sandboxes` row outside sandbox-store, +# and their calls to sandbox-portability's assignment renderers. They move into +# the store; lower each cap as they go. +[pattern_ratchet.sandbox_writes] +"crates/libs/api/src/controllers/sandbox/lifecycle.rs" = 4 +"crates/libs/api/src/controllers/sandbox/queries.rs" = 4 +"crates/libs/api/src/controllers/sandbox/responses.rs" = 3 +"crates/libs/api/src/controllers/sandbox/updates.rs" = 4 +"crates/libs/api/src/controllers/sandbox/wake_completion.rs" = 1 +"crates/libs/api/src/controllers/substrate/direct.rs" = 5 +"crates/libs/api/src/controllers/substrate/kubernetes/store/activation_fence.rs" = 1 +"crates/libs/api/src/controllers/substrate/kubernetes/store/execution_rows.rs" = 1 +"crates/libs/api/src/controllers/substrate/kubernetes/store/pod_placement.rs" = 1 +"crates/libs/api/src/controllers/substrate/kubernetes/store/pod_tracking.rs" = 5 +"crates/libs/api/src/controllers/substrate/kubernetes/store/restore_lease.rs" = 5 +"crates/libs/api/src/controllers/substrate/kubernetes/store/suspend.rs" = 8 +"crates/libs/api/src/controllers/substrate/mod.rs" = 1 +"crates/libs/cluster/controller/server/src/drained_node_terminator/store.rs" = 2 +"crates/libs/cluster/controller/server/src/failed_restorable_repair/store.rs" = 3 +"crates/libs/cluster/controller/server/src/node_drain/store.rs" = 15 +"crates/libs/cluster/controller/server/src/orphaned_sandboxes.rs" = 5 +"crates/libs/cluster/controller/server/src/orphaned_sandboxes/quarantine_recovery/store.rs" = 1 +"crates/libs/cluster/controller/server/src/started_sandbox_repair/store.rs" = 3 +"crates/libs/cluster/controller/server/src/suspended_execution_reaper/store.rs" = 1 +"crates/libs/cluster/controller/server/src/tombstone_retention.rs" = 1 +"crates/libs/cluster/controller/server/src/transient_lifecycle_sweeper/store.rs" = 10 +"crates/libs/internal/api/server/src/data_plane_sessions/service_route.rs" = 1 +"crates/libs/internal/api/server/src/published_state.rs" = 9 +"crates/libs/internal/api/server/src/sandbox_quarantine_store.rs" = 3 +"crates/libs/internal/api/server/src/status.rs" = 1 +"crates/libs/placement/machine/src/postgres.rs" = 1 +"crates/libs/sandbox/teardown/src/lib.rs" = 1 + # sbx-mgr code outside its SQLite store uses only the store's own handle, # transaction and error types, so no file is listed. [pattern_ratchet.sbxmgr_sqlite]