User Story Details
User Story 1: Create [extractor].[Job] and [extractor].[JobStatus] tables for tracking extraction jobs and status transitions
Description
As a developer,
I want to create Flyway migrations that define the [extractor].[Job] and [extractor].[JobStatus] tables,
so that we can persist extractor job metadata and capture detailed status transitions for auditing and polling.
Acceptance Criteria
- Create a Flyway SQL migration in the
fluxx-sql-serverrepo - The migration must:
- Drop each table if it exists (
IF OBJECT_ID(...) IS NOT NULL) - Create
[extractor].[Job]table with:JobID(PK),FileId,ClientApplicationExtractorIDStatus,StatusMessage,ExtractorResponsePollAttemptCount,LastPollAttemptAtIsActive,CreatedAt,UpdatedAt,CompletedAt
- Create
[extractor].[JobStatus]table with:JobStatusID(PK),JobID(FK),Status,MessageAttemptNumber,Timestamp
- Drop each table if it exists (
- Apply proper CLA naming conventions:
IDsuffix for PK/FKIdfor reference columns- Use
IsActivefor soft deletion
- Add constraints:
FK_Job_FileId_File_FileIDFK_Job_ClientApplicationExtractorID_Registration_ClientApplicationExtractorIDFK_JobStatus_JobID_Job_JobID
- Use
SYSDATETIMEOFFSET()as default for all datetime values - Migration must be idempotent and pass in local dev container
User Story 2: Expose endpoint to return active extractors by ClientApplicationId
Description
As an extractor service,
I need a Registration endpoint that returns all active extractors registered for a given ClientApplication,
so I can start the appropriate extraction jobs for an uploaded file.
Acceptance Criteria
- Route:
GET /registration/:clientAppId/extractors - Response: array of objects with the following shape:
- Must filter on
IsActive = true - Must query
[registration].[ClientApplicationExtractor]byClientApplicationId - Swagger/OpenAPI spec must be updated
- Unit and integration tests must cover:
- Valid
clientAppIdwith results - Valid
clientAppIdwith no extractors (returns empty array) - Invalid
clientAppId(returns 404 with problem+json format)
- Valid
User Story 3: Add PollingUrl to [registration].[ClientApplicationExtractor] table (optional)
Description
As a platform architect,
I want to optionally store a polling URL in the extractor registration table,
so that polling-style extractors can have a separate endpoint for job status queries if needed.
Acceptance Criteria
- Create a Flyway migration that adds a nullable
PollingUrlcolumn to[registration].[ClientApplicationExtractor] - Data type:
VARCHAR(500) - Must preserve existing constraints and indexes
- Update the Registration API response to include
pollingUrlif defined - Add OpenAPI schema update to reflect optional field in extractor objects
User Story 4: Add SQL connector for [extractor] schema to Extractor service
Description
As a developer,
I want to enable the Extractor service to connect to the [extractor] schema in SQL Server,
so it can insert and update records for job tracking.
Acceptance Criteria
- Use existing SQL connector pattern from another service (e.g. Registration)
- Connection string should support
.envand App Config override - Extractor service must have access to the following tables:
[extractor].[Job][extractor].[JobStatus]
- Add service-level integration test to verify connection and basic insert/select
- Update
README.mdwith database configuration steps
User Story 5: Add BlobStorage access to Extractor service for writing result files
Description
As a developer,
I want the Extractor service to write result JSON files to a structured blob path,
so that raw extraction results can be stored and retrieved later.
Acceptance Criteria
- Grant Extractor service write access to Azure Storage blob container with HNS enabled
- Configure storage access using environment variables and Azure SDK
- Define blob path pattern:
- Write blob using stream or buffer based on result size
- Validate successful write operation through unit test
- Store path reference in
ExtractorResponsefield in[extractor].[Job]
User Story 6: Add Key Vault integration for API key lookup by ClientApplicationExtractorID
Description
As a developer,
I want to retrieve the API key for a given extractor from Azure Key Vault,
so that the Extractor service can securely call the configured ExtractionUrl.
Acceptance Criteria
- Use
@azure/keyvault-secretsSDK to retrieve secrets - Secret name =
ClientApplicationExtractorID(UUID string) - Secret value = API key (string)
- Fallback to fail job and log error if key is not found
- Add wrapper module with retry logic and unit test coverage
- Update Extractor service configuration docs to describe required Key Vault setup
User Story 7: Initialize Job and publish JobQueued event
Description
As a developer,
I want the Extractor service to insert a new Job record and emit a JobQueued event,
so that we can begin tracking the job and notify the platform that processing has started.
Acceptance Criteria
- On
TOPIC_ACCEPTED_FILEmessage:- Parse
FileId,ClientApplicationId - Call Registration API to get active extractors
- Select the first extractor from the list
- Parse
- Generate a new
JobID(UUID) - Retrieve the API key from Key Vault using
ClientApplicationExtractorID - Insert into
[extractor].[Job]:JobID,FileId,ClientApplicationExtractorIDStatus = 'Queued',PollAttemptCount = 0,CreatedAt,UpdatedAt
- Publish
JobQueuedmessage toTOPIC_JOB_STATUS - Add integration test to verify DB record and message emission
User Story 8: Submit file to extractor via ExtractionUrl
Description
As a developer,
I want the Extractor service to submit the uploaded file to the extractor endpoint,
so that external processing can begin using the correct job context.
Acceptance Criteria
- Retrieve file blob from Azure Storage using signed URL (generated or received)
- Make
POSTrequest toExtractionUrlwith body:
- Use extractor-specific API key as header (e.g.,
Authorization: Bearer) - On success (201 Created):
- Update
[extractor].[Job]toStatus = 'Started' - Add
JobStatusrecord:Status = 'Started',AttemptNumber = 0 - Publish
JobStartedto Service Bus
- Update
- On failure:
- Update job to
Status = 'Failed' - Store response in
StatusMessage - Publish
JobFailedmessage
- Update job to
User Story 9: Implement polling logic for job status updates
Description
As a developer,
I want the Extractor service to periodically poll extractors that require status checks,
so that we can track job completion without relying on push mechanisms.
Acceptance Criteria
- For jobs with known polling requirement:
- Construct URL:
{extractionUrl}/{jobId} - Include API key if needed
- Construct URL:
- Call polling URL and parse JSON response
- If
isReady: false:- Insert
JobStatuswith updatedAttemptNumberand timestamp - Increment
PollAttemptCount, setLastPollAttemptAtin[extractor].[Job]
- Insert
- If
isReady: true:- Extract result or
resultUrl - Proceed to result storage step
- Extract result or
- Implement exponential backoff or polling interval
- Add unit tests and one integration test using mock polling endpoint
User Story 10: Store extractor result to blob and finalize job
Description
As a developer,
I want to persist the completed job result to structured blob storage,
so that the full raw extraction result is available for downstream use or auditing.
Acceptance Criteria
- If result is inline:
- Serialize JSON to blob as
result.json
- Serialize JSON to blob as
- If result is at
resultUrl:- Download content and store it as blob
- Blob path must follow convention:
- Update
[extractor].[Job]:Status = 'Succeeded',CompletedAt = now, updateExtractorResponse
- Insert final
JobStatusrecord - Publish
JobSucceededto Service Bus - If failure during write or download:
- Update job to
Failed - Publish
JobFailedwith error detail
- Update job to
User Story 11: Publish job status messages to Service Bus
Description
As a developer,
I want the Extractor service to emit structured status events for job progress,
so that the Status service and other consumers can track job lifecycles.
Acceptance Criteria
- Publish to
TOPIC_JOB_STATUSfor each status event:JobQueuedJobStartedJobSucceededJobFailed
- Message format must include:
JobID,TimestampFileId,ClientApplicationExtractorID- Optional:
ResultSummary,ErrorMessage
- Follow existing platform message envelope structure
- Add integration test to confirm emission of each event
User Story 12: Start all extractors for a file (parallel job initiation)
Description
As a developer,
I want the Extractor service to start a job for each active extractor on a file,
so that the platform can support multi-extractor workflows.
Acceptance Criteria
- On
TOPIC_ACCEPTED_FILE:- Retrieve all active extractors from Registration API
- For each extractor:
- Generate unique
JobID - Lookup API key
- Insert Job record
- Submit file to
ExtractionUrl - Publish
JobQueuedandJobStartedevents
- Generate unique
- Job status for each extractor must be tracked independently
- Store each extractor’s result in its own blob folder under:
- Add test coverage to confirm independent lifecycle tracking
User Story 13: Add retry limits or polling thresholds for extractor jobs
Description
As a platform owner,
I want to define limits for polling attempts per job,
so that failed or long-running extractors do not consume system resources indefinitely.
Acceptance Criteria
- Add configurable max polling attempts (e.g.,
MAX_POLL_ATTEMPTS) - After limit is reached:
- Mark job as
Failed - Insert final
JobStatusrecord with reason - Publish
JobFailedevent to Service Bus
- Mark job as
- Optionally track time-based thresholds (e.g., timeout window)
- Add unit test to simulate retry exhaustion and finalization logic
User Story 14: Expose internal API to retrieve job statuses by FileID or JobID
Description
As an internal consumer,
I want to retrieve extractor job details for a file or job ID,
so I can display, troubleshoot, or audit job progress and results.
Acceptance Criteria
- Create routes:
GET /extractor/job/:jobIdGET /extractor/job/file/:fileId
- Response for each job includes:
JobID,FileId,ClientApplicationExtractorID- Current
Status,CreatedAt,CompletedAt,PollAttemptCount - Blob path to result (if available)
- History of transitions from
JobStatus
- Use pagination or limits for large job lists
- Add Swagger/OpenAPI spec and integration test