From a2f013955cc9579bfaad09dadfc7f92577fa2810 Mon Sep 17 00:00:00 2001 From: Stephen Waits Date: Thu, 29 Oct 2026 16:16:28 -0600 Subject: [PATCH 1/3] docs(audit): the re-audit runbook (P10) A re-audit is only worth its name if every figure is traced and every receipt and entry carries an attestation that counts, so the runbook drives the audit tools in the order the books need: audit, correct by reversal (never edit), attest at the bar, attest the periods, then compare both CPA packages and sign off in the journal. D-12's evidence link uses link_attachment: the books have no inherit_evidence tool. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01HS8WZziDnyxunC23MUxrLp --- docs/runbooks/re-audit.md | 189 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 189 insertions(+) create mode 100644 docs/runbooks/re-audit.md diff --git a/docs/runbooks/re-audit.md b/docs/runbooks/re-audit.md new file mode 100644 index 0000000..5d963a7 --- /dev/null +++ b/docs/runbooks/re-audit.md @@ -0,0 +1,189 @@ +# The re-audit, through the MCP alone + +The owner's acceptance of the whole programme (plan/served.md, P10). An +agent audits a company's books using only the books tools, the books' own +filed documents and the viewer's Export menu. Nothing here reads the data +root, a database file or the journal file. The engineering mirror of this +runbook is `crates/ledger-cli/tests/defects.rs` (every tool named below +exists; every defect has a named regression test); the run itself is the +owner's, on the owner's books. + +Money is a decimal string everywhere. Every tool named is a tool in +`books.start`'s list; if one is missing, stop: the runbook is stale, not +the books. + +## Before you start + +- Two roles. The **auditor** runs `books.audit`, reads, and proposes fixes. + The **verifiers** (at least two agents, not the auditor, not whoever made + the entry) attest. The human confirms every correction. +- The bar is `policy.attestation_bar` (default `effort=high verifiers=2 + adversarial=true`, D-9). Read it with `books.settings`. A verifier's + `method` is its own claim, said truthfully: `{ model, reasoning_effort, + adversarial, verifier_agents }`. A reading that differs from the entry is + recorded with `disagrees: true` and never counts; never bend a reading to + fit. +- Context discipline. A receipt is read as text first + (`books.get_receipt`, the default `meta` view is under 8 KiB, `view: + "text"` gives the whole text), as one page picture (`view: "page_image"`, + `page`, `max_px`) only when the text is empty or looks wrong, and never as + `bytes`. Record each verifier's own token count for receipts; the exit + bar is under 100K tokens per verifier. +- Start with `books.start` (rules, conventions, open items, its `next_steps`) + and `books.handbook` section `attestations-and-audit`. The `audit` prompt + (`scope`: `month`, `annual`, `cpa_handoff` or `full`) is the same + procedure for a client that offers prompts. +- Audits and attestations need the books' `audits` feature (the owner's + format upgrade). Without it the tools refuse with `format_upgrade_pending`: + do the upgrade first, as its own step, with the owner. + +## The two cases + +- **T1, desktop only.** Claude Code and Codex on the owner's machine, through + `wanderbooks connect`. Separation between the agent and the data root is + advisory. The evidence that it held is the access log + (`books.access_log`, `books:admin`) and the agents' transcripts, which never + name the data root or a path into it. +- **T2, a self-host machine running** (D-30, D-31; `deploy/README.md`). The + same steps, plus claude.ai through the tunnel once P9b exists. Separation + is enforced by the machine's account split. Until P9b, T2 is T1 with the + server on its own machine. + +The steps are identical; only the evidence in step 8 differs. + +## Step 1. The first audit and its fixes + +1. `books.audit { scope: "full", through: }` without + `confirmed`: it lists the checks and changes nothing. Show the human. + Then run it with `confirmed: true` and the `preview` digest. Do the same + for `{ scope: "cpa_handoff", through: }`. +2. `books.workpaper { id }` for each run: the findings and the PDF as filed. +3. Fix every finding through journaled corrections, never an edit: + - a wrong posted entry: `books.reverse_entry { entry_id, date, + description }`, previewed, then `confirmed: true`; then the right entry + through `books.draft_entry`, `books.add_line`, `books.post_entry` + (preview, then `confirmed`); + - a reversal with no receipt of its own (an old one, `legacy_reversal_exempt`): + its evidence is its original's (D-12, D-47). Link the original's receipt + to the reversal with `books.link_attachment { attachment_id, subject }`; + `books.list_receipts { entry_id }` finds the receipt; + - a duplicate file: `books.consolidate_duplicates` (preview, then + `confirmed`); `books.find_attachment` to look one up by hash or number; + - a receipt's facts: `books.annotate_attachment`; + - an open draft: post it, or `books.discard_draft { reason }`; + - an undecided 1099 vendor: `books.set_vendor_1099`; + - a missing tax category: `books.set_tax_category`; + - a judgment the books should remember: `books.record_decision`. + Nothing is fixed without the human's confirmation. +4. Audit again at the new head. An audit is idempotent on the same books + (`already_run`), so a new run means the books changed. Repeat until the + `full` and `cpa_handoff` runs have no error findings; keep the final two + run ids. Each journaled fix can be traced with `books.journal_history + { subject }`. + +## Step 2. Every receipt attested + +For every receipt on a posted entry (`books.list_receipts`, entry by entry, +or `books.search_entries` then `books.get_entry`): + +1. Verifier A reads `books.get_receipt` (text; a page image only if needed) + and calls `books.attest_receipt { attachment_id, entry_id, amount, date, + method, note? }` with the total and the date as read off the receipt. +2. Verifier B does the same independently, without seeing A's reading. +3. `books.attestations { kind: "receipt" }` shows each claim with + `counted`. A claim below the bar, or with `disagrees: true`, does not + count; the disagreement is a finding: reopen step 1 for that entry. + +## Step 3. Every entry reviewed, every period attested + +1. `books.review_entry { entry_id, method }` for every posted entry, by a + verifier that did not make it. +2. `books.attest_period { period, method }` for each month since the + company's formation (`books.list_periods`), each against a standing + audit at the current head (`scope: "month"` or `"full"`), then once for + the fiscal year against the `cpa_handoff` run. A period attestation is + refused when the books changed since the audit: audit again, then attest. +3. `books.attestations` with no filter: every row has `counted: true` and + none has `disagrees`. + +## Step 4. The defect regressions + +The 2026-10-16 audit found 20 defects; P0 found two more (21, 22) and P3 +two (23, 24). Each has a named regression test in the workspace; +`crates/ledger-cli/tests/defects.rs` holds the list `(defect, test, file)` +and proves each name exists. Show them green, from a checkout of the build +under audit: + +``` +cargo test -p ledger-cli --test defects +``` + +then each test named in that list, by name, for example +`cargo test -p ledger-core --test spec_cash_flow +defect1_two_member_llc_first_year_regression`. Two defects have no code +fix to test: defect 2 (reversals without their own evidence) is closed by +D-12 and D-47 (its test shows the rule now) and defect 20 (the migration +scripts) was retired at the cutover (no tool exists; the test checks none +came back). Record the test run's output in the sign-off decision (step 7). + +## Step 5. The Sheet comparison + +The old Google Sheet and the books were frozen as of 2026-10-11 and +compared a final time before the cutover (P2, `verify-sheet`); that run is +filed as a document. There is no comparison tool and this +runbook builds none. + +1. `books.find_attachment { text: "verify-sheet" }` finds the filed run + (its extracted text names the tool); `books.get_receipt { attachment_id }` + reads it (text view; its `links` and `sha256` identify it). Cite it by + attachment id and sha256. If it is not found, stop: the comparison was + not filed at the cutover and the owner decides what stands in for it. +2. `books.record_decision { subject: "Sheet comparison as of 2026-10-11", + decision_md, decided_by: [], decided_on, evidence_attachment: + }` stating that the frozen Sheet and the books + agreed then, and nothing newer is claimed. + +## Step 6. The CPA package from both doors + +1. The agent's door: `books.export_cpa_package { as_of: , + include_tins: true }` (TINs are included by default, D-29). Without a + counted audit it is refused (`audit_required`); never use + `without_audit_reason` for the re-audit, the point is that it is not + needed. Save the zip with the download command in its `file`. +2. The viewer's door: `wanderbooks gui `, the Export menu, the CPA + package as of the same day. It lands in the Downloads folder. +3. Compare the two zips file by file: byte-identical except for timestamps + (the README's generated-at line and the zip entries' times). A difference + in any figure is a defect: stop and report it. + +## Step 7. Verify, back up, sign off + +1. `books.verify { blobs: true }`: `status: "ok"` (not `healed`: a healed + result means a torn tail was set aside; note it and look at + `books.journal_history`'s `tail_repairs`). +2. `books.backup`: every target not `stale`, `last_error` empty, and + `last_drill_at` set with no `drill_error`. If not, `books.back_up_now` + (an admin action) and read it again. The drill is the restore test; a + backup that was never restored is not a backup. +3. The owner's sign-off: `books.record_decision { subject: "Re-audit + sign-off", decision_md, decided_by: [], decided_on }` naming + the final `cpa_handoff` and `full` run ids, the journal head + (`books.verify`'s `head`), the defects run, and the CPA package's sha256. + +## Step 8. The exit evidence + +The re-audit passes only when all of these are true. The owner checks them; +the agent presents the proof for each. + +| Exit bar | Proof | +|---|---| +| Zero failing workpaper checks | the final `full` and `cpa_handoff` workpapers (`books.workpaper`) | +| Cash flow difference 0.00 | `books.cash_flow` for the year; no `cash_flow_unfooted` finding | +| Every receipt and posted entry has a counted attestation, none disagrees | `books.attestations` (step 3.3) | +| Every action under the right principal | `books.access_log`: the auditor's token and each verifier's token appear on their own calls; no action by an admin token that an agent token did | +| No transcript names the data root | grep each agent's transcript for the root path before sign-off (T1); T2: the tenant root is not readable by the agent's account | +| No result carried a full TIN to a client that should not see one | `books.access_log` shows `include_tins` only on `books.export_cpa_package` and `books.get_w9`/`books.vendor_1099` calls by the owner's agent; the agents' exports may carry them (D-29), nothing else | +| Receipt reading stayed under 100K tokens per verifier | each verifier's own token count for its receipt calls (the harness's count, written into the attestation `note`) | +| The sign-off is journaled | `books.decisions` shows it | + +When all are true the books are re-audited through the MCP alone. From 01aff4c04eee643f8c06eee1ae982fc3b7311cf4 Mon Sep 17 00:00:00 2001 From: Stephen Waits Date: Thu, 29 Oct 2026 16:16:28 -0600 Subject: [PATCH 2/3] test(audit): every defect keeps a named regression test (P10) The audit's 24 defects must not lose their reproductions quietly; the table names the test guarding each, checks the runbook names only tools that exist, and records the mirror and its For-P12 targets in docs/testing.md. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01HS8WZziDnyxunC23MUxrLp --- crates/ledger-cli/tests/defects.rs | 265 +++++++++++++++++++++++++++++ docs/testing.md | 30 ++++ 2 files changed, 295 insertions(+) create mode 100644 crates/ledger-cli/tests/defects.rs diff --git a/crates/ledger-cli/tests/defects.rs b/crates/ledger-cli/tests/defects.rs new file mode 100644 index 0000000..cc3fe43 --- /dev/null +++ b/crates/ledger-cli/tests/defects.rs @@ -0,0 +1,265 @@ +//! The re-audit's engineering mirror (plan P10; `docs/runbooks/re-audit.md`): +//! every defect the 2026-10-16 audit found (1 to 20), P0 (21, 22) and P3 +//! (23, 24) has a named regression test somewhere in the workspace, and the +//! runbook names only tools and commands that exist. +//! +//! The list below is the single place that says which test stands guard for +//! which defect. A test renamed or deleted fails here, so a defect's +//! reproduction cannot be lost quietly. This proves the tests exist, are +//! real `#[test]`s and are not ignored; that they pass is `cargo test`'s +//! job (the runbook's step 4 runs them by name). +//! +//! SOURCE: plan/served.md 8.0 ("defect n") and P10 step 2. + +use std::path::{Path, PathBuf}; + +fn root() -> PathBuf { + Path::new(env!("CARGO_MANIFEST_DIR")) + .join("../..") + .canonicalize() + .unwrap() +} + +fn read(rel: &str) -> String { + std::fs::read_to_string(root().join(rel)).unwrap_or_else(|e| panic!("{rel}: {e}")) +} + +/// `(defect, test name, file)`. Defect 2 is closed by D-12 and D-47 (the +/// rule it now shows is that a reversal needs no receipt of its own); +/// defect 20 (the migration scripts) was retired at the cutover and is +/// checked separately below. +const DEFECTS: [(u32, &str, &str); 23] = [ + ( + 1, + "defect1_two_member_llc_first_year_regression", + "crates/ledger-core/tests/spec_cash_flow.rs", + ), + ( + 2, + "a_reversal_needs_no_receipt_of_its_own", + "crates/ledger-cli/tests/drafts.rs", + ), + ( + 3, + "get_receipt_default_has_text_and_no_bytes", + "crates/ledger-cli/tests/evidence.rs", + ), + ( + 4, + "same_bytes_one_record_many_links", + "crates/ledger-store/tests/attachments.rs", + ), + ( + 5, + "a_draft_waits_for_its_receipt_and_posts", + "crates/ledger-cli/tests/drafts.rs", + ), + ( + 6, + "digest_since_seq_is_posting_time", + "crates/ledger-cli/tests/drafts.rs", + ), + ( + 7, + "the_readme_lists_what_the_iif_writes_differently", + "crates/ledger-package/tests/package.rs", + ), + ( + 8, + "add_account_warns_without_tax_category_defect_8", + "crates/ledger-cli/tests/spec_tax_policy.rs", + ), + ( + 9, + "defect9_a_new_schedule_spreads_by_the_day", + "crates/ledger-core/tests/schedules.rs", + ), + ( + 10, + "annotations_replay_and_project", + "crates/ledger-store/tests/attachments.rs", + ), + ( + 11, + "defect11_the_directory_shows_a_members_tin_last4", + "crates/ledger-cli/tests/spec_vault.rs", + ), + ( + 12, + "payment_methods_round_trip_and_classify_payments", + "crates/ledger-cli/tests/spec_1099.rs", + ), + ( + 13, + "four_contributed_devices_each_take_the_lower_of_value_and_basis", + "crates/ledger-core/tests/spec_asset_basis.rs", + ), + ( + 14, + "defect14_party_totals_take_a_basis", + "crates/ledger-cli/tests/spec_party_totals.rs", + ), + ( + 15, + "rename_account_end_to_end_defect_15", + "crates/ledger-cli/tests/mcp_journeys.rs", + ), + ( + 16, + "one_default_period_everywhere_defect_16", + "crates/ledger-cli/tests/mcp_journeys.rs", + ), + ( + 17, + "the_audit_odd_cent_never_piles_up_across_closes", + "crates/ledger-core/tests/odd_cent.rs", + ), + ( + 18, + "undecided_is_not_not_reportable", + "crates/ledger-core/tests/form1099.rs", + ), + ( + 19, + "no_tool_takes_a_path", + "crates/ledger-mcp/src/server_tests.rs", + ), + ( + 21, + "as_of_package_iif_stops_at_as_of", + "crates/ledger-package/tests/package.rs", + ), + ( + 22, + "defect22_depreciation_through_mid_final_month_is_prorated_not_plugged", + "crates/ledger-core/tests/spec_assets_payroll.rs", + ), + ( + 23, + "a_vendor_marked_no_1099_is_not_undecided", + "crates/ledger-cli/tests/spec_1099.rs", + ), + ( + 24, + "defect24_an_accrual_through_another_liability_counts_once", + "crates/ledger-core/tests/spec_party_totals.rs", + ), +]; + +/// Defects 1 to 24 each have a named test that exists, is a test, and is +/// not ignored (defect 20 is the retired scripts, below). +#[test] +fn every_defect_reproduction_is_a_named_test() { + let mut covered: Vec = DEFECTS.iter().map(|d| d.0).collect(); + covered.push(20); + covered.sort_unstable(); + assert_eq!( + covered, + (1..=24).collect::>(), + "a defect is missing" + ); + + let mut missing = Vec::new(); + for (defect, name, file) in DEFECTS { + let text = read(file); + let lines: Vec<&str> = text.lines().collect(); + let needle = format!("fn {name}("); + let Some(at) = lines.iter().position(|l| { + l.trim_start().starts_with(&needle) + || l.trim_start().starts_with(&format!("pub {needle}")) + }) else { + missing.push(format!("defect {defect}: no `fn {name}` in {file}")); + continue; + }; + // The attributes and doc lines above the function, up to the + // previous blank line or item. + let above: String = lines[at.saturating_sub(8)..at].join("\n"); + let in_proptest = text.contains("proptest!"); + if !above.contains("#[test]") && !in_proptest { + missing.push(format!( + "defect {defect}: `{name}` in {file} is not a #[test]" + )); + } + if above.contains("#[ignore") { + missing.push(format!("defect {defect}: `{name}` in {file} is ignored")); + } + } + assert!(missing.is_empty(), "{}", missing.join("\n")); +} + +/// Defect 20, the migration scripts, was retired at the cutover: there is +/// no tool for them, and none may come back as a way past the journal. +/// The defect 19 mirror (`import_local_file` and every path parameter) is +/// `no_tool_takes_a_path` above. +#[test] +fn defect_20_no_migration_tool_exists() { + let scopes = read("crates/ledger-mcp/src/scopes.rs"); + for line in scopes.lines().filter(|l| l.contains("(\"books.")) { + for banned in [ + "migrate", + "import_local_file", + "import_sheet", + "verify_sheet", + ] { + assert!(!line.contains(banned), "a retired tool is back: {line}"); + } + } +} + +/// The runbook names only tools and commands that exist: each +/// `books.` is in the scopes table (the 8.0.3 list of tools), each +/// `wanderbooks ` is a command `main.rs` dispatches, and each +/// test file it names is a file. +#[test] +fn the_re_audit_runbook_names_only_what_exists() { + let runbook = read("docs/runbooks/re-audit.md"); + let scopes = read("crates/ledger-mcp/src/scopes.rs"); + let main = read("crates/ledger-cli/src/main.rs"); + + let mut tools = Vec::new(); + let mut rest = runbook.as_str(); + while let Some(i) = rest.find("books.") { + let tail = &rest[i..]; + let end = tail + .find(|c: char| !(c.is_ascii_alphanumeric() || c == '_' || c == '.')) + .unwrap_or(tail.len()); + let name = tail[..end].trim_end_matches('.'); + // Prose such as "books.start's" ends at the apostrophe; a bare + // "books." (the company's books followed by a full stop) is not a name. + if name.len() > "books.".len() && !tools.contains(&name.to_string()) { + tools.push(name.to_string()); + } + rest = &tail[end.max(1)..]; + } + assert!( + tools.len() > 20, + "the runbook should name its tools: {tools:?}" + ); + let unknown: Vec<&String> = tools + .iter() + .filter(|t| !scopes.contains(&format!("(\"{t}\""))) + .collect(); + assert!( + unknown.is_empty(), + "tools the runbook names that do not exist: {unknown:?}" + ); + + for cmd in ["gui", "connect"] { + if runbook.contains(&format!("wanderbooks {cmd}")) { + assert!(main.contains(&format!("\"{cmd}\"")), "no command {cmd}"); + } + } + assert!( + runbook.contains("wanderbooks gui"), + "step 6 names the viewer" + ); + + for step in 1..=8 { + assert!( + runbook.contains(&format!("## Step {step}.")), + "the runbook lost step {step}" + ); + } + // The runbook must not point at the data root or tell anyone to read it. + assert!(!runbook.contains("wanderbooks.v"), "names a database file"); +} diff --git a/docs/testing.md b/docs/testing.md index db1c715..a643a37 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -3922,3 +3922,33 @@ export gate (`cpa_audit_standing`'s four conditions and its feature test, the `refuse`/`warn` split, the reason's trim); `audit_items` in Needs attention (the declared test, the attested set, the last-ended fiscal year, the three year conditions). + +## P10: the re-audit runbook's mirror + +`docs/runbooks/re-audit.md` is the owner's acceptance, run on his books +through the MCP alone. What CI can check of it is in +`crates/ledger-cli/tests/defects.rs`: + +- `every_defect_reproduction_is_a_named_test`: a table of `(defect, test + name, file)` for the 2026-10-16 audit's defects 1 to 19, P0's 21 and 22 + and P3's 23 and 24 (23 rows); each named function exists in its file, is + a `#[test]` (or inside `proptest!`) and is not `#[ignore]`d. Defect 2 is + closed by D-12 and D-47, so its row is the rule as it now stands + (`a_reversal_needs_no_receipt_of_its_own`). +- `defect_20_no_migration_tool_exists`: defect 20 (the migration scripts) + was retired at the cutover; no tool of that kind is in the scopes table. +- `the_re_audit_runbook_names_only_what_exists`: every `books.` in + the runbook is in `crates/ledger-mcp/src/scopes.rs`, the commands it + names are dispatched by `main.rs`, and its eight steps are present. + +The mirror proves the tests exist, not that they pass; the runbook's step 4 +runs them by name. No CI job was added: the test runs in the existing +`check` job's `cargo test --workspace`. The run itself (a counted +attestation on every receipt and entry, the two CPA packages equal, the +sign-off) is the owner's and has no CI equivalent. + +**For P12 (mutation targets):** the `DEFECTS` table's file/name lookup (a +`starts_with` on `fn (`; the `#[test]`/`#[ignore]` window of eight +lines above it) and the scopes-line scan in `defect_20_*`. A mutant that +widens the window or drops the `ignore` check survives only if no ignored +or untested name is in the table; the table is the oracle. From 7fe8570acd3322cd0881e245418526bc88fc53da Mon Sep 17 00:00:00 2001 From: Stephen Waits Date: Thu, 29 Oct 2026 16:16:28 -0600 Subject: [PATCH 3/3] docs(plan): P10 as built Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01HS8WZziDnyxunC23MUxrLp --- plan/served.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plan/served.md b/plan/served.md index 5ad3f33..10e279a 100644 --- a/plan/served.md +++ b/plan/served.md @@ -1639,7 +1639,7 @@ Each unit follows the 8.0.4 template. "A-n" is an owner answer numbered in 8.3.1 5. **Files:** owns `docs/runbooks/re-audit.md`, `crates/ledger-cli/tests/defects.rs`. 6. **Tests and proofs:** the one test. 7. **Done when (engineering):** the runbook exists and names only tools and commands that exist; the test passes. **Exit (the owner's run):** zero failing workpaper checks; cash flow difference 0.00; every receipt and posted entry carries a counted attestation and none disagrees; the access log shows every action under the right principal and no transcript names the data root; no result carried a full TIN to a client that should not see one (D-29: the agent's exports may); agent context on receipts stayed under 100K tokens per the harness's own token count; the sign-off journaled. -8. **As built:** none yet. +8. **As built:** (2026-10-29, branch `P10`.) `docs/runbooks/re-audit.md`: eight steps (the audit and its fixes; receipts attested; entries reviewed and periods attested; the defect regressions; the Sheet comparison; the CPA package from both doors; verify, backup drill and sign-off; the exit table), T1 and T2, every tool named is in the scopes table. `crates/ledger-cli/tests/defects.rs`: `every_defect_reproduction_is_a_named_test` (23 rows, defects 1 to 19 and 21 to 24), plus two the unit did not list: `defect_20_no_migration_tool_exists` and `the_re_audit_runbook_names_only_what_exists` (Done-when "names only tools that exist" needed a check). Departures: (a) **`books.inherit_evidence` does not exist** (only the event type is gated in `format.rs`; no tool, no scope row); the runbook links the original's receipt to the old reversal with `books.link_attachment`, which journals `attachment.linked` and satisfies the evidence rule, and `legacy_reversal_exempt` is informational so the audit is clean either way. If the owner wants the D-12 annotation as such, a tool is a later unit. (b) Defect 2 (closed by D-12/D-47) maps to `a_reversal_needs_no_receipt_of_its_own`'s rule; defect 20 has no test of its own and gets the no-tool check. (c) The Sheet workpaper is found with `books.find_attachment { text }` because `workpaper` is not a `books.file_document` category (the audit files its own). (d) No CI job added: `check` runs the new test. **Owner's, not checked here:** the run itself (zero failing checks, counted attestations, equal CPA packages, backup drill, sign-off, transcripts free of the data root, token counts). #### P11a: packaging and updates (D-19, D-22, D-24, D-25, D-26)