feat(encord) 4/5: Encord headless curate, system-info, roundtrip smoke spec - #385
Open
jonlwowski012 wants to merge 4 commits into
Open
feat(encord) 4/5: Encord headless curate, system-info, roundtrip smoke spec#385jonlwowski012 wants to merge 4 commits into
jonlwowski012 wants to merge 4 commits into
Conversation
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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.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 todoThis 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
curatetoolRef reachable.system-infolands here because it reports both push's media categories and curate's metric allowlist.Headless
encord curatenpa 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.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.--collectionthat already holds items is refused: Encord's evaluation only adds, so a stale selection could not be told apart from this run's.--poll-secondsruns out.plannedreceipt lands before the first mutation; the finalnpa.encord.curate_receipt.v1records the parsed filters, the exact preset JSON,items_total,items_selected, andpreset_deleted. The transient preset (npa-curate-<run-id>, or a timestamped random-suffixed title for ad-hoc runs) is deleted in afinally, after a crash as well as a success.system-inforeports 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
curate,system-info; SDKcurate,system_info;CurateReceiptre-exported;resolve_collectiongainscreate_in_folder_uuid.workbench.encord.curateandencord-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). Theverifystage is added in PR 5.system-info, the smoke spec, and three troubleshooting rows;docs/workbench/encord-headless-curation.mdrecords the design and the live spike evidence behind the pinned filter shape; catalog-doc row; generateddocs/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 jointest_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
validate-spec encord-roundtrip-smoke.yamlmake testorigin/main, no new failuresencord-roundtrip-smokepreset_deleted: trueStack
encord push(+ cleanup)encord pullencord curate(+system-info),encord-roundtrip-smoke.yamlencord verify, smoke gains its verify stage, skill + docs completion🤖 Generated with Claude Code