Skip to content

Add the Datalab Convert rendition provider - #199

Open
salmonumbrella wants to merge 1 commit into
kenn-io:mainfrom
salmonumbrella:feat/datalab-rendition-provider
Open

Add the Datalab Convert rendition provider#199
salmonumbrella wants to merge 1 commit into
kenn-io:mainfrom
salmonumbrella:feat/datalab-rendition-provider

Conversation

@salmonumbrella

@salmonumbrella salmonumbrella commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

What changed

Docbank now has a fixed-route Datalab Convert adapter for every format a profile declares and the media inspector authorizes. It uploads the exact verified source, polls the known request, and maps Marker Document/Page output into page, slide, sheet, or image evidence. Unverified structure falls back to bounded Markdown with degraded provenance.

Provider callback and result URLs are ignored. Hosted profiles require HTTPS and a named API-key binding; redirects, ambient cookies, proxies, oversized responses, partial results, and version drift fail closed. Once Datalab returns a request ID, Docbank stays on that job instead of risking a second paid submission.

Why

Datalab can cover more formats than a PDF-only adapter, but the same exact-byte, local-inspection, destination, and provenance rules still apply. Ambiguous or partial conversions must not become silent duplicate charges or incomplete durable evidence.

Usage

For library use, construct the client with datalab.New(profile, secrets, httpClient). The profile pins its HTTPS origin, named API-key binding, locally inspectable formats, and finite source, result, and artifact limits.

This PR adds the adapter contract only. Daemon, API, and CLI registration is intentionally deferred to S1–S3.

Part of #176 (R8). Stacks on #198.

@roborev-ci

roborev-ci Bot commented Aug 24, 2026

Copy link
Copy Markdown

roborev: Combined Review (c915c64)

Medium

  • Bridge contract diverges from runtime validationdocument/bridge/source-evidence-v1.schema.json:75, document/bridge/openapi.yaml:175
    Unit omissions require a locator at runtime, but the schema neither defines it nor permits extra properties. OpenAPI also accepts any object—including {}—as a receipt, while runtime validation requires a closed, populated RenditionReceipt. Generated implementations may fail to interoperate.
    Fix: Add the omission locator and kind-specific requirements to the evidence schema, fully define receipt and usage schemas, and test representative responses against both schemas and runtime validation.

  • Artifact limits are enforced after payload retrievaldocument/bridge/client.go:390
    Payloads are downloaded before checking MaxArtifacts, AllowedArtifactRoles, or MaxTotalResultBytes. A bridge can therefore force the daemon to download and retain many or unauthorized payloads before rejection.
    Fix: Validate artifact count and roles before resolving payloads, then enforce an overflow-safe cumulative byte limit while reading inline and result-route artifacts.


Reviewers: 2 done | Synthesis: codex, 9s | Total: 21m42s

@salmonumbrella salmonumbrella changed the title feat: add Datalab rendition provider Add the Datalab Convert rendition provider Aug 25, 2026
@salmonumbrella
salmonumbrella force-pushed the feat/datalab-rendition-provider branch from c915c64 to 728a95c Compare August 25, 2026 07:38
@roborev-ci

roborev-ci Bot commented Aug 25, 2026

Copy link
Copy Markdown

roborev: Combined Review (728a95c)

Verdict: Changes requested—three medium-severity issues could cause runaway processing or incorrect authorization and version metadata behavior.

Medium

  • Terminal extraction failures loop indefinitelyinternal/store/extraction.go:480
    After lexical cutover, terminal failures remain queued and are immediately reprocessed. A batch of invalid or oversized blobs can create a tight loop that repeatedly rewrites extraction state and rebuilds lexical generations, starving later work.
    Fix: Remove queue entries for terminal results from the current extractor and rely on extractor-version changes to requeue them. Add a worker-level test covering persistent failures after lexical publication.

  • Authorization expiry is not enforced during bridge jobsdocument/bridge/client.go:124
    Render checks authorization.ExpiresAt only initially and then uses TotalTimeout. A job may continue polling and fetching artifacts after authorization expires; endpoint-controlled timestamps could also allow post-expiry results to appear valid.
    Fix: Bound execution by the earliest caller deadline, total timeout, or authorization expiry. Check the local clock before requests, artifact fetches, and successful return, and cancel the remote job immediately upon expiry.

  • Historical versions receive current attachment metadatainternal/store/source_metadata.go:194
    Historical content metadata is combined with the node’s current filename, path, and latest provenance. After a rename, move, or content replacement, the versions API can attribute old bytes to newer attachment facts.
    Fix: Persist and query attachment/provenance snapshots per content version, or omit facts that cannot reliably be associated with the requested version.


Reviewers: 2 done | Synthesis: codex, 13s | Total: 31m3s

@salmonumbrella
salmonumbrella force-pushed the feat/datalab-rendition-provider branch from 728a95c to c71c39a Compare August 25, 2026 10:38
@roborev-ci

roborev-ci Bot commented Aug 25, 2026

Copy link
Copy Markdown

roborev: Combined Review (c71c39a)

The PR has one high-severity transport vulnerability and four medium-severity correctness and concurrency issues.

High

  • Cleartext provider origins expose documents and credentialsdocument/docling/client.go:678, document/bridge/client.go:546
    Both clients permit non-loopback http:// origins under the operator_network trust boundary, transmitting source documents and API credentials in plaintext. Network observers or attackers could steal or alter documents, credentials, and returned renditions. Require HTTPS for all non-loopback origins. If local HTTP is needed, use a restricted transport that ensures every resolved address is loopback-only, while preserving transport injection for tests.

