Review, fix, and retry quarantined rows
A month-end spreadsheet is never clean. One invoice has the date in
DD/MM/YYYY, one vendor cell is blank, one total doesn't add up. Most tools
give you two bad options: fail the whole file, or let the bad rows through and
find out later.
DataChord takes a third path. Rows that fail are quarantined — held in a dead-letter store with the exact reason and the exact place they came from — while every good row is delivered on time. You then review the held rows in FlowBridge → Quarantine, fix a cell inline, and retry it. The next run re-ingests the fixed row and delivers it.
By the end of this page you will have:
- Uploaded an Excel workbook (
.xlsx) to StructScan and profiled it. - Attached row-level validation rules to a transform so bad rows are held instead of dropped.
- Run the pipeline and seen good rows delivered while bad rows were quarantined.
- Fixed a held row inline in the Quarantine tab and retried it.
- Dismissed a row that shouldn't be reprocessed, with a reason recorded.
- A MapCraft project on the document lane with a saved mapping — open
/flowbridge/{projectId}and create the mapping first if prompted. - An
.xlsxfile to work with. A messy one is better for this walkthrough: include a row with a blank required cell and a row whose date is in the wrong format. - The Quarantine tab enabled. It ships on by default
(
NEXT_PUBLIC_FLOWBRIDGE_QUARANTINE=true); if your deployment has pinned it off, the tab is hidden and the route shows a notice telling you which variable to set. Rows are still quarantined server-side either way.
Step 1 — Upload the workbook
Open StructScan and drop your .xlsx onto the upload zone — the zone
accepts CSV, JSON, XML, or XLSX up to 100MB.
Excel files are read directly, with two behaviours worth knowing:
- The header row is found for you. Title and period banners above the table ("Purchase Register", "Apr-2026") are skipped; the first all-text row with the most filled columns wins. You don't have to trim the sheet first.
- Amounts stay as they are.
1,00,000.00is not silently reinterpreted at parse time. Cleaning numbers is the job of a mapping rule (see Step 3), which keeps the transformation auditable instead of hidden in the importer.
Profile the file. Each field comes back with a detected type and sample values.
As the workbook is parsed, each row is stamped with its origin — the file name, the sheet, and the 1-based worksheet row. That stamp travels with the record all the way through the transform, which is why the Quarantine tab can tell you "row 9 of the Purchases sheet" instead of "record 4 of the batch". You never see these internal fields in your profile or your mapping — they are filtered out of the field list.
Step 2 — Push the profile into a mapping
Use Push to MapCraft to turn the profile into a source schema and map it to your target as usual. Nothing about Excel changes this step.
If you've mapped a file with this exact layout before, DataChord recognises it.
The layout is fingerprinted on the shape of the columns only — not on the file
name and not on whether a column happened to contain nulls this month — so
april.xlsx and may.xlsx with identical columns produce the same fingerprint
and route to the same saved mapping. Map once, reuse every month.
Step 3 — Add validation rules to the transform
Validation rules live on the transform node, in the DAG document's validations
array. Each rule names a validator, an optional field, and a level.
"validations": [
{ "field": "vendor", "validator": "not_null" },
{ "field": "order_date", "validator": "regex",
"params": { "pattern": "^\\d{4}-\\d{2}-\\d{2}$" } },
{ "validator": "sum_tolerance",
"params": {
"sum_fields": ["taxable_amount", "tax_amount"],
"total_field": "total_amount",
"tolerance": 0.01
} }
]
These validators ship with the platform:
| Validator | Scope | Parameters | Fails when |
|---|---|---|---|
not_null | field | — | the value is null or a blank string |
regex | field | pattern | the pattern doesn't match (uses search — anchor it yourself) |
enum | field | values | the value isn't one of values |
min_max | field | min, max (either optional) | the number falls outside the range, or isn't numeric |
sum_tolerance | record | sum_fields, total_field, tolerance | the components don't add up to the total within tolerance |
fields_equal | record | left, right, tolerance | the two fields differ by more than the tolerance |
Two things control what a failure does:
"level": "error"(the default) quarantines the row. It is not delivered."level": "warning"keeps the row and annotates it, so you get a signal without holding up the data.
Omit field to make a rule record-level — the validator sees the whole record,
which is what sum_tolerance and fields_equal need.
An unknown validator name, or a validator that raises on a bad cell, is recorded as a failure on that row — not as a crashed pipeline. A typo in a rule costs you quarantined rows you can inspect, not a dead run at 2am.
Step 4 — Run it, and see what was held back
Trigger a run. When it finishes, the transform reports how many records it
delivered and how many it quarantined, and the run raises a
records.quarantined audit event — so the hold is in your audit log, not just
in a log file.
Two kinds of failure land in quarantine:
- The transform itself rejected the row — for example a rule tried to parse
"n/a"as a number. - A row failed an error-level validation rule after the mapping was applied.
Everything else is delivered on schedule. A messy file does not become a failed file.
Step 5 — Fix a row inline and retry it
Open FlowBridge → Quarantine for the project.
Each held row shows, in this order: its status, where it came from
(purchases-apr-2026.xlsx · Purchases · row 9), the node that held it, and the
reason it failed with the validator's name in parentheses. The fields a rule
actually named are hoisted to the top of the record and shown in red — that's
the cell you came here to fix.
Use the filters along the top to move through the review:
| Filter | Shows |
|---|---|
| Quarantined | rows waiting for you — the default view |
| Retry pending | rows you've fixed and queued for the next run |
| Retried | rows that have already gone back through a run |
| Dismissed | rows you closed without reprocessing |
| All | everything |
When more than one run has contributed rows, a Run selector appears so you can narrow to a single run.
Now fix one:
- Click Edit on the row with the blank vendor. The record becomes an editable grid, failing fields first.
- Type the correct value and click Save.
- Click Retry.
The row's badge changes to Retry pending, and its Retry button disappears — a row can only be queued once. On the next run, it re-enters the stream ahead of the file's own rows, its status flips to Retried, and — assuming your fix satisfied the rule — it is delivered.
Retrying doesn't wave the row through. It goes back through the same validators. If the fix was wrong, the row is quarantined again with the new reason — which is the point: nothing reaches your target without passing the same checks as everything else.
Step 6 — Dismiss what shouldn't come back
Some rows shouldn't be reprocessed at all — a duplicate, a cancelled invoice, a figure the vendor has confirmed is wrong on their side.
Click Dismiss and enter a reason. The row moves to Dismissed and your note is stored with it, so six months later the record shows not just that the row was excluded but why, and that a person decided it.
Verify
You've completed the loop when all of these are true:
- The
.xlsxuploaded and profiled, with the header row detected correctly and banner rows above the table ignored. - The run delivered your good rows and reported a non-zero quarantined count — a partial file did not become a failed file.
- Every held row in the Quarantine tab names its source file, sheet, and row number, plus the validator that rejected it.
- After an inline fix and a Retry, the row read Retry pending, then Retried after the next run, and the delivered row count went up by one.
- A dismissed row appears under Dismissed with your reason attached.
Troubleshooting
The Quarantine tab isn't in the project chrome.
The surface is gated by NEXT_PUBLIC_FLOWBRIDGE_QUARANTINE. It defaults to on;
if your deployment set it to false, the tab is hidden. Because this is a
build-time variable, changing it needs a frontend rebuild, not just a restart.
Rows continue to be quarantined server-side while the tab is hidden — nothing is
lost in the meantime.
Nothing is in quarantine even though rows are missing from the target.
Check that your transform node actually carries a validations array. Without
rules, only rows the transform itself rejects are held.
Edit is missing on a row. Only rows in Quarantined or Retry pending can be edited. A row that has already been retried or dismissed is part of the audit trail and is read-only.
Retry is missing on a row. Only a row in Quarantined can be queued for retry. If it already reads Retry pending, it's queued — trigger a run.
A retried row keeps coming back. Your fix isn't satisfying the rule. Read the reason on the row again: it is regenerated on each attempt, so it reflects the latest failure, not the original one.
What's next
- Detect and fix schema drift (zero-loss) — the companion guarantee: a column nobody told you about is preserved rather than dropped.
- Schedule a mapping and watch its runs — run the cleanup loop unattended, then review whatever it held back.
- Manage files in the StructScan library — find and re-profile the workbooks you've uploaded.