Skip to main content

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.
You'll need
  • A MapCraft project on the document lane with a saved mapping — open /flowbridge/{projectId} and create the mapping first if prompted.
  • An .xlsx file 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.00 is 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.

Every row remembers where it came from

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.

a transform node's validations
"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:

ValidatorScopeParametersFails when
not_nullfieldthe value is null or a blank string
regexfieldpatternthe pattern doesn't match (uses search — anchor it yourself)
enumfieldvaluesthe value isn't one of values
min_maxfieldmin, max (either optional)the number falls outside the range, or isn't numeric
sum_tolerancerecordsum_fields, total_field, tolerancethe components don't add up to the total within tolerance
fields_equalrecordleft, right, tolerancethe 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.

A broken rule never breaks a run

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:

FilterShows
Quarantinedrows waiting for you — the default view
Retry pendingrows you've fixed and queued for the next run
Retriedrows that have already gone back through a run
Dismissedrows you closed without reprocessing
Alleverything

When more than one run has contributed rows, a Run selector appears so you can narrow to a single run.

Now fix one:

  1. Click Edit on the row with the blank vendor. The record becomes an editable grid, failing fields first.
  2. Type the correct value and click Save.
  3. 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.

Your fix still has to pass

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 .xlsx uploaded 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