Normalization and quarantine

When the upstream submits an item, FG.AI runs a normalization pipeline: schema validation, then business-rule validation, then mapping into FG.AI’s canonical shape. Each item has one of four outcomes, reported individually inside a batch response.

Per-item outcomes

Status

Meaning

Caller action

ACCEPTED

Normalized successfully. internal_id is returned.

None.

REPLAY

The same (partner_id, source_id, source_version) was already accepted.

None.

QUARANTINED

Schema valid; business validation failed. The item is held in a quarantine queue pending fix or operator override.

Fix the underlying source data and resubmit, or release through /quarantine/{id}/release.

REJECTED

Schema invalid at the request level.

Fix the payload and resubmit.

Per-item outcomes coexist within a single batch — a batch of 1,000 SKUs with 3 bad UoM references returns 997 ACCEPTED + 3 QUARANTINED. Quarantine is per-item, never per-batch.

The three common quarantine causes

Most quarantines fall into one of three patterns:

  1. Master-data dependency missing. A SKU references a UoM that was never upserted; a Bin references a parent Zone that hasn’t been registered; a Receiver references a po_source_id not in FG.AI yet.

  2. Hierarchy unresolved. A Bin’s parent_source_id points at a Zone that hasn’t been upserted. A Zone points at a Warehouse not yet seen. This is a special case of #1 but worth calling out because it dominates onboarding loads.

  3. Cross-entity reference unresolved. A document references a SKU or party not yet ingested. A movement references a lot that hasn’t been registered.

The fix is almost always order of operations — upsert dependencies before dependents. See the Rollout page for the recommended onboarding sequence.

Quarantine record shape

{
  "quarantine_id": "qn-01HY...",
  "partner_id":    "ACME-TENANT-A",
  "entity_kind":   "sku",
  "source_id":     "SKU-FOO-001",
  "reason":        "Unknown UoM 'KG'. Register via /master/uoms first.",
  "submitted_payload": {},
  "quarantined_at":    "2026-05-22T03:14:01Z",
  "state": "PENDING"
}

state lifecycle: PENDING → (RESOLVED_BY_RESUBMIT | RESOLVED_BY_RELEASE | EXPIRED).

Resolving a quarantine

Two paths, both auditable:

Path A — fix the underlying data and resubmit

This is the right answer in the vast majority of cases. The upstream:

  1. Fixes the dependency (e.g. registers the missing UoM).

  2. Re-upserts the original item. The new attempt normalizes successfully and the prior quarantine record moves to RESOLVED_BY_RESUBMIT.

The fixed data is the truth going forward. No operator override.

Path B — operator override

POST /wms-ingest/v1/quarantine/{quarantine_id}/release

Releases the quarantined item into the canonical store as-is, suppressing the failed business validation. Use sparingly: it papers over a real data-quality issue and the bad reference will still exist. Releases are audited with the operator identity and a required reason body field.

Listing and filtering

GET /wms-ingest/v1/quarantine?state=PENDING&entity_kind=sku&since=2026-05-22T00:00:00Z

Returns a paginated list. Filter by state, entity_kind, time range, or partner_id (if the credential has multi-tenant scope, which is unusual).

This endpoint backs the supervisor’s quarantine triage UI. Most integrations should expose GET /quarantine (or the equivalent webhook — see Webhooks) in their own ops tool so the data team can react without an FG.AI engineer in the loop.

Expiration

Quarantine records expire after 30 days in PENDING state. An expired record moves to EXPIRED and the original item is dropped — re-ingestion requires a fresh upsert from the upstream. The 30-day window is documented in GET /capabilities.

If a quarantine ages past 30 days, the underlying data has been wrong for a month. The right answer is fixing the upstream’s data, not extending the window.

Webhook notification

When an item is quarantined, FG.AI emits a master.normalization-conflict webhook to the caller’s registered URL. The webhook carries the same quarantine_id and reason as the synchronous response. Subscribers can use this to drive triage UIs without polling GET /quarantine.

See Webhooks for delivery semantics and HMAC verification.

What does not quarantine

The following always REJECT, never QUARANTINE:

  • Malformed JSON.

  • Missing required envelope fields (partner_id, correlation_id, source_id).

  • Unknown entity-kind in a /master/* path.

  • Type mismatches in field values.

These are request-level failures, not business-rule failures. They cannot be queued for later because there is nothing actionable to queue — the request itself is broken.

Quarantine vs the document-level supervisor workflow

Quarantine is for ingest-time normalization failures. Once a document is normalized and ACCEPTED, downstream supervisor workflows (e.g. WES discrepancy resolution) handle different problems — those have nothing to do with quarantine and live elsewhere in the FG.AI platform.