The disclosure lab host (sprint 4.55, sprint 3.8 E1): the first-party server and probe page that measure what our own browsers and drivers give away, starting with the input battery. Composes forge modules only; settings are off and loopback by construction.
| Tier | platform |
| Role | infrastructure |
| Path | crates/platform/disclosure-lab |
| Edition | 2021 |
| Targets | disclosure-lab, platform_disclosure_lab |
| Public items | 78 across 9 modules |
| Tests | 19 |
What it is for
The disclosure lab host (sprint 4.55; sprint 3.8 E1, ruled 2026-09-05).
A platform host is composition: it binds forge modules to a port and a database and holds no capability of its own. This crate's own logic is the settings shape, the single-use page tokens, and the page. Grading lives in observability-analytics::disclosure::probe; the schema and the beacon route live in application-analytics.
Sprint 3.8's decisions this host inherits: D5, aggressive probes are off by construction (#serde(default), every default false); D9, quiet by default; D2 widened to an opt-in, the bind is loopback unless settings say otherwise; D7, the lab's own database, never the browsing-history corpus.
Capabilities
battery (other)
The battery: one script, six drivers, the page opened by the battery and
| Item |
|---|
fn fnv64(bytes : & u8) -> String |
fn query_encode(s : & str) -> String |
fn parse_driver(s : & str) -> Option <Driver> |
fn screen_point(rect : DomRect, win : WindowMetrics) ->(i64, i64) |
fn typing_delays(h : & mut Humanizer, text : & str) -> Vec <(char, Duration)> |
fn humanizer(seed : u64) -> Humanizer |
DomRect
The battery: one script, six drivers, the page opened by the battery and
| Item |
|---|
pub struct DomRect |
DriveError
The battery: one script, six drivers, the page opened by the battery and
| Item |
|---|
pub enum DriveError |
DriveReport
The battery: one script, six drivers, the page opened by the battery and
| Item |
|---|
pub struct DriveReport |
MoveStep
The battery: one script, six drivers, the page opened by the battery and
| Item |
|---|
pub struct MoveStep |
fn xdo_moves(h : & mut Humanizer, from :(i64, i64), to :(i64, i64)) -> Vec <MoveStep> |
Script
The battery: one script, six drivers, the page opened by the battery and
| Item |
|---|
pub const DEFAULT_SCRIPT: & str |
pub struct Script |
Script :: fn new(text : & str) -> Self |
fn page_url(base : & str, driver : Driver, driver_version : & str, script : & Script) -> String |
WindowMetrics
The battery: one script, six drivers, the page opened by the battery and
| Item |
|---|
pub struct WindowMetrics |
BrowserTestCli
The bridge CLI as an argument-vector subprocess (Gate 1.5.6): navigate,
| Item |
|---|
pub struct BrowserTestCli |
BrowserTestCli :: fn new(bin : & str, timeout : Duration) -> Self |
BrowserTestCli :: async fn navigate(& self, url_match : & str, url : & str) -> Result <(), DriveError> |
BrowserTestCli :: async fn click(& self, url_match : & str, selector : & str) -> Result <(), DriveError> |
BrowserTestCli :: async fn type_text(& self, url_match : & str, selector : & str, text : & str,) -> Result <(), DriveError> |
BrowserTestCli :: async fn element_rect(& self, url_match : & str, selector : & str,) -> Result <(DomRect, WindowMetrics), DriveError> |
BrowserTestCli :: async fn raw(& self, url_match : & str, body : & serde_json::Value,) -> Result <String, DriveError> |
battery::cdp (other)
The DevTools drivers: cdp under the safety policy, and cdp-enable-on,
| Item |
|---|
fn drive(endpoint : & str, page_url_match : & str, script : & str, enable_runtime : bool,) -> Result <DriveReport, DriveError> |
battery::run (other)
battery run --driver <x>: open the page with the battery's own label,
| Item |
|---|
pub const PAGE_PATH: & str |
pub const PAGE_WINDOW_TITLE: & str |
RunReport
battery run --driver <x>: open the page with the battery's own label,
| Item |
|---|
pub struct RunReport |
async fn run(settings : & LabSettings, pool : & PgPool, driver : Driver, script : & Script,) -> Result <RunReport, DriveError> |
Xdo
The hardware driver: xdotool as an argument-vector subprocess, the pointer
| Item |
|---|
pub struct Xdo |
Xdo :: fn new(bin : & str) -> Self |
Xdo :: async fn focus_window(& self, title : & str) -> Result <(), DriveError> |
Xdo :: async fn humanized_click(& self, h : & mut Humanizer, from :(i64, i64), to :(i64, i64),) -> Result <usize, DriveError> |
Xdo :: async fn humanized_type(& self, h : & mut Humanizer, text : & str) -> Result <usize, DriveError> |
async fn drive(xdo : & Xdo, h : & mut Humanizer, window_title : & str, editor :(i64, i64), send :(i64, i64), script : & str,) -> Result <DriveReport, DriveError> |
grade (other)
battery grade: the cohort out of the rows (or a committed fixture), the
| Item |
|---|
fn grade(cohort : & Cohort, expectations : & ExpectationRow) -> Result <Graded, Refusal> |
fn report_drift(committed : & str, rendered : & str) -> bool |
pub const VERDICT_WORDS: & & str |
pub const CITATION: & str |
fn uncited(doc : & str) -> Vec <(usize, & 'static str)> |
Cohort
battery grade: the cohort out of the rows (or a committed fixture), the
| Item |
|---|
pub struct Cohort |
async fn load_cohort(pool : & PgPool, script_hash : & str) -> Result <Cohort, String> |
DriverGrade
battery grade: the cohort out of the rows (or a committed fixture), the
| Item |
|---|
pub struct DriverGrade |
FixtureRun
battery grade: the cohort out of the rows (or a committed fixture), the
| Item |
|---|
pub struct FixtureRun |
Graded
battery grade: the cohort out of the rows (or a committed fixture), the
| Item |
|---|
pub struct Graded |
fn render(g : & Graded) -> String |
async fn write_verdicts(pool : & PgPool, g : & Graded) -> Result <usize, String> |
serve (other)
The router: the probe page, its layout report, and the beacon route,
| Item |
|---|
pub const PROBE_INPUT_PATH: & str |
pub const LAYOUT_PATH: & str |
pub const PROBE_INPUT_HTML: & str |
pub const TOKEN_PLACEHOLDER: & str |
pub const BEACON_PATH_PLACEHOLDER: & str |
pub const PROBE_SCHEMA_VERSION: u32 |
pub const CANARY_SET_VERSION: u32 |
fn render_probe_page(template : & str, token : & str, settings_hash : & str) -> String |
AppState
The router: the probe page, its layout report, and the beacon route,
| Item |
|---|
pub struct AppState |
fn app_state(settings : & LabSettings, key : IngestKey, sink : Arc <dyn ProbeRunSink>, pool : Option <sqlx::PgPool>,) -> AppState |
fn router(state : AppState) -> Router |
LayoutReport
The router: the probe page, its layout report, and the beacon route,
| Item |
|---|
pub struct LayoutReport |
ProbeRunIngestState
The router: the probe page, its layout report, and the beacon route,
| Item |
|---|
ProbeRunIngestState :: fn from_ref(s : & AppState) -> Self |
settings (other)
Lab settings: the fail-closed shape.
| Item |
|---|
pub const DEFAULT_BIND: & str |
pub const DEFAULT_BEACON_MAX_BYTES: usize |
pub const DEFAULT_BEACON_MAX_EVENTS: usize |
pub const USAGE: & str |
Command
Lab settings: the fail-closed shape.
| Item |
|---|
pub enum Command |
fn dispatch(args : & String) -> Result <Command, String> |
LabSettings
Lab settings: the fail-closed shape.
| Item |
|---|
pub struct LabSettings |
LabSettings :: fn default() -> Self |
LabSettings :: fn from_json(json : & str) -> Result <Self, serde_json::Error> |
LabSettings :: fn hash(& self) -> String |
LabSettings :: fn binds_lan(& self) -> bool |
Owed
Lab settings: the fail-closed shape.
| Item |
|---|
pub struct Owed |
tokens (other)
Single-use page tokens (sprint 4.55 task 5).
| Item |
|---|
pub const DEFAULT_TOKEN_LIFETIME: Duration |
PageTokenStore
Single-use page tokens (sprint 4.55 task 5).
| Item |
|---|
pub struct PageTokenStore |
PageTokenStore :: fn default() -> Self |
PageTokenStore :: fn new(lifetime : Duration) -> Self |
PageTokenStore :: fn mint(& self) -> String |
PageTokenStore :: fn is_live(& self, token : & str) -> bool |
PageTokenStore :: fn consume(& self, token : & str) -> bool |
How to use it
No examples/ target and no doctest in this crate's rustdoc. The tests listed under Verification are the closest executable usage.
Module structure
platform_disclosure_lab
batterybattery::browser_testbattery::cdpbattery::runbattery::xdogradeservesettingstokens
flowchart TD n_platform_disclosure_lab["platform_disclosure_lab"] n_platform_disclosure_lab --> n_battery["battery"] n_battery --> n_battery__browser_test["browser_test"] n_battery --> n_battery__cdp["cdp"] n_battery --> n_battery__run["run"] n_battery --> n_battery__xdo["xdo"] n_platform_disclosure_lab --> n_grade["grade"] n_platform_disclosure_lab --> n_serve["serve"] n_platform_disclosure_lab --> n_settings["settings"] n_platform_disclosure_lab --> n_tokens["tokens"]
Public surface
`battery`
| Item | What it is |
|---|---|
pub const DEFAULT_SCRIPT: & str | The fixed text every driver types |
fn fnv64(bytes : & u8) -> String | FNV-1a 64 as 16 hex digits: the same digest the settings hash uses, so a reader can recompute either by hand |
pub struct Script | The script and its hash, stamped on every run. |
Script :: fn new(text : & str) -> Self | The hash is over the trimmed, lower-cased text: the page hashes what was typed the same way, so a capital initial or a trailing newline is still the script |
fn query_encode(s : & str) -> String | Percent-encode a query value: unreserved bytes pass, everything else is %XX |
fn parse_driver(s : & str) -> Option <Driver> | Parse a driver name as the command line gives it. |
fn page_url(base : & str, driver : Driver, driver_version : & str, script : & Script) -> String | The page URL for one run |
pub struct DomRect | A DOM box in viewport CSS pixels, as element-rects reports it. |
pub struct WindowMetrics | Window metrics as element-rects reports them beside the boxes. |
fn screen_point(rect : DomRect, win : WindowMetrics) ->(i64, i64) | The screen point at the centre of a DOM box: the window's screen origin, plus the browser chrome (outer minus inner, all of it assumed above and half of any width difference on the left), plus the box centre scaled by the device pixel ratio. |
pub struct MoveStep | One hardware pointer step: move there, then wait. |
fn xdo_moves(h : & mut Humanizer, from :(i64, i64), to :(i64, i64)) -> Vec <MoveStep> | The humanized pointer path from from to to, each step with the delay to wait after it |
fn typing_delays(h : & mut Humanizer, text : & str) -> Vec <(char, Duration)> | Per-character typing delays for text, from the humanizer's cadence. |
fn humanizer(seed : u64) -> Humanizer | A humanizer with the default profile and a fixed seed, so a battery run is reproducible from its seed. |
pub struct DriveReport | What a driver reports after it has driven the page. |
pub enum DriveError | Why a driver could not run. |
`battery::browser_test`
| Item | What it is |
|---|---|
pub struct BrowserTestCli | The bridge CLI, by path. |
BrowserTestCli :: fn new(bin : & str, timeout : Duration) -> Self | — |
BrowserTestCli :: async fn navigate(& self, url_match : & str, url : & str) -> Result <(), DriveError> | Open url in a tab matching url_match (or a new tab). |
BrowserTestCli :: async fn click(& self, url_match : & str, selector : & str) -> Result <(), DriveError> | — |
BrowserTestCli :: async fn type_text(& self, url_match : & str, selector : & str, text : & str,) -> Result <(), DriveError> | — |
BrowserTestCli :: async fn element_rect(& self, url_match : & str, selector : & str,) -> Result <(DomRect, WindowMetrics), DriveError> | The first element's box and the window metrics, for hardware input. |
BrowserTestCli :: async fn raw(& self, url_match : & str, body : & serde_json::Value,) -> Result <String, DriveError> | The extension's own humanized commands, by raw JSON. |
`battery::cdp`
| Item | What it is |
|---|---|
fn drive(endpoint : & str, page_url_match : & str, script : & str, enable_runtime : bool,) -> Result <DriveReport, DriveError> | Drive the open probe page over CDP |
`battery::run`
| Item | What it is |
|---|---|
pub const PAGE_PATH: & str | The substring every lab page URL carries; the bridge targets tabs by it. |
pub const PAGE_WINDOW_TITLE: & str | The page's <title>, which the hardware driver focuses by. |
pub struct RunReport | What battery run prints. |
async fn run(settings : & LabSettings, pool : & PgPool, driver : Driver, script : & Script,) -> Result <RunReport, DriveError> | Open the page for driver, drive it, and wait for its row. |
`battery::xdo`
| Item | What it is |
|---|---|
pub struct Xdo | xdotool by path, every call one argument vector with a timeout. |
Xdo :: fn new(bin : & str) -> Self | — |
Xdo :: async fn focus_window(& self, title : & str) -> Result <(), DriveError> | Bring the window whose title contains title to the front. |
Xdo :: async fn humanized_click(& self, h : & mut Humanizer, from :(i64, i64), to :(i64, i64),) -> Result <usize, DriveError> | Move the pointer along a humanized path, then click. |
Xdo :: async fn humanized_type(& self, h : & mut Humanizer, text : & str) -> Result <usize, DriveError> | Type text one character at a time at the humanizer's cadence. |
async fn drive(xdo : & Xdo, h : & mut Humanizer, window_title : & str, editor :(i64, i64), send :(i64, i64), script : & str,) -> Result <DriveReport, DriveError> | Drive the page by hardware: focus the window, path to the editor, click, type, path to the send button, click. |
`grade`
| Item | What it is |
|---|---|
pub struct FixtureRun | A run as the fixture stores it: the observable facts plus what the report names it by. |
pub struct Cohort | The whole cohort, as loaded. |
async fn load_cohort(pool : & PgPool, script_hash : & str) -> Result <Cohort, String> | Load the cohort for script_hash: every hand run, and the latest run per other driver, all on the most recent browser build seen for the hand. |
pub struct DriverGrade | One driver's graded row of the table. |
pub struct Graded | Everything the report renders. |
fn grade(cohort : & Cohort, expectations : & ExpectationRow) -> Result <Graded, Refusal> | Grade a cohort |
fn render(g : & Graded) -> String | The report, as --write renders it |
fn report_drift(committed : & str, rendered : & str) -> bool | --check: the committed report equals what the rows render now. |
pub const VERDICT_WORDS: & & str | The verdict vocabulary that may not appear in prose without a citation of the generated table on the same line or the line above. |
pub const CITATION: & str | — |
fn uncited(doc : & str) -> Vec <(usize, & 'static str)> | uncited_verdict findings in one document: (line number, word). |
async fn write_verdicts(pool : & PgPool, g : & Graded) -> Result <usize, String> | Write the verdict rows for a graded cohort |
`serve`
| Item | What it is |
|---|---|
pub const PROBE_INPUT_PATH: & str | The probe page's path. |
pub const LAYOUT_PATH: & str | Where the page reports its own layout, once per load. |
pub const PROBE_INPUT_HTML: & str | The page, with the per-load placeholders the server fills. |
pub const TOKEN_PLACEHOLDER: & str | The placeholder the minted token replaces. |
pub const BEACON_PATH_PLACEHOLDER: & str | The placeholder the beacon path replaces. |
pub const PROBE_SCHEMA_VERSION: u32 | Versions stamped into every run the page produces. |
pub const CANARY_SET_VERSION: u32 | — |
pub struct AppState | — |
ProbeRunIngestState :: fn from_ref(s : & AppState) -> Self | — |
fn render_probe_page(template : & str, token : & str, settings_hash : & str) -> String | Render the page for one load: a fresh token, the beacon path, the versions, and the settings hash the run will carry. |
pub struct LayoutReport | What the page reports about its own layout, once per load. |
fn app_state(settings : & LabSettings, key : IngestKey, sink : Arc <dyn ProbeRunSink>, pool : Option <sqlx::PgPool>,) -> AppState | Build the state from settings and a sink. |
fn router(state : AppState) -> Router | The router: GET /probe/input, POST /lab/layout, POST /lab/probe-run. |
`settings`
| Item | What it is |
|---|---|
pub const DEFAULT_BIND: & str | The default bind: loopback |
pub const DEFAULT_BEACON_MAX_BYTES: usize | Body cap on the beacon, bytes |
pub const DEFAULT_BEACON_MAX_EVENTS: usize | Event-count cap on one beacon. |
pub struct LabSettings | — |
LabSettings :: fn default() -> Self | — |
LabSettings :: fn from_json(json : & str) -> Result <Self, serde_json::Error> | Parse stored JSON; unknown fields and malformed values refuse. |
LabSettings :: fn hash(& self) -> String | A stable identity for these settings, stamped on every run so a cohort is comparable only across runs made under the same lab configuration |
LabSettings :: fn binds_lan(& self) -> bool | Whether the bind reaches beyond this machine. |
pub struct Owed | A command the host knows but has not been wired yet |
pub enum Command | The top-level command surface, parsed without a dependency. |
pub const USAGE: & str | — |
fn dispatch(args : & String) -> Result <Command, String> | — |
`tokens`
| Item | What it is |
|---|---|
pub const DEFAULT_TOKEN_LIFETIME: Duration | How long a minted token stays spendable. |
pub struct PageTokenStore | Live tokens, by value. |
PageTokenStore :: fn default() -> Self | — |
PageTokenStore :: fn new(lifetime : Duration) -> Self | — |
PageTokenStore :: fn mint(& self) -> String | Mint a fresh token |
PageTokenStore :: fn is_live(& self, token : & str) -> bool | Whether token is minted and unexpired, without spending it |
PageTokenStore :: fn consume(& self, token : & str) -> bool | — |
Re-exports. Exported here, defined elsewhere.
| Export | Defined in |
|---|---|
PageTokenStore | tokens::PageTokenStore |
{LabSettings,Owed} | settings::{LabSettings,Owed} |
Boundary
Reaches into application, foundation, infrastructure, observability.
Shares tier platform with 9 other crates: platform-api, platform-corpus-console, platform-customer-ui, platform-dto, platform-html-components, platform-leptos-components, platform-privacy-scan-api, platform-staff-ui, … (9 total).
_What this crate deliberately does NOT own is a judgment. No committed registry records one for it, so none is stated here._
Where it sits
| Tier (ontology) | platform |
| Architectural role (taxonomy) | infrastructure |
| Location | crates/platform/disclosure-lab |
| Vocabulary in force (lexicon) | current |
Tier flow. Which tiers this crate's own edges cross.
flowchart LR n_platform["platform"] --> n_application["application"] n_platform["platform"] --> n_foundation["foundation"] n_platform["platform"] --> n_infrastructure["infrastructure"] n_platform["platform"] --> n_observability["observability"]
Dependencies
Runtime, in this workspace.
| Crate | Tier | Optional | Only on |
|---|---|---|---|
| `application-analytics` | application | no | always |
| `application-core` | application | no | always |
| `foundation-bounded-io` | foundation | no | always |
| `infrastructure-browser-automation` | infrastructure | no | always |
| `infrastructure-browser-cdp` | infrastructure | no | always |
| `observability-analytics` | observability | no | always |
Runtime, from outside the workspace.
| Crate | Requirement | Features | Optional | Only on |
|---|---|---|---|---|
axum | ^0.7 | multipart | no | always |
chrono | ^0.4 | serde | no | always |
serde | ^1 | derive | no | always |
serde_json | ^1 | — | no | always |
sqlx | ^0.8 | runtime-tokio, postgres, chrono, uuid, json | no | always |
thiserror | ^2 | — | no | always |
tokio | ^1 | full | no | always |
tracing | ^0.1 | — | no | always |
tracing-subscriber | ^0.3 | — | no | always |
uuid | ^1 | v4, v7, serde, js | no | always |
Development. None.
Build. None.
Depended on by. Nothing in this workspace.
Signal flow — what reaches this crate, and what it reaches.
flowchart LR SELF["platform-disclosure-lab"] SELF -->|runtime| n_application_analytics["application-analytics"] SELF -->|runtime| n_application_core["application-core"] SELF -->|runtime| n_foundation_bounded_io["foundation-bounded-io"] SELF -->|runtime| n_infrastructure_browser_automation["infrastructure-browser-automation"] SELF -->|runtime| n_infrastructure_browser_cdp["infrastructure-browser-cdp"] SELF -->|runtime| n_observability_analytics["observability-analytics"] classDef self fill:#1f883d,stroke:#1f883d,color:#fff; class SELF self;
Feature flags
No Cargo features are defined: every capability is unconditional, so no consumer can receive a half-wired crate.
Targets
| Kind | Name | Source |
|---|---|---|
| bin | disclosure-lab | `src/main.rs` |
| lib | platform_disclosure_lab | `src/lib.rs` |
Error model
| Error type | Named by |
|---|---|
DriveError | drive, run |
Operational characteristics
| Property | Evidence |
|---|---|
| async public surface | yes |
| async runtime | yes |
| database access | yes |
| network I/O | yes |
| unsafe code | none detected |
| environment variables | yes |
No unsafe block, unsafe fn, unsafe impl or unsafe trait was found by the parser anywhere in this crate's source.
Configuration
| Variable | Read in |
|---|---|
CARGO_PKG_VERSION | src/battery/cdp.rs |
DISCLOSURE_LAB_DB | src/main.rs |
DISCLOSURE_LAB_INGEST_KEY | src/main.rs |
DISCLOSURE_LAB_SETTINGS | src/main.rs |
Related capabilities
No workspace crate depends on this one.
Verification
| Kind | Count |
|---|---|
| Unit tests | 19 |
| Integration tests | 0 |
| Examples | 0 |
| Doctests | 0 |
Evidence by module. How often each public module is named by something executable.
| Module | Tests | Examples | Consumers |
|---|---|---|---|
battery | 15 | 0 | 0 |
battery::browser_test | 1 | 0 | 0 |
battery::cdp | 1 | 0 | 0 |
battery::run | 4 | 0 | 0 |
battery::xdo | 2 | 0 | 0 |
grade | 12 | 0 | 0 |
serve | 12 | 0 | 0 |
settings | 8 | 0 | 0 |
tokens | 2 | 0 | 0 |
What the tests establish, by name:
a_dom_box_maps_to_the_screen_through_origin_chrome_and_dpr—src/battery/mod.rsthe_page_url_carries_the_battery_chosen_label_and_the_script_hash—src/battery/mod.rsthe_pointer_path_is_many_steps_ending_at_the_target_with_delays—src/battery/mod.rstyping_delays_vary_per_character—src/battery/mod.rsbattery_grade_check_masks_the_cohort_id_and_sees_real_drift—src/grade.rsbattery_grade_refuses_when_any_required_driver_is_removed_from_the_fixture—src/grade.rsbattery_grade_renders_the_expected_verdicts_from_the_fixture—src/grade.rsbattery_grade_uncited_scan_flags_prose_verdicts_without_a_citation—src/grade.rsthe_layout_report_is_strict—src/serve.rsthe_page_loads_nothing_external—src/serve.rsthe_page_records_the_enumerated_schema_fields—src/serve.rsthe_rendered_page_carries_its_token_and_the_paths_and_no_placeholder—src/serve.rsa_misspelled_flag_refuses_instead_of_defaulting—src/settings.rsan_empty_settings_row_is_the_safest_lab—src/settings.rsbattery_run_needs_a_driver_and_grade_takes_its_paths—src/settings.rslan_reach_is_an_explicit_bind—src/settings.rsa_token_spends_exactly_once—src/tokens.rsan_expired_token_is_refused_and_swept—src/tokens.rstokens_are_long_and_distinct—src/tokens.rs
Documentation coverage
| Measure | Documented | Total |
|---|---|---|
| Public items with rustdoc | 63 | 78 |
Public modules with a //! block | 9 | 9 |
pie showData
title Public items with rustdoc
"Documented" : 63
"No rustdoc detected" : 15
Metrics
| Metric | Value |
|---|---|
| Rust source files | 11 |
| Source lines | 2321 |
| Code lines | 1928 |
| Public API items | 78 |
| Public modules | 9 |
| Tests | 19 |
| Examples | 0 |
| Cargo features | 0 |
| Direct runtime dependencies | 16 |
| Workspace reverse dependencies | 0 |
pie showData
title Public API by kind
"constant" : 17
"enum" : 2
"function" : 21
"method" : 21
"struct" : 17
pie showData
title Rust source composition
"Code" : 1928
"Blank or comment" : 393
Generation
Rendered by tools-corpus corpus readme from repository evidence alone, renderer schema 2, lexicon current. No model, network service or database was consulted. Regenerate with tools-corpus corpus readme --write; verify with --check.