Skip to content

feat(encord) 3/5: Pull from Encord - #384

Open
jonlwowski012 wants to merge 3 commits into
nebius:mainfrom
jonlwowski012:encord/03-pull
Open

feat(encord) 3/5: Pull from Encord#384
jonlwowski012 wants to merge 3 commits into
nebius:mainfrom
jonlwowski012:encord/03-pull

Conversation

@jonlwowski012

@jonlwowski012 jonlwowski012 commented Sep 3, 2026

Copy link
Copy Markdown

Summary

Third of five stacked PRs replacing #339 (prereqs → push → pull → headless curate → verify). Stacked on encord/02-push; review only the last commit until that merges.

Vision. Encord is the curation-SaaS tool in the npa workbench marketplace. Media already in the customer's Nebius object store is registered in place into Encord (bytes never leave the bucket), curated either headlessly with quality-metric filters or by humans in the Encord app, and pulled back to S3 as curated media + labels + a lineage manifest. Every artifact carries exact identity (npa.source_uri, never a filename) and content checksums, and encord verify proves the roundtrip fail-closed. The tool is in-process (no container) and composes through workbench.encord.* toolRefs in npa.workflow specs.

flowchart LR
    S3[("Your S3 bucket")]
    FOUND["Foundations · PR 1<br/>contracts · exact identity · checksums<br/>Encord SDK seam · credentials + preflight"]
    PUSH["encord push (+ cleanup) · PR 2"]
    ENC["Encord folder / dataset"]
    CUR["encord curate (headless) · PR 4"]
    HCUR{{"or: Encord app"}}
    COLL["Curated Collection"]
    PULL["encord pull · this PR"]
    OUT[("Curated media + labels<br/>+ manifest, in S3")]
    VER["encord verify · PR 5"]
    FOUND -.-> PUSH & CUR & PULL & VER
    S3 --> PUSH --> ENC
    ENC --> CUR --> COLL
    ENC -.-> HCUR -.-> COLL
    COLL --> PULL --> OUT
    ENC --> PULL
    OUT --> VER
    PUSH -. receipt .-> VER
    classDef done fill:#d9ead3,stroke:#38761d,color:#000
    classDef this fill:#fff2cc,stroke:#bf9000,stroke-width:3px,color:#000
    classDef todo fill:#f3f3f3,stroke:#999,stroke-dasharray:4 3,color:#777
    class FOUND,PUSH done
    class PULL this
    class CUR,VER todo
Loading

This PR solves the Encord → S3 edge, from either a dataset or a curated Collection. With push and pull both merged the loop closes for the first time through the human path: register in place, curate in the Encord app, pull the Collection back to S3 with a lineage manifest.

encord pull

npa workbench encord pull --source {collection|dataset|project} --source-id <id-or-title> --output-path s3://… materializes media, per-item JSON, and (for projects) label rows into an S3 prefix with an npa.encord.pull_manifest.v1 manifest.

  • Zero-egress by default. When the signed URL points at the configured endpoint the item is copied server-side bucket-to-bucket and the destination ETag is recorded. A failed copy falls back to a signed-URL download and the manifest row records copy_error, so paid egress is never silent.
  • Hashed in-stream. Downloads are sha256'd as they stream; registered copies carry the source's md5-as-ETag. Both land in the manifest, so the later verify verb has something to compare.
  • Never loses a record. One item's failed signed-URL fetch (or any per-item exception) becomes an error row; the manifest keeps every item that landed, so resume and cleanup can see what is in the prefix. The manifest is written before any failure exit and any failed item fails the command closed.
  • Composite items (image groups, DICOM) have no single signed URL and are recorded as per-item errors. Transfers run in a bounded thread pool.

Surface

  • CLI pull and SDK pull on the group and module PR 2 created; PullManifest re-exported.
  • toolRef workbench.encord.pull with encord-pull.yaml (CPU-only, terminal at the manifest; plan-only twin in the live matrix, because no standalone submit can supply a human-curated id).
  • Operator doc gains the "curate in the Encord app" table, the Pull section, SDK pull, and the pull spec; catalog-doc row; generated docs/cli/encord.md.

Tests

test_encord_pull.py (same-endpoint detection for path-style and virtual-hosted URLs, server-side copy, composite-item error, cross-origin download through a stubbed httpx.stream, copy-fallback reason, per-source enumeration, manifest on errors, one raising signed-URL fetch keeps every record, labels crash still writes the manifest, expired-URL retry, empty source fails closed). The shared fakes gain stub_httpx_stream. CLI tests: pull help, text and JSON output, path contract, bad --source, missing-credential path, EncordToolError exit. Workflow tests: pull spec structure, argv rendering, CPU-only resources, secret hint.

Validation

Gate Result
ruff clean
PR-3 targeted tests + coupling guardrails 386 passed
validate-spec encord-pull.yaml valid
harness guardrails 2,442 passed
full make test 12,924 passed; the same 73 macOS host failures as PRs 1 and 2 and clean origin/main, no new failures
docs-drift up to date

Stack

  1. prereqs
  2. encord push (+ cleanup)
  3. this PRencord pull
  4. headless encord curate (+ system-info), encord-roundtrip-smoke.yaml
  5. encord verify, skill + docs completion

🤖 Generated with Claude Code

jonlwowski012 and others added 3 commits September 3, 2026 16:42
Lays the groundwork for the Encord curation-SaaS workbench tool without
exposing a verb yet. This is the first of five stacked PRs that replace nebius#339
(prereqs -> push -> pull -> curate -> verify).

