feat(imports): add SureImport session batches (#1785)

* feat(imports): add SureImport session batches

Add first-class SureImport sessions for ordered multi-file NDJSON imports.

Persist source mappings across chunks, make session/chunk processing idempotent, expose progress readback, and keep existing single-file import behavior compatible.

Includes the devcontainer libvips runtime dependency needed by ActiveStorage variant tests.

Addresses #1610.

Related to #1458.

* fix(imports): avoid scanner-like API key test data

* test(imports): assert skipped balances are not persisted

* fix(imports): harden session publish retries

Validate expected import chunk sequences exactly before publish, and restore session state with error details when enqueueing the publish job fails.

* fix(imports): close session retry edge cases

Backfill expected chunk counts after client-session insert races and enqueue import-session jobs after the status transition commits. Persist a safe enqueue failure body so API readback does not expose raw queue errors.

* fix(imports): address session publish review gaps

Remove dead transaction external-id assignment, harden session publish retry/sync behavior, align session chunk status docs, and add regression coverage for partial retries and safe enqueue error readback.

* fix(imports): include sessions in family reset

Clear import sessions through the family reset job so chunk imports and source mappings do not survive a reset.

Expose import session and source mapping counts in the reset status response and regenerated OpenAPI schema so polling reflects the full reset surface.

* test(imports): cover split import mapping invariants

* test(imports): cover session verification invariants

* fix(imports): scope SureImport session reimports

* Tighten SureImport session batching

* fix(imports): export rule source ids for sessions

* test(imports): stabilize rule id export assertion

* test(imports): restore reset status session fixture
This commit is contained in:
ghost
2026-06-04 02:48:44 -07:00
committed by GitHub
parent 76d4c2a2fe
commit 6e04c6927d
23 changed files with 3542 additions and 93 deletions

View File

@@ -2086,6 +2086,115 @@ components:
properties:
data:
"$ref": "#/components/schemas/ImportDetail"
ImportSessionChunk:
type: object
required:
- id
- sequence
- status
- rows_count
- summary
- created_at
- updated_at
properties:
id:
type: string
format: uuid
sequence:
type: integer
minimum: 1
client_chunk_id:
type: string
nullable: true
status:
type: string
enum:
- pending
- importing
- complete
- failed
rows_count:
type: integer
minimum: 0
summary:
type: object
additionalProperties:
type: object
additionalProperties:
type: integer
error:
type: object
nullable: true
additionalProperties: true
created_at:
type: string
format: date-time
updated_at:
type: string
format: date-time
ImportSession:
type: object
required:
- id
- type
- status
- chunks_count
- summary
- chunks
- created_at
- updated_at
properties:
id:
type: string
format: uuid
type:
type: string
enum:
- SureImport
status:
type: string
enum:
- pending
- importing
- complete
- failed
client_session_id:
type: string
nullable: true
expected_chunks:
type: integer
nullable: true
minimum: 1
chunks_count:
type: integer
minimum: 0
summary:
type: object
additionalProperties:
type: object
additionalProperties:
type: integer
error:
type: object
nullable: true
additionalProperties: true
chunks:
type: array
items:
"$ref": "#/components/schemas/ImportSessionChunk"
created_at:
type: string
format: date-time
updated_at:
type: string
format: date-time
ImportSessionResponse:
type: object
required:
- data
properties:
data:
"$ref": "#/components/schemas/ImportSession"
ProviderConnectionInstitution:
type: object
required:
@@ -2892,6 +3001,8 @@ components:
- account_statements
- family_exports
- imports
- import_sessions
- import_source_mappings
- import_rows
- import_mappings
- accounts
@@ -2933,6 +3044,12 @@ components:
imports:
type: integer
minimum: 0
import_sessions:
type: integer
minimum: 0
import_source_mappings:
type: integer
minimum: 0
import_rows:
type: integer
minimum: 0
@@ -4607,6 +4724,266 @@ paths:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
"/api/v1/import_sessions":
post:
summary: Create import session
description: Create or idempotently retrieve a multi-file SureImport session
keyed by client_session_id.
tags:
- Import Sessions
security:
- apiKeyAuth: []
parameters: []
responses:
'201':
description: import session created
content:
application/json:
schema:
"$ref": "#/components/schemas/ImportSessionResponse"
'401':
description: unauthorized
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'403':
description: insufficient scope
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'409':
description: client session conflict
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'422':
description: validation error
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
requestBody:
content:
application/json:
schema:
type: object
properties:
type:
type: string
enum:
- SureImport
description: Import session type. Only SureImport is supported.
client_session_id:
type: string
nullable: true
description: Client-provided idempotency key for the full import
session.
expected_chunks:
type: integer
minimum: 1
nullable: true
description: Expected number of ordered chunks before publish is
allowed.
"/api/v1/import_sessions/{id}":
parameters:
- name: id
in: path
required: true
description: Import session ID
schema:
type: string
get:
summary: Retrieve import session
description: Retrieve import session status, chunk status, per-entity summary
counts, and safe error details.
tags:
- Import Sessions
security:
- apiKeyAuth: []
responses:
'200':
description: import session retrieved
content:
application/json:
schema:
"$ref": "#/components/schemas/ImportSessionResponse"
'401':
description: unauthorized
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'403':
description: insufficient scope
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'404':
description: import session not found
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
"/api/v1/import_sessions/{id}/chunks":
parameters:
- name: id
in: path
required: true
description: Import session ID
schema:
type: string
post:
summary: Upload import session chunk
description: Attach an ordered Sure NDJSON chunk to an import session. Chunks
are idempotent by sequence and client_chunk_id with content verification.
tags:
- Import Sessions
security:
- apiKeyAuth: []
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- sequence
- raw_file_content
properties:
sequence:
type: integer
minimum: 1
description: One-based chunk sequence. Earlier dependency chunks
must have lower sequence numbers.
client_chunk_id:
type: string
nullable: true
description: Client-provided idempotency key for this chunk.
raw_file_content:
type: string
description: Raw Sure NDJSON content. Each chunk is limited to 10MB.
multipart/form-data:
schema:
type: object
required:
- sequence
- file
properties:
sequence:
type: integer
minimum: 1
description: One-based chunk sequence. Earlier dependency chunks
must have lower sequence numbers.
client_chunk_id:
type: string
nullable: true
description: Client-provided idempotency key for this chunk.
file:
type: string
format: binary
description: Multipart Sure NDJSON file upload. Each chunk is limited
to 10MB.
parameters: []
responses:
'201':
description: chunk uploaded
content:
application/json:
schema:
"$ref": "#/components/schemas/ImportSessionResponse"
'401':
description: unauthorized
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'403':
description: insufficient scope
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'409':
description: chunk conflict
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'404':
description: import session not found
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'422':
description: missing or invalid content
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
"/api/v1/import_sessions/{id}/publish":
parameters:
- name: id
in: path
required: true
description: Import session ID
schema:
type: string
post:
summary: Publish import session
description: Queue ordered chunk processing for a SureImport session. Later
chunks can reference source IDs mapped by earlier chunks.
tags:
- Import Sessions
security:
- apiKeyAuth: []
responses:
'202':
description: import session publish queued
content:
application/json:
schema:
"$ref": "#/components/schemas/ImportSessionResponse"
'401':
description: unauthorized
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'403':
description: insufficient scope
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'422':
description: max_row_count_exceeded
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'409':
description: missing expected chunks
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'503':
description: enqueue failed
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'404':
description: import session not found
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
"/api/v1/imports":
get:
summary: List imports