Lattice File Ingestion - Technical Documentation
Technical reference for how the Lattice File Ingestion system works internally, with diagrams, and how to configure connections. For a task-oriented walkthrough see the User Guide.
1. System overview
Lattice File Ingestion is a pull-based, claim-check file integration framework for D365 Finance & Operations. It pulls large files from cloud storage on a schedule, brings them into F&O safely (large payload stored out-of-band), decomposes each file into per-record segments, transforms each record with a Liquid template, dispatches the result to a target service, and cleans up.
The system spans three layers:
Design principles
- Claim-check - the raw file bytes live in Document Management (Azure blob behind a
DocuRef); only a lightweight reference travels through transactional tables. - Decomposition - one large file becomes one bundle and many small segments, so work is divisible.
- Top-picking, retryable processing - N batch threads drain the segment table concurrently and recover cleanly from interruptions.
- Configuration over code - storage, format, transform, and target are all per-connection settings.
2. Component responsibilities
| Layer | Component | Responsibility |
|---|---|---|
| C# | ICloudFileSource | Abstraction: ListFiles, OpenRead (stream), DeleteFile, GetLastModifiedUtc, GetSizeBytes, MoveFile. Streaming only - never returns byte[]. |
| C# | AzureBlobFileSource / AzureFileShareFileSource / SftpFileSource | Concrete sources (Azure.Storage.* / SSH.NET). |
| C# | CloudFileSourceFactory | Builds the right source from a storage-type token + connection JSON. |
| C# | LiquidTransformer | Parses & renders Liquid templates via the Fluid engine; parse-once/render-many cache keyed by template hash; every output is escaped for use inside a JSON string. |
| C# | FileIngestionService | Façade consumed from X++. The pull uses ListFiles, GetFileAgeSeconds, GetFileSizeBytes, DownloadToTempFile, DecryptSourceFile, MoveFile and DeleteFile, and its parsers ConvertCsvToJson and ConvertFixedWidthToJson; the segment processor uses ConvertXmlToJson, RenderTemplate and ValidateStrictJson; the service-invoke target uses ValidateStrictJson. |
| X++ | MGCLatticeFileIngestPull | Scheduled ingestion: claim the connection's lease → list → hold back files that are too new or still changing size → download once (PGP-decrypt when set) → skip duplicates → parse → claim-check + stage in one transaction → move the source to the archive folder, or delete it; a failed file goes to the error folder. At most MaxFilesPerRun files per connection and run. |
| X++ | MGCLatticeFileIngestParser (+ Xml/Json/Csv/FixedWidth + Factory) | Split file content into per-record raw fragments; MGCLatticeFileIngestParserFactory::constructForConnection applies the connection's record path, CSV settings and column specification. |
| X++ | MGCLatticeFileIngestSegmentController / …SegmentService | SysOperation batch worker; top-picking transform→dispatch→complete loop. MGCLatticeFileIngestSegmentService::transformRecord turns one stored record into the request content. |
| X++ | MGCLatticeFileIngestDispatcher (+ CustomService + Factory) | Invokes the connection's target service operation with the transformed payload; the target class must implement MGCLatticeIFileIngestServiceTarget. |
| X++ | MGCLatticeFileIngestServiceInvokeTarget | The shipped target: reads a { "service", "operation", "requestKey", "request" } routing envelope, fills the request's common members from the run context (§5) and calls that Lattice service operation, when the connection's Allowed service operations list it. |
| X++ | MGCLatticeFileIngestCleanup | Removes claim-checked files + staging for complete bundles (every segment processed) past retention, and the records of ingested files older than the retention period. |
| X++ | MGCLatticeFileIngestLogger | Writes MGCLatticeFileIngestLog entries (info / warning / error). |
3. End-to-end lifecycle
4. The pull (ingestion) in detail
MGCLatticeFileIngestPull (a RunBaseBatch, canRunInNewSession = true) iterates enabled connections. For each file it performs one atomic decomposition transaction so a file is either fully staged or not at all.
Key points:
- One run per connection - the pull claims a 60-minute lease on the connection in
MGCLatticeFileIngestConnectionLock, renews it before each file and releases it at the end; a run that finds the lease held skips the connection, and a run whose lease another run took over stops working on it. - Upload-complete check - a file last written less than
MinFileAgeSecondsago (0 means 60 seconds) is left for a later run. WithRequireStableSize, a file whose size differs from the size recorded inMGCLatticeFileIngestSizeObservationon the previous run (or that was not seen before) is left too, and its size is recorded. - Streaming download, in-memory parse - each file is streamed once to a temp file (the C# sources never return whole files as byte arrays); PGP decryption and the parser then read the whole temp file into memory.
- Duplicate guard -
MGCLatticeFileIngestProcessedFileholds the full path, a SHA-256 hash of the path and the content hash of every committed file, with a unique index on connection, path hash and content hash. A file already ingested with the same path and content is skipped and archived or deleted at the source. The cleanup removes these records after the connection's retention period, and an administrator can delete one on the connection page (Ingested files) so an identical file is ingested again. - Path length - the full path the storage returns is stored in a 1,024-character field; a longer path is refused and logged as a failed file.
- Failed files - a file that is empty, produces no records or fails any other step is not committed: the error is logged and the file is moved to
ErrorFolderPath, or stays in the source folder (and is tried again by the next run) when the connection has no error folder. - Run limit - a connection with
MaxFilesPerRunabove 0 ingests at most that many files per run; the remaining files wait for the next run. - Claim-check uses
DocumentManagement::attachFileForRecordagainst the bundle record, with the company's default file document type (DocuType::typeFile()); the returnedDocuRefRecId is stored onMGCLatticeFileIngestBundle.ClaimDocuRefId. When no attachment is created the transaction rolls back and the source file stays. - Bulk insert - segments are inserted with
RecordInsertList; the table's unique key is(BundleId, Sequence), and a segment is identified by itsRecIdin the log, the monitor and the dead-letter queue. - Archive or delete happens after commit: the source file is moved to
ArchiveFolderPathor, without one, deleted; if that fails, a warning is logged but the bundle still stands. The next run lists the file again, skips it as already ingested (duplicate guard) and retries. - Configuration only - the pull never updates the connection record, so the platform's encryption audit (
SysEncryptionLog) records a write only when an administrator saves the connection.
Parsing (decomposition)
MGCLatticeFileIngestParserFactory::constructForConnection builds the parser for the connection's FileFormat; construct(format, recordPath) serves callers without a connection (CSV then uses comma, header row and double quote, and the fixed-width parser has no column specification and refuses to parse).
RecordPath identifies the records:
- XML - the name of the repeating element or a path of element names separated by
/:Order,/Order,//Order,/Orders/OrderandOrders/Orderall work. Empty steps and blanks are ignored, each name is matched by its local name (namespaces ignored), and the first name may occur at any depth, soOrders/Orderbecomes//*[local-name()="Orders"]/*[local-name()="Order"](MGCLatticeFileIngestXmlParser::recordXPath). Blank: the child elements of the root element.isValidRecordPathaccepts element names only (no predicates, attributes, wildcards or prefixes); the wizard uses it. - JSON - a Newtonsoft JSON path, with or without
$, evaluated withSelectTokens:orders,$.orders,data.records,$.orders[*],orders[*]. A single match keeps the single-token rule (an array gives one record per element, an object or scalar one record); several matches (a wildcard) give one record each. Blank: the root, with the same single-token rule.MGCLatticeFileIngestJsonParser::recordPathErrorchecks the syntax for the wizard. - CSV and fixed width ignore it.
A record fragment is stored in MGCLatticeFileIngestSegment.RawContent as received: an XML record stays XML. The segment processor converts it (§5).
5. Segment processing (top-picking)
MGCLatticeFileIngestSegmentController (a SysOperationServiceController) starts MGCLatticeFileIngestSegmentService.processSegments as a reliable asynchronous batch operation. The service implements BatchRetryable with isRetryable() = true, and is idempotent (it only ever claims still-Queued segments), so the whole batch is safe to restart.
Each worker runs a top-picking loop: it claims one queued row under a pessimistic lock that skips rows already locked by other workers (readPast), so many workers drain the same table without blocking each other.
Parallelism: add multiple tasks of the segment processor to the same batch job - each becomes an independent draining worker on its own UserConnection.
Record transform (MGCLatticeFileIngestSegmentService::transformRecord, public static so other callers can produce the same content):
recordJsonreturns the record as JSON. For an XML connection, content that starts with<is converted withFileIngestionService.ConvertXmlToJson, the converter used for a whole file: the record element is omitted and its attributes and child elements become properties named without namespace prefixes (<Order id="7"><Qty>5</Qty></Order>→{"id":"7","Qty":"5"}; an attribute keeps an@prefix only when a child element has the same name). CDATA becomes text, an element with text and attributes becomes its text plusElement_attributeproperties, and a repeated element is an array only when it occurs more than once, so templates loop over it with| as_array. Other content is returned unchanged.- With a template, the JSON is rendered through
RenderTemplate(every value is escaped for a JSON string, including'; numbers are written with| number) and the$xrefplaceholders are resolved. Output that holds placeholders must be strict JSON (ValidateStrictJson) before they are resolved. With a blank template the JSON record is returned unchanged. - The conversion runs on every attempt, so
RawContentkeeps the record as received: the monitor, the inbound message inquiry and the dead-letter payload show the source XML,TransformedContentshows what was sent, and a segment that was queued or failed before is converted when it is replayed. A record that is not well-formed XML fails with a CLR error, classified as a format error like a template error.
After a successful dispatch the segment keeps the RequestId it was sent with, and the log gets an information row with the operation's message, or a warning when an earlier request had already claimed that RequestId: the operation answered with the earlier outcome and posted nothing (usually a requestKey that is not unique per record).
A Held segment (put on hold from the monitor, from Queued or Failed) is not Queued, so the sweep skips it until Release returns it to Queued. A failure recorded while a segment is held leaves its state unchanged. Replaying a dead-lettered segment sets it back to Queued with ProcessingAttempts = 0 (MGCLatticeIntegrationDlqManager.replayFileIngestionSegment). When the dead letter carries an operator-edited payload (fix and resubmit), the edit first replaces the segment's RawContent, so the worker transforms and dispatches the correction; the dead-letter row keeps the original as OriginalPayload.
Dispatch resolution
Only a class that implements MGCLatticeIFileIngestServiceTarget is constructed and invoked; the shipped one is MGCLatticeFileIngestServiceInvokeTarget (operation handleIngestedContent). The dispatcher hands it the connection's Allowed service operations and the run context: the connection id, the bundle id and the segment's RecId. It parses the content as a { "service", "operation", "requestKey", "request" } routing envelope, strictly: the content must be one RFC 8259 JSON object (FileIngestionService.ValidateStrictJson: no single quotes, unquoted names, comments or numbers with leading zeros), no object may repeat a member name in any casing, the root may hold only those four members spelled exactly, service and operation are strings and requestKey is a string or a whole number. A value inside an ingested file therefore cannot add or override envelope or request members. It then refuses an operation that is not in Allowed service operations (one ServiceClass.operation or ServiceClass.* entry per line; empty allows none). It then fills the request's RequestId, LegalEntityId, UserContext and CorrelationId where the template left them empty (MGCLatticeFileIngestServiceInvokeTarget::requestWithRunContext), deserializes request into the operation's request contract and invokes it through MGCLatticeServiceTestRunner::run. The operation must be known to the service operation metadata (run the metadata refresh once).
| Member | Filled with, only when the request leaves it empty |
|---|---|
RequestId | A name-based GUID. With a non-empty envelope requestKey it comes from the connection, the legal entity the request posts into, the operation and the key, so a record re-sent to the same company in any file replays. Otherwise it comes from the connection, the bundle and the segment's RecId, so a retried or dead-letter-replayed segment replays. |
LegalEntityId | curExt(): the company the segment processing batch job runs in. |
UserContext | curUserId(): the user the batch job runs as. |
CorrelationId | A name-based GUID of the connection and the bundle, shared by the records of one file. |
Empty means absent, null or blank, and for the two GUID members also the all-zero GUID; member names are matched without regard to case, and a value the template writes is never replaced. A RequestId or CorrelationId the template writes must be a GUID; other text is rejected (put an external id in requestKey). A name-based GUID is the SHA-256 hash of a UTF-8 name, with its first 16 bytes written as a version 8 GUID (nameBasedGuid). The names are requestKey|<connection>|<legal entity>|<service>.<operation>|<key>, segment|<connection>|<bundle>|<segment RecId> and correlation|<connection>|<bundle>. The legal entity is the request's LegalEntityId when the template sets one, otherwise the company the processing job runs in. The connection, legal entity, service, operation and bundle are trimmed and lower-cased, and the key is trimmed. requestKey never reaches the request.
The simulator and the sample Replay never post. MGCLatticeFileIngestServiceInvokeTarget::previewIngestedContent runs the strict parse, the allow-list and the run-context fill with a new random RequestId (so a simulation never shares an idempotency key with the scheduled run), then calls the operation in preview mode when its request contract supports preview (MGCLatticeIPreviewableRequest), and otherwise only builds the request contract. An operation that answers PreviewUnsupported counts as checked. A custom target class is not called. The run context uses a SIM- bundle id, and curExt() and curUserId() are the simulating user's company and user.
The dispatcher interface/factory is the extension point for future targets (OData / HTTP endpoints).
6. Data model
The connection entity shows the main fields; the full field list is in the User Guide §3.
Notes:
- Bundle state - a bundle has no state of its own: it is complete when every segment is
Processed, which is what the cleanup checks. - Segment key - the unique key is
(BundleId, Sequence)(SegmentBundleIdx); the segment id shown everywhere is theRecId.SegmentQueueIdx (State, RecId)serves the top-picking claim in claim order andSegmentRetryIdx (State, NextAttemptDateTime)the retry schedule. - Cascade delete - deleting a bundle cascades to its segments; cleanup deletes the
DocuRefseparately. A connection that has bundles cannot be deleted (delete actionRestricted; disable it instead); deleting a connection deletes its lease, size observations and ingested-file records (Cascade). Bundles, logs, ingested-file records, leases and size observations relate to the connection, so renaming a connection id carries over to them. - Run guards -
MGCLatticeFileIngestConnectionLockholds one lease row per connection;MGCLatticeFileIngestProcessedFileis the duplicate guard (unique indexDedupIdxon connection, path hash and content hash, with the full path and the bundle id);MGCLatticeFileIngestSizeObservationholds the size last seen per connection and path (uniqueConnectionFileIdxon connection and path hash). The pull removes a size row once its file is ingested, and the cleanup job removes rows not refreshed for 7 days. MGCLatticeFileIngestLogis queried by connection / bundle / segment (relations to the connection and the bundle, no delete actions; the log cleanup purges it). ItsLogLevelfield is the non-extensibleMGCLatticeLogLevelenum:Info(0),Warning(1),Error(2).
State model
MGCLatticeFileIngestState: None(0), Queued(1), Processed(3), Failed(4), Held(6) (non-extensible). The worker claims a segment with a row lock, so there is no in-progress state.
7. Configuring connections
Connections are the unit of setup. Open System administration → Setup → Lattice → File ingestion → File ingestion connections (MGCLatticeFileIngestConnection) and create one record per source, or use New file ingestion connection (the wizard) in the same menu.
7.1 Field reference
The full list is in the User Guide §3.
| Group | Field | Meaning |
|---|---|---|
| Source | ConnectionId | Unique key for the connection. |
Name | Friendly description. | |
StorageType | Blob / FileShare / Sftp. | |
Endpoint | Account URL / service endpoint for Blob and File share (may instead come from the connection string in the auth JSON). The SFTP host is the host key of the auth JSON. | |
ContainerOrShare | Blob container or file-share name; for SFTP the remote root path (blank: the account's home directory). | |
FolderPath | Sub-path to scan. For SFTP, SftpFileSource.ResolvePath places a relative path under the root path and uses a path that starts with / as is; the same rule applies to every file path and to the archive and error folders. | |
FilePattern | Filename filter, e.g. *.xml, orders_*.json. | |
SftpHostKeyFingerprint | Expected SFTP host key (SHA256: base64, or SHA-256 / MD5 hex); when set, a server presenting another key is refused. | |
AuthSecret | The connection secret: credentials/connection settings as JSON (see 7.3), encrypted with the platform encryption service. Entered through the write-only Authentication field; never displayed. | |
AuthSecretKvRef | An Azure Key Vault secret URI (up to 255 characters), when the secret is kept in Key Vault (see 7.3); otherwise empty. A value that is not a Key Vault secret URI is refused. | |
ArchiveFolderPath | Folder an ingested source file is moved to; blank deletes the file instead. | |
ErrorFolderPath | Folder a file that cannot be ingested is moved to; blank leaves it in place, and the next run tries it again. | |
| File format | FileFormat | Xml, Json, Csv or FixedWidth select the parser (§4, Parsing). |
RecordPath | Record locator for XML (element name or element path) and JSON (JSON path, wildcards allowed); see §4, Parsing. | |
CsvDelimiter, CsvHasHeader, CsvQuoteChar | The CSV parser's delimiter (blank: comma), header flag and quote character (blank: double quote). A new connection starts with CsvHasHeader = Yes (initValue, and the wizard). A quote opens a quoted field only as the first character of a field; an unterminated quoted field fails the file. Enter \t or tab as the delimiter for a tab. | |
FixedWidthColumnSpec | JSON array of {"name","start","width"} objects for the fixed-width parser (exactly these keys, start 0-based, width above 0, whole numbers); anything else fails the file. Required for FixedWidth. | |
DecryptSourceFiles, PgpPrivateKeyKvRef, PgpPassphraseKvRef | PGP-decrypt each downloaded file with the key and passphrase from Key Vault; the message must be integrity protected. | |
| Processing | LiquidTemplate | Per-record transformation template, rendered against the record as JSON; blank sends the JSON record unchanged. |
TargetServiceClass | X++ class to receive transformed records; must implement MGCLatticeIFileIngestServiceTarget. | |
TargetServiceOperation | Method on that class invoked per record (handleIngestedContent for the service-invoke target). | |
AllowedServiceOperations | The operations the service-invoke target may call, one ServiceClass.operation or ServiceClass.* per line; empty allows none. Only an administrator fills it. | |
| Schedule | Enabled | Only enabled connections are scanned. |
MinFileAgeSeconds | A file is picked up only when it was last written at least this long ago; 0 means 60 seconds, negative is refused. | |
RequireStableSize | When Yes, a file is picked up only when its size equals the size seen on the previous run. | |
MaxFilesPerRun | The most files the pull ingests for the connection in one run; 0 means no limit. | |
RetentionDays | Days to keep a complete bundle, its stored file and the record that the file was ingested before cleanup. New connections start with 30; 0 keeps them forever (with a warning); negative is refused. |
7.2 Choosing and wiring a storage type
The StorageType enum element name (Blob/FileShare/Sftp) is passed to the C# CloudFileSourceFactory, which matches it case-insensitively (aliases: AzureBlob → Blob, File/Share/AzureFileShare → FileShare, Ftp → Sftp). Any other token is refused.
7.3 Authentication JSON
The connection secret, typed in the Authentication field, is a small JSON object. Provide only the keys relevant to the storage type; property names are matched case-insensitively and accept aliases. The Endpoint/ContainerOrShare form fields, when set, override the matching keys here.
On save the secret is encrypted with the platform encryption service (Global::editEncryptedField, the mechanism the platform uses for the email SMTP password) into the container field AuthSecret, and the Authentication field stays empty afterwards: type into it only to replace the secret, and enter {} to switch an Azure Blob connection to the managed identity. The key belongs to the environment, so every AOS and batch server can decrypt the secret. Alternatively enter an Azure Key Vault secret URI whose value is this JSON object; it is kept in AuthSecretKvRef as a reference and fetched when the connection runs (Key Vault secrets). The read-only Secret storage field shows which applies (Encrypted, Key Vault reference or None). Every reader resolves the secret through MGCLatticeFileIngestSecretProtector::resolveConnectionSecret.
| Purpose | Keys (aliases) |
|---|---|
| Azure connection string | connectionString (connStr, connection) |
| Endpoint / account URL | endpoint (accountUrl, serviceUrl, url) |
| Account name / key | accountName (account) · accountKey (key, sharedKey) |
| SAS token | sasToken (sas) |
| Container / share / SFTP root path | container (share, containerOrShare) |
| SFTP host / port | host (hostName, server) · port (default 22) |
| SFTP credentials | username (user, userName) · password (pass, pwd) · privateKey (key, pem) · privateKeyPassphrase (passphrase) |
| SFTP host key | hostKeyFingerprint (hostKey, fingerprint); the connection's SftpHostKeyFingerprint field, when set, overrides it |
Azure Blob - connection string:
{ "connectionString": "DefaultEndpointsProtocol=https;AccountName=acct;AccountKey=...;EndpointSuffix=core.windows.net" }
Azure Blob - endpoint + SAS:
{ "endpoint": "https://acct.blob.core.windows.net", "container": "inbound", "sasToken": "sv=2023-...&sig=..." }
Azure File share - connection string:
{ "connectionString": "DefaultEndpointsProtocol=https;AccountName=acct;AccountKey=...;EndpointSuffix=core.windows.net" }
SFTP - password:
{ "host": "sftp.partner.com", "port": 22, "username": "lattice", "password": "••••••" }
SFTP - private key:
{ "host": "sftp.partner.com", "username": "lattice", "privateKey": "-----BEGIN OPENSSH PRIVATE KEY-----\n...", "privateKeyPassphrase": "••••" }
Least privilege: scope credentials to the inbound location only, with read + delete, plus write to the archive and error folders when they are set (the framework moves or deletes each source file after a pull). Prefer a container-scoped SAS for Blob and a dedicated key-based SFTP account.
7.4 Worked example
A partner drops orders_*.json on SFTP; each file is { "orders": [ { ... }, ... ] }; you want each order created through the Lattice sales order capture service.
| Field | Value |
|---|---|
| StorageType | Sftp |
| FolderPath | /outbound/orders |
| FilePattern | orders_*.json |
| SftpHostKeyFingerprint | the server's SHA256: fingerprint |
| Authentication | { "host": "sftp.partner.com", "username": "lattice", "privateKey": "..." } |
| FileFormat | Json |
| RecordPath | orders |
| LiquidTemplate | (maps a record → a routing envelope for the operation, below) |
| TargetServiceClass | MGCLatticeFileIngestServiceInvokeTarget |
| TargetServiceOperation | handleIngestedContent |
| AllowedServiceOperations | MGCLatticeSalesOrderCaptureService.createSalesOrder |
| MinFileAgeSeconds / RequireStableSize | 0 (60 seconds) / Yes |
| RetentionDays | 7 |
| Enabled | Yes |
Example Liquid template (the request members are those of MGCLatticeCreateSalesOrderRequestContract; the service-invoke target fills RequestId, UserContext and CorrelationId, §5):
{
"service": "MGCLatticeSalesOrderCaptureService",
"operation": "createSalesOrder",
"requestKey": "{{ orderNumber }}",
"request": {
"LegalEntityId": "USMF",
"CustAccount": "{{ customer.id }}",
"CustomerRef": "{{ orderNumber }}",
"Lines": [
{% for l in lines %}
{ "ItemId": "{{ l.item }}", "Qty": {{ l.qty | number }} }{% unless forloop.last %},{% endunless %}
{% endfor %}
]
}
}
requestKey is the order's own number: the target derives RequestId from the connection, the legal entity, the operation and that key, so an order the partner sends again, even in another file, replays the created sales order instead of creating a second one. | number writes the quantity as a JSON number (blank becomes null, zero-padded text is read as decimal). LegalEntityId is written explicitly here; left out, it is the company the processing batch job runs in.
8. Scheduling & operations
| Job | Class | Typical schedule | Notes |
|---|---|---|---|
| Pull | MGCLatticeFileIngestPull | every 5-15 min | Bounded & restartable; one transaction per file. Menu item Run file ingestion, on the connections page. |
| Process | MGCLatticeFileIngestSegmentController | recurring / continuous | Add multiple batch tasks for throughput. Menu item Process file ingestion segments, at System administration → Periodic tasks → Lattice; started from there it checks the Lattice license first (MGCLatticeLicense::checkUiAction), like Run file ingestion. |
| Cleanup | MGCLatticeFileIngestCleanup | daily background process | Honors RetentionDays; 0 skips. Deletes complete bundles and their stored files, the ingested-file records older than the retention period, and stale MGCLatticeFileIngestSizeObservation rows. Menu item Clean up file ingestion bundles, at System administration → Periodic tasks → Lattice. |
Monitoring: File ingestion monitor (MGCLatticeFileIngestBundleInquiry) shows bundles → segments → log, with Hold / Release on segments. The File ingestion operations workspace (MGCLatticeFileIngestOperations) adds tiles and recent bundles, segments, ingestion log and dead-letter items. Both are under System administration → Inquiries → Lattice → File ingestion. MGCLatticeFileIngestLog records milestones, warnings, and errors (MGCLatticeLogLevel) keyed by connection / bundle / segment.
Liquid template tester: the MGCLatticeLiquidTester diagnostic form (System administration → Inquiries → Lattice → File ingestion → Liquid template tester) renders a pasted Liquid template against a pasted JSON payload via the same FileIngestionService.RenderTemplate path used at runtime - useful for authoring and debugging a connection's template offline. See the User Guide §13 for details.
9. Failure handling & idempotency
| Concern | Mechanism |
|---|---|
| Partial file staging | Whole decomposition is one transaction - all-or-nothing. |
| File still being uploaded | Held back until it is MinFileAgeSeconds old (0 means 60 seconds) and, with RequireStableSize, until its size is unchanged between two runs. |
| Empty file, no records or any other file failure | Not committed; logged as an error; the file is moved to the error folder, or stays in the source and is retried by every run when there is none. |
| Reprocessing the same file | Duplicate guard: a file whose path and content hash are already in MGCLatticeFileIngestProcessedFile for the connection is skipped and archived or deleted at the source. Records older than the retention period are purged; deleting one on the connection page lets the file in again. |
| Same record sent again in another file | With a requestKey in the routing envelope, the service-invoke target derives the same RequestId for the same company, and the Lattice service replays the first result instead of posting twice; the segment log shows a warning. |
| Source archive or delete failure | Logged as a warning; bundle still committed; the next run skips the file as already ingested and retries. |
| Overlapping pull runs | Per-connection lease (MGCLatticeFileIngestConnectionLock, 60 minutes, renewed per file); a run that finds the lease held skips the connection. |
| Worker crash mid-segment | Queued-only claim + readPast make the batch restart-safe and non-blocking. |
| Transient dispatch error | ProcessingAttempts++, retried after a backoff (30 s, doubling, at most 1 h); Failed and dead-lettered after 3 attempts; replay re-queues with the attempts reset. |
| Concurrency | Per-worker UserConnection + pessimistic lock + readPast = lock-and-skip. |
10. Security
| Artifact | Grants |
|---|---|
MGCLatticeFileIngestMaintain (privilege) | Configure connections (page and wizard); run pull / processor / cleanup; the Liquid template tester. |
MGCLatticeFileIngestView (privilege) | Read-only monitor access. |
MGCLatticeFileIngestDuty (duty) | Both privileges, plus MGCLatticeFileIngestOperationsView (the operations workspace); referenced by MGCLatticeIntegrationAdministratorRole. |
MGCLatticeFileIngestSimulatorPrivilege (privilege) | The simulator and the sample payloads page; in MGCLatticeFileIngestSimulatorDuty on MGCLatticeIntegrationAdministratorRole and MGCLatticeIntegrationOperatorRole. |
Connection credentials live in MGCLatticeFileIngestConnection.AuthSecret, encrypted with the platform encryption service, or as a Key Vault reference in AuthSecretKvRef; every save or decrypt writes a platform encryption audit row (SysEncryptionLog). The pull and the segment processor never save the connection. Apply least-privilege as in §7.3.
11. Extensibility
| Axis | Interface / factory | Add by |
|---|---|---|
| Storage | ICloudFileSource + CloudFileSourceFactory | implementing the interface; extending the factory + MGCLatticeFileIngestStorageType. |
| Format | MGCLatticeFileIngestParser + MGCLatticeFileIngestParserFactory | adding a parser; extending the factory + MGCLatticeFileIngestFileFormat. |
| Dispatch | MGCLatticeFileIngestDispatcher + MGCLatticeFileIngestDispatcherFactory | adding a dispatcher (e.g. OData/HTTP); extending the factory. |
| Target | MGCLatticeIFileIngestServiceTarget | a class that implements the interface and takes the transformed content as one str argument; the custom-service dispatcher invokes no other class. |
See also: User guide (how-to), Liquid transformer (the Liquid transform stage), API reference: file ingestion framework (full API).