Medium

  • Uploaded content is not verified against its authorized digestdocument/bridge/client.go:258
    The bridge checks only upload length, so a mutable or custom AuthorizedUpload can transmit different same-length bytes while claiming the authorized SHA-256 digest. Hash the stream, compare it with metadata.SHA256, and ensure multipart writing completes successfully before accepting the response.

  • Cancellation and transport errors lose their classificationdocument/bridge/client.go:172
    Raw context errors violate the rendition-provider error contract, causing document.RenderRendition to replace them with an “unclassified rendition provider error.” Wrap cancellation, deadline, and transport failures using the appropriate RenditionProviderError codes, with coverage through document.RenderRendition.

  • Historical versions can be reported with current provenanceinternal/store/source_metadata.go:194
    Historical metadata combines the requested version’s blob evidence with the node’s current path and latest provenance, potentially attributing a replacement’s source path and timestamps to an older version. Associate provenance with the content version or introducing operation; otherwise omit facts that cannot be tied to the requested version.

  • Backfill batches can block exclusive maintenance for hourscmd/docbank/daemon.go:179
    Checksum and source-metadata backfills hold the mutation gate for an entire batch, including reads of potentially large originals, while new mutations are rejected as maintenance-busy. Acquire and release the gate per target, or yield between targets so queued exclusive maintenance can take precedence.


Reviewers: 2 done | Synthesis: codex, 12s | Total: 16m11s

@salmonumbrella
salmonumbrella force-pushed the feat/datalab-rendition-provider branch from c71c39a to 6c2bfd2 Compare August 25, 2026 11:12
@roborev-ci

roborev-ci Bot commented Aug 25, 2026

Copy link
Copy Markdown

roborev: Combined Review (6c2bfd2)

Verdict: One high-severity security issue and four medium-severity correctness issues require fixes.

High

  • Windows upload spools may expose private document contents through inherited DACLs
    Location: document/upload/spool_windows.go:29
    Mkdir(..., 0o700) and OpenFile(..., 0o600) do not guarantee owner-only permissions on Windows. The spool directory and file inherit the parent DACL, potentially allowing another local account to read or replace the staged document.
    Fix: Create and pin the directory with a protected, current-user-only DACL using the established winsecurity.MkdirPrivatePinnedAt pattern, then validate the restricted DACL. Alternatively, reject spool roots that are not owner-restricted.

Medium

  • Authorized upload integrity is not verified
    Location: document/bridge/client.go:258
    The bridge checks only upload length, so different same-length content can be uploaded while the manifest, idempotency key, and result remain bound to the authorized SHA-256.
    Fix: Read into a bounded local spool, verify both length and SHA-256 before starting the HTTP request, and upload only the verified bytes.

  • Operations can continue after authorization expires
    Location: document/bridge/client.go:124
    The operation deadline ignores authorization.ExpiresAt, allowing polling, artifact retrieval, and remote processing beyond the authorization interval.
    Fix: Bound the operation context by the earliest caller deadline, total timeout, or authorization expiry, and classify expiry as a policy rejection.

  • Auxiliary MD5 enrichment breaks exact-build retries
    Location: internal/store/processing_catalog.go:313
    Retry validation compares the submitted build against a loaded record enriched with MD5 from blob_checksums, although MD5 is not stored in rendition_artifacts. This can incorrectly report different immutable metadata for a staged publication.
    Fix: Exclude auxiliary MD5 data from immutable build equality and enrich returned views only after reuse validation.

  • XMP parsing collapses sibling RDF values
    Location: internal/processing/source_metadata.go:337
    XML character data is copied into every open ancestor without preserving child boundaries, causing RDF list values such as multiple creators to be concatenated and deeply nested input to amplify allocations.
    Fix: Parse RDF list items explicitly and use bounded buffers only for recognized fields, preserving individual rdf:li values.


Reviewers: 2 done | Synthesis: codex, 14s | Total: 19m6s

@salmonumbrella
salmonumbrella force-pushed the feat/datalab-rendition-provider branch from 6c2bfd2 to 0bd3535 Compare August 25, 2026 11:39
@roborev-ci

roborev-ci Bot commented Aug 25, 2026

Copy link
Copy Markdown

roborev: Combined Review (0bd3535)

The changes introduce no concrete, plausibly exploitable security regression under the stated trust model.

No issues found.


Reviewers: 2 total (1 done, 1 skipped) | Synthesis: codex | Total: 16m14s

@salmonumbrella
salmonumbrella force-pushed the feat/datalab-rendition-provider branch 4 times, most recently from e17e8ff to 3495ce2 Compare August 25, 2026 12:37
@salmonumbrella
salmonumbrella force-pushed the feat/datalab-rendition-provider branch from 3495ce2 to 030fcf5 Compare August 25, 2026 12:51
@roborev-ci

roborev-ci Bot commented Aug 25, 2026

Copy link
Copy Markdown

roborev: Combined Review (030fcf5)

Two medium-severity issues need attention; no security regressions were identified.

Medium

  • Permanent extraction failures are requeued indefinitelyinternal/store/processing_migration.go:239
    After lexical cutover, RecordExtraction preserves queue entries for live blobs, while migration treats failed results as ineligible and reinserts them. Terminal failures such as oversized or invalid UTF-8 files will therefore retry forever. Treat current-version ExtractionFailed results as completed terminal outcomes and clear their queue entries. Add a worker test with an active lexical head confirming the queue drains.

  • Artifact response media types conflict between the OpenAPI contract and clientdocument/bridge/openapi.yaml:74, client.go:479
    OpenAPI declares application/octet-stream, but the client requires Content-Type to exactly match the artifact manifest’s media type. A server generated from the published contract may consequently be rejected. Specify a wildcard/dynamic response media type compatible with the manifest, or allow application/octet-stream in the client while retaining checksum and length validation.


Reviewers: 2 done | Synthesis: codex, 9s | Total: 19m40s

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant