Skip to main content

rowspec

A specification and executable conformance suite for tabular data that must survive version control.

A .mdtbl file is a markdown table where a computed column carries its formula in the header, rows have opaque ids, and nothing is addressed by position. The point is that stock git merges it correctly, or refuses — never quietly wrong, with none of this project's tooling installed.

| id     | item   | qty | unit  | total = qty * unit |
| ------ | ------ | --: | ----: | -----------------: |
| r_0001 | widget |  10 | 12.00 |                    |

key   := id
grand := sum(total)

Two branches insert a row far apart. Stock git merge — no driver, no .gitattributes, nothing installed — merges cleanly, and grand is correct. The same table with A1-style references merges just as cleanly and reports a number that is 27% wrong, with no marker anywhere. That difference is the entire project.

What it does not do, measured

If your table is a list of facts, you probably do not need this. We replayed 7,446 real commits from four public CSV-in-git registries, including 528 real three-way merges. Neither .mdtbl nor plain CSV ever merged silently wrong. For an ordinary sorted registry CSV, stock git already does what this format promises, and none of those maintainers has wanted a computed column in four years.

The format earns its keep when a table computes — when a merge can leave a total that is quietly wrong, which is the failure a CSV cannot even represent.

Two branches each adding a column conflict badly, across most of the file. That is inherent to one-row-per-line and it is exactly what happens to a CSV. Nothing here fixes it; a column addition rewrites every row either way.

It is not a data-quality tool. Whether a country code is in ISO 3166, whether a URL resolves, whether a date is plausible — none of that is here, and Frictionless does it well. rowspec asks a smaller question: will this table survive being edited by several people over years?

It runs on the CSV you already have

rowspec check data/

No migration. On an ordinary .csv that alone refuses a committed conflict marker — which Python's own csv module parses as valid rows without complaint — a duplicate column name, a ragged row, invalid UTF-8, and an invisible character in a column name. A five-line data/countries.csv.rowspec.json naming the key and the order column adds the rest. See docs/csv.md, and copy docs/ci/rowspec-check.yml into .github/workflows/.

In CI, in three lines

- uses: actions/checkout@v7
- uses: kindspec/rowspec@v0.2.0
  with:
    paths: data

It runs both commands, because that is the whole reason it exists: check applies the structural refusals, eval computes every table and fails on an unresolved value, and neither subsumes the other. A misspelled column name or a pasted 1,299.00 is #REF! and correctly not a structural refusal, so a pipeline with only check is green on a broken total.

The action's own test suite proves that in both directions: one job asserts it passes a good tree, and another feeds it a well-formed table whose total is #REF! and asserts the action fails — then runs the same file with eval: false and asserts it passes, so the first assertion is about eval and not about the file.

Or copy docs/ci/rowspec-check.yml if you would rather own the workflow.

Run against 1,564 CSVs in three public data repositories it refused 16: two grouped-header spreadsheet exports, thirteen truncated CDC snapshots in owid/covid-19-data whose header declares 14 columns for a 6-field row, and one deliberately broken fixture in iptv-org/database's own test suite. No false positives.

What is actually shipped

The conformance suite is the deliverable. It checks out two branches, runs stock git merge, evaluates the merged file, and asserts on the computed number. No prior art does this. The spec, the validator and the reference implementation exist so the suite has something to check.

410 conformance cases          two implementations, both passing
 76 mutants                    74 killed, 2 equivalent
 74 distinct mutations          0 survived, 0 stale, 0 broken

The second implementation is the point. reference/rowspec_alt/ was written from SPEC.md alone by an author forbidden to read reference/rowspec/, and it runs against the same fixture tree in CI on every push. It is the only evidence that this document defines the format rather than describing one program, and it has repeatedly been the half that was right: on the last three questions where the two disagreed, the independent implementation was correct and the reference was wrong.

The mutation gate is the other half. The suite is only worth its green tick if it can go red, so the implementation is deliberately broken in 76 specific ways. 74 must be caught by a case; the other two carry a recorded claim that no input could distinguish them from the original, and the gate fails the run if a case turns out to catch one anyway. Two (old, new) pairs are duplicated, so 74 of the 76 are distinct mutations and 74 killed is 72 distinct kills — #48. A mutant that survives is reported as a failure, and so is a stale one whose pattern no longer matches the source — because a check that quietly stopped running is the failure this project keeps finding in itself.

