This document records the policies and maintenance rules that still matter after the recent refactors.
For first-time local bootstrap, prefer:
./scripts/setup.shThat setup script installs repo dependencies, ensures the Rust toolchain is ready, prepares local directories, and builds the CLI plus dashboard before you start iterating.
Preferred full local verification command:
./scripts/ci-local.shExpanded commands:
bash -n scripts/*.sh scripts/lib/*.sh site/data-build/*.sh deploy/cloud-vps/*.sh deploy/toolforge/*.sh
node --check site/admin-auth.cjs
node --check site/admin-server.cjs
node --check site/observablehq.config.js
for f in site/data-build/*.cjs; do node --check "$f"; done
node --test site/admin-auth.test.cjs
node --test site/admin-server.test.cjs
node scripts/generate-stack-reference.cjs --check
node --test scripts/check-compute-versions.test.cjs
node scripts/check-compute-versions.cjs
cargo fmt --all -- --check
cargo clippy --locked --all-targets --all-features -- -D warnings
cargo test --locked --all-targets --all-features
cargo doc --locked --no-deps
cargo llvm-cov --locked --workspace --all-features --all-targets --lcov --output-path target/llvm-cov.info
python3 scripts/check_lcov.py target/llvm-cov.info
cargo deny check advisories bans licenses sources
cargo audit -D warnings
node scripts/check-npm-advisories.cjs
node scripts/check-npm-licenses.cjs
scripts/check_vendor_patches.sh
python3 -m py_compile scripts/check_lcov.py scripts/test_check_lcov.py
python3 -m unittest discover -s scripts -p 'test_*.py'Cargo build artifacts use the standard, gitignored target/ directory. This
keeps local builds and CI cache paths aligned. Override CARGO_TARGET_DIR if
you need a different local cache location.
Expected bar:
- clippy warnings are errors
- coverage is expected to stay at 100% line coverage in the exported LCOV artifact
- dependency and advisory checks are part of the normal workflow
The local verification script also checks:
- shell syntax for the shared operational scripts
- Node syntax for the dev/operator admin server and Observable config
- helper entrypoint usage for
scripts/build-site.shandscripts/refresh.sh - a production site build when merged dashboard artifacts are already present locally
The repo is intentionally one codebase with two orchestration modes:
local:scripts/setup.sh,scripts/dev.sh, and the admin UI/API for interactive onboardingproduction:deploy/cloud-vps/ordeploy/toolforge/wrappers, static site serving, scheduled refreshes, and an optional authenticated admin surface
The shared contract across both modes is:
- the Rust CLI
- the Rust patrol fetch and compute path
- the merged artifact format under
output/ - the Observable production site build
The main rule is that deployment differences should live in scripts and env, not in forked pipeline logic.
The repo-level operational scripts are:
scripts/setup.shfor local bootstrapscripts/dev.shfor the local dashboard plus dev/operator admin APIscripts/refresh.shfor shared batch refresh orchestrationscripts/build-site.shfor production-style Observable builds
Shared runtime path handling lives in scripts/lib/wiki_econ.sh.
Production wrappers under deploy/cloud-vps/ and deploy/toolforge/ should
call those shared scripts rather than reimplementing pipeline steps.
GitHub Actions is split into eight jobs, gated by a changes job that uses
dorny/paths-filter to detect whether a push/PR touches anything under the
backend path set (src/**, Cargo.{toml,lock}, rust-toolchain.toml,
deploy/**, scripts/**, site/data-build/**, config/**, or the workflow
file itself):
changes: computes thebackendboolean other jobs gate onquality-node: formatting-free Node/shell/Python checks — shellcheck,node --check/unit tests, the small Python LCOV helper tests, and generated-document consistency. No Rust toolchain, always runs.quality-rust:cargo fmt --check,cargo clippy,cargo doc. Gated onbackend— skipped when a change touches only site content.site: a clean npm workspace install, advisory check, Rust-generated deterministic fixture, and real Observable production build with page and attachment verification. Always runs (front-end changes need this signal too);Swatinem/rust-cachekeeps it fast when Rust is unchanged.coverage:cargo llvm-covLCOV export plusscripts/check_lcov.pyenforcing zero uncovered lines. Gated onbackend— this is the single most expensive job (a fully instrumented rebuild), and it produces no new signal when Rust source hasn't changed.security-node: fail-closed npm advisory/license policies, REUSE, and registered vendored-patch validation. No Rust toolchain, always runs.security-rust:cargo-denyandcargo-audit. Gated onbackend.toolforge-release: after the other jobs pass (or are skipped, for the Rust-gated ones) onmain, builds and retains the attested Linux release envelope, three SBOMs, checksums, provenance, and complete notices for an operator-driven SSH deploy. Runs unconditionally onmainpushes — it bundles the browser dist too, so front-end-only changes still need a new release;Swatinem/rust-cachekeeps the--releaserebuild fast when the Rust source itself hasn't changed.
That split is intentional. Keep fast correctness failures separate from
coverage drift and dependency-policy drift, and keep the expensive Rust
recompiles (instrumented coverage, lint, dependency audit) off the critical
path for changes that only touch site/** content. GitHub has no production
credentials; Toolforge deployment remains an explicit operator action. The
coverage run subsumes the ordinary Rust test suite.
Dependabot checks Cargo, npm, and pinned GitHub Actions weekly using
.github/dependabot.yml; its pull requests must still pass all five jobs.
The LCOV check is deliberate. cargo llvm-cov --summary-only can under-report line coverage on fully exercised lines because of sub-line region artifacts around ? and similar expressions. CI treats the exported LCOV file as the source of truth for line coverage.
Performance claims should be backed by the built-in benchmark command, not just by code inspection.
Benchmark after changes that affect:
- fetch behavior in ways that may shift downstream bottlenecks
- ingest shape or filtering
- analytical storage layout
- compute partitioning
- Polars version or Polars-facing query code
Use docs/benchmarking.md as the operator-facing reference. Treat compute_all timing as the primary number when comparing real pipeline performance across commits.
Runtime logging is based on tracing and tracing-subscriber.
Current conventions:
infofor stage boundaries and completed workdebugfor skip paths, compatibility branches, and extra detailwarnfor degraded but recoverable behavior
Prefer structured fields such as:
wikimetricrowscolumnsbyteselapsed_ms
Do not add new long-running operational logging with unstructured println!.
The following are live architecture contracts, not incidental implementation details:
- new metric input lives under
data/metric-input/<wiki>/_snapshots/<snapshot>/ data/parquet/anddata/warehouse/remain schema-v1 read-compatibility paths- ingest markers live inside the corresponding analytical snapshot generation
data/snapshots/<wiki>/current-snapshot.jsonselects the only generation compute may read- a marker is valid only when it resolves to one exact supported layout and every output it claims
- partition names are
year=YYYY/year_month=YYYY-MM - compute prefers the partitioned incremental path when that layout exists
- compatibility fallback for older parquet layouts still exists for both full-wiki and partitioned loads and should not be broken casually
- per-wiki metric outputs should include a
wikicolumn before merge - merge uses Rust to refresh
defaults_*.jsonandmeta_*.json, then runs the checked-insite/data-build/manifest.json.shentrypoint to validate and atomically publishmanifest.json - patrol fetch and compute are Rust subcommands; the typed logging stream in
src/logging.rsis shared by patrol, rights, local block-history, and account-creation consumers, and patrol compute participates in the same merge/default materialization path as the history metrics - deterministic stage receipts live under
data/stages/andoutput/_stages/; algorithm changes must increment the owning*_ALGORITHM_VERSIONconstant - core history compute has independent
monthly,activity_tiers,lifecycle, andpage_weekreceipts; patrol is not a core-history input - unpinned fetch/run commands resolve one completed snapshot for the entire run and fail when no dump exists within
WIKI_ECON_MAX_SNAPSHOT_LAG_MONTHS
If any of these change, update docs/architecture.md, tests, and storage helpers together.
scripts/check-compute-versions.cjs compares changed compute paths with the
family constants in src/compute/*/mod.rs. A source change must either bump
every mapped family version or be listed exactly in
config/compute-no-semantic-change.json with the unchanged versions and a
meaningful rationale. The declaration is deliberately per path and per base
commit; it is an auditable assertion for a mechanical refactor, not a durable
allowlist. Patrol changes map to no history family and therefore never force a
history algorithm bump.
Dependency versions and lifecycle state are deliberately absent from this
narrative guide. Update their manifests, run
node scripts/generate-stack-reference.cjs --write, and let CI validate the
generated stack reference.
The workspace currently patches polars-utils through:
[patch.crates-io]
polars-utils = { path = "vendor/polars-utils" }This is an in-tree fork, not a warning suppression mechanism.
The current Polars dependency graph still needed a security-conscious intervention around the advisory-bearing bincode 2 edge. The project policy was to fix the graph rather than ignore the advisory in tooling.
vendor/polars-utils/src/pl_serialize.rs was patched so the compact serialization path also uses rmp-serde instead of the upstream bincode path.
That means:
- we own a small Polars-internal fork
- future Polars upgrades must review that patch explicitly
- upstream removal of the need for this patch is preferable to carrying it forever
If you touch the vendored patch:
- compare the vendored files against upstream
polars-utilsfor the target version - rerun the full workspace quality, coverage, and security commands
- run the vendored crate tests directly:
cargo test --manifest-path vendor/polars-utils/Cargo.toml - document why the fork is still needed
Do not let the vendored patch turn into a general-purpose divergence from upstream Polars behavior.
cargo-deny and cargo-audit are expected to stay meaningful.
Current policy choices that matter:
- wildcard dependencies are denied
- unknown registries and git sources are denied
- only a narrow set of permissive licenses is allowed
- the crate is private (
publish = false)
There may still be upstream duplicate-version warnings in the dependency graph. They are not currently suppressed. Treat them as dependency-graph debt, not as a reason to weaken the checks.
For non-trivial changes:
- decide which stage contract changes: fetch, ingest, storage, compute, merge, or CI
- update the relevant docs under
docs/ - preserve compatibility paths deliberately, or remove them deliberately with test changes
- benchmark if the change can plausibly alter runtime or memory behavior
- rerun the full local quality gates
This project is intentionally strict about explicit contracts. Keep that discipline.