Skip to content

feat(encord) 4/5: Encord headless curate, system-info, roundtrip smoke spec - #385

Open
jonlwowski012 wants to merge 4 commits into
nebius:mainfrom
jonlwowski012:encord/04-curate
Open

feat(encord) 4/5: Encord headless curate, system-info, roundtrip smoke spec#385
jonlwowski012 wants to merge 4 commits into
nebius:mainfrom
jonlwowski012:encord/04-curate

Conversation

@jonlwowski012

@jonlwowski012 jonlwowski012 commented Sep 3, 2026

Copy link
Copy Markdown

Summary

Fourth of five stacked PRs replacing #339 (prereqs → push → pull → headless curate → verify). Stacked on encord/03-pull; 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) · this PR"]
    HCUR{{"or: Encord app"}}
    COLL["Curated Collection"]
    PULL["encord pull · PR 3"]
    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,PULL done
    class CUR this
    class VER todo
Loading

This PR solves the machine path through the middle of the loop. The dashed "Encord app" alternative stays available, but a workflow can now select a Collection server-side with declared quality filters and no human in the UI. The roundtrip smoke spec (push → curate → pull → pull-curated) is the executable proof and the vehicle that makes the curate toolRef reachable. system-info lands here because it reports both push's media categories and curate's metric allowlist.

Headless encord curate

npa workbench encord curate --folder … --filter brightness:0.2:0.8 --filter width:640:4096 --collection … --output-path s3://… maps workbench-declared filters over Encord's built-in quality metrics onto a run-scoped Encord filter preset and lets Encord evaluate the selection server-side into a Collection: no human in the app, no media movement.

  • Pinned filter shape. The per-metric filter JSON is undocumented in the SDK (filters: List[Dict]); a live spike against the real SaaS pinned it, and found that an invalid shape hangs Encord's evaluation indefinitely. The verb therefore allowlists live-verified metrics and fails closed on anything else before any Encord call. Intrinsic metrics (width, height, area, aspect-ratio) work on any folder; computed ones (brightness, sharpness, file-size) match nothing until quality metrics are computed once in the app, and the zero-selection error names that cause.
  • Fail-closed selection. Zero selected items is exit 1, never a vacuous success. A --collection that already holds items is refused: Encord's evaluation only adds, so a stale selection could not be told apart from this run's.
  • Asynchronous, one-shot evaluation handled. Items pushed moments earlier may not be metric-indexed yet (observed live), so while the selection is empty the verb re-issues the evaluation every 15s until --poll-seconds runs out.
  • Receipt and lifecycle. A write-ahead planned receipt lands before the first mutation; the final npa.encord.curate_receipt.v1 records the parsed filters, the exact preset JSON, items_total, items_selected, and preset_deleted. The transient preset (npa-curate-<run-id>, or a timestamped random-suffixed title for ad-hoc runs) is deleted in a finally, after a crash as well as a success.

system-info reports SDK pin, API domain, and configured credential names (never values), plus the supported media and curate metrics: the management verb CONTRIBUTING asks new tools to expose.

Surface

  • CLI curate, system-info; SDK curate, system_info; CurateReceipt re-exported; resolve_collection gains create_in_folder_uuid.
  • toolRef workbench.encord.curate and encord-roundtrip-smoke.yaml (push → curate → pull → pull-curated, CPU-only), registered as the live CPU case in the submit matrix; push and pull now name it as their executable twin. The fixture set deliberately includes a 64×64 thumbnail the width filter excludes, so a green run proves actual filtering (3 of 4 items curated with the shipped fixtures). The verify stage is added in PR 5.
  • Docs: the operator guide gains "Curate headlessly", system-info, the smoke spec, and three troubleshooting rows; docs/workbench/encord-headless-curation.md records the design and the live spike evidence behind the pinned filter shape; catalog-doc row; generated docs/cli/encord.md.

Tests

test_encord_curate.py (filter parsing incl. comma-separated form, the pinned preset JSON, happy path creating collection + preset, planned receipt before the first mutation, reuse of an empty collection, refusal of a populated one, re-issued evaluation until indexing catches up, zero selection fails closed with a receipt, preset deleted when evaluation raises, a failed preset delete recorded, empty folder and unknown metric fail before any scaffolding, missing folder writes the receipt then raises). Two resolver tests join test_encord_client.py. CLI tests: curate help, JSON and text output, path contract, tool error, and system-info making no API call. Workflow tests: smoke step order and schema chain, curate argv, secret hint, CPU-only resources.

Validation

Gate Result
ruff clean
PR-4 targeted tests + coupling guardrails (incl. schema chaining, daily coverage) 444 passed, 1 skipped
validate-spec encord-roundtrip-smoke.yaml valid (states: push, curate, pull, pull-curated)
harness guardrails 2,433 passed
full make test 12,958 passed; the same 73 macOS host failures as PRs 1–3 and clean origin/main, no new failures
docs-drift up to date
live encord-roundtrip-smoke not yet run from this branch (needs a cluster + the Encord integration); the prior run on the monolithic branch curated 3 of 4 fixtures with preset_deleted: true

Stack

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

🤖 Generated with Claude Code

jonlwowski012 and others added 4 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>
Fourth of five stacked PRs replacing nebius#339 (prereqs -> push -> pull -> curate
-> verify). Adds the machine path through the middle of the loop.

`npa workbench encord curate --folder ... --filter metric:min:max ...
--collection ...` maps workbench-declared filters over Encord's built-in
quality metrics onto a run-scoped Encord filter preset and lets Encord
evaluate the selection server-side into a Collection: no human in the app, no
media movement.
- The per-metric filter JSON is undocumented in the SDK; a live spike pinned
  it and found that an invalid shape hangs Encord's evaluation. The verb
  allowlists live-verified metrics and fails closed before any Encord call.
  Intrinsic metrics (width, height, area, aspect-ratio) work on any folder;
  computed ones need a one-time in-app metric computation, and the
  zero-selection error names that cause.
- Zero selected items is exit 1. A populated --collection is refused because
  Encord's evaluation only adds. Freshly pushed items may not be indexed yet,
  so an empty selection re-issues the evaluation until --poll-seconds runs
  out.
- Write-ahead planned receipt; final npa.encord.curate_receipt.v1 records
  the parsed filters, exact preset JSON, items_total, items_selected and
  preset_deleted. The transient preset is deleted in a finally block.

`system-info` reports SDK pin, API domain, configured credential names (never
values), supported media and curate metrics.

Surface: CLI curate/system-info, SDK curate/system_info, toolRef
workbench.encord.curate, encord-roundtrip-smoke.yaml (push -> curate -> pull
-> pull-curated, CPU-only) registered as the live CPU matrix case and named
as the executable twin of the push and pull specs. Operator doc gains the
curate section, system-info, the smoke spec and troubleshooting rows;
docs/workbench/encord-headless-curation.md records the design and live spike
evidence; catalog-doc row; regenerated CLI docs. The verify stage of the
smoke arrives with the verify verb in the next PR.

Tests: test_encord_curate.py, two resolver tests in test_encord_client.py,
curate and system-info CLI tests, smoke/curate workflow tests, curate row in
the spec-declared-outputs guardrail.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@jonlwowski012 jonlwowski012 changed the title Encord/04 curate feat(encord) 4/5: Encord headless curate, system-info, roundtrip smoke spec 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