Both are kindkit — the shared machinery every kindspec kind runs on. What lives here is the part that knows what a rowspec case means; walking the tree, reading fixtures as exact bytes, splicing mutants and accounting for the verdicts are the kit's, and rowspec is its first consumer.

Neither number is a claim about correctness in general. See docs/rationale.md for what has been measured, and what has been measured and found wanting.

Run it

just setup      # uv sync
just check      # fmt-check + lint
just test       # conformance suite + mutation gate + corpus checks
just conform    # the suite alone, against a stock git binary
just mutants    # deliberately break the implementation; the suite must notice
just conform-alt # the SECOND implementation, against the same fixture tree
just test-xlsx  # the exporter, WITH its optional extra installed
just run FILE   # validate an artifact (rowspec check)
just eval FILE  # print computed values and FAIL on any #REF!

A real git binary is required. The suite's central claim is about what stock git does, so it uses stock git.

Export to xlsx, only if you ask for it

pip install rowspec           # no xlsx dependency, and none is coming
pip install 'rowspec[xlsx]'   # openpyxl, and the exporter that uses it
python -m rowspec_xlsx table.mdtbl table.xlsx

The exporter lives in export/, outside reference/, and that is the whole design. reference/ is standard-library-only because a dependency there is a dependency every independent implementation of the format inherits, and an independent implementation of a table format must not be required to read xlsx. A test fails if anything under reference/ ever imports outside the standard library, so the boundary is a check rather than a convention.

What it writes today is deliberately minimal: one sheet, literal values, the declared aggregates below the grid. Structured references and SUBTOTAL (#35) and a test that asserts on what LibreOffice recalculates (#36) are what make the round-trip claim worth stating, and neither has landed — so the claim is not made here.

Two commands, and you need both

check applies the refusals in §9 — the structural ones, the things that make a table survive version control. eval computes the table and fails on any #REF!.

They catch different things and neither subsumes the other. A misspelled column name, a pasted 1,299.00, or a non-breaking space in a number are all #REF! under §8 and are correctly not §9 refusals — the file is well-formed, its total is wrong. check alone reports 0 refused on a table whose total is wrong, so a CI recipe built on check is green on a broken total.

Run both:

rowspec check .    # will this table survive being edited by several people?
rowspec eval  .    # does it currently say anything false?

Licensing

Per directory, deliberately — see LICENSE. The fixtures are CC0 so they can be vendored into an implementation in any language under any licence.

Credit

Standing on Coopy/daff, ClassSheets, Object Spreadsheets, Lotus Improv and org-mode. See NOTICE.

Metadata

Release files for rowspec 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for rowspec 0.2.0
File Size Uploaded Provenance
rowspec-0.2.0.tar.gz 282.5 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for rowspec 0.2.0
File Interpreter ABI Platform
rowspec-0.2.0-py3-none-any.whl Python 3 none any Details

Total release size: 334.9 kB

Release files / rowspec-0.2.0.tar.gz

Download URL rowspec-0.2.0.tar.gz
Size 282.5 kB
Tags Source
SHA-256 checksum
How to use checksums
0a67f2322caa1816708387480cd2f7452e2d5141bae27df0cd23da89602cbceb
BLAKE2b-256 checksum
How to use checksums
ab7d4a68a656b5276a2c1cd7883a499fb6d914f0162e07786250088d2d1a354f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 7, 2026.

Transparency log

Release files / rowspec-0.2.0-py3-none-any.whl

Download URL rowspec-0.2.0-py3-none-any.whl
Size 52.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
66ee203a23c80b810c02fd0f5788503fb496379c6b2d9b6f73e63dade78118cc
BLAKE2b-256 checksum
How to use checksums
769a0a248c5a48c65a03d28997cd5603160f80367d72fe101e56093b1fa3a74e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 7, 2026.

Transparency log

Security for rowspec 0.2.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for rowspec-0.2.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 7, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page