fix(docs): fix formatting issues in README.md
- adapt meta-/code-/obs- rules to the local Python benchmark pipeline - replace db-sql-ddl, code-config-env-scope, test-e2e-pytest with pipeline equivalents - add code-python-style, data-determinism, data-naming-units, bench-methodology, build-pipeline-tasks - normalize specs and README typography to ASCII per code-data-formatting - rewrite root CLAUDE.md trigger table; add .cursorrules and .gitignore - specs: pipeline plan and task specs 00-11 - rules: 19 binding rule files adapted for this project - docs: CLAUDE.md rule-trigger table - config: .cursorrules commit convention, .gitignore excluding out/ and .venv/ - docs: rewrite README.md with pipeline diagrams, setup guide, result placeholders - config: requirements.txt for the closed dependency list - datagen: make_lab_config.py writes out/config/lab_config.yaml and .done marker
This commit is contained in:
112
docs/rules/bench-methodology.md
Normal file
112
docs/rules/bench-methodology.md
Normal file
@@ -0,0 +1,112 @@
|
||||
# Bench / Methodology - isolation, repetitions, timeouts, honest reporting
|
||||
|
||||
**Purpose + scope.** The measurement discipline for the benchmark matrix
|
||||
(Q1-Q7 x 5 formats, spec [08](../specs/08_benchmark_queries.md) /
|
||||
[09](../specs/09_benchmark_execution.md)) and for the extrapolation and
|
||||
reporting built on it (specs 10-11). Binding for `bench_runner.py`, every
|
||||
query implementation, `extrapolate.py`, and `report.py`.
|
||||
|
||||
The product of this project IS these measurements; a sloppy protocol
|
||||
invalidates everything downstream.
|
||||
|
||||
---
|
||||
|
||||
## 1. Cell isolation - one subprocess per measurement
|
||||
|
||||
- Every benchmark cell (format x query x run) executes in its OWN
|
||||
subprocess, so peak RSS, CPU time, and IO counters attribute to exactly
|
||||
that cell.
|
||||
- The runner samples the child with `psutil` every 50 ms for peak RSS;
|
||||
CPU time is user+sys at exit; read bytes from `io_counters`.
|
||||
- Nothing else runs in the child: no logging setup beyond minimum, no
|
||||
imports the query does not need - the measurement must not pay for the
|
||||
harness.
|
||||
|
||||
## 2. Repetition protocol
|
||||
|
||||
- Per cell: **1 warm-up run (discarded) + 3 measured runs.** Report the
|
||||
median in `results_median.csv`; keep min/max in the raw data.
|
||||
- **Randomize the cell execution order** and record the actual order in
|
||||
the raw results - fixed order lets cache effects masquerade as format
|
||||
differences.
|
||||
- SQLite additionally reports run 1 (cold) and run 3 (warm) separately
|
||||
(spec 08).
|
||||
- Never average TIMEOUT/ERROR runs into medians; a cell with a failed run
|
||||
reports the failure, not a prettier subset.
|
||||
|
||||
## 3. Timeouts and failures are data
|
||||
|
||||
- Hard per-run timeout: **30 minutes.** On expiry the runner records
|
||||
`TIMEOUT` for that cell and CONTINUES the matrix (an expected outcome:
|
||||
RDF Q5). Never retry a timeout hoping for a better number.
|
||||
- A crashed cell records `ERROR` the same way (see
|
||||
[code-error-handling.md](code-error-handling.md) pattern C).
|
||||
- In extrapolation and scoring, TIMEOUT/FAIL project to score 0 - they
|
||||
are never silently dropped from tables or charts (spec 10/11 flags:
|
||||
projection > 1 h = `IMPRACTICAL`, > 24 h = `FAIL`).
|
||||
|
||||
## 4. Result correctness gates the measurement
|
||||
|
||||
- Every measured run validates its result rows against the canonical
|
||||
checksum (sorted rows, float tolerance 1e-9) and records `checksum_ok`
|
||||
(see [test-pipeline-validation.md](test-pipeline-validation.md)).
|
||||
- A fast run with `checksum_ok = false` is an INVALID measurement - it
|
||||
must never enter medians, extrapolation, or the report.
|
||||
- **Q5 reads raw `friction_cycles` in every format** - summary tables and
|
||||
materialized views are forbidden for it; verify PostgreSQL did not
|
||||
satisfy it from `track_summary`
|
||||
(`EXPLAIN (ANALYZE, BUFFERS)` is recorded for PostgreSQL runs anyway).
|
||||
|
||||
## 5. Fairness across formats
|
||||
|
||||
- Identical result-set contract for all 5 implementations of each query;
|
||||
no format answers a simplified question.
|
||||
- Each format uses its idiomatic strength honestly: CSV Q1 reads the one
|
||||
target file by path; JSON Q1 loads the one coupon file; SQL uses its
|
||||
indexes. What is forbidden is capping or sampling (`LIMIT`, partial
|
||||
scans) that changes the answer.
|
||||
- The pandas CSV variant is measured as a SEPARATE variant, never mixed
|
||||
into the plain-CSV numbers (spec 08).
|
||||
- Storage sizes compare like with like: for every format record both the
|
||||
working footprint and the gzip archival footprint in
|
||||
`storage_sizes.csv` (spec 09).
|
||||
|
||||
## 6. Environment honesty - this machine, stated limits
|
||||
|
||||
- Record the hardware and software environment (CPU model, cores, RAM,
|
||||
disk type, OS, engine versions) once per benchmark session into the
|
||||
raw results; the report cites it.
|
||||
- **Cold-cache protocol on this host:** the spec's page-cache drop
|
||||
(`/proc/sys/vm/drop_caches`) is Linux-only; this project runs on
|
||||
Windows Server 2025, where it is unavailable. The cold-cache matrix
|
||||
pass is therefore SKIPPED and marked as such in results and report -
|
||||
never approximated by a fake (e.g. file re-copy tricks) without a
|
||||
rules update describing the method.
|
||||
- Quiesce the machine during measured runs (no parallel pipeline tasks,
|
||||
no background loads); the runner runs cells sequentially by design.
|
||||
|
||||
## 7. Extrapolation discipline (task 10)
|
||||
|
||||
- Scaling laws per access pattern are those fixed in spec 10 (O(1),
|
||||
O(log n + k), O(k log n), O(n), O(n / min(cores, partitions)));
|
||||
constants calibrate on the measured 150 MB point.
|
||||
- Every projected number in the report is labeled as projected, with its
|
||||
scaling assumption; measured and projected values never mix in one
|
||||
column unlabeled.
|
||||
- Hardware cost figures come from `hw_prices.yaml`
|
||||
(see [code-config-yaml.md](code-config-yaml.md)), never inlined.
|
||||
|
||||
## 8. Anti-patterns
|
||||
|
||||
- **Running cells in-process** - RSS/CPU attribution becomes fiction.
|
||||
- **Fixed execution order** - cache warmth leaks between formats.
|
||||
- **Retrying or dropping slow runs** - the slow run IS the result.
|
||||
- **Averaging over a TIMEOUT** or reporting medians of fewer than 3 valid
|
||||
runs without flagging it.
|
||||
- **A "fast" result that fails the checksum** entering any downstream
|
||||
table.
|
||||
- **Tuning one format's query while leaving others naive** - fairness is
|
||||
per-scenario, not per-format-champion.
|
||||
- **Faking cold-cache on Windows** without a documented, rules-approved
|
||||
method.
|
||||
- **Presenting projected numbers as measured** in tables or charts.
|
||||
103
docs/rules/build-pipeline-tasks.md
Normal file
103
docs/rules/build-pipeline-tasks.md
Normal file
@@ -0,0 +1,103 @@
|
||||
# Build / Pipeline tasks - the ./out tree, .done markers, task order
|
||||
|
||||
**Purpose + scope.** How the 11 pipeline tasks compose: where artifacts
|
||||
live, how completion is signaled, what order tasks run in, and what git
|
||||
does and does not track. Binding for every task implementation and for
|
||||
anyone running the pipeline.
|
||||
|
||||
---
|
||||
|
||||
## 1. The artifact tree - everything under `./out/`
|
||||
|
||||
All generated artifacts live under `./out/` relative to the repo root
|
||||
(spec 00_PLAN):
|
||||
|
||||
```
|
||||
out/
|
||||
config/ lab_config.yaml, hw_prices.yaml
|
||||
csv/ the canonical raw corpus + MANIFEST.csv
|
||||
json/ full/ (per-coupon JSON-LD), hybrid/dataset.jsonld
|
||||
sqlite/ tribo.db
|
||||
pg/ tribo.dump (pg_dump -Fc; the live DB is in PostgreSQL)
|
||||
rdf/ dataset.nt.gz, dataset.ttl, oxigraph_store/
|
||||
bench/ queries/, expected/, results_raw.csv, results_median.csv,
|
||||
storage_sizes.csv, extrapolation.csv, hardware_sizing.csv
|
||||
report/ REPORT.md, charts/, tables/, process_flow.md, diagrams/
|
||||
.done/ completion markers, one per task
|
||||
```
|
||||
|
||||
- A task writes ONLY under its own output paths; it treats its inputs as
|
||||
read-only.
|
||||
- Tasks communicate exclusively through these artifacts - never through
|
||||
shared in-process state
|
||||
(see [code-python-style.md](code-python-style.md)).
|
||||
- **`./out/` is git-ignored** (except nothing - the whole tree). Sources
|
||||
of truth are the specs, the rules, and the code; artifacts are
|
||||
reproducible from them ([data-determinism.md](data-determinism.md)).
|
||||
Never commit generated artifacts "for convenience".
|
||||
|
||||
## 2. Completion markers
|
||||
|
||||
- Each task NN writes `./out/.done/<NN>.ok` after - and only after - its
|
||||
outputs are complete and validated. The marker body carries the
|
||||
headline counts (rows, bytes, triples) downstream tasks check
|
||||
(see [test-pipeline-validation.md](test-pipeline-validation.md)).
|
||||
- A task verifies its dependencies' markers before starting and aborts
|
||||
loudly if one is missing.
|
||||
- Re-running a task: delete its own marker first, regenerate outputs,
|
||||
rewrite the marker. If its outputs changed, downstream markers are now
|
||||
stale - delete them too (the dependency graph below says which).
|
||||
|
||||
## 3. Task graph and execution order
|
||||
|
||||
```
|
||||
01 -> 02
|
||||
01 -> 03 -> (04 | 05 | 06 | 07) -> 08 -> 09 -> 10 -> 11
|
||||
09 ------> 11
|
||||
```
|
||||
|
||||
- Sequential order: 01, 02, 03, then 04-07 (independent, may run in
|
||||
parallel), 08, 09, 10, 11.
|
||||
- 02 (process diagrams) needs only 01 and can run any time after it.
|
||||
- Task 09 (benchmark execution) must run WITHOUT 04-07 style parallel
|
||||
work in the background - measurements need a quiet machine
|
||||
([bench-methodology.md](bench-methodology.md) section 6).
|
||||
|
||||
## 4. `storage_sizes.csv` - the shared append contract
|
||||
|
||||
- Location: `./out/bench/storage_sizes.csv`; columns:
|
||||
`format, variant, bytes`.
|
||||
- Tasks 04-07 APPEND their format's rows (e.g. `json,full,...`,
|
||||
`json,hybrid,...`, `sqlite,db,...`, `pg,live,...`, `pg,dump,...`,
|
||||
`rdf,nt_gz,...`, `rdf,store,...`); task 09 appends the gzip archival
|
||||
variants and consolidates.
|
||||
- Appends are idempotent per (format, variant): a re-run REPLACES its own
|
||||
prior rows, never duplicates them and never touches other formats'
|
||||
rows.
|
||||
- RDF load wall-time and similar per-format load metrics recorded per
|
||||
spec 07 go into the task's `.done` marker and the report, not into
|
||||
extra ad-hoc files.
|
||||
|
||||
## 5. Runs are resumable, not restartable-from-zero
|
||||
|
||||
- The marker system exists so a failed pipeline resumes at the failed
|
||||
task; do not delete the whole `./out/` tree to retry one task.
|
||||
- Conversely, after a deliberate corpus regeneration (03), ALL downstream
|
||||
markers and artifacts (04-11) are stale and must be removed - a mixed
|
||||
tree of old and new artifacts is worse than an empty one
|
||||
([meta-no-assumptions.md](meta-no-assumptions.md) applies before any
|
||||
such deletion).
|
||||
|
||||
## 6. Anti-patterns
|
||||
|
||||
- **Writing an artifact outside `./out/`** or a task writing into another
|
||||
task's output directory.
|
||||
- **Committing `./out/` content to git.**
|
||||
- **A marker written on partial success** - see
|
||||
[code-error-handling.md](code-error-handling.md).
|
||||
- **Running benchmarks while converters are still running.**
|
||||
- **Duplicate rows in `storage_sizes.csv`** after re-runs - replace your
|
||||
own rows.
|
||||
- **Hand-editing generated artifacts** (a "quick fix" inside
|
||||
`MANIFEST.csv` or a result CSV) - regenerate them through the owning
|
||||
task instead.
|
||||
61
docs/rules/code-comments-policy.md
Normal file
61
docs/rules/code-comments-policy.md
Normal file
@@ -0,0 +1,61 @@
|
||||
# Code comments - write WHY, not WHAT
|
||||
|
||||
**Binding** for all source files: Python, SQL, SPARQL, PowerShell, YAML, shell.
|
||||
|
||||
## Rule
|
||||
|
||||
Default to **no comment**. Add one only when the *why* is non-obvious from
|
||||
the code. If removing the comment wouldn't confuse a future reader, delete it.
|
||||
|
||||
A comment earns its place when it captures something the code can't:
|
||||
hidden constraint, ordering invariant, workaround for a named bug, vendor
|
||||
quirk, cross-file invariant the type system can't enforce.
|
||||
|
||||
## Delete on sight
|
||||
|
||||
```python
|
||||
# Read the manifest file
|
||||
manifest = read_manifest(csv_root) # function name says it
|
||||
```
|
||||
|
||||
```python
|
||||
# Increment counter
|
||||
counter += 1 # pure restatement
|
||||
```
|
||||
|
||||
```python
|
||||
# Added for the benchmark rework (see task 09)
|
||||
def run_cell(...): # belongs in commit message, rots in code
|
||||
```
|
||||
|
||||
```python
|
||||
# TODO: refactor later
|
||||
# no owner, no trigger - decoration
|
||||
```
|
||||
|
||||
## Keep
|
||||
|
||||
```python
|
||||
# Q5 must scan raw friction_cycles - track_summary is forbidden for this
|
||||
# query in every format (spec 08); do not "optimize" this into the summary.
|
||||
```
|
||||
Cross-file invariant a refactor must preserve.
|
||||
|
||||
```python
|
||||
# Column order matches MANIFEST.csv writer in generate_data.py - keep in sync.
|
||||
```
|
||||
Cross-file invariant the type system can't enforce.
|
||||
|
||||
```sql
|
||||
-- BRIN, not b-tree: cycle values are physically ordered within each
|
||||
-- partition, so BRIN gives the same pruning at ~1% of the index size.
|
||||
```
|
||||
Non-obvious engineering decision with a reason.
|
||||
|
||||
## Length
|
||||
|
||||
Inline: one line. Block: 1-4 lines. If you need more than a paragraph,
|
||||
the right home is a rule or spec doc - link it from a one-line comment.
|
||||
|
||||
Stale comments are worse than missing ones. When you touch a file, fix or
|
||||
delete comments whose *why* no longer holds.
|
||||
94
docs/rules/code-config-yaml.md
Normal file
94
docs/rules/code-config-yaml.md
Normal file
@@ -0,0 +1,94 @@
|
||||
# Code / Config - all tunables live in YAML, no magic numbers, no .env
|
||||
|
||||
**Purpose + scope.** Governs where every configurable value lives. Binding
|
||||
for all Python code in this repository. Any new configurable parameter MUST
|
||||
follow this rule when deciding where it goes.
|
||||
|
||||
The goal: one authoritative source per parameter, so regenerating any
|
||||
artifact with the same config is byte-identical, and changing a parameter
|
||||
never requires hunting constants across scripts.
|
||||
|
||||
---
|
||||
|
||||
## 1. The two YAML configs
|
||||
|
||||
| File | Owns | Written by |
|
||||
|---|---|---|
|
||||
| `./out/config/lab_config.yaml` | The laboratory model: volumes (batches / wafers / coupons / tracks / cycles), the deposition matrix, physical-model coefficients, nominal test conditions, **the random seed 20260711** | Task 01, from the spec; regenerated only deliberately |
|
||||
| `./out/config/hw_prices.yaml` | Hardware cost parameters for extrapolation (RAM $/GB, NVMe $/TB, server base prices) | Task 10 seeds editable defaults; the operator may edit |
|
||||
|
||||
Rules:
|
||||
|
||||
- **Scripts READ these files; they never duplicate values from them.**
|
||||
Task 03 reads volumes, coefficients, and the seed from
|
||||
`lab_config.yaml` - it does not hardcode 588 coupons or seed 20260711.
|
||||
Task 10 reads prices from `hw_prices.yaml` - it does not inline dollar
|
||||
figures.
|
||||
- A parameter needed by more than one task lives in the YAML, never
|
||||
copy-pasted between scripts.
|
||||
- New tunables go into the appropriate YAML with a self-describing key;
|
||||
if neither file fits, propose a new config file in a docs/rules update
|
||||
first.
|
||||
|
||||
## 2. Spec-fixed constants - named, sourced, at the top
|
||||
|
||||
Values fixed by the specs that are NOT operator-tunable (the 120-200 MB
|
||||
size gate, the 30-minute cell timeout, the 50k-row transaction batch, the
|
||||
50 ms RSS sampling interval) are module-level named constants at the top of
|
||||
the owning script, each with a one-line comment naming the spec:
|
||||
|
||||
```python
|
||||
SIZE_GATE_MB = (120, 200) # abort bounds, spec 03
|
||||
CELL_TIMEOUT_S = 30 * 60 # hard per-run timeout, spec 09
|
||||
```
|
||||
|
||||
A bare number inline in the logic (`if size > 209715200`) is forbidden.
|
||||
|
||||
## 3. The PostgreSQL DSN - one env var, never hardcoded, never logged
|
||||
|
||||
The only external resource is the local PostgreSQL 16 instance. Its DSN:
|
||||
|
||||
- Comes from the environment variable **`TRIBO_PG_DSN`**; default when
|
||||
unset: `postgresql://postgres@localhost:5432/tribo`.
|
||||
- Is read in ONE shared helper (e.g. `common/pg.py: get_dsn()`); scripts
|
||||
call the helper, never `os.environ` directly.
|
||||
- Is never hardcoded in a script, never committed in a config file, and
|
||||
never logged (a DSN may embed a password - see
|
||||
[obs-python-logging.md](obs-python-logging.md)).
|
||||
|
||||
There are no other secrets in this project. There is no `.env` file; do
|
||||
not introduce one.
|
||||
|
||||
## 4. CLI flags - per-run choices only
|
||||
|
||||
Command-line flags are for choices that vary per invocation, not for
|
||||
model parameters: `--log-level`, `--dry-run`, an output-root override, a
|
||||
`--limit` for smoke runs. A physical coefficient or a volume as a CLI flag
|
||||
is forbidden - it would silently fork the config out of `lab_config.yaml`.
|
||||
|
||||
## 5. Anti-patterns
|
||||
|
||||
- **Hardcoding a value that exists in `lab_config.yaml`** (seed, volumes,
|
||||
coefficients) - the YAML is the single source; drift here breaks
|
||||
reproducibility silently.
|
||||
- **Adding a `.env` file or reading `os.environ` scattered through
|
||||
scripts** - the DSN goes through the one shared helper; nothing else
|
||||
lives in the environment.
|
||||
- **Inline magic numbers** for spec-fixed gates and timeouts - name them
|
||||
at the top with the spec reference.
|
||||
- **Dollar prices inlined in `extrapolate.py`** - they live in
|
||||
`hw_prices.yaml` so the operator can re-cost without touching code.
|
||||
- **A model parameter as a CLI flag** - per-run flags are for run
|
||||
mechanics, not for the lab model.
|
||||
- **Committing `./out/config/` generated files to git as if they were
|
||||
sources** - the YAML under `./out/` is a generated artifact; its source
|
||||
of truth is spec 01 (see
|
||||
[build-pipeline-tasks.md](build-pipeline-tasks.md)).
|
||||
|
||||
## 6. Related documents
|
||||
|
||||
- [data-determinism.md](data-determinism.md) - the seed contract.
|
||||
- [build-pipeline-tasks.md](build-pipeline-tasks.md) - the `./out/` tree
|
||||
and what is git-ignored.
|
||||
- [obs-python-logging.md](obs-python-logging.md) - `--log-level` flag; DSN
|
||||
never logged.
|
||||
132
docs/rules/code-data-formatting.md
Normal file
132
docs/rules/code-data-formatting.md
Normal file
@@ -0,0 +1,132 @@
|
||||
# Code / Data / Formatting - ASCII-only typographic characters in source files
|
||||
|
||||
**Binding scope.** Strings that end up in any of the following MUST use ASCII
|
||||
typographic characters; never their Unicode "smart" equivalents:
|
||||
|
||||
- Python source, SQL and SPARQL query files
|
||||
- Generated data and artifacts written to disk (CSV, JSON-LD, N-Triples,
|
||||
Turtle, MANIFEST and benchmark result CSVs, log messages)
|
||||
- Configuration files (`.yaml`, `.json`, `.toml`)
|
||||
- PowerShell / shell scripts (any file processed by Windows tooling)
|
||||
- Markdown documentation (`docs/`, `README.md`, `CLAUDE.md`) - the specs
|
||||
were normalized to ASCII typography on 2026-07-11 and stay that way
|
||||
|
||||
**Long dashes are an absolute exception, no carve-outs.** Em-dash (U+2014)
|
||||
and en-dash (U+2013) MUST be replaced with ASCII hyphen-minus (`-`) in
|
||||
**every** file in this repository, including markdown documentation, code
|
||||
comments, and report prose. Reason: long dashes are visually
|
||||
indistinguishable from a hyphen in many monospace fonts, break diffs / grep /
|
||||
IDE search, and round-trip badly through Windows tooling chains (section 2).
|
||||
|
||||
---
|
||||
|
||||
## 1. The substitution table
|
||||
|
||||
| Unicode char | Codepoint | Name | ASCII replacement |
|
||||
|---|---|---|---|
|
||||
| em-dash | U+2014 | em-dash | `-` (single hyphen-minus) |
|
||||
| en-dash | U+2013 | en-dash | `-` (single hyphen-minus) |
|
||||
| horizontal bar | U+2015 | horizontal bar | `-` |
|
||||
| minus sign | U+2212 | minus sign | `-` |
|
||||
| ellipsis | U+2026 | horizontal ellipsis | `...` (three ASCII dots) |
|
||||
| left double quote | U+201C | left double quotation mark | `"` |
|
||||
| right double quote | U+201D | right double quotation mark | `"` |
|
||||
| left single quote | U+2018 | left single quotation mark | `'` |
|
||||
| right single quote | U+2019 | right single quotation mark | `'` |
|
||||
| left guillemet | U+00AB | left guillemet | `"` |
|
||||
| right guillemet | U+00BB | right guillemet | `"` |
|
||||
| nbsp | U+00A0 | non-breaking space | regular space |
|
||||
|
||||
**Single hyphen, not double.** When replacing an em-dash that separates two
|
||||
clauses, use a single `-` surrounded by spaces (`text - clause`), NOT `--`.
|
||||
|
||||
**Ranges use a single hyphen:** `Q1-Q7`, `120-200 MB`, `0.3-1.1 um` - never
|
||||
an en-dash.
|
||||
|
||||
## 2. Why - documented failure modes
|
||||
|
||||
1. **PowerShell 5.1 reads no-BOM files as Windows-1252.** Non-ASCII UTF-8
|
||||
bytes become mojibake and break string-literal parsing. This project is
|
||||
developed on Windows; any script or config touched by PowerShell is
|
||||
exposed.
|
||||
|
||||
2. **Windows -> git-bash -> tar / packaging round-trip.** At any step an
|
||||
unset locale or wrong encoding assumption produces mojibake. A UTF-8 byte
|
||||
sequence for U+2014 interpreted under a non-UTF-8 locale renders as a
|
||||
`?` replacement character in the packaged output.
|
||||
|
||||
3. **Copy-paste from Word / Google Docs / Notion / web articles.** These
|
||||
tools auto-substitute typography (`"` -> smart quotes, `'` -> smart
|
||||
apostrophe, `--` -> em-dash, `...` -> ellipsis) silently. The
|
||||
substitution survives copy into any editor and lands in source files
|
||||
unnoticed. The original specs arrived with en/em dashes this way.
|
||||
|
||||
4. **Data files must diff and grep cleanly.** The CSV corpus, MANIFEST, and
|
||||
benchmark results are compared by checksum and processed by streaming
|
||||
parsers; a stray typographic character breaks byte-level reproducibility.
|
||||
|
||||
## 3. Where the rule does NOT apply
|
||||
|
||||
- **Scientific and unit notation is content, not typography.** Characters
|
||||
such as `µ` (micro), `±`, `°C`, `×`, `Ø`, and Greek letters (`τ`, `σ`) in
|
||||
formulas inside the specs are meaningful scientific notation and are kept.
|
||||
Note the boundary: in **column names and code identifiers** the units are
|
||||
always plain ASCII (`_um`, `_nm`, `_GPa` - see
|
||||
[data-naming-units.md](data-naming-units.md)); the scientific characters
|
||||
live only in explanatory prose.
|
||||
- **International language strings** (Cyrillic, CJK, etc.) are meaningful
|
||||
content, not typographic substitutions. UTF-8 throughout is the right
|
||||
answer. (Repository artifacts are English; this matters only for quoted
|
||||
material.)
|
||||
- **Imported reference materials** in `docs/examples/` are kept verbatim as
|
||||
received; they are not binding documents.
|
||||
|
||||
## 4. Detection / enforcement
|
||||
|
||||
There is no automated check for this rule yet - **not in CI yet**. The rule is
|
||||
enforced by review and by the editing discipline in this file. The intent of a
|
||||
future detector is documented here so it can be built consistently:
|
||||
|
||||
- **What it flags.** Any occurrence of the Unicode codepoints in the section 1
|
||||
substitution table inside in-scope files. The section 3 exemptions
|
||||
(scientific notation, international content, `docs/examples/`) are the only
|
||||
carve-outs.
|
||||
- **Exit behavior.** Non-zero exit when any flagged codepoint is found under
|
||||
the scan roots, so it can later gate a commit hook or a build step.
|
||||
- **Tooling.** When this detector is created it MUST be a PowerShell script
|
||||
(this is a Windows project) and live under `tools/`.
|
||||
|
||||
## 5. Examples
|
||||
|
||||
### Good
|
||||
|
||||
```python
|
||||
# safe through every Windows tooling step
|
||||
print("Corpus size 152.3 MB - within the 120-200 MB gate.")
|
||||
label = "steady-state COF, cycles 500-1000"
|
||||
```
|
||||
|
||||
### Bad
|
||||
|
||||
```python
|
||||
# em-dash literal (U+2014) - mojibake risk on PowerShell read/write
|
||||
print("Corpus size 152.3 MB [U+2014] within the gate.")
|
||||
|
||||
# en-dash range (U+2013) - breaks grep for "Q1-Q7"
|
||||
label = "queries Q1[U+2013]Q7"
|
||||
```
|
||||
|
||||
## 6. Anti-patterns
|
||||
|
||||
- **Copy-pasting strings from a word processor or web page** into source /
|
||||
specs / config without normalization.
|
||||
- **Trusting tar / packaging to "preserve as-is".** They preserve bytes; the
|
||||
*encoding interpretation* is what gets lost when locale or encoding
|
||||
declaration is wrong on either end.
|
||||
- **Assuming the source file encoding alone is enough.** The source file may
|
||||
be fine - the failure is at downstream consumers (PowerShell read, build
|
||||
write, OS locale).
|
||||
- **Using `--` instead of `-` "for emphasis".** Single ASCII hyphen
|
||||
surrounded by spaces is the project's chosen separator.
|
||||
- **Putting `µ` or `°` into a column name or identifier** - units in names
|
||||
are ASCII (`_um`, `_C`); see [data-naming-units.md](data-naming-units.md).
|
||||
181
docs/rules/code-error-handling.md
Normal file
181
docs/rules/code-error-handling.md
Normal file
@@ -0,0 +1,181 @@
|
||||
# Code / Error handling - never swallow, always surface, fail loud
|
||||
|
||||
**Purpose + scope.** Governs every `try:` / `except:` / `raise` site in
|
||||
Python code in this repository (data generation, format converters, benchmark
|
||||
runner, extrapolation, reporting, helper tools). Binding for all contributors.
|
||||
|
||||
The pipeline's core failure principle: **a wrong artifact is worse than no
|
||||
artifact.** Every task's output feeds downstream tasks; silently degraded
|
||||
data poisons every result built on it. When in doubt, abort with a clear
|
||||
message and a non-zero exit code.
|
||||
|
||||
---
|
||||
|
||||
## 1. The rule
|
||||
|
||||
1. **Never `except: pass`.** A bare swallow is always a bug. Forbidden
|
||||
absolutely.
|
||||
2. **Never `except Exception: logger.warning("...") + continue`** for
|
||||
anything that was not an EXPECTED, documented, transient condition. If
|
||||
you swallow and continue, you must be able to state why the program can
|
||||
correctly proceed.
|
||||
3. **Always `logger.error(..., exc_info=True)` in `except` blocks.**
|
||||
`exc_info=True` captures the traceback into the log record (see
|
||||
[obs-python-logging.md](obs-python-logging.md)). Without it the original
|
||||
exception is lost.
|
||||
4. **Re-raise unless you have a defined recovery path.** If the caller
|
||||
cannot continue meaningfully without the failed operation, re-raise
|
||||
after logging.
|
||||
5. **Surface to the right channel:**
|
||||
- **CLI script (every pipeline task):** print the error to stderr and
|
||||
exit non-zero. A task that failed MUST NOT write its
|
||||
`./out/.done/<task>.ok` marker (see
|
||||
[build-pipeline-tasks.md](build-pipeline-tasks.md)).
|
||||
- **Validation gate (row counts, checksums, size bounds):** a failed
|
||||
gate is an abort, not a warning. Example: task 03 aborts when the
|
||||
generated tree is outside 120-200 MB; converters abort when row counts
|
||||
do not match `MANIFEST.csv`.
|
||||
- **Library / helper function:** re-raise; let the caller decide.
|
||||
6. **Defensive "this cannot happen" branches must surface, not silently
|
||||
`return`.** If you reach a defensive branch, log at ERROR with the
|
||||
branch name and the inputs that got you there, then raise.
|
||||
|
||||
## 2. The exception ladder - the only three sanctioned swallow patterns
|
||||
|
||||
Code may swallow ONLY in these three documented patterns. Anything else
|
||||
re-raises after logging.
|
||||
|
||||
### Pattern A: documented, recoverable transient (retry)
|
||||
|
||||
```python
|
||||
for attempt in range(MAX_RETRIES):
|
||||
try:
|
||||
return do_thing()
|
||||
except (psycopg.OperationalError, TimeoutError) as exc:
|
||||
logger.warning("attempt %d/%d failed: %s", attempt + 1, MAX_RETRIES, exc)
|
||||
time.sleep(backoff(attempt))
|
||||
# All retries exhausted - surface.
|
||||
logger.error("do_thing failed after %d retries", MAX_RETRIES)
|
||||
raise
|
||||
```
|
||||
|
||||
Justified: the per-attempt warning is acceptable; the final failure
|
||||
re-raises.
|
||||
|
||||
### Pattern B: optional best-effort cleanup
|
||||
|
||||
```python
|
||||
def _close_conn(conn) -> None:
|
||||
try:
|
||||
conn.close()
|
||||
except Exception:
|
||||
# Cleanup is best-effort; the operation already finished, log and continue.
|
||||
logger.warning("closing connection failed", exc_info=True)
|
||||
```
|
||||
|
||||
Justified: the comment names why continuing is correct; the traceback is
|
||||
still captured.
|
||||
|
||||
### Pattern C: benchmark-cell isolation - record and continue the matrix
|
||||
|
||||
```python
|
||||
try:
|
||||
result = run_cell(fmt, query, timeout_s=30 * 60)
|
||||
except CellTimeout:
|
||||
logger.error("cell %s/%s timed out", fmt, query)
|
||||
write_result_row(fmt, query, status="TIMEOUT") # recorded, not hidden
|
||||
except Exception:
|
||||
logger.error("cell %s/%s failed", fmt, query, exc_info=True)
|
||||
write_result_row(fmt, query, status="ERROR")
|
||||
```
|
||||
|
||||
Justified: spec 09 requires the benchmark matrix to survive a failing cell
|
||||
(RDF Q5 is expected to time out). The failure is RECORDED as data
|
||||
(`TIMEOUT` / `ERROR` in `results_raw.csv`), never silently dropped, and the
|
||||
run continues with the next cell. This pattern applies ONLY to the benchmark
|
||||
runner's per-cell boundary - a failure in the runner itself (setup, result
|
||||
writing) still aborts.
|
||||
|
||||
## 3. Loading into SQLite / PostgreSQL - all-or-nothing per table
|
||||
|
||||
A converter that half-loads a table leaves a database that LOOKS complete
|
||||
and silently skews every benchmark run against it.
|
||||
|
||||
1. Wrap each table's bulk load in a transaction; on any error roll back
|
||||
and abort the converter with a non-zero exit.
|
||||
2. After loading, validate row counts against `MANIFEST.csv` and fail loud
|
||||
on any mismatch (see
|
||||
[test-pipeline-validation.md](test-pipeline-validation.md)).
|
||||
3. Never write the task's `.done` marker after a partial load.
|
||||
|
||||
## 4. Anti-patterns
|
||||
|
||||
- **`except: pass`** - forbidden absolutely.
|
||||
- **`except Exception: logger.warning("could not X")` + continue** -
|
||||
forbidden unless the function documents that "could not X" is a
|
||||
recoverable expected outcome. If it is, phrase it "X skipped (expected
|
||||
condition Y)" so a reader does not grep "could not" hunting for bugs.
|
||||
- **Catching `BaseException`** - forbidden (swallows `KeyboardInterrupt`,
|
||||
`SystemExit`). Use `Exception`.
|
||||
- **`logger.error("X")` without `exc_info=True` in an `except` block** -
|
||||
forbidden. The traceback IS the diagnostic.
|
||||
- **`logger.error(str(exc))`** - forbidden; redundant with `exc_info=True`
|
||||
and loses type and stack.
|
||||
- **Returning `None` to signal failure** from a function whose contract is
|
||||
"return T" - forbidden. Either raise, or return `T | None` with
|
||||
documented semantics the caller checks.
|
||||
- **`raise Exception("...")`** - forbidden. Use a specific class:
|
||||
`ValueError` / `RuntimeError` / `KeyError` for trivial cases, a custom
|
||||
domain exception subclass (e.g. `ManifestMismatchError`) for domain
|
||||
conditions.
|
||||
- **`raise` chained without context** - inside an `except`, prefer
|
||||
`raise NewError(...) from exc` so the original cause stays visible in the
|
||||
chained traceback. A bare `raise` that re-raises the same exception is
|
||||
fine; `raise SomethingElse(...)` without `from exc` is forbidden.
|
||||
- **Writing a `.done` marker in a `finally` block** - the marker means
|
||||
"completed successfully", never "attempted".
|
||||
- **Clamping / repairing out-of-range generated values silently** - a
|
||||
physical-model bug must surface, not be masked by a `max(0, ...)` added
|
||||
"to be safe". Deliberate clamps defined by the spec (e.g. COF floor 0.12)
|
||||
are model logic, not error handling.
|
||||
|
||||
## 5. Defensive branches
|
||||
|
||||
```python
|
||||
# Good:
|
||||
def open_store(fmt: str) -> Store:
|
||||
if fmt == "sqlite":
|
||||
return SqliteStore()
|
||||
if fmt == "postgresql":
|
||||
return PgStore()
|
||||
# Defensive: a new format added without updating this factory lands here.
|
||||
logger.error("open_store: unknown format=%r", fmt)
|
||||
raise ValueError(f"unknown storage format: {fmt!r}")
|
||||
```
|
||||
|
||||
The silent-`return None` form lets the unknown value propagate and breaks
|
||||
downstream code with a confusing `AttributeError` on the wrong line.
|
||||
|
||||
## 6. Logging vs surfacing - the decision
|
||||
|
||||
For a caught exception, ask in order:
|
||||
|
||||
1. **Will the pipeline produce a wrong artifact if I continue?** Re-raise /
|
||||
abort.
|
||||
2. **Is this expected and recoverable in the caller's contract?** Log at
|
||||
WARNING with `exc_info=True`, return a sentinel the caller checks.
|
||||
3. **Is this an isolated benchmark cell (pattern C)?** Log at ERROR, record
|
||||
the status as data, continue the matrix.
|
||||
4. **Is the process going down anyway?** Log at CRITICAL with
|
||||
`exc_info=True`, then `sys.exit(1)` or let it propagate.
|
||||
|
||||
## 7. Related documents
|
||||
|
||||
- [obs-python-logging.md](obs-python-logging.md) - logging API and severity
|
||||
levels.
|
||||
- [test-pipeline-validation.md](test-pipeline-validation.md) - the
|
||||
validation gates that turn silent corruption into loud failures.
|
||||
- [build-pipeline-tasks.md](build-pipeline-tasks.md) - `.done` marker
|
||||
semantics.
|
||||
- [bench-methodology.md](bench-methodology.md) - the TIMEOUT contract for
|
||||
benchmark cells.
|
||||
93
docs/rules/code-python-style.md
Normal file
93
docs/rules/code-python-style.md
Normal file
@@ -0,0 +1,93 @@
|
||||
# Code / Python style - tabs, 3.11+, closed dependency list, streaming
|
||||
|
||||
**Purpose + scope.** Baseline style and dependency policy for every Python
|
||||
file in this repository (pipeline tasks 01-11, query implementations,
|
||||
helper tools). Binding for all contributors.
|
||||
|
||||
---
|
||||
|
||||
## 1. Language and formatting
|
||||
|
||||
- **Python 3.11+.** Use modern stdlib freely (`pathlib`, `dataclasses`,
|
||||
`tomllib` if needed); no compatibility shims for older versions.
|
||||
- **Indentation: TABS.** Set by the project owner and repeated in the
|
||||
specs (00_PLAN, 03, 09). Every Python file in this repository indents
|
||||
with tab characters, never spaces. Configure the editor per file type;
|
||||
do not "fix" tab-indented files to spaces.
|
||||
- `from __future__ import annotations` at the top of every module.
|
||||
- Type hints on public function signatures; internal helpers at author's
|
||||
discretion.
|
||||
- English for all identifiers, docstrings, and comments (see
|
||||
[meta-language-style.md](meta-language-style.md) - Russian is the
|
||||
conversation language, not the code language).
|
||||
- Comments follow [code-comments-policy.md](code-comments-policy.md):
|
||||
default to none, keep only non-obvious WHY.
|
||||
|
||||
## 2. Dependencies - a closed list
|
||||
|
||||
The stdlib is the default. The ONLY permitted third-party packages:
|
||||
|
||||
| Package | Used by | For |
|
||||
|---|---|---|
|
||||
| `numpy` | 03 | vectorized generation, seeded RNG |
|
||||
| `PyYAML` | 01, 03, 10 | reading/writing the YAML configs |
|
||||
| `rdflib` | 04, 07 | JSON-LD validation, RDF serialization |
|
||||
| `oxigraph` | 07, 09 | embedded queryable triplestore |
|
||||
| `ijson` | 08, 09 | streaming JSON parsing |
|
||||
| `psycopg` (v3) | 06, 08, 09 | PostgreSQL COPY and queries |
|
||||
| `psutil` | 09 | RSS / CPU / IO measurement |
|
||||
| `matplotlib` | 11 | report charts |
|
||||
| `pandas` | 08 (optional) | ONLY as the separately-measured second CSV query variant; never in the pipeline itself |
|
||||
|
||||
Adding any other package requires updating this table first. In
|
||||
particular forbidden: ORMs, logging frameworks
|
||||
([obs-python-logging.md](obs-python-logging.md)), CLI frameworks
|
||||
(argparse suffices), `requests`/HTTP clients (nothing to call).
|
||||
|
||||
Task 03 (data generation) is stricter by spec: **stdlib + numpy only.**
|
||||
|
||||
## 3. Script shape - every task is a self-contained CLI
|
||||
|
||||
- One entry script per pipeline task, runnable as
|
||||
`python <script>.py [flags]`.
|
||||
- `argparse` with `--log-level` (see
|
||||
[obs-python-logging.md](obs-python-logging.md)) and task-appropriate
|
||||
per-run flags ([code-config-yaml.md](code-config-yaml.md) section 4).
|
||||
- A module docstring stating the task number, what it produces, and its
|
||||
spec file.
|
||||
- `main() -> int` returning the exit code; `sys.exit(main())` under
|
||||
`if __name__ == "__main__":`.
|
||||
- Exit code 0 only on full success (outputs written, validation passed,
|
||||
marker written).
|
||||
|
||||
## 4. Streaming discipline - the corpus never fits in memory by design
|
||||
|
||||
The generated corpus is ~150 MB and its scaled-up target is 600 GB; code
|
||||
must behave as if the data never fits in RAM:
|
||||
|
||||
- **Write generated rows as you produce them**; never accumulate the full
|
||||
cycle/loop dataset in a list (spec 03: "stream rows; never hold the
|
||||
full cycle dataset in memory").
|
||||
- **Read CSV/JSON with iterators** (`csv.reader` row-by-row, `ijson`
|
||||
events); never `read()` a bulk file whole or `json.load` the full
|
||||
corpus.
|
||||
- Batch database work (`executemany` / `COPY` in chunks), keeping only
|
||||
the current batch in memory.
|
||||
- Bounded aggregation state is fine (per-track summaries, counters);
|
||||
unbounded accumulation of raw rows is the thing being forbidden.
|
||||
|
||||
## 5. Anti-patterns
|
||||
|
||||
- **Spaces for indentation** in Python files - the project standard is
|
||||
tabs.
|
||||
- **An unlisted third-party import** - extend the section 2 table first
|
||||
or use the stdlib.
|
||||
- **`pd.read_csv` in pipeline code** - pandas exists only as the
|
||||
separately-measured Q-variant in spec 08.
|
||||
- **Loading a bulk artifact whole** (`json.load` on the full corpus,
|
||||
`readlines()` on a cycles CSV) - stream it.
|
||||
- **A task importable only from a specific CWD** - resolve paths from a
|
||||
repo-root anchor, not the current directory.
|
||||
- **Two tasks sharing mutable module state** - tasks communicate through
|
||||
`./out/` artifacts only (see
|
||||
[build-pipeline-tasks.md](build-pipeline-tasks.md)).
|
||||
85
docs/rules/data-determinism.md
Normal file
85
docs/rules/data-determinism.md
Normal file
@@ -0,0 +1,85 @@
|
||||
# Data / Determinism - one seed, byte-identical regeneration
|
||||
|
||||
**Purpose + scope.** The reproducibility contract for all generated data.
|
||||
Binding for task 03 (CSV generation) and for every piece of code that
|
||||
produces values which end up inside the corpus or its conversions.
|
||||
|
||||
The entire evaluation rests on this: benchmark results are only comparable
|
||||
because every format holds EXACTLY the same data, and the study is only
|
||||
reviewable because anyone can regenerate that data bit-for-bit.
|
||||
|
||||
---
|
||||
|
||||
## 1. The seed
|
||||
|
||||
- **The global seed is `20260711`.** It lives in `lab_config.yaml`
|
||||
(written by task 01) and is READ from there - never hardcoded in a
|
||||
generation script (see [code-config-yaml.md](code-config-yaml.md)).
|
||||
- Changing the seed is a project-level decision, never a side effect. It
|
||||
invalidates the whole `./out/` tree, every checksum, and every measured
|
||||
result (see [meta-no-assumptions.md](meta-no-assumptions.md)).
|
||||
|
||||
## 2. Sanctioned randomness - seeded generators only
|
||||
|
||||
- The ONLY random sources are `random.Random(seed)` and
|
||||
`numpy.random.default_rng(seed)`, constructed explicitly from the
|
||||
config seed.
|
||||
- Derive per-entity generators deterministically when parallel or
|
||||
out-of-order generation needs them (e.g.
|
||||
`default_rng([seed, batch_index, coupon_index])`) - never from time,
|
||||
never from a global unseeded state.
|
||||
- Forbidden anywhere in generation code: module-level `random.*` calls,
|
||||
`numpy.random.*` legacy global functions, `os.urandom`, `uuid.uuid4`,
|
||||
`hash()`-dependent ordering (Python string hashing is salted per
|
||||
process).
|
||||
|
||||
## 3. No wall-clock in the data
|
||||
|
||||
- Every timestamp inside the corpus is SIMULATED - computed from the lab
|
||||
schedule model - never `datetime.now()` / `time.time()`.
|
||||
- Real wall-clock time may appear only in logs and in measurement records
|
||||
(benchmark wall times in `./out/bench/` are measurements, not corpus
|
||||
data).
|
||||
|
||||
## 4. Deterministic everything else
|
||||
|
||||
Randomness is not the only nondeterminism; all of these are binding:
|
||||
|
||||
- **Iteration order.** Iterate in a defined order (sorted paths, explicit
|
||||
index loops). Never rely on `os.listdir` / `glob` / `set` / dict-of-set
|
||||
ordering for anything that affects output. Sort directory listings
|
||||
before use.
|
||||
- **Float formatting.** Format floats with an explicit spec (e.g.
|
||||
`f"{x:.6g}"`), chosen once per column family; repr drift between runs
|
||||
or Python versions must not change file bytes.
|
||||
- **Line endings and encoding.** Generated files are UTF-8 with `\n`
|
||||
newlines, on every platform (`open(..., newline="")` for csv writers).
|
||||
- **Parallelism.** If generation is parallelized, each worker's output is
|
||||
deterministic (derived generators, fixed partitioning) and the merge
|
||||
order is fixed.
|
||||
|
||||
## 5. The proof - MANIFEST sha256
|
||||
|
||||
- `./out/csv/MANIFEST.csv` records sha256 per generated file (spec 03).
|
||||
**Regenerating with the same `lab_config.yaml` MUST reproduce every
|
||||
sha256 exactly.** This is the acceptance test for this whole rule -
|
||||
run generation twice when touching generator code and diff the
|
||||
manifests.
|
||||
- Downstream conversions must be equally deterministic in CONTENT (same
|
||||
rows, same values); byte-identity of database files is not required
|
||||
(SQLite/PostgreSQL internals may differ), which is why their gate is
|
||||
row counts + query-result checksums instead
|
||||
(see [test-pipeline-validation.md](test-pipeline-validation.md)).
|
||||
|
||||
## 6. Anti-patterns
|
||||
|
||||
- **Hardcoding `20260711`** in a script instead of reading the config.
|
||||
- **An unseeded or globally-seeded random call** anywhere in generation.
|
||||
- **`datetime.now()` in corpus values** - simulated schedule only.
|
||||
- **Ordering that depends on the filesystem or hash salting** - sort
|
||||
explicitly.
|
||||
- **"Harmless" float repr changes** - a formatting change is a corpus
|
||||
change; it must be deliberate and re-baselines every checksum.
|
||||
- **Regenerating the corpus to "refresh" it** - identical input config
|
||||
must yield identical output; if bytes changed without a config change,
|
||||
that is a determinism bug to fix, not a new baseline to accept.
|
||||
120
docs/rules/data-naming-units.md
Normal file
120
docs/rules/data-naming-units.md
Normal file
@@ -0,0 +1,120 @@
|
||||
# Data / Naming and units - IDs, timestamps, SI-explicit column names
|
||||
|
||||
**Purpose + scope.** The naming grammar for entities, timestamps, and
|
||||
measurement columns across ALL representations of the corpus: CSV, JSON-LD,
|
||||
SQLite, PostgreSQL, RDF. Binding for generation, conversion, queries, and
|
||||
reporting. This file also fixes two points the specs leave ambiguous
|
||||
(sections 3 and 4).
|
||||
|
||||
---
|
||||
|
||||
## 1. Entity identifiers
|
||||
|
||||
The hierarchy encodes containment left-to-right; each level appends one
|
||||
segment:
|
||||
|
||||
| Entity | Code grammar | Example |
|
||||
|---|---|---|
|
||||
| batch | `B<nnn>` | `B721` |
|
||||
| wafer | `<batch>-W<n>` | `B721-W1` |
|
||||
| coupon | `<wafer>-C<nn>` (01..49, the 7x7 grid) | `B721-W1-C07` |
|
||||
| track | `<coupon>-T<n>` (1..3) | `B721-W1-C07-T2` |
|
||||
| run | `R<n>` (1..4) | `R3` |
|
||||
|
||||
- **CSV** stores the bare code (`B721-W1-C07`) in `<entity>_code`-style
|
||||
columns.
|
||||
- **JSON-LD / RDF** use the prefixed CURIE form as the node identity:
|
||||
`batch:B721`, `wafer:B721-W1`, `coupon:B721-W1-C07`,
|
||||
`track:B721-W1-C07-T2`, `run:R1` (spec 00_PLAN global conventions).
|
||||
- **SQL** stores the bare code as the `<singular>_code` TEXT UNIQUE column
|
||||
next to the integer surrogate key
|
||||
(see [db-sql-schema.md](db-sql-schema.md)).
|
||||
- A code is never parsed to recover its parent where a proper reference
|
||||
exists; the parent relation is explicit in every format (directory
|
||||
nesting, `partOf`, FK).
|
||||
|
||||
## 2. Timestamps
|
||||
|
||||
- ISO-8601, UTC, `Z` suffix, second precision:
|
||||
`2026-03-14T09:30:00Z`.
|
||||
- All corpus timestamps are simulated
|
||||
(see [data-determinism.md](data-determinism.md) section 3).
|
||||
- In SQL: `TIMESTAMPTZ` in PostgreSQL; TEXT in ISO-8601 form in SQLite
|
||||
(sortable lexicographically by construction).
|
||||
|
||||
## 3. Measurement columns - unit suffix is part of the name
|
||||
|
||||
Every numeric measurement column ends in an explicit SI unit token. The
|
||||
canonical tokens (as they appear in CSV headers):
|
||||
|
||||
| Token | Unit | Example columns |
|
||||
|---|---|---|
|
||||
| `_um` | micrometre | `thickness_um`, `position_um` |
|
||||
| `_nm` | nanometre | `ra_nm` |
|
||||
| `_GPa` | gigapascal | `hardness_GPa`, `reduced_modulus_GPa` |
|
||||
| `_mN` | millinewton | `max_load_mN`, `friction_force_mN` |
|
||||
| `_wtpct` | weight percent | `au_wtpct` |
|
||||
| `_um3` | cubic micrometre | `wear_volume_um3` |
|
||||
| `_m` | metre | `sliding_distance_m` |
|
||||
| `_mm_s` | millimetre per second | `speed_mm_s` |
|
||||
| `_V` | volt | `discharge_voltage_V` |
|
||||
| `_W` | watt | `power_W` |
|
||||
| `_C` | degree Celsius | `temperature_C` |
|
||||
| `_pct` | percent (non-composition) | `rh_pct`, `cpu_util_pct` |
|
||||
| `_s` / `_mb` | seconds / megabytes (benchmark outputs) | `wall_s`, `peak_rss_mb` |
|
||||
|
||||
- Unit tokens are plain ASCII - never `µ`, `°`, or Unicode superscripts
|
||||
(see [code-data-formatting.md](code-data-formatting.md)).
|
||||
- Dimensionless quantities carry no token (`cof`, `cycle`, `k_archard`).
|
||||
- New measurement columns pick from this table or add a row to it in the
|
||||
same commit.
|
||||
|
||||
## 4. Canonical case mapping CSV -> SQL (ambiguity resolved)
|
||||
|
||||
The specs write CSV headers with SI capitalization (`hardness_GPa`, spec
|
||||
03) but SQL DDL in lowercase (`hardness_gpa`, spec 05). Binding
|
||||
resolution:
|
||||
|
||||
- **CSV headers keep SI capitalization** (`_GPa`, `_mN`, `_V`, `_W`,
|
||||
`_C`) - they mirror instrument output conventions.
|
||||
- **SQL / JSON-LD / RDF property names are the plain lowercase of the CSV
|
||||
name** (`hardness_gpa`, `max_load_mn`). One mechanical rule, no
|
||||
per-column table to maintain.
|
||||
- Converters map with `csv_name.lower()` - never hand-maintained rename
|
||||
dicts that can drift.
|
||||
|
||||
## 5. `au_wtpct_mean` (spec gap closed)
|
||||
|
||||
Specs 05/06 index `coupons(au_wtpct_mean)` but spec 03 never names the
|
||||
column. Binding definition:
|
||||
|
||||
- `au_wtpct_mean` = the arithmetic mean of `au_wtpct` over the coupon's
|
||||
400 XRF map points, computed by task 03.
|
||||
- It is written into `coupon_info.csv` by generation (not derived later
|
||||
by converters), so every format inherits the identical value, and Q2
|
||||
("coupons with mean Au = 10 +/- 0.5 wt%") means the same rows
|
||||
everywhere.
|
||||
|
||||
## 6. Benchmark artifact columns
|
||||
|
||||
Result files under `./out/bench/` follow the same discipline:
|
||||
`results_raw.csv` columns are
|
||||
`format, query, run, wall_s, peak_rss_mb, cpu_s, cpu_util_pct, read_mb,
|
||||
rows, checksum_ok` (spec 09); `storage_sizes.csv` columns are
|
||||
`format, variant, bytes` (see
|
||||
[build-pipeline-tasks.md](build-pipeline-tasks.md)). Extending either
|
||||
file extends this list in the same commit.
|
||||
|
||||
## 7. Anti-patterns
|
||||
|
||||
- **A measurement column without a unit token** (`thickness`, `load`) -
|
||||
unreadable and un-greppable across formats.
|
||||
- **Unit in a comment instead of the name** - comments do not survive
|
||||
format conversion; names do.
|
||||
- **Parsing `B721-W1-C07` to find the wafer** where a real reference
|
||||
exists in the format at hand.
|
||||
- **A hand-maintained CSV->SQL rename mapping** - the mapping is
|
||||
`lower()`, nothing else.
|
||||
- **Recomputing `au_wtpct_mean` in a converter** - it is generated once
|
||||
into `coupon_info.csv`; converters copy it.
|
||||
- **Unicode units in identifiers** (`µm`, `°C`) - ASCII tokens only.
|
||||
122
docs/rules/db-sql-schema.md
Normal file
122
docs/rules/db-sql-schema.md
Normal file
@@ -0,0 +1,122 @@
|
||||
# DB / SQL / Schema - conventions for the SQLite and PostgreSQL benchmark targets
|
||||
|
||||
**Purpose.** Naming, key, and loading conventions for the two relational
|
||||
storage formats under evaluation: the SQLite file (`./out/sqlite/tribo.db`,
|
||||
spec [05](../specs/05_convert_sqlite.md)) and the PostgreSQL 16 database
|
||||
`tribo` (spec [06](../specs/06_convert_postgresql.md)).
|
||||
**Binding scope.** Every table, index, and load routine in either engine.
|
||||
The two engines carry the SAME logical schema; they differ only in types,
|
||||
storage options, and load mechanics. The full table list and DDL signatures
|
||||
live in the specs - this file codifies the conventions the DDL must follow,
|
||||
and it wins where a spec detail is ambiguous.
|
||||
|
||||
---
|
||||
|
||||
## 1. Two kinds of tables
|
||||
|
||||
- **Dimension tables** (`batches`, `wafers`, `coupons`, `runs`,
|
||||
`instruments`, `tracks`): small, joined constantly.
|
||||
- PK: integer surrogate, named `<singular>_id` (`coupon_id`, `track_id`).
|
||||
Never a bare `id`, never TEXT.
|
||||
- The natural key from the CSV corpus (`B721`, `B721-W1-C07`,
|
||||
`B721-W1-C07-T2` - see [data-naming-units.md](data-naming-units.md))
|
||||
is stored as a TEXT column named `<singular>_code`, declared `UNIQUE`.
|
||||
It is for lookups and human-readable output, never the join key.
|
||||
- **Bulk tables** (`friction_cycles`, `friction_loop_points`, `xrf_points`,
|
||||
`profilometry_points`, `nanoindentation`, `afm`, `wear`): millions of
|
||||
rows, scanned and filtered.
|
||||
- Reference dimensions by integer FK columns only. **TEXT foreign keys in
|
||||
bulk tables are forbidden** - they bloat the file and slow every scan.
|
||||
- PK is the natural composite where one exists: `(track_id, cycle)` for
|
||||
cycles, `(track_id, cycle, pt)` for loop points; single-column
|
||||
`track_id` / `coupon_id` where the grain is one row per entity. In
|
||||
SQLite, composite-PK bulk tables are `WITHOUT ROWID`.
|
||||
- No surrogate `bigserial` on bulk tables - it adds bytes and nothing
|
||||
else.
|
||||
|
||||
## 2. Table and column naming
|
||||
|
||||
- Tables: lowercase, snake_case, plural (`friction_cycles`, not
|
||||
`FrictionCycle`).
|
||||
- Columns: lowercase snake_case; measurement columns carry the SI unit
|
||||
suffix in **lowercase** (`hardness_gpa`, `max_load_mn`,
|
||||
`wear_volume_um3`). The CSV corpus keeps SI capitalization
|
||||
(`hardness_GPa`); the SQL mapping is a plain lowercase of the CSV name -
|
||||
see the canonical mapping in
|
||||
[data-naming-units.md](data-naming-units.md).
|
||||
- Denormalization is deliberate and limited: `environment` and `load_mn`
|
||||
are copied onto `tracks` (from `runs` / test conditions) because
|
||||
Q4 and Q7 filter on them constantly. Do not denormalize further without
|
||||
updating this rule.
|
||||
|
||||
## 3. Indexes - only what Q1-Q7 use
|
||||
|
||||
- Create indexes AFTER the bulk load, never before.
|
||||
- The index set is driven by the benchmark queries: `coupons(au_wtpct_mean)`,
|
||||
`tracks(run_id)`, `tracks(coupon_id)`, `runs(environment)`, composite
|
||||
`tracks(environment, load_mn)`; PostgreSQL adds BRIN on
|
||||
`friction_cycles(cycle)` per partition.
|
||||
- Naming: `ux_<table>_<cols>` for unique, `ix_<table>_<cols>` for
|
||||
non-unique.
|
||||
- Do not over-index: an index no query uses is disk footprint that skews
|
||||
the storage comparison AND load-time cost. Every index must name the
|
||||
query it serves (a one-line comment in the DDL).
|
||||
|
||||
## 4. Loading
|
||||
|
||||
- **SQLite:** during load `PRAGMA journal_mode=OFF, synchronous=OFF,
|
||||
cache_size=-262144`; insert via `executemany` in transactions of 50k
|
||||
rows; after load `PRAGMA journal_mode=WAL`, `ANALYZE`, `VACUUM`.
|
||||
- **PostgreSQL:** load via `COPY FROM STDIN` (psycopg3 `copy`), never
|
||||
row-by-row INSERT; `friction_cycles` and `friction_loop_points` are
|
||||
declaratively RANGE-partitioned by `track_id` (12 partitions of 120
|
||||
tracks); `VACUUM ANALYZE` after load.
|
||||
- Loads are all-or-nothing per table and validated against `MANIFEST.csv`
|
||||
row counts - see [code-error-handling.md](code-error-handling.md)
|
||||
section 3 and [test-pipeline-validation.md](test-pipeline-validation.md).
|
||||
- On every SQLite connection that relies on FK behaviour:
|
||||
`PRAGMA foreign_keys = ON` (SQLite silently ignores FK clauses without
|
||||
it). Bulk tables MAY declare FK columns without enforced constraints for
|
||||
load speed - row-count validation is the integrity gate; whichever choice
|
||||
is made must be the same in both engines.
|
||||
|
||||
## 5. `track_summary` - precomputed, and off-limits to Q5
|
||||
|
||||
Both engines carry a `track_summary` (plain table computed during load in
|
||||
SQLite; materialized view in PostgreSQL) with per-track derived values
|
||||
(`cof_ss_mean`, `run_in_cycles`, ...). It exists for Q2/Q3/Q4/Q6. **Q5 must
|
||||
never read it** - Q5 is the forced full-scan benchmark over raw
|
||||
`friction_cycles` in every format (spec
|
||||
[08](../specs/08_benchmark_queries.md)).
|
||||
|
||||
## 6. Engine parity
|
||||
|
||||
- Same logical schema, same table names, same column names, same row
|
||||
counts in both engines.
|
||||
- Q1-Q7 executed against SQLite and PostgreSQL MUST return identical
|
||||
result sets (checksum parity with 1e-9 float tolerance - the canonical
|
||||
expected checksums are computed from PostgreSQL and cross-validated
|
||||
against SQLite; see
|
||||
[test-pipeline-validation.md](test-pipeline-validation.md)).
|
||||
- A schema change in one engine is a schema change in both, in the same
|
||||
commit.
|
||||
|
||||
## 7. Anti-patterns
|
||||
|
||||
- **Bare `id` PK, TEXT PK, or a GUID column** - this is a load-once
|
||||
analytical schema; surrogate integer `<singular>_id` on dimensions,
|
||||
natural composites on bulk tables.
|
||||
- **TEXT FK in a bulk table** (`friction_cycles.track_code`) - integer FKs
|
||||
only.
|
||||
- **A `bigserial` surrogate on `friction_cycles` / `friction_loop_points`**
|
||||
- dead weight at 1.4M / 2.9M rows.
|
||||
- **Creating indexes before the bulk load** - doubles load time for
|
||||
nothing.
|
||||
- **An index without a named query** - footprint that skews the format
|
||||
comparison.
|
||||
- **Rewriting Q5 to use `track_summary`** - breaks the benchmark's
|
||||
full-scan contract.
|
||||
- **`updated_at` / audit columns / triggers** - this is a read-only
|
||||
benchmark target rebuilt from CSV; it has no mutation lifecycle.
|
||||
- **Divergent schemas between the engines** - parity is what makes the
|
||||
benchmark comparison valid.
|
||||
59
docs/rules/meta-concise-answers.md
Normal file
59
docs/rules/meta-concise-answers.md
Normal file
@@ -0,0 +1,59 @@
|
||||
# Meta / Concise answers
|
||||
|
||||
**Binding on every response.** Match length to the question. Stop after the answer.
|
||||
|
||||
## Rule
|
||||
|
||||
Do not append: background, rationale, anticipated follow-ups, recap, summary,
|
||||
TL;DR, "why this matters", offers to do more. If the user said "concise",
|
||||
"short", "one line", or "just" - obey literally.
|
||||
|
||||
## Length budget
|
||||
|
||||
| Question shape | Max response |
|
||||
|---|---|
|
||||
| "What is X?" / "Where is Y?" / yes-no | 1 line |
|
||||
| "How do I do X?" | 1-3 lines, commands only |
|
||||
| "How do I test X?" | 3-5 lines, commands + check |
|
||||
| "Show me X" (file, diagram, output) | The thing itself, no preamble |
|
||||
| "Compare A and B" | 1 short table or 2-3 bullets/side |
|
||||
| Open-ended architecture / discussion | Full prose acceptable |
|
||||
|
||||
Diagrams / lists / tables: **8-line ceiling** unless user asks for more.
|
||||
No ASCII box-drawing unless asked. When the user says "I don't care about X" -
|
||||
omit X, do not list it "for completeness".
|
||||
|
||||
## Examples
|
||||
|
||||
**Bad** - user asks "what does this option do?", assistant returns 6-option
|
||||
table with pros/cons. **Good** - one sentence about the one option.
|
||||
|
||||
**Bad** - user asks "show ER diagram, no fields", assistant returns 80-line
|
||||
ASCII with cardinality table and "what changes" section. **Good** - 4-5
|
||||
lines of `entity --< child` arrows for the asked entities only.
|
||||
|
||||
**Bad** - yes/no question answered with a paragraph of nuance before the
|
||||
yes/no. **Good** - yes/no first, nuance only if asked.
|
||||
|
||||
## Exceptions (these ARE the answer, no length cap)
|
||||
|
||||
File contents requested, code requested, test/log output, final summary
|
||||
after multi-step tool work, plans the user asked to be detailed.
|
||||
|
||||
## Self-check before sending
|
||||
|
||||
- Did the user ask a yes/no? -> answer yes/no in the first line, nuance
|
||||
only after, only if useful.
|
||||
- Did the user ask for one thing? -> deliver that one thing only.
|
||||
- About to write more than ~10 lines for a question that needs ~5? -> cut
|
||||
everything past line 10 unless it is code, output, or a file path.
|
||||
- Did the user say "concise" / "short" / "one line" / "just"? -> obey
|
||||
literally: one sentence or one command/path.
|
||||
|
||||
## Recovery
|
||||
|
||||
If the user complains the output is too long:
|
||||
|
||||
1. Acknowledge in one line.
|
||||
2. Re-answer the original question in one line.
|
||||
3. Capture it as an anti-pattern in the Examples section above.
|
||||
33
docs/rules/meta-concise-questions.md
Normal file
33
docs/rules/meta-concise-questions.md
Normal file
@@ -0,0 +1,33 @@
|
||||
# Ask extremely short, concise questions
|
||||
|
||||
Binding on EVERY question put to the user.
|
||||
|
||||
## Rules
|
||||
|
||||
- **One decision per question.** Do not bundle unrelated questions.
|
||||
- **Minimal context.** State only what is needed to answer: the situation in one
|
||||
short clause, the concrete options, and what the answer changes. Nothing else.
|
||||
- **No preamble.** Do NOT precede a question with analysis, restated reasoning,
|
||||
background, evidence, or a summary of what you just did. The user has that
|
||||
context; if a fact is truly needed to decide, compress it to one clause.
|
||||
- **Prefer the smallest form.** A yes/no, or a 2-3 option pick. Put the question
|
||||
itself first or make it unmissable.
|
||||
- **Self-contained but short.** The options must be understandable without opening
|
||||
another document, but spell them out in a few words, not paragraphs.
|
||||
- **No useless information.** Every word must help the user decide. Delete anything
|
||||
that does not.
|
||||
|
||||
## Anti-patterns
|
||||
|
||||
- A multi-paragraph lead-in before the actual question.
|
||||
- Re-explaining the problem the user already stated.
|
||||
- Listing options with a paragraph of trade-offs each.
|
||||
- Asking two or more separate decisions in one run of prose.
|
||||
|
||||
## Shape to aim for
|
||||
|
||||
```
|
||||
<one-clause situation>. <Question>?
|
||||
1. <option> - <few words>
|
||||
2. <option> - <few words>
|
||||
```
|
||||
62
docs/rules/meta-cursor-rules.md
Normal file
62
docs/rules/meta-cursor-rules.md
Normal file
@@ -0,0 +1,62 @@
|
||||
# Meta / Cursor rule artifact - commit-message generation
|
||||
|
||||
**Binding** for keeping Cursor's commit-message generation aligned with this
|
||||
project's commit convention.
|
||||
|
||||
The development environment is the Cursor IDE with the Claude Code extension.
|
||||
Claude Code reads the root `CLAUDE.md` and `docs/rules/` directly and needs no
|
||||
`.cursorrules`. Cursor's OWN built-in AI (Chat/Composer and the SCM "sparkle"
|
||||
commit-message generator) does not read those files - it reads `.cursorrules`.
|
||||
Since the user creates all commits themselves (see
|
||||
[`meta-git-commits.md`](meta-git-commits.md)) and may use the one-click SCM
|
||||
generator to draft the message, `.cursorrules` must exist and must encode the
|
||||
commit convention.
|
||||
|
||||
This project ships a SINGLE Cursor rules file: the root
|
||||
[`.cursorrules`](../../.cursorrules). There is no `.cursor/rules/*.mdc`
|
||||
directory here, and none should be added. All Cursor rule content lives in
|
||||
`.cursorrules`.
|
||||
|
||||
---
|
||||
|
||||
## 1. The requirement
|
||||
|
||||
`.cursorrules` MUST encode this project's commit-message convention so that
|
||||
Cursor's generator emits conforming messages. The authoritative convention
|
||||
lives in [`meta-git-commits.md`](meta-git-commits.md); `.cursorrules` mirrors
|
||||
it as plain-text instructions to the generator.
|
||||
|
||||
The failure mode this rule prevents: a missing or stale `.cursorrules`, so the
|
||||
one-click SCM generator falls back to Cursor defaults and emits unprefixed junk
|
||||
(`Update files`, `Improve project`).
|
||||
|
||||
## 2. What `.cursorrules` must encode
|
||||
|
||||
1. **First-line format** - `type(scope): description`, lowercase imperative
|
||||
summary in English, under 72 chars, no trailing period.
|
||||
2. **Allowed types (closed list)** - `feat fix perf refactor security docs ci
|
||||
chore test`.
|
||||
3. **Allowed scopes (closed list)** - `specs rules docs config datagen convert
|
||||
bench report tools all`, with the area mapping from
|
||||
[`meta-git-commits.md`](meta-git-commits.md).
|
||||
4. **Coverage rule** - the subject is only a title; multi-area diffs need a
|
||||
blank line and a 2-6 bullet body covering every meaningful changed area; use
|
||||
`all` when no narrower scope honestly fits; split unrelated work.
|
||||
5. **Anti-examples** - forbid vague/unprefixed lines (`Update files`, `Improve
|
||||
project`, `clarify rules`).
|
||||
6. **Allowed special forms** - `Merge ...` commits are allowed as-is;
|
||||
`release(scope): vX.Y.Z` is allowed for releases.
|
||||
|
||||
## 3. Keeping the mirror in sync
|
||||
|
||||
No commit-msg hook is installed in this repository; the convention is enforced
|
||||
by review. `.cursorrules` and [`meta-git-commits.md`](meta-git-commits.md)
|
||||
must agree: same type list, same scope list, same first-line format. If the
|
||||
convention changes, update both files in the same change set.
|
||||
|
||||
## 4. Scope boundary
|
||||
|
||||
This rule governs the commit-message *convention materialization* - not who may
|
||||
create commits. Commit authorship is governed by
|
||||
[`meta-git-commits.md`](meta-git-commits.md): rules files describe the
|
||||
convention; only the user creates actual commits.
|
||||
99
docs/rules/meta-git-commits.md
Normal file
99
docs/rules/meta-git-commits.md
Normal file
@@ -0,0 +1,99 @@
|
||||
# Meta / Git commits - never on behalf of the user
|
||||
|
||||
**Binding** for every contributor (human or AI agent) touching this
|
||||
repository. Applies to **all** git history-modifying operations.
|
||||
|
||||
---
|
||||
|
||||
## 1. The rule
|
||||
|
||||
**Never create a git commit on behalf of the user. Ever.** The user is the
|
||||
only entity allowed to materialize a commit in this repo.
|
||||
|
||||
This covers, without exception:
|
||||
|
||||
- `git commit` (with or without `-a`, `-m`, `--amend`)
|
||||
- `git merge` - never finalize one
|
||||
- `git rebase` (interactive or otherwise) - never start one
|
||||
- `git revert` - never run; the user decides whether a change is undone
|
||||
- `git cherry-pick`
|
||||
- `git stash`
|
||||
- `git tag` (annotated or lightweight)
|
||||
- `git push` of any kind - pushing is the user's call
|
||||
- `git reset` / `git checkout` that changes the working tree or refs
|
||||
- Any wrapper script or `gh` API call that would create a commit / tag /
|
||||
merge on the user's behalf
|
||||
|
||||
For an AI agent, git is **read-only**: reading history (`git log`,
|
||||
`git diff`, `git status`, `git show`) and inspecting branches is fine;
|
||||
anything that writes to the index, refs, or `objects/` - including
|
||||
`git add` - is left to the user. (This matches the machine-level
|
||||
enforcement: git write commands by the agent are denied by a hook.)
|
||||
|
||||
## 2. Why
|
||||
|
||||
- Commit authorship and message wording are decisions the user owns - they are
|
||||
durable, public, and signed against the user's name and email.
|
||||
- "Tiny" commits that look harmless ("fix typo", "delete unused file") still
|
||||
rewrite history and cannot be quietly reversed once pushed.
|
||||
- Generated data and measured results in this project take hours to rebuild;
|
||||
what enters history and when is a conscious decision, not a side effect.
|
||||
|
||||
## 3. What to do instead
|
||||
|
||||
- Make the working-tree changes.
|
||||
- Report what changed and offer a draft commit message **as plain text** in the
|
||||
conversation, together with the exact `git add` / `git commit` commands for
|
||||
the user to run themselves.
|
||||
|
||||
## 4. Commit-message format
|
||||
|
||||
When you draft a message, follow this project's convention:
|
||||
|
||||
- **First line** - `type(scope): description`, lowercase imperative summary in
|
||||
English, under 72 chars, no trailing period.
|
||||
- **Types (closed list)** - `feat fix perf refactor security docs ci chore
|
||||
test`.
|
||||
- **Scopes (closed list, owner-approved 2026-07-11)** - `specs rules docs
|
||||
config datagen convert bench report tools all`. Pick by the area of the
|
||||
changed files:
|
||||
- `specs` = `docs/specs/**`
|
||||
- `rules` = `docs/rules/**`
|
||||
- `docs` = other prose docs (`README.md`, root `CLAUDE.md`,
|
||||
`docs/examples/**`)
|
||||
- `config` = configuration files (`.gitignore`, `.cursorrules`, editable
|
||||
YAML defaults such as hardware prices)
|
||||
- `datagen` = lab-configuration and data-generation code (tasks 01-03:
|
||||
`lab_config` generation, process diagrams, `generate_data.py`)
|
||||
- `convert` = format converters (tasks 04-07: JSON-LD, SQLite,
|
||||
PostgreSQL, RDF)
|
||||
- `bench` = benchmark queries, runner, and extrapolation (tasks 08-10)
|
||||
- `report` = reporting code and templates (task 11)
|
||||
- `tools` = helper scripts under `tools/`
|
||||
- `all` = multiple areas or in doubt
|
||||
- **Coverage** - multi-area diffs get a blank line and a 2-6 bullet body
|
||||
covering every meaningful changed area; split unrelated work.
|
||||
- **Special forms** - `Merge ...` and `release(scope): vX.Y.Z` are allowed
|
||||
as-is.
|
||||
|
||||
No `commit-msg` hook is installed in this repository yet; the convention is
|
||||
enforced by review. If a hook is added later, it must implement exactly this
|
||||
format and this file must be updated in the same change. The same convention
|
||||
is mirrored into `.cursorrules` for Cursor's built-in commit-message
|
||||
generator - see [meta-cursor-rules.md](meta-cursor-rules.md).
|
||||
|
||||
## 5. Anti-patterns
|
||||
|
||||
- **"I'll just commit it so we have a checkpoint."** No. Working-tree state is
|
||||
the checkpoint; the user decides when it becomes history.
|
||||
- **Amending the user's last commit to fix something you noticed.** No. Surface
|
||||
the issue, let the user choose between amend and new commit.
|
||||
- **Suggesting `git add -A` in the draft commands.** List explicitly named
|
||||
files, so the user sees exactly what would land.
|
||||
- **Vague subjects** (`Update files`, `Improve project`, `clarify rules`).
|
||||
Say what changed and where.
|
||||
|
||||
## 6. Exception (closed list)
|
||||
|
||||
There are no exceptions. If a workflow seems to require a commit, pause,
|
||||
explain the dependency, and let the user run the git command.
|
||||
35
docs/rules/meta-language-style.md
Normal file
35
docs/rules/meta-language-style.md
Normal file
@@ -0,0 +1,35 @@
|
||||
# Meta / Communication language and register
|
||||
|
||||
**Binding on every response and on all prose directed at the user**
|
||||
(explanations, status updates, questions, summaries, commit messages, PR text).
|
||||
|
||||
## Rule
|
||||
|
||||
1. **Language: Russian.** All communication with the user is in standard
|
||||
literary Russian.
|
||||
2. **Register: neutral and professional. No jargon, no slang, no profanity.**
|
||||
- No profanity or vulgar words - in any language, under any circumstances.
|
||||
- No slang or jargon: street slang, gamer/internet slang, or untranslated
|
||||
English buzzwords used as slang.
|
||||
- Prefer the established Russian word over a transliterated English one
|
||||
whenever a normal Russian equivalent exists.
|
||||
- Plain, grammatical, complete sentences.
|
||||
|
||||
## Allowed terminology (not slang)
|
||||
|
||||
Field-standard technical terms are fine - they are terminology, not jargon:
|
||||
e.g. «коммит», «билд», «дебаг», «релиз», «мерж». When unsure whether a word
|
||||
is accepted terminology or slang, choose the neutral Russian equivalent.
|
||||
|
||||
## Scope
|
||||
|
||||
Does **not** apply to source code, identifiers, file paths, code comments,
|
||||
shell/tool output, or verbatim quotes of errors and logs - those keep the
|
||||
language of the codebase.
|
||||
|
||||
## Canonical source
|
||||
|
||||
This file is the project-level binding rule. It restates the project owner's
|
||||
standing preference (communication in Russian; code, docs, and artifacts in
|
||||
English). If a global user-level rule ever diverges from this file, the
|
||||
stricter wording wins.
|
||||
51
docs/rules/meta-no-assumptions.md
Normal file
51
docs/rules/meta-no-assumptions.md
Normal file
@@ -0,0 +1,51 @@
|
||||
# Meta / No assumptions
|
||||
|
||||
**Binding** on every interaction. Strictest when the cost of a wrong
|
||||
guess is high: deleting or regenerating `./out/` artifacts, dropping or
|
||||
reloading the `tribo` PostgreSQL database, changing the random seed or
|
||||
`lab_config.yaml`, editing specs in ways that invalidate generated data,
|
||||
git history, anything with data-loss consequences.
|
||||
|
||||
## Rule
|
||||
|
||||
Before producing a plan or change:
|
||||
|
||||
1. Re-read the user's most recent message. Watch for enumerations
|
||||
("both", "and also", "in addition to"), explicit purpose statements
|
||||
("X for A, Y for B"), and distinct entities that sound similar.
|
||||
2. Don't collapse two distinct entities into one. "X for A and Y for B"
|
||||
means **two** setups, not one.
|
||||
3. If anything is ambiguous after careful re-reading - **ask**. Don't
|
||||
pick the most likely interpretation and proceed.
|
||||
4. Echo back the parsed interpretation before producing the plan, so a
|
||||
misread is cheap to correct.
|
||||
|
||||
## When the rule is most strict
|
||||
|
||||
Strictness scales with the cost of a wrong guess:
|
||||
|
||||
| Domain | Why a wrong assumption is expensive |
|
||||
|---|---|
|
||||
| **Deleting / regenerating `./out/`** | The artifact tree holds hours of generation, conversion, and benchmark work. Regenerating CSV invalidates every downstream format, benchmark result, and report. Confirm which subtree is affected before deleting or overwriting. |
|
||||
| **The `tribo` PostgreSQL database** | Dropping or reloading it discards a long `COPY` load and invalidates benchmark results derived from it. Confirm before any `DROP` / `TRUNCATE` / reload. |
|
||||
| **The random seed / `lab_config.yaml`** | Seed 20260711 and the lab configuration are the root of the whole reproducibility chain. Changing either silently invalidates all generated data, all conversions, all checksums, and all measured results. Never change them as a side effect of another task. |
|
||||
| **Editing specs (`docs/specs/`)** | Specs are the task contract. A "small clarification" that changes volumes, schemas, or file layouts desynchronizes already-generated artifacts. Confirm intent when an edit is more than wording. |
|
||||
| **Benchmark results (`./out/bench/`)** | Measured results cannot be re-derived without re-running the full matrix (hours). Never overwrite `results_raw.csv` / `results_median.csv` without confirmation. |
|
||||
| **git history** | A wrong rebase/reset/force-push rewrites shared history; recovery is manual and error-prone. (The AI agent never runs git write operations at all - see [meta-git-commits.md](meta-git-commits.md).) |
|
||||
|
||||
For all of the above: **the cost of one extra clarifying question is
|
||||
zero; the cost of a wrong assumption is hours-to-days** of recovery.
|
||||
Always ask.
|
||||
|
||||
## Forms a clarifying question takes
|
||||
|
||||
- "Confirm: you want X for A AND Y for B (two setups), not just one?"
|
||||
- "Two readings: (a) ..., (b) ... - which?"
|
||||
- "What does '<word>' refer to here - (a) ... or (b) ...?"
|
||||
|
||||
## When the assumption was already wrong
|
||||
|
||||
1. Acknowledge the misread explicitly. Quote the user's words back.
|
||||
2. Discard the wrong artifact entirely; don't repair piecemeal unless
|
||||
the user asks.
|
||||
3. Restart with the corrected interpretation, echo it first.
|
||||
119
docs/rules/meta-rules-naming.md
Normal file
119
docs/rules/meta-rules-naming.md
Normal file
@@ -0,0 +1,119 @@
|
||||
# Meta / Rule file naming
|
||||
|
||||
**Binding** for files inside [`docs/rules/`](docs/rules/). Adding, renaming, or
|
||||
splitting a rule file follows this document.
|
||||
|
||||
## Layout
|
||||
|
||||
- `docs/rules/` - binding rules (this folder): reproducible conventions on HOW
|
||||
work is done. Reference content that supports a rule also lives here,
|
||||
named `<rule>-reference.md`.
|
||||
- `docs/specs/` - the pipeline task specifications (WHAT to build: tasks
|
||||
01-11, their inputs, outputs, and dependencies). Binding for scope and
|
||||
deliverables, but conventions that span tasks are codified in `docs/rules/`.
|
||||
- `docs/examples/` - imported reference materials (master plan, ontology).
|
||||
Descriptive, not binding.
|
||||
|
||||
Files live flat at the top level of `docs/rules/`. No subfolders.
|
||||
|
||||
## Naming
|
||||
|
||||
```
|
||||
<domain>-<subdomain>-<topic>[-<aspect>].md
|
||||
```
|
||||
|
||||
- Lowercase, dashes between segments. No underscores, no CamelCase.
|
||||
- Max 4 segments. Most general first.
|
||||
- Reading the filename should answer: category -> subcategory -> topic.
|
||||
|
||||
## Approved domain prefixes
|
||||
|
||||
| Prefix | Covers |
|
||||
|---|---|
|
||||
| `bench-` | Benchmark execution, resource measurement, extrapolation, reporting methodology |
|
||||
| `build-` | Pipeline task orchestration: the `./out/` tree, `.done` markers, task order |
|
||||
| `code-` | General code style across languages (Python, SQL, PowerShell): comments, formatting, error handling, configuration |
|
||||
| `data-` | The simulated dataset: determinism, identifiers, units, column naming |
|
||||
| `db-` | SQLite / PostgreSQL benchmark target schemas, loading, indexing |
|
||||
| `meta-` | Rules about the rules system and working conventions |
|
||||
| `obs-` | Observability and logging |
|
||||
| `test-` | Validation gates, checksums, pytest conventions |
|
||||
|
||||
Don't invent a new prefix without adding it to this table in the same
|
||||
commit.
|
||||
|
||||
### Subdomains (second / third segment)
|
||||
|
||||
Within a domain, the second segment is a recognised tool, subsystem, or
|
||||
concern; the third is the concrete topic. Established families:
|
||||
|
||||
- `db-sql-*` - relational benchmark targets (SQLite, PostgreSQL): schema,
|
||||
loading, indexing.
|
||||
- `data-*` - dataset conventions (`data-determinism`, `data-naming-units`).
|
||||
- `bench-*` - measurement methodology (`bench-methodology`).
|
||||
- `build-pipeline-*` - task orchestration and artifact layout.
|
||||
- `code-*` - general code style (comments, formatting, error handling,
|
||||
config, Python style).
|
||||
- `meta-*` - rules about the rules system and working conventions.
|
||||
- `obs-*` - observability and logging (`obs-python-logging`).
|
||||
- `test-*` - validation and testing (`test-pipeline-validation`).
|
||||
|
||||
## Examples
|
||||
|
||||
| Good | Reads as |
|
||||
|---|---|
|
||||
| `data-determinism.md` | Data -> deterministic generation |
|
||||
| `db-sql-schema.md` | DB -> SQL -> schema conventions |
|
||||
| `bench-methodology.md` | Benchmarks -> measurement methodology |
|
||||
| `code-python-style.md` | Code -> Python -> style |
|
||||
| `obs-python-logging.md` | Observability -> Python logging |
|
||||
| `meta-rules-naming.md` | Meta -> rules system -> naming |
|
||||
|
||||
| Bad | Why |
|
||||
|---|---|
|
||||
| `BenchMethodology.md` | CamelCase forbidden |
|
||||
| `data_determinism.md` | Underscores forbidden |
|
||||
| `sqlite-loading-conventions.md` | Use `db-` prefix, drop redundant `conventions` |
|
||||
| `bench-execution-measurement-rules-doc.md` | 5+ segments + redundant `rules` suffix |
|
||||
|
||||
## Adding a rule
|
||||
|
||||
1. Pick the domain prefix; add new ones to the table above first.
|
||||
2. Check `ls docs/rules/` - extend a sibling rule before creating a new one.
|
||||
3. Create the file with binding content, examples, and exceptions.
|
||||
4. Add a row for the new file to the rule-trigger table in the root
|
||||
[`CLAUDE.md`](../../CLAUDE.md) in the same commit.
|
||||
5. If replacing an older doc, `git mv` to preserve history and `Grep` the
|
||||
repo for the old path.
|
||||
|
||||
## Rules vs specs vs memory files
|
||||
|
||||
Three homes for a statement; pick by its kind:
|
||||
|
||||
| Source | Stores |
|
||||
|---|---|
|
||||
| `docs/rules/<name>.md` | A reproducible, mandatory behaviour - the same answer for every contributor on every task within the topic. |
|
||||
| `docs/specs/NN_<task>.md` | The definition of one pipeline task: scope, inputs, outputs, dependencies. |
|
||||
| `memory/<name>.md` | A fact about *this user*, *this project*, or *the current moment* - preferences, ongoing initiatives, locked decisions, infra specifics. |
|
||||
|
||||
- A convention that must hold across several tasks -> `docs/rules/`.
|
||||
- A deliverable of one task -> its spec in `docs/specs/`.
|
||||
- A fact about the user or the present state of things -> `memory/`.
|
||||
|
||||
A rule file may reference a spec or a memory note for context, but the
|
||||
binding convention lives in the rule file. When a rule and a spec disagree
|
||||
on a convention (naming, formats, thresholds), the rule wins and the spec
|
||||
is updated to match.
|
||||
|
||||
## Anti-patterns
|
||||
|
||||
- **Duplicating rule content across rule files** - pick one canonical
|
||||
location and cross-link from siblings instead of copying.
|
||||
- **Restating a whole spec inside a rule** - the rule codifies the
|
||||
reusable convention and links the spec for task detail.
|
||||
- **Adding rules to the body of CLAUDE.md** instead of creating a rule
|
||||
file - CLAUDE.md carries only the trigger table, the project preamble,
|
||||
and the canonical-source-of-truth note.
|
||||
- **Naming a file after the implementation** instead of the domain
|
||||
(`oxigraph-loading.md` vs `db-rdf-loading.md`) - the implementation may
|
||||
be replaced; the topic endures.
|
||||
113
docs/rules/obs-python-logging.md
Normal file
113
docs/rules/obs-python-logging.md
Normal file
@@ -0,0 +1,113 @@
|
||||
# Observability / Python logging - standard library only, namespaced, plain text
|
||||
|
||||
**Purpose + scope.** Every Python module in this repository (data generation,
|
||||
converters, benchmark runner, extrapolation, reporting, helper tools) MUST
|
||||
use the standard-library `logging` module following these conventions. No
|
||||
external logging dependency.
|
||||
|
||||
---
|
||||
|
||||
## 1. The rule
|
||||
|
||||
1. **Standard library `logging` only.** No `loguru`, no `structlog`, no
|
||||
`python-json-logger`. Stdlib is sufficient and keeps the dependency list
|
||||
closed (see [code-python-style.md](code-python-style.md)).
|
||||
2. **Namespaced loggers.** Every module obtains its logger via
|
||||
`logging.getLogger(__name__)`. Never the root logger directly.
|
||||
3. **`print()` is for the user-facing CLI protocol only.** The specs
|
||||
explicitly require certain stdout output: progress summaries, the real
|
||||
generated tree size (task 03), per-cell benchmark progress, final
|
||||
counts. That protocol output goes through `print()`. Everything
|
||||
diagnostic - internal flow, warnings, errors, per-row detail - goes
|
||||
through a logger.
|
||||
4. **Log level from a CLI flag.** Every pipeline script accepts
|
||||
`--log-level {DEBUG,INFO,WARNING,ERROR}`, default `INFO`. There is no
|
||||
settings table and no `.env` in this project (see
|
||||
[code-config-yaml.md](code-config-yaml.md)).
|
||||
5. **Plain-text format on stderr.** These are one-shot CLI scripts, not
|
||||
services; JSON logs are overkill. One line per record via
|
||||
`logging.basicConfig`. Keeping the logger on stderr leaves stdout clean
|
||||
for the protocol output of item 3.
|
||||
6. **Never log secrets.** The only credential material in this project is
|
||||
the PostgreSQL DSN - never log it (a DSN may embed a password). Log the
|
||||
database name or host, never the full connection string.
|
||||
|
||||
## 2. API by severity
|
||||
|
||||
| Method | Use for |
|
||||
|---|---|
|
||||
| `logger.critical(...)` | The process cannot continue - before `sys.exit(1)` or an unrecoverable state. |
|
||||
| `logger.error(...)` | Failed operation, exception caught and handled, broken invariant, a benchmark cell recorded as TIMEOUT/ERROR. Always include `exc_info=True` when an exception is in scope. |
|
||||
| `logger.warning(...)` | Recoverable problem, retry triggered, suspicious state (e.g. a value near a validation bound), an optional step skipped (e.g. cold-cache pass unavailable). |
|
||||
| `logger.info(...)` | Normal flow milestones - task start/finish, per-batch progress, row counts written, validation gates passed. |
|
||||
| `logger.debug(...)` | Verbose tracing - per-coupon detail, generated value samples, SQL statements, per-run raw measurements. Hidden at default `INFO`. |
|
||||
|
||||
**Default level: `INFO`.** `DEBUG` is opt-in via `--log-level DEBUG` for a
|
||||
diagnosis session.
|
||||
|
||||
## 3. Logger acquisition and setup
|
||||
|
||||
```python
|
||||
# Top of every module that logs:
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Once, in each script's main():
|
||||
logging.basicConfig(
|
||||
level=args.log_level,
|
||||
stream=sys.stderr,
|
||||
format="%(asctime)s %(levelname)-7s %(name)s: %(message)s",
|
||||
)
|
||||
```
|
||||
|
||||
Inside functions:
|
||||
|
||||
```python
|
||||
def load_table(conn, name: str, rows) -> int:
|
||||
logger.info("loading %s", name)
|
||||
try:
|
||||
...
|
||||
except psycopg.Error:
|
||||
logger.error("loading %s failed", name, exc_info=True)
|
||||
raise
|
||||
```
|
||||
|
||||
`__name__` gives the module path as the namespace tree used for filtering.
|
||||
Never hardcode a logger name string. Clamp noisy third-party libraries
|
||||
(`psycopg`, `matplotlib`) to `WARNING` in the same setup block if they
|
||||
pollute the output.
|
||||
|
||||
## 4. Progress reporting in long loops
|
||||
|
||||
Generation and loading loops run over millions of rows. Report progress at
|
||||
INFO on a coarse cadence (per batch / per wafer / per table, or every N
|
||||
seconds), never per row. Per-row output belongs at DEBUG, and even there
|
||||
prefer sampled output ("first row of each file") over full dumps.
|
||||
|
||||
## 5. Anti-patterns
|
||||
|
||||
- **`print()` for diagnostics** - forbidden; `print()` is only the
|
||||
user-facing CLI protocol (progress summaries, final counts, sizes).
|
||||
- **Diagnostics on stdout** - stdout is the protocol channel; the logger
|
||||
writes to stderr.
|
||||
- **Root logger calls** (`logging.info(...)`) - forbidden; always
|
||||
`logger = logging.getLogger(__name__)`.
|
||||
- **Third-party logging frameworks** (`loguru`, `structlog`) - forbidden.
|
||||
- **Hardcoded logger names** (`logging.getLogger("datagen")`) - forbidden;
|
||||
pass `__name__`.
|
||||
- **Logging the PostgreSQL DSN / connection string** - forbidden; log
|
||||
host/database name only.
|
||||
- **Log level from an environment variable or a config file** - the level
|
||||
is a per-run diagnostic choice; it comes from `--log-level`.
|
||||
- **`logger.error(...)` without re-raise** when the function semantically
|
||||
cannot proceed - see [code-error-handling.md](code-error-handling.md).
|
||||
- **Per-row INFO logging in bulk loops** - drowns the signal and slows the
|
||||
run; use coarse milestones.
|
||||
|
||||
## 6. Related documents
|
||||
|
||||
- [code-error-handling.md](code-error-handling.md) - `exc_info=True`
|
||||
requirement and the swallow ladder.
|
||||
- [code-config-yaml.md](code-config-yaml.md) - configuration scope (no
|
||||
`.env`, no settings table).
|
||||
- [code-python-style.md](code-python-style.md) - the closed dependency list.
|
||||
125
docs/rules/test-pipeline-validation.md
Normal file
125
docs/rules/test-pipeline-validation.md
Normal file
@@ -0,0 +1,125 @@
|
||||
# Test / Pipeline validation - markers, manifests, checksums
|
||||
|
||||
**Purpose + scope.** The validation contract between pipeline tasks: how a
|
||||
task proves it completed correctly and how downstream tasks verify their
|
||||
inputs before trusting them. Binding for every task implementation (01-11)
|
||||
and for any pytest suite added to this repository.
|
||||
|
||||
The pipeline has no human QA step between tasks - these gates are the only
|
||||
thing standing between a silent generation/conversion bug and a report full
|
||||
of wrong numbers.
|
||||
|
||||
---
|
||||
|
||||
## 1. Completion markers - written last, verified first
|
||||
|
||||
- Every task writes `./out/.done/<NN>.ok` **only after** all its outputs
|
||||
are complete and its own validation passed. The marker contains the
|
||||
task's headline counts (rows, bytes, triples) for downstream checks.
|
||||
- A task MUST verify the `.done` markers of its declared dependencies
|
||||
before starting, and abort with a clear message if one is missing.
|
||||
- A task that re-runs deletes its own marker first and rewrites it at the
|
||||
end. A marker is never written on failure and never from a `finally`
|
||||
block (see [code-error-handling.md](code-error-handling.md)).
|
||||
|
||||
## 2. The CSV corpus manifest - the root of trust
|
||||
|
||||
- Task 03 writes `./out/csv/MANIFEST.csv`: one row per generated file with
|
||||
path, row count, byte size, sha256.
|
||||
- Every converter (04-07) validates what it read against the manifest: row
|
||||
counts per source file, and sha256 where the file is consumed whole. A
|
||||
mismatch is an abort, not a warning.
|
||||
- The manifest is also the reproducibility proof: regenerating with the
|
||||
same `lab_config.yaml` must reproduce identical sha256 values (see
|
||||
[data-determinism.md](data-determinism.md)).
|
||||
|
||||
## 3. Canonical expected volumes
|
||||
|
||||
The lab configuration fixes the corpus size exactly; validation asserts
|
||||
these numbers, not "roughly that many":
|
||||
|
||||
| Entity | Count |
|
||||
|---|---|
|
||||
| batches | 4 |
|
||||
| wafers | 12 |
|
||||
| coupons | 588 (480 friction + 108 characterization-only) |
|
||||
| tracks | 1,440 |
|
||||
| friction cycle rows | 1,440,000 |
|
||||
| friction loop points | 2,880,000 |
|
||||
| XRF points | 235,200 (588 x 400) |
|
||||
| generated tree size | 120-200 MB (hard abort bounds) |
|
||||
|
||||
If a deliberate `lab_config.yaml` change moves these numbers, this table
|
||||
and the validation code change in the same commit.
|
||||
|
||||
## 4. Query-result checksums - all formats agree
|
||||
|
||||
- Each benchmark query Q1-Q7 has ONE canonical expected result: rows
|
||||
sorted, floats compared with tolerance 1e-9, checksummed to
|
||||
`./out/bench/expected/q<N>.sha256`.
|
||||
- The canonical checksums are computed from **PostgreSQL** and
|
||||
cross-validated against **SQLite** before any benchmarking starts; a
|
||||
disagreement between the two engines is a converter or query bug and
|
||||
blocks task 09.
|
||||
- Every measured benchmark run records a `checksum_ok` flag; a cell whose
|
||||
result does not match the expected checksum is an invalid measurement
|
||||
regardless of how fast it ran.
|
||||
|
||||
## 5. Format smoke tests
|
||||
|
||||
Each converter proves basic well-formedness of its output before writing
|
||||
its marker:
|
||||
|
||||
- **JSON-LD (04):** one sample coupon file passes `json.load` AND an
|
||||
rdflib JSON-LD parse.
|
||||
- **SQLite (05) / PostgreSQL (06):** row counts per table match the
|
||||
manifest-derived expectations; `track_summary` row count equals 1,440.
|
||||
- **RDF (07):** the loaded store answers a SPARQL `COUNT` matching the
|
||||
serialized triple count recorded in the `.done` marker.
|
||||
|
||||
## 6. If a pytest suite is added
|
||||
|
||||
The specs define no pytest suite; validation is built into the tasks. If
|
||||
unit/integration tests are added later:
|
||||
|
||||
- **Skip, don't fail, when PostgreSQL is unreachable.** A checkout without
|
||||
a running PostgreSQL still gets a green (skipped) run;
|
||||
connection failure in a fixture is `pytest.skip(...)`, not an error.
|
||||
- **Never mutate real `./out/` artifacts.** Tests that need data build a
|
||||
small corpus into a temp directory (a reduced `lab_config` is fine) or
|
||||
copy the artifact; hours of generated/measured data are not test
|
||||
fixtures to be overwritten.
|
||||
- **Ephemeral databases only.** A test touching PostgreSQL creates and
|
||||
drops its own database (e.g. `tribo_test`) next to `tribo`; it never
|
||||
writes to `tribo` itself.
|
||||
- **Independent and order-free.** Every test passes run alone; no residue
|
||||
between tests, no execution-order dependencies.
|
||||
- **Deterministic assertions.** With the fixed seed, exact counts and
|
||||
exact values are assertable - prefer exact assertions over "greater than
|
||||
zero".
|
||||
|
||||
## 7. Anti-patterns
|
||||
|
||||
- **Writing a `.done` marker before validation, on failure, or in
|
||||
`finally`** - the marker means "completed and verified".
|
||||
- **Skipping manifest validation "because generation just ran"** - the
|
||||
gate exists precisely for the runs where the assumption is wrong.
|
||||
- **Warning-and-continuing on a count mismatch** - a converter with a row
|
||||
deficit produces a database that LOOKS complete; abort.
|
||||
- **Regenerating expected checksums to make a failing format pass** - the
|
||||
canonical result comes from PostgreSQL cross-validated with SQLite;
|
||||
a mismatch in another format is a bug in that converter or query.
|
||||
- **Tests writing into `./out/`** or connecting to the real `tribo`
|
||||
database.
|
||||
- **Failing (not skipping) when PostgreSQL is unavailable** in a pytest
|
||||
run.
|
||||
|
||||
## 8. Related documents
|
||||
|
||||
- [build-pipeline-tasks.md](build-pipeline-tasks.md) - marker layout and
|
||||
task order.
|
||||
- [data-determinism.md](data-determinism.md) - why byte-identical
|
||||
regeneration is assertable.
|
||||
- [code-error-handling.md](code-error-handling.md) - fail-loud mechanics.
|
||||
- [bench-methodology.md](bench-methodology.md) - how `checksum_ok` enters
|
||||
the measurement protocol.
|
||||
Reference in New Issue
Block a user