Shared fixes surfaced while running Encord live:
- StorageClient bounds every boto network wait (connect 10s, read 60s,
  16 pooled connections); a stalled TCP connection during workflow-preflight
  staging blocked a submit indefinitely.
- parse_bucket_uri becomes public so a new tool reuses it instead of
  re-implementing S3 URI parsing; the two private importers migrated.
- json_stdout_contract reads the value of enum-typed output_format options,
  which a (str, Enum) member otherwise stringifies as OutputFormat.json.
- scripts/build_docs.sh guards an empty-array expansion that bash 3.2 rejects
  under set -u.

Encord foundations (npa.workbench.encord, no CLI yet):
- schemas: PushReceipt, CurateReceipt, PullManifest, RoundtripReport, error
  types, schema ids and filenames.
- identity: exact identity via npa.source_uri client metadata and normalized
  object URLs; same-basename and percent-encoding aliasing are pinned, and
  conflicting signals fail closed.
- integrity: single-part ETag as md5, streamed sha256 otherwise.
- storage: the S3-only artifact writer.
- client: the one seam that touches the Encord SDK (lazy import) with
  title-or-id resolution for integrations, folders, datasets, projects and
  collections.
- credentials: exactly two transports, ENCORD_SSH_KEY_B64 (pods) and
  ENCORD_SSH_KEY_FILE (laptops); a raw PEM paste is rejected.
- npa[encord] optional extra pins the SDK.

Preflight: `npa workbench health preflight --checks encord` proves the
credential before anyone spends SaaS time (missing -> WARN naming the two
transports, SDK absent -> WARN with a pip remedy, handshake failure -> FAIL).

Tests: the Encord unit tests are split per module with shared fakes in
npa/tests/workbench/encord_fakes.py; the enum stdout-contract fix gets its own
unit test. docs/cli/health.md is regenerated.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Second of five stacked PRs replacing nebius#339 (prereqs -> push -> pull -> curate
-> verify). First user-facing verb.

`npa workbench encord push` registers S3 media in place into an Encord storage
folder (default `register` mode: bytes stay in the bucket through an
S3-compatible integration; `upload` copies bytes to Encord) and links the
folder to a dataset.
- Write-ahead `planned` receipt before the first Encord mutation, so an
  uncatchable kill still leaves a durable record of intent.
- Caller-side idempotency in both modes: identities resolve against the folder
  before anything is sent, so a retried stage makes zero registration
  round-trips and copies no duplicate bytes; register never falls through to
  upload.
- Exact identity via npa.source_uri client metadata; same-basename objects
  resolve to distinct items and conflicting signals fail the item closed.
- Per-item size, ETag, and content checksum in the receipt; counters derived
  from per-item outcomes. An empty --integration in register mode is rejected
  before any I/O; .mcap inputs fail closed as experimental_error rows.

`encord cleanup --title-prefix` tears down run-scoped folders, collections and
presets and reports datasets (the SDK cannot delete them).

Surface: the `npa workbench encord` Typer group and `npa.sdk.workbench.encord`
module (one implementation, thin clients; every verb under
@json_stdout_contract, EncordToolError and path-contract violations exit 1,
bugs propagate as exit 2); `encord` joins the three-tier seam set. toolRef
`workbench.encord.push` with `encord-push.yaml` (CPU-only, plan-only twin in
the live submit matrix); renderer secret hint, `npa[encord]` pip extra for
image-less stages, and a fail-closed setup preamble when ENCORD_SSH_KEY_B64 is
empty. Operator doc for push and cleanup, catalog-doc row, generated CLI docs.

Tests: test_encord_push.py, test_encord_cleanup.py (shared fakes gain
folder_item, FakeUploadFolder, FakeDownloadStorage), test_encord_cli.py,
test_encord_workflow.py, push row in the spec-declared-outputs guardrail.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Third of five stacked PRs replacing nebius#339 (prereqs -> push -> pull -> curate
-> verify). Closes the loop through the human path: register in place, curate
in the Encord app, pull the Collection back to S3.

`npa workbench encord pull --source {collection|dataset|project} --source-id
... --output-path s3://...` materializes media, per-item JSON and (for
projects) label rows into an S3 prefix with an npa.encord.pull_manifest.v1
manifest.
- Zero-egress by default: when the signed URL points at the configured
  endpoint the item is copied server-side and the destination ETag recorded;
  a failed copy falls back to download and records copy_error so paid egress
  is never silent.
- Downloads are hashed in-stream (sha256); registered copies carry the source
  md5-as-ETag, so verify has something to compare later.
- One item's failed fetch becomes an error row, never a lost record; the
  manifest is written before any failure exit and any failed item fails the
  command closed. Composite items are per-item errors. Transfers run in a
  bounded thread pool.

Surface: CLI `pull`, SDK `pull`, toolRef `workbench.encord.pull` with
`encord-pull.yaml` (CPU-only, plan-only twin in the live matrix), operator doc
sections for in-app curation and pull, catalog-doc row, regenerated CLI docs.

Tests: test_encord_pull.py (shared fakes gain stub_httpx_stream), pull cases
in test_encord_cli.py and test_encord_workflow.py, pull row in the
spec-declared-outputs guardrail.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@jonlwowski012 jonlwowski012 changed the title Encord/03 pull feat(encord) 3/5: Pull from Encord Sep 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant