Repository navigation
ART M5: versioned datasets, per-tenant dedup, cross-version duplicate flags (#109) - #226
Conversation
…ate flags (M5, #109) BR-28..31, decisions 2 and 3. - A repeated filename is the next version of its dataset, stored in csv_<slug>_v<n>. Nothing is overwritten; the overwrite flag is ignored. Same-name uploads are serialised per dataset with an advisory lock so concurrent uploads get distinct versions. - Exact-file dedup stays per registry (per tenant, or the default registry for a request with no session); overwrite no longer bypasses it. - Rows already in an earlier version are loaded but flagged (row_flags, insert-only) and shown on the review page; the user decides per row with the existing include/exclude decision. - A new version's columns are compared with the previous version's and the difference is reported. The previous table is never altered. - T&E loads match registry entries by file name only among non-dynamic entries, so they can't delete dataset versions. - Migration 0009 adds dataset/version (backfilled), replaces UNIQUE(file_name) with UNIQUE(dataset, version) plus a partial unique index for non-dynamic modes, and creates row_flags. Tests: tests/test_versioned_datasets.py (22), migration tests for 0009; tests that pinned the old overwrite behaviour now pin versioning. Full suite: 418 passed, 0 failed, 0 skipped. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011T6WqhUfEMZv2Dxerqs2Eq
row_flags was insert-only by trigger but missing from APPEND_ONLY, so the runtime role still held UPDATE and DELETE grants on it. The trigger blocked the change anyway; this restores the privilege layer the other insert-only tables have, and covers it in the runtime-role test. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011T6WqhUfEMZv2Dxerqs2Eq
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011T6WqhUfEMZv2Dxerqs2Eq
Main moved on: #195 took migration 0009 (types_provenance), the tests were reorganised into category folders (#167), and the loader, routes, db and tests were edited. - Renumbered this branch's migration 0009 -> 0010 so it chains after #195's. - Registry insert carries both types_provenance (#195) and dataset/version (M5). - APPEND_ONLY keeps row_flags and takes main's alembic_version_te. - tests/test_versioned_datasets.py moved to tests/system/ (it needs PostgreSQL). - Migration tests keep both sets; mine are renumbered. Head checks use get_current_head(), as main now does. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011T6WqhUfEMZv2Dxerqs2Eq
…3, BR-34; ADR-0002) For dynamic-mode uploads: stage, review, submit, approve or reject, then import in one transaction. - POST /api/csv/uploads stores the original and parsed rows; nothing is imported. - Submit, then a reviewer approves or rejects (a rejection needs a reason; a rejected upload can be reviewed again and resubmitted). A signed-in member can't approve data they uploaded or submitted unless the tenant allows it. - Approval snapshots the review (newest decision and edit ids), and review is refused once approved, so what was approved is what is imported. - Import (Idempotency-Key header): COPY into a temporary staging table, promote into csv_<slug>_v<n> with _source_row_id and _import_id, register the file, write the imports record and the audit row, commit. Any failure rolls all of it back. A repeat with the same key returns the original; another key is a 409. - Reconciliation is stored per import and checked by the database: rows_in = excluded + duplicate + imported. - Migration 0011: submissions, approvals, imports (insert-only), the staged status, uploads.types_provenance, and tenants.allow_self_approval (off by default). - The direct /api/csv/upload path and T&E mode are unchanged. Merges the updated M5 branch (which carries current main): migration renumbered 0010 -> 0011, tests moved to tests/system/. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011T6WqhUfEMZv2Dxerqs2Eq
The CORS allow-list lacked Idempotency-Key, so a browser's preflight for
POST /api/csv/uploads/{id}/import was refused with 400 and the import could
only be called by scripts. Adds the header and a preflight test.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011T6WqhUfEMZv2Dxerqs2Eq
…aged-import ART M6: approval and transactional staged import (#110)
|
The This isn't caused by this PR. It changes no frontend files (0 of 29), and the accessibility job stubs the API, so backend changes can't alter what it scans. The same scenario has passed on this branch's earlier heads and on the stacked PRs' heads, and failed once before, on #228's first attempt, then passed on retry. The 13 elements are all I'll re-run the job once when the rest of this run finishes (GitHub won't re-run a single job while the run is in progress). If it fails again, I'll treat it as real and dig in. Generated by Claude Code |
A dialog still fading in is blended with the page behind it, so axe reported false color-contrast violations in the column mapping dialog. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011T6WqhKR6dS4KmR9nHSMJr
What
M5 of the ART plan: deduplication and naming (BR-28..31, decisions 2 and 3). Tracker: #109.
csv_<slug>_v<n>. Nothing is overwritten and theoverwriteflag is ignored. Same-name uploads are serialised per dataset (advisory lock), so concurrent uploads get distinct versions.overwriteno longer bypasses it.row_flags, insert-only) and shown on the review page. The user decides per row with the existing M4 include/exclude decision; M6 applies it.dataset/version(backfilled, oldest first), replacesUNIQUE (file_name)withUNIQUE (dataset, version)plus a partial unique index for non-dynamic modes, createsrow_flags. Downgrade refuses while versions share a file name.Decisions to check
csv_<slug>_v<n>, not the plan's bare<slug>_v<n>. The prefix keeps the existingcsv_guards working and stops a filename such asuploads.csvcolliding with a registry table.Not in this PR
overwritereturns the same duplicate. The UI has no display for flagged rows orschemaCheckyet.csv_<hash>tables and filename dedup. docs: fix STARTER_GUIDE walkthrough accuracy (#165, #166) #206 owns those files, so I left them.Verification
After merging current main: the full suite (everything except the browser job) passes against PostgreSQL 16 (495 tests). Offline evals 23/23,
scripts/lint.shclean,docs/TEST_CASES.mdregenerated. Earlier, before the merge,scripts/test_report.py --strictgave 418 passed, 0 failed, 0 skipped.New:
tests/test_versioned_datasets.py(22 tests) and four migration tests. With the duplicate-row lookup disabled, four of them fail. Seven older tests that pinned overwrite or single-name behaviour were updated to the new behaviour.Also includes a fix found while reviewing my own work:
row_flagswas missing fromAPPEND_ONLY, so the runtime role still held UPDATE and DELETE on it. It is covered in the runtime-role test.Closes nothing yet: #109 stays open until this merges.
https://claude.ai/code/session_011T6WqhUfEMZv2Dxerqs2Eq
Generated by Claude Code