Repository navigation
Settings without a consumer and consumers without settings #1403
Description
Many features need two things in a deployment before they do anything. One is a settings section. The other is the consumer that acts on it, being a middleware, a module, a request filter, a page request preparer or a part of the adapter the deployment runs on. When one is present without the other, Trusted Server refuses to start, logs a warning, or runs as though nothing were wrong, and which of the three happens depends on the feature. Each feature that checks at all has written its own check, the checks run at different times and say different things, and several features have no check.
Code references are at 3aaf720c0, the head of the draft stack, which is the cookies by path change (#1400). Startup means an adapter loading the settings and building its state. Push means ts config validate, ts config diff and ts config push, which run the same checks. In the Code column below, core/ stands for crates/trusted-server-core/src/.
One case, the 51Degrees client hints
The client hints change (#1386, row 44 of the stack) adds the middleware 51degrees.client_hints, which writes the Delegate-CH tag and the client hint headers on the pages a [[serve]] entry names it for. The middleware needs [shared.51degrees], which holds the cloud endpoint and the license key. Whenever that table is present, the crate registers two things, a request filter that asks the cloud service which hints the browser should send, and the middleware that writes the answer into the page (lib.rs lines 110 to 132).
The settings without the consumer. With [shared.51degrees] and no [[serve]] entry naming the middleware, push accepts the document and startup logs one warning, that the middleware "changes no page" (registry.rs lines 1724 to 1731). The request filter is not tied to the entry, and core runs every registered filter (registry.rs lines 2640 to 2674), so on Fastly the filter still asks the service on every page request (lib.rs lines 352 to 381). A deployment that has not placed the middleware therefore asks the service, through its cache, about every page request, and no page gets the tag.
The consumer without the settings. With the [[serve]] entry and no [shared.51degrees], the crate does not run, so nothing supplies the middleware. Startup refuses the entry as naming "51degrees.client_hints, which no module that runs supplies" (registry.rs lines 1669 to 1685). The message never says that [shared.51degrees] is missing, because the note naming absent shared tables (lines 1162 to 1178) is added to a refused section selection and not to a refused entry. Push accepts the same document, because push checks only the shape of each entry (settings.rs lines 4776 to 4791) and the crate's own validation stops early when the table is absent (lib.rs lines 142 to 150).
Both present, on an adapter without request filters. Only the Fastly adapter runs request filters (Fastly app.rs lines 836 to 867). The Axum, Cloudflare and Spin adapters call no request filter, so there the middleware runs on every page an entry names, finds no hints left for it (lib.rs lines 264 to 268 and 286 to 298) and writes nothing, with no log line.
The cases
| Feature | Settings section | Consumer | Today with the settings missing | Today with the consumer missing | Code |
|---|---|---|---|---|---|
| 51Degrees client hints (#1386) | [shared.51degrees] |
51degrees.client_hints in a [[serve]] entry, and its request filter |
Refused at startup, with no word of the missing table. Accepted at push. | One warning at startup. On Fastly every page request still asks the service. | middleware/51degrees lib.rs 110-132, 352-381, core/integrations/registry.rs 1669-1685, 1724-1731 |
| Request filters (DataDome protection, 51Degrees client hints) | enable_protection = true in [bot-protection.datadome], or [shared.51degrees] |
The adapter's request filter step, which only the Fastly adapter has | No filter is registered, as intended | On Axum, Cloudflare and Spin, accepted with no log. DataDome checks no request, and the client hints middleware writes nothing. | bot-protection/datadome lib.rs 1148-1150, Fastly app.rs 836-867 |
| DataDome page tag (#1351) | client_side_key in [bot-protection.datadome] |
bot-protection.datadome.tag in a [[serve]] entry |
Accepted at push and at startup with no log, and the tag writes nothing. Startup checks only the tag's URL. | One warning at startup. Accepted at push. | bot-protection/datadome lib.rs 509-511, 1016-1026, core/integrations/registry.rs 1724-1731 |
| InMobi consent prompt (#1359) | [cmp] module = "inmobi" with [cmp.inmobi] |
cmp.inmobi in an entry for text/html |
Refused at startup, with a note that no section selects cmp.inmobi. Accepted at push. |
Refused at push and at startup by the module's own check, which counts any text/html entry whatever its path. |
cmp/inmobi lib.rs 141-158, core/integrations/registry.rs 1118-1134 |
| ads.txt sellers (#1361) | [ads-txt] module = "iabtl" with [ads-txt.iabtl] |
ads-txt.iabtl in a [[fetch]] entry for text/plain |
Refused at startup. Accepted at push. | Refused at push and at startup by the module's own check, which counts any text/plain entry whatever its path. |
ads-txt/iabtl lib.rs 152-167 |
| robots.txt (#1366) | [robots-txt] |
robots_txt in a [[fetch]] entry for text/plain |
Refused at startup. Accepted at push. | Refused at push and at startup by core's own check, which requires path = "/robots.txt" exactly. |
core/settings.rs 4414-4462, core/integrations/registry.rs 2375-2393 |
| Middleware settings (#1379) | [middleware.<name>] |
A running module that supplies <name> |
The middleware runs on its defaults, as intended | Refused at startup. Accepted at push. | core/integrations/registry.rs 1702-1723 |
| Shared vendor tables (#1367) | [shared.<vendor>] |
A linked builder that reads the table | The builder does not run, and a selection naming its module is refused with a note naming the table | Refused at startup. Accepted at push. | core/integrations/registry.rs 2044-2069, 2138-2150 |
| Section selections (#1348) | A section's module, such as [cmp] module |
A linked module of that name | The module is not built, as intended | Refused at startup. Not checked at push, deliberately, because the tool may not link the module. | core/integrations/registry.rs 1774-1825, 2163-2168 |
| Loading cache tables (#1373) | [cache.<name>] |
A linked module that declares the cache | On a stock build, the one loading cache module the build links, as intended. A build that links none, or several, loads through no cache, with no log | Refused at push and at startup | core/integrations/mod.rs 1008-1025, 1083-1104, core/platform/loading_cache.rs 240-242, 269-272 |
| Auction analytics (#1357, #1389) | [analytics] module = "tinybird" |
The adapter's telemetry sink, which only the Fastly adapter reads | No events are sent, as intended | On Axum, Cloudflare and Spin, resolved and checked at startup, and nothing is sent, with no log | core/integrations/registry.rs 1505-1540, Fastly app.rs 389-392 |
| Asset routes (#1125) | [[proxy.asset_routes]] |
The adapter's asset route dispatch, which only the Fastly adapter has | No asset routes, as intended | On Axum, Cloudflare and Spin, checked at push and at startup and never dispatched, with no log | Fastly app.rs 1191-1194, 1310-1313 |
| Origin certificate check (#1132) | [proxy] certificate_check = false |
The adapter's origin client, which only the Fastly adapter configures from it | Certificates are checked, as intended | On Axum the client ignores it, and on Cloudflare and Spin it changes only a backend's name, with no log. Fastly warns. | Fastly backend.rs 492-496, Axum platform.rs 321-333, Cloudflare platform.rs 70-78, Spin platform.rs 92-100 |
| Host geo lookup | [geo] module = "platform" |
The adapter's own geo lookup | No geo lookup, as intended | On Axum and Spin, accepted with no log, and every request gets no location | core/integrations/registry.rs 1480-1482, Axum platform.rs 210-218, Spin platform.rs 809-817 |
| Host device module | [device] module = "platform" |
The adapter's own device module, which only the Fastly adapter builds | No host device module, as intended | On Axum, Cloudflare and Spin, accepted with no log, and no host device module runs | core/integrations/registry.rs 1323, Fastly lib.rs 1256-1263, Fastly Cargo.toml 65, device/fastly lib.rs 69 |
| Edge Cookie cluster settings (#1097) | cluster_trust_threshold and cluster_recheck_secs in [ec] |
None in any crate | Defaults apply, and nothing reads them | Accepted with no log | core/settings.rs 783-795 |
Three more things stand out across the table.
- The first check that fails stops the start, and most checks stop at the first gap they find, so a document with two gaps of different kinds is fixed one failed start at a time.
- Push builds no registry, so every check that lives in the registry runs at startup only. A document that push accepts can still take a service down when it next starts.
- Each placement check was written for one feature. InMobi and the ads.txt sellers count any entry for their media type, whereas robots.txt requires an exact path.
The common startup model
One model in core, in four points.
- Every consumer declares the settings it needs. A builder already declares the shared table it reads, its secret settings, the caches it loads through and the middleware it supplies with its phase and media type, as the 51Degrees builder does (lib.rs lines 94 to 100). The builder would also declare, for each middleware, module, request filter and page request preparer it supplies, the settings that consumer needs and what places it, being a section selecting it or an entry naming it for a media type and path. A request filter or preparer that only feeds one middleware is declared as part of that middleware, so it runs only where an entry names the middleware. A consumer that runs on defaults declares its settings optional, so leaving them out is not a gap.
- Every settings section has a consumer. Core's own sections declare their consumers in the same way, for example
[robots-txt]and its middleware, so a section with no consumer that is linked, selected and placed is a gap. - Each adapter declares the parts it has, such as running request filters, dispatching asset routes, reading the telemetry sink, honoring
certificate_check, a host geo lookup and a host device module. A section or a consumer that needs a part the adapter lacks is then a gap on that adapter, reported by name, rather than accepted and ignored. - Startup,
ts config validateandts config pushgive one report listing every gap. Before any builder builds, core goes through the declarations once and collects every gap into one report, in which each line names the section, the consumer, the missing half and what to add. Each line refuses or warns, as the design document sets out, and startup refuses when any line refuses. Push runs the same check against the builders the tool links and the parts of the adapter the document is pushed for, so a document with a gap that refuses is stopped before it is written to the config store.
The declaration types and the check live in core and name no vendor, standard or platform. Each vendor crate declares its own consumers in its own folder, and each adapter declares its own parts.
What it would replace
- Core's refusals of an entry and of a
[middleware.<name>]table naming a middleware nothing supplies, and the warning for a middleware no entry names (registry.rs lines 1669 to 1731). - The refusal of a shared table no builder reads and the note naming absent tables (registry.rs lines 2044 to 2069 and 1162 to 1178).
- The section selection check and its notes on modules no section selects (registry.rs lines 1774 to 1825 and 1118 to 1157).
- Core's robots.txt placement check (settings.rs lines 4414 to 4462) and the placement checks the InMobi prompt and the ads.txt sellers each wrote (inmobi lib.rs lines 141 to 158, iabtl lib.rs lines 152 to 167).
- The cache table check (mod.rs lines 1008 to 1025), which already runs at push and becomes one kind of line in the report.
- The one-off notice for a setting nothing acts on, being the DataDome warning for its reserved GraphQL flag (datadome lib.rs lines 513 to 515).
- The fixes the related issues below each ask for one setting at a time, such as a startup warning for asset routes (proxy.asset_routes is parsed and validated on every adapter but dispatched only on Fastly #1125) or for
certificate_check(proxy.certificate_check is accepted and ignored by the Axum adapter #1132).
Related issues
Of the same shape:
proxy.asset_routesis checked on every adapter and dispatched only on Fastly (proxy.asset_routes is parsed and validated on every adapter but dispatched only on Fastly #1125)proxy.certificate_checkis accepted and ignored by the Axum adapter (proxy.certificate_check is accepted and ignored by the Axum adapter #1132)- Unknown integration IDs and fields pass validation and are ignored (Unknown integration IDs and fields in [integrations.*] pass validation and are ignored #1201)
- Two Edge Cookie cluster settings are documented as controls and nothing reads them (Two Edge Cookie cluster settings are parsed and documented as controls but nothing reads them #1097)
- One health and startup failure contract across the adapters (Define a uniform cross-adapter health and startup-failure contract #1142)
- Tinybird telemetry settings are accepted on adapters that send nothing (Implement or reject Tinybird telemetry configuration coherently #1148)
- An adapter with no geo lookup fails closed on Edge Cookie creation and the auction (An adapter with no geo module fails closed on Edge Cookie creation and on the auction #1134)
- On Cloudflare no region is resolved, so every US privacy signal fails open (On Cloudflare no region is resolved, so every US privacy signal fails open #1102)
- Cloudflare startup does not read the store
ts config pushwrites (Connect Cloudflare config store to ts config push output #1140) - Axum does not read the local store
ts config pushwrites (Make Axum consume the local config store written by ts config push #1141) - Adapter stores that are declared and never built (Implement adapter stores or retire dead store manifests #1139)
- The test-only list of integration IDs that deploy validation checks against (Remove or harden the test-only deploy ID inventory #1146)
- The Spin adapter cannot build its state and answers every request but the health probe with 503 (The Spin adapter cannot build application state, so it answers every request except the health probe with 503 #1101)
- The plan to refuse unknown integration IDs in a new configuration layout (Define integrations crate split and ordered configuration #1194)
RUST_LOGhad no effect on the Axum adapter (RUST_LOG has no effect on the Axum adapter, which always logs at trace #1129, closed)- Configuration errors silently disabled integrations (Configuration errors silently disable integrations #408, closed)
The issues whose changes added checks of this shape, each written for its own feature, are a vendor's shared table (#1284), a module's own loading cache (#1320), a middleware's own settings table (#1331) and the 51Degrees client hints (#1333). The stack as a whole is listed in #777.
A design document for this model follows in the pull request that holds every design document of the stack (#1342).
Activity
Add the settings and consumers design