Skip to content

Latest commit

 

History

History
649 lines (583 loc) · 38.4 KB

File metadata and controls

649 lines (583 loc) · 38.4 KB

Changelog

Notable changes to tods-validate. Rule IDs are never renumbered or reused; new checks may be added in minor releases.

Unreleased

v0.10.0 - 2026-08-21

v0.9.1 was tagged and signed (commit edd2ea1) but its GitHub Release object was never created, so pypi-publish.yml never ran: PyPI's latest published version stayed 0.9.0 while pyproject.toml and the tag said 0.9.1 (#136). Sixteen PRs landed on main after that tag, several changing validator behavior, so re-publishing the number 0.9.1 would misdescribe what actually ships. This release supersedes it. The v0.9.1 tag is left in place, signed and unmoved, and is not the version anyone should install; v0.10.0 is.

The version is a MINOR bump, not a PATCH, because two of the changes below are not backward-compatible: the Python floor rises to 3.12 (drops installs on 3.11), and TODS-E301/TODS-E303/companion-GTFS reference checks now fail closed on an unreadable file instead of silently skipping or inventing findings, which can change a previously-clean run's exit code. Per this repo's pre-1.0 SemVer policy (docs/standards/RELEASE-AND-VERSIONING-STANDARD.md REL-05), a 0.y.z MINOR release may carry a breaking change; this is not yet the v1.0.0 release described in docs/v1-contract-audit.md, which is reserved for a conformance-only release after the contract snapshot has gone unchanged for one full release cycle. This one does not qualify -- it adds a rule (TODS-E207) and changes coverage-manifest behavior in three commands.

Fixed:

  • A companion GTFS file that could not be decoded (bad encoding, empty, unparseable CSV) counted as present. The reference rules that read it ran against an empty table instead of being skipped, invented ERRORs against every real ID in the TODS file that referenced it, and the coverage manifest recorded them ran. The same shape reached two TODS-internal checks: an unreadable run_events.txt or vehicles.txt produced invented TODS-E301/TODS-E303 findings the same way. All three now treat an unreadable file the same as a missing one — the rule is skipped (skipped:needs_gtfs_table for the companion-GTFS case), and TODS-W302 discloses that the file could not be read (pointing to TODS-E103 for the reason on the TODS side), instead of silently reporting has no <file> or, worse, inventing errors against it.
  • diff OLD NEW reported a rule's old finding "fixed" whenever it was absent from NEW, without checking whether the rule ran in NEW at all. A rule that stopped running — a companion GTFS feed dropped, or newly unreadable (#125), between OLD and NEW — makes its old findings disappear the same way a genuine fix does, and diff could not tell them apart: comparing tests/fixtures/invalid/TODS-E307 (a bad trip_id reference) against the same package with the companion trips.txt removed reported fixed: 1 and exit 0, when the bad reference was never re-checked, let alone fixed. diff now uses run_with_coverage and only counts an OLD-only finding fixed when its rule ran in NEW; otherwise it lands in a new unknown bucket, named in the counts line. Every rule that ran in OLD and not in NEW is also named below the findings, whether or not it had a finding to lose — a dropped companion can zero out 16 checks with 0 findings on either side, which used to read as a silently clean diff.
  • batch used the two-tuple run() wrapper, so none of its three formats (text, --format json, --format markdown) had a coverage manifest to disclose: a TODS-only feed in a fleet run skipped 16 of 42 checks, 9 of them ERROR-severity, and its row read 0 0 0 pass — exactly the numbers a fleet compliance artifact is read for, with nothing saying the run was partial. batch now uses run_with_coverage. Every format carries the manifest: text and Markdown gain a "checks not run" column beside each feed's status plus a fleet-wide Rule-set coverage line in the roll-up (pooling every feed's outcomes, the same disclosure a single-feed report already carries); --format json adds a per-feed checksNotRun count and a coverage block matching validate --format json's. --require-complete-run (#124) is now available on batch too: a feed with an unrequested skip (missing/unreadable companion GTFS) fails that feed, the same as it does for validate.
  • uv.lock pins pip at 26.2.1, past PYSEC-2026-3721 (disclosed after 26.1.2 was pinned). Vendored only as a transitive build/audit tool, never imported by tods_validate itself, but it was failing make audit (and would fail it for any PR, unrelated to that PR's own change) until bumped.
  • fix -o OUT and anonymize -o OUT no longer destroy a file the loader could not read. Both commands rebuild every file from the loader's headers and rows; a file whose decode or CSV parse failed has neither, so it was written out as a single newline — the user's data replaced by an empty file. fix compounded it by printing Nothing to fix., because no trim/blank/duplicate counter had moved, so the run reported that it had changed nothing while it was the run that lost the data. Both commands now refuse to write such a package and name the offending file; fix's dry run reports it instead of claiming there was nothing to fix. Use --encoding if the file is deliberately not UTF-8. Packages that load cleanly are unaffected.
  • --format github now discloses the checks that did not run. It is the only format the composite action emits, and it was the one format that never carried the coverage manifest: render_github took no coverage argument at all, so a feed validated without a companion GTFS feed printed 0 error(s), 0 warning(s), 0 info and stopped there, while 16 of 42 checks had not run, 9 of them ERROR-severity. An agency or vendor who left the gtfs: input out of the workflow got a green check and had no way to learn that no reference was ever resolved. The summary line now carries the run's scope, and each reason a check did not run becomes its own ::notice annotation naming the rules, so the disclosure reaches the pull request's Checks tab and not only the log.
  • Every report format now names the rules that did not run, not just how many, and a run that skipped nothing says so (Every applicable check ran (42 of 42).) rather than staying silent. Silence could not be told apart from a format that does not disclose, which is how this defect survived.
  • The Markdown report states its rule-set coverage with or without --stamp. The block used to be printed only under --stamp, which tied a statement of what the run checked to a statement of when it ran; the unstamped report is the default and the one people paste into issues.

Changed:

  • Minimum supported Python raised from 3.11 to 3.12 (#72), closing CQ-01 directly against the standard's stated floor (Python 3.10 reaches EOL October 2026) instead of via the declared deviation docs/adr/0001 had recorded since 2026-07-09. docs/adr/0006-python-312-floor.md supersedes
    1. README.md and CONTRIBUTING.md now say "Requires Python 3.12 or newer"; CI's test matrix is 3.12/3.13 (3.11 dropped). Installed releases are unaffected; this binds new installs, upgrades, and local dev.

Added:

  • TODS-E207 checks that routes_supplement.txt's route_color and route_text_color are valid GTFS Color values: six hexadecimal digits, no leading # (GTFS reference, "Field Types > Color"). Every other field a supplement file inherits from its GTFS base is typed Text by schema._supplement() regardless of the base file's real GTFS type, so these two carried no format check at all before this; _supplement() gained a field_types override used only for these two fields, rather than transcribing the full GTFS field-type inventory for a single rule. (#101)
  • --require-complete-run fails the run when a check could not run because an input was missing, such as a companion GTFS feed that was not given. Skips the caller asked for (--ignore, opt-in rules left off, --spec-version scoping) are disclosed but do not fail it. The GitHub Action exposes it as the require-complete-run input.
  • A skipped check still does not change the exit code by default. That is deliberate: this tool has shipped as a merge gate since 0.1.0 and every feed validated without a companion GTFS feed skips 16 checks, so failing on a skip would turn existing pipelines red on upgrade for something they never asked the tool to promise. The README now states it instead of leaving 0 to be read as "fully checked".

Docs:

  • The Standards Conformance section's intro paragraph enumerated eleven standards ("code quality, security & supply chain, ... AI-evaluation") while the table below it declares fifteen -- Performance, Incident Response, Data Governance, and AI Development Measurement were in the table and missing from the prose. v0.9.1 (#118) fixed the table itself (a comma in the Accessibility row's state broke the vendored portfolio-standards v2.0.0 DOC-11 checker, which the prose drift did not: the checker grades the table, not the paragraph above it); this is the second, smaller half of #113. The paragraph now points at the table instead of maintaining a second, driftable count.

v0.9.1 - 2026-08-18

A patch release that repairs the release pipeline itself and ships one playground change. No validator behaviour changes: no rule added, removed, renumbered or re-severitied, and the CLI, Action, and report contracts are untouched. It matters because the two pipeline defects below are why the deployed playground still serves tods-validate 0.7.0 today; this is the release that moves it forward.

Fixed:

  • The playground deploy no longer races the PyPI upload it depends on. Both pages.yml and pypi-publish.yml fired on release: published, and the page micropip-installs the exact wheel it pins, so the deploy's guard ("refuse to publish a page pinned to a wheel PyPI does not have") checked PyPI seconds after the release was published -- long before the upload finished -- and refused, correctly, on both v0.8.0 and v0.9.0. The deploy is now a deploy-playground job at the end of pypi-publish.yml, called via workflow_call after verify-published has re-downloaded the wheel from PyPI and checked its provenance, so the ordering is structural rather than a race. The guard stays, now with a 10-minute retry window for index propagation, and a wheel PyPI never serves still fails the deploy naming the missing wheel -- it never deploys anyway and never skips quietly. workflow_dispatch remains for out-of-band deploys.
  • The PyPI publish step accepts what the build backend now produces. v0.9.0 built, signed, and attested cleanly, then failed at upload with "'2.5' is not a valid metadata version": the pinned pypa/gh-action-pypi-publish commit predated Metadata-Version 2.5. The pin is now v1.14.2, which publishes 2.5 metadata; v0.9.0 reached PyPI through a manual re-run after that fix, and v0.9.1 is the first release to publish through it end to end. (#116)

Added:

  • The playground footer has a support link beside the version line -- a plain text link whose text says where it goes, sized to a deliberately spare page. This is the change that needs a release to reach the deployed site: the playground only ever serves web/index.html as of the latest tag, which is what scripts/check-deployed-playground.sh enforces. (#96)

Docs:

  • The README's Action examples pin the current release instead of the previous one; the v0.9.0 miss was corrected in #115 and this release bumps the pins to v0.9.1 in the same commit as the version, so the quickstart cannot trail the release again.
  • The Standards Conformance table's Accessibility row states its scope in the same Applies (scope) form as every other row. (#118)

v0.9.0 - 2026-08-16

Behaviour change for Action and CLI consumers: two checks now report findings they did not report in v0.8.0, and one stops reporting findings it should never have reported. On the same feed, tods-validate can therefore exit 1 where v0.8.0 exited 0, or exit 0 where v0.8.0 exited 1. Nothing about the exit-code contract itself changed (0 clean, 1 findings at or above the threshold, 2 usage error), and no rule ID was renumbered, removed, or given a new severity. The three checks are, in this section: TODS-E204 on employee_run_dates.txt duplicates, TODS-E201 on supplement rows that add a GTFS entry, and the companion-GTFS detection fix.

Fixed:

  • CQ-09's lockfile-drift gate was missing the half that fails. Every CI job and the release verification workflow installed with uv sync --frozen, which installs exactly what uv.lock records and exits 0 whether or not the lock still agrees with pyproject.toml -- so a version bump or an added dependency could ship a stale environment with every check green. Measured on this repo: with pyproject.toml at 0.9.0 and uv.lock still at 0.8.0, uv sync --frozen --extra dev exits 0 and installs 0.8.0, while uv lock --check exits 1. uv lock --check now runs before every uv sync in CI and in the release verify workflow, and as the first gate in make verify (make lockfile). ADR 0005, which asserted that --frozen "fails on any lockfile drift", records the correction.
  • The accessibility gate now runs an accessibility check. make a11y began with npm audit --audit-level=high, so once an unpatched HIGH advisory appeared in the pa11y-ci development toolchain the recipe aborted on its first line and npm run a11y stopped executing entirely — the job went red for a dependency reason and audited nothing, on every commit, for weeks. The npm dependency audit is now its own gate (make npm-audit, in the audit job, at the same HIGH floor) and make verify runs every gate independently and reports each one's result, instead of stopping at the first failure. The one advisory behind this, GHSA-jmr9-qjv8-65gv in extract-zip, is recorded in waivers.yml with an owner and a 2026-11-15 expiry; a different advisory, the same advisory on another package, or the same advisory escalated in severity still fails the gate, which tests/test_npm_audit_gate.py pins.
  • A stray GTFS file next to the TODS files no longer promotes the package to its own companion GTFS feed. A package is a companion only when it carries a file TODS IDs resolve against (trips.txt, stops.txt, stop_times.txt, routes.txt, calendar.txt, calendar_dates.txt); one agency.txt used to be enough, which made all 16 GTFS cross-reference rules run against a feed with no trips, stops or calendars — 28 invented errors on a valid feed, and a coverage manifest reporting 39 of 42 rules as having run.
  • Every rule that reads the companion GTFS feed now declares which files it reads, and is reported skipped:needs_gtfs_table when the companion does not have them, instead of running against data that cannot answer it. A TODS supplement file no longer counts as its own GTFS base table: trips_supplement.txt modifies trips.txt, so without trips.txt there is nothing to resolve a trip_id against. This also stops TODS-I501 reporting trip coverage computed from supplement rows alone.
  • docs/report.schema.json now lists skipped:spec_version, which the validator has emitted since v0.8.0 without documenting: a report from --spec-version 1.0.0 failed the schema it publishes.

Changed:

  • Supplement rows known to add a GTFS entry now require every field the GTFS reference marks Required for that file. Updates and deletes still require only their primary-key fields. The check stays permissive when no companion GTFS is available because an addition cannot then be distinguished from an update.
  • employee_run_dates.txt now uses the explicit four-field primary key agreed in the #152 discussion. Exact duplicates produce TODS-E204; TODS-W408 remains as a grouped compatibility signal for existing machine consumers.
  • The current GTFS supplement field inventory now recognizes trips.safe_duration_factor, trips.safe_duration_offset, stops.stop_access, and routes.cemv_support.

Added:

  • A reviewed v1-candidate public-contract snapshot and blocking drift check covering rule IDs/severities/categories, exit codes, supported spec versions, Python exports, and required JSON report fields. It runs on every pull request and in the test suite; previously it reached CI only through the release workflows, so the contract was first verified after a release tag was cut. The CLI's exit codes now have names in tods_validate.policy (EXIT_CLEAN/EXIT_FINDINGS/EXIT_USAGE) that the check reads, instead of literals restated inside the checker.
  • A blocking WCAG 2.1 AA accessibility job using both axe-core and HTML_CodeSniffer on the playground and a generated HTML report. The same gate runs during release verification, and the npm lockfile is vulnerability- audited. That job audits this repository's web/index.html, which is the source of the deployed playground and not the deployment; the deployed page is now checked separately, against the live URL, by the same runners and standard.
  • The playground is deployed when a release is published, rather than only by manual dispatch, and the deploy refuses to publish a page pinned to a wheel PyPI does not have yet. After each deploy, and weekly, the served page is compared against the page this repository publishes and audited for accessibility, so a stale or silently failed deployment is reported instead of going unnoticed. tests/test_playground.py pins the playground's TODS_VALIDATE_VERSION to pyproject.toml's version.
  • Currency stamps on docs/getting-started.md and docs/api.md (DOC-15), added after re-running every command, exit code, signature, and member those pages document. make docs-check now fails when a stamped page changes without a fresh verification, so the date means the text was checked rather than that someone typed a date once.
  • The perf budget is enforced (QM-02). scripts/check_perf_budget.py validates a 50,000-trip synthetic feed and fails when throughput regresses past the factor in perf/baseline.json; scripts/benchmark.py could measure this before, but nothing compared the measurement to anything. Throughput is measured in rows per CPU-second rather than wall clock, so a busy shared runner is not reported as a regression, and the check fails rather than passes when it has no baseline to compare against.

v0.8.0 - 2026-07-16

This release broadens compatibility and makes operational changes easier to inspect: TODS v1 feeds can be validated directly, GTFS changes can be checked for broken TODS references, and HTML reports can include accessible run timelines. It also tightens field-format and conformance-corpus safeguards.

Added:

  • TODS-E203 now checks Latitude, Longitude, and Non-negative float fields, not only Time, Date, and Non-negative integer. An out-of-range ops_location_lat or ops_location_lon (outside -90..90 / -180..180) and a negative or non-numeric shape_dist_traveled are now reported instead of passing silently. Messages for the existing field types are unchanged.
  • The VS Code client now packages reproducibly from its lockfile in CI, includes its Apache-2.0 license, uploads a reviewable VSIX artifact, and offers a setup guide when tods-validate-lsp is not available on PATH.
  • --format html --timeline adds an opt-in visual time rail for each (service_id, run_id). Event rows with findings use a dashed bar and diamond marker, and every rail has a complete sequence-ordered table with the same times, work, movement, and finding IDs for screen-reader and non-visual use.
  • The browser playground is deployed at https://chelseakr.github.io/tods-validate/ and linked from the README. Feed files remain in the browser during validation.
  • An ingest-ready named profile for CAD/AVL import gates. It fails on warnings, enables coverage and advisory checks, and adds no ignored rules; select it with --profile ingest-ready or profile = "ingest-ready" in tods-validate.toml.
  • --spec-version 1.0.0 validates against the TODS spec text as it stood before v2.0.0-alpha.1 (deadheads.txt/ops_locations.txt/deadhead_times.txt, runs_pieces.txt, and a differently-shaped run_events.txt), transcribed from the last commit before v2 spec work began; see docs/spec-versions.md for the full file/field delta, citations, and exactly which rule bands run under each version (structure and field-value rules run against either version's schema; reference/semantic/coverage/advisory rules, which assume v2.1.0-only mechanisms, are skipped and disclosed via the coverage manifest's new skipped:spec_version status). --spec-version previously parsed and validated the flag but had no effect on which schema was checked.
  • tods-validate drift OLD_GTFS NEW_GTFS --tods FEED (EXP-02): diagnoses the "your GTFS moved under your TODS" failure directly, reporting exactly which referenced trip_id/stop_id values disappeared and which trips' block_id changed between two GTFS versions, with a conservative rename guess offered only when exactly one new GTFS ID is an unambiguous close match. --format text|markdown|json; exits non-zero on any break so it can gate a GTFS update in CI.

Fixed:

  • Malformed feed values and baseline files now produce validator findings or clear input errors instead of uncaught exceptions. Numeric parsing requires ASCII digits, GitHub annotation properties are escaped, LSP diagnostics stay within the validated feed, and baseline documents must contain a findings array.
  • GHCR release builds now use the lowercase image reference created by Docker metadata when running the blocking Trivy scan. The Docker workflow can also rebuild an existing signed release tag through workflow_dispatch.
  • The advisory spec watcher now recognizes a field labeled Optional whose description makes it conditionally required. This stops vehicle_assignments.service_id from opening a false spec-drift issue while preserving TODS-E205's conditional requirement.
  • Conformance-corpus expectations are now a committed, reviewed oracle. CI and the release builder compare every fixture's exact rule-ID set against it instead of regenerating expected outcomes from the validator under test.
  • The README standards table now uses the canonical Security & Supply-Chain, AI Evaluation, and Responsible-Tech Framework labels consumed by the portfolio conformance checker.

v0.7.0 - 2026-07-11

Findings now reach the editor (a language server with hovers and quick fixes, plus a thin VS Code client), reports state exactly which checks ran and which were skipped, and local severity policy is supported with mandatory disclosure. Also new: fix suggestions (validate --suggest), an offline explain command with worked examples, pytest helpers for exporters, and two run-continuity warnings (TODS-W316, TODS-W409).

Added:

  • An architecture decision record log under docs/adr/: 0000 records the practice, 0001–0005 backfill the decisions already in force (the Python 3.11 floor, the i18n N/A declaration, the nested editor/vscode project, rules-as-registry, the uv/lockfile adoption). A committed .python-version pins local development to 3.12, the same interpreter CI runs its gates on. Closes CQ-01, CQ-26, CQ-44/45, and DOC-04/05 in docs/CONFORMANCE-GAPS.md.
  • A permanent per-rule web page for every rule ID, generated into web/rules/ by scripts/generate_rules_doc.py alongside docs/rules.md, plus a web/rules/index.html catalog grouped by band. Deployed with the rest of web/ by .github/workflows/pages.yml. SARIF helpUri and the language server's hover text now link to these stable URLs (https://chelseakr.github.io/tods-validate/rules/<RULE_ID>.html) instead of the spec section directly, so the link keeps resolving even if the spec text moves; the spec citation itself is still carried in the SARIF rule's properties.specSection and on the rule page. scripts/generate_rules_doc.py --check now also fails CI if a committed rule page has drifted from the registry.
  • TODS-W316: the time companion of W315. A run event that works a trip end to end should start at the trip's first scheduled departure and end at its last scheduled arrival; a mismatch is a warning, skipped for mid-trip events. Uses the stop_times the companion GTFS already ingests.
  • TODS-W409: consecutive events in one run should connect in space — an event's end_location should be the next event's start_location, since an operator cannot teleport between locations. A gap is a warning (legitimate exceptions exist), and adjacencies with a blank endpoint are skipped. TODS-only, no companion GTFS needed.
  • A language server (tods-validate lsp, or the tods-validate-lsp entry point) that re-validates the whole feed when you open or save any TODS file and shows each finding inline at its row and field. Findings name a field, so the diagnostic underlines the offending value, not just the line. Needs the new lsp extra (pip install 'tods-validate[lsp]', which brings in pygls); the diagnostic-mapping core is pure and unit-tested without an editor.
  • The language server now offers quick fixes and hovers. Hovering a finding shows the rule's title, description, and spec link; the fixable findings carry a code action — "Trim surrounding whitespace" (TODS-W206) and "Delete duplicate row" (TODS-W408) — that edits the document in place.
  • A VS Code extension under editor/vscode/ that launches the language server for TODS files, so the diagnostics, hovers, and quick fixes show up in the editor. It is a thin client (build it with npm install && npm run compile, press F5 to try it); it is not published to the Marketplace.
  • tods-validate validate --suggest lists concrete fix suggestions for the mechanically-fixable findings after the report, each marked auto (safe and meaning-preserving, the kind tods-validate fix applies) or review (derivable but worth a human's confirmation, such as a time written 9:45 -> 09:45:00 or a date written 2026-03-15 -> 20260315). A suggestion is only offered when its proposed value is one the validator would accept and is reachable by adding leading zeros, a zero seconds field, or removing date separators, so it never changes what a value means. Text and Markdown output only; the JSON report is left untouched so it stays a stable machine contract. The same suggestions are available programmatically via tods_validate.suggest_fixes.
  • A test-helper module (tods_validate.testing) with assert_feed_valid and assert_feed_produces, so a TODS exporter can gate its own pytest suite on the same checks the CLI and Action run without shelling out. On failure they raise with the human-readable report rather than a stack trace. See docs/api.md.
  • A contributor guide for authoring rules (docs/authoring-rules.md): how to pick a severity and allocate an ID, the scheduler-grade message style, and the fixture/conformance contract CI enforces.
  • Reports now state their own scope. Every run records a coverage manifest — which rules ran, and which were skipped and why (no companion GTFS feed, opt-in rule not enabled, or suppressed by --ignore) — so "no problems found" is qualified by what was actually checked. The JSON report carries it as an additive coverage block (report schema 1.2.0, documented in docs/report.schema.json), SARIF records it under invocations, and the text/Markdown/HTML reports add a one-line "Checks skipped: …" disclosure (plus a coverage footer on stamped Markdown). Library callers can get the manifest via the new tods_validate.runner.run_with_coverage; run is unchanged.
  • Reference findings (TODS-E301/E303/E307/E308/E309/E310/E311/E312/E314) now carry structured data parameters — the broken value and what it references — and the SARIF output is enriched from the rule registry: each descriptor gains the rule's title, description, and spec link (helpUri), and each result carries its finding's structured data in properties.
  • tods-validate explain RULE_ID: an offline command that prints a rule's full detail — description, spec citation, and a worked before/after example — with --format markdown for pasting into an issue. Every core rule (and the opt-in coverage/advisory rules) now ships a worked example, sourced from one registry (tods_validate.rules.EXAMPLES) that explain, docs/rules.md, and LSP hovers all render through the same render_rule_detail(), so the three cannot drift from each other.
  • An optional [severity] table in tods-validate.toml remaps individual rule severities to encode local policy, with a hard honesty constraint: every remapped finding is disclosed in every report format (a "Local policy" block plus a per-finding "(spec: ORIGINAL)" note), and downgrading a rule the spec declares ERROR requires an explicit acknowledged = true. The report schema (1.2.0) documents findings[].severity_original. (#25)

Changed:

  • The --format html report is now an explicit accessibility pass: it declares its language and a responsive viewport, uses header/main landmarks, gives the findings table a caption and column-scoped headers, and lightens the info severity color so all three severities clear WCAG AA contrast on the white background. The README gained a short accessibility statement.
  • tods-validate fix now does more than trim whitespace: it also drops entirely-blank rows (the ,,, lines that otherwise raise a wall of E201) and removes rows that are byte-identical to an earlier one (the TODS-W408 duplicate assignment). A row that shares a primary key but differs in any value is a real conflict and is left untouched for a human. Still a dry run by default.

Fixed:

  • The reported tool version (toolVersion in the JSON/HTML reports and --version) is now read from the installed package metadata instead of a hand-edited constant that had drifted to 0.4.0.
  • The README and merge-recipe GitHub Action snippets now reference the current @v0.6.0 instead of the stale @v0.4.0 they were pinned at.
  • TODS-W302 now also discloses when vehicle_assignments.txt references could not be checked: block_id resolution needs the companion feed's trips.txt and service_id resolution needs calendar.txt/calendar_dates.txt; when a used column's target file is missing, those checks used to no-op silently.

Security / process (2026-07-05 standards-conformance remediation):

  • make audit (pip-audit) now audits the exact uv.lock pins minus the project itself, so a release version bump (a version that is not on PyPI until after the release publishes) cannot fail the gate; release tags are SSH-signed and verify.yml verifies them against the committed .github/allowed_signers.

  • The release pipeline (pypi-publish.yml, docker.yml, release-corpus.yml) no longer publishes anything without first re-running the full gate set (make verify, new) at the tagged commit, plus a version-consistency check and an annotated/signed-tag check; a verify-published job now re-checks the published artifact's provenance/signature after publish.

  • Fixed template-injection-shaped patterns in action.yml and the release workflows (${{ }} no longer interpolated directly into run: shells).

  • Added Semgrep, CodeQL (python + actions), zizmor, gitleaks (pre-commit

    • CI), and a blocking pip-audit gate; adopted uv with a committed uv.lock; added a Trivy CVE scan and a digest-pinned base image to the Docker build; the Dockerfile now runs as a non-root user.
  • Added a README.md Standards Conformance table, docs/CONFORMANCE-GAPS.md, docs/RESPONSIBLE-TECH-AUDITS.md, DEFINITION_OF_DONE.md, .github/PULL_REQUEST_TEMPLATE.md, .github/CODEOWNERS, and a vendored copy of the engineering standards this project is held to (docs/standards/).

  • No user-facing behavior changed in this entry; see docs/CONFORMANCE-GAPS.md for the full list of what closed and what remains open.

v0.6.0 - 2026-06-29

New surfaces for working with a feed live (--watch, browser playground), acting on findings (fix), and sharing results (stats --format markdown, conformance corpus), plus a new cross-feed operational check (TODS-W315).

Added:

  • tods-validate validate --watch re-validates whenever the feed changes (polls the files), the cheap interim ahead of editor/LSP integration.
  • A browser playground (web/) that validates a feed entirely in the browser via Pyodide, with no upload, deployable to GitHub Pages. The Python it calls is guarded by tests; the page itself needs a browser to verify.
  • TODS-W315: a run event that works a trip end to end should start at the trip's first stop and end at its last stop (in the supplemented stop_times.txt); a mismatch is a warning, skipped for mid-trip events. The companion GTFS now ingests stop_times, so this checks an operational consistency constraint no GTFS-only validator can see.
  • tods-validate fix applies safe, deterministic fixes — currently trimming the TODS-W206 whitespace padding that stops IDs from matching. It is a dry run by default and writes a cleaned, UTF-8/no-BOM package with -o.
  • tods-validate stats --format markdown prints a feed profile (now including a date range and a file-presence list) suitable for pasting into an issue or a working-group thread.
  • A downloadable conformance corpus, attached to each release: every fixture plus an expectations.json mapping each to the rule IDs it should produce, so another validator can run the suite without cloning the repo (scripts/build_conformance_corpus.py).

v0.5.0 - 2026-06-22

Correctness fixes (no rule IDs changed), a runnable bundled sample feed with a fixed quickstart, and a conformance check that runs the spec's own examples.

Fixed (no rule IDs changed):

  • TODS-E204 now detects duplicate vehicle_assignments primary keys when the optional service_id is blank (the common case). Previously a blank optional key component silently suppressed the whole uniqueness check, so real duplicate keys passed clean and coalesced during merge.
  • Time values with hours >= 100:00:00 are now accepted (GTFS time has no upper hour bound). They previously raised a false TODS-E203 and were dropped from the time-based semantic checks (E401/E402/W403).
  • TODS-E314 no longer fires on a stop_times_supplement row whose trip was deleted via trips_supplement (TODS_delete=1); the spec says such stop_times are ignored, not an error.
  • Duplicate header columns now keep the first occurrence's value (matching the TODS-E105 message that the duplicate column is ignored) instead of letting a later duplicate column silently win.
  • All-blank data rows (a stray ,,, line past the header) are no longer silently dropped; their missing required values are now reported (TODS-E201).
  • TODS-E205 (vehicle_assignments block ambiguity) is now marked as requiring a companion GTFS feed, so a TODS-only run reports it as unchecked instead of silently passing the check.

Other:

  • Bundled a runnable sample feed at examples/sample-feed/ and pointed the README quickstart at it, so a new install has something that passes on the first run. The GitHub Action now sets up Python explicitly.

v0.4.0 - 2026-06-20

Distribution, reporting, and analysis surfaces. No rule IDs changed; the JSON report gained fields (it is now reportVersion 1.1.0) without removing any.

Added:

  • tods-validate rules lists the rule catalog from the tool itself (--format json for tooling, now including category, default-enabled, and spec-interpretation metadata).
  • Published JSON Schema for the --format json report (docs/report.schema.json), enforced by tests.
  • Dockerfile and a workflow publishing images to GHCR on each release.
  • pre-commit hook definition (.pre-commit-hooks.yaml).
  • New report formats: --format sarif (GitHub code-scanning / security dashboards) and --format html (a standalone, shareable report).
  • JSON report now carries toolVersion, reportVersion, a per-rule summary.byRule breakdown, and a stable location pointer per finding.
  • Text and Markdown reports group findings by rule, show the shortest path to a clean run, and add root-cause hints when one rule clusters.
  • New flags on validate: --enable (opt-in rules/categories), --profile (default/strict/lenient presets), --spec-version, --baseline (fail only on findings new since a previous JSON report), --max-findings, --quiet, --stamp (citable Markdown footer), and --encoding.
  • New subcommands: diff (compare two feeds), batch (validate many feeds with a roll-up table), stats (descriptive feed metrics), and anonymize (pseudonymize person-identifying fields).
  • merge now writes a merge-report.json manifest alongside the merged feed.
  • The GitHub Action exposes error-count, warning-count, and info-count outputs and accepts an enable input.
  • New opt-in rules: TODS-I501 / TODS-I502 (coverage) and TODS-I601 (advisory).
  • Public Python API: from tods_validate import validate_feed.
  • Input-safety hardening of zip ingestion (zip-bomb and path-traversal defenses, size limits) and a SECURITY.md.
  • scripts/benchmark.py for throughput measurement on large synthetic feeds.

v0.3.0 - 2026-06-12

  • New merge subcommand writes the "TODS-Supplemented GTFS" dataset (the GTFS feed after supplement rows are applied) to a directory or .zip, with per-file counts of updated, added, and deleted rows. The merged feed can then be checked with MobilityData's gtfs-validator.
  • New rule TODS-E314: a supplement row references a route, service, trip, or stop that does not exist in the supplemented feed.
  • The CLI now has explicit validate and merge subcommands; tods-validate PATH without a subcommand still validates, so existing invocations and the GitHub Action are unaffected.

v0.2.0 - 2026-06-12

  • --ignore RULE_ID (repeatable) suppresses specific rules.
  • Optional tods-validate.toml configuration file (ignore, fail-on), discovered in the working directory or passed with --config.
  • --format markdown: a report suitable for pasting into an issue or a working-group thread.

v0.1.0 - 2026-06-12

First release: 35 checks against TODS v2.1.0 covering file structure, field values, references (including into the companion GTFS feed after supplements are applied), and schedule semantics. CLI with text, JSON, and GitHub annotation output, plus a composite GitHub Action.