diff --git a/README.md b/README.md index 22579fa..8cbd409 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ The customer-facing CLI for the tracebloc declarative ingestion path. Wraps the **v0.3.0 is released** — the latest stable [release](https://github.com/tracebloc/cli/releases/latest), cut from `develop`. It builds on v0.2.0's guided `data ingest` and `dataset rm` with a new `dataset list` command plus home-screen / output polish (clearer copy, guided-first framing). The binary implements `version`, `completion`, `data validate`, `cluster info`, and the full `data ingest` / `dataset list` / `dataset rm` flow — local schema validation, cluster discovery, data staging, submission, and Job watching, end to end. -`data ingest` covers **9 of 10 task categories**: `image_classification`, `object_detection`, `keypoint_detection`, `text_classification`, `masked_language_modeling`, `tabular_classification`, `tabular_regression`, `time_series_forecasting`, and `time_to_event_prediction`. `semantic_segmentation` is pending mask-sidecar support upstream ([data-ingestors#136](https://github.com/tracebloc/data-ingestors/issues/136)); `instance_segmentation` is not yet implemented. +`data ingest` covers **9 of 10 task categories**: `image_classification`, `object_detection`, `keypoint_detection`, `text_classification`, `masked_language_modeling`, `tabular_classification`, `tabular_regression`, `time_series_forecasting`, and `time_to_event_prediction`. `semantic_segmentation` is pending mask-sidecar support upstream ([data-ingestors#136](https://github.com/tracebloc/data-ingestors/issues/136)). The release pipeline ships [`v0.3.0`](https://github.com/tracebloc/cli/releases/latest) as **cosign-signed, multi-arch binaries** — Linux (`amd64`, `arm64`, `386`, `arm`), macOS (`amd64`, `arm64`), and Windows (`amd64`, `arm64`) — each with `SHA256SUMS` and the install scripts. Install via [Customer experience](#customer-experience) or [build from source](#building-from-source). (A Homebrew tap and the `install.tracebloc.io` vanity URL are later follow-ups; the GitHub release URL serves installs today.) @@ -117,7 +117,7 @@ All v0.1 phases are merged: Beyond the original phases, `data ingest` was widened from image-classification-only to 9 of 10 modalities, and the test suite gained unit-coverage wins plus a kind-based integration harness for the real-I/O seams. -**v0.2.0** added a friendlier guided `data ingest` and `dataset rm` on the home screen (#44, #47). **v0.3.0** added the `dataset list` command (#53) plus home-screen / output-spacing polish and feedback-copy refinements (#52, #56). **Next:** cloud-source ingestion (S3/GCS/HTTPS) for datasets above the 1 GiB local cap; `semantic_segmentation` ([data-ingestors#136](https://github.com/tracebloc/data-ingestors/issues/136)) and `instance_segmentation`. Smaller follow-ups are tracked as [open issues](https://github.com/tracebloc/cli/issues). +**v0.2.0** added a friendlier guided `data ingest` and `dataset rm` on the home screen (#44, #47). **v0.3.0** added the `dataset list` command (#53) plus home-screen / output-spacing polish and feedback-copy refinements (#52, #56). **Next:** cloud-source ingestion (S3/GCS/HTTPS) for datasets above the 1 GiB local cap; `semantic_segmentation` ([data-ingestors#136](https://github.com/tracebloc/data-ingestors/issues/136)). Smaller follow-ups are tracked as [open issues](https://github.com/tracebloc/cli/issues). Epic: [tracebloc/client#147](https://github.com/tracebloc/client/issues/147). diff --git a/internal/cli/data.go b/internal/cli/data.go index b392123..a0e35cb 100644 --- a/internal/cli/data.go +++ b/internal/cli/data.go @@ -449,8 +449,8 @@ other collaborators train against it without ever seeing the raw files.`)) // supported case push.IsKnown(a.Spec.Category): // A recognized category data ingest doesn't implement yet — image - // (semantic_segmentation / instance_segmentation) or text - // (causal_language_modeling). Routed here (not the default branch) so the + // (semantic_segmentation) or text (causal_language_modeling, seq2seq, + // …). Routed here (not the default branch) so the // user gets the registry's per-category pending-support reason, not a // misleading "unrecognized category". Supported categories were already // caught above, so IsKnown here means known-but-unsupported. diff --git a/internal/cli/data_test.go b/internal/cli/data_test.go index 703191e..49716e1 100644 --- a/internal/cli/data_test.go +++ b/internal/cli/data_test.go @@ -94,8 +94,8 @@ func execDataIngest(t *testing.T, args []string) (exitCode int, stdout, stderr s func TestDataIngest_UnsupportedCategory_ExitsTwo(t *testing.T) { root := imgcLayout(t) for _, badCategory := range []string{ - "semantic_segmentation", // blocked on the ingestor (data-ingestors#136) - "instance_segmentation", // not implemented + "semantic_segmentation", // known but blocked on the ingestor (data-ingestors#136) + "instance_segmentation", // dead — removed from the registry (#1005), now unrecognized "definitely-not-a-category", // nonsense; gate catches this too } { t.Run(badCategory, func(t *testing.T) { diff --git a/internal/cli/interactive.go b/internal/cli/interactive.go index 9ad71d0..d945de9 100644 --- a/internal/cli/interactive.go +++ b/internal/cli/interactive.go @@ -19,8 +19,8 @@ import ( // category picker. It derives from the push registry's CLI-supported // set — the exact categories runDataIngest's gate accepts — so the // picker can't drift from what `data ingest` actually supports. -// semantic_/instance_segmentation are excluded (CLISupported=false) -// until they're implemented. +// semantic_segmentation is excluded (CLISupported=false) until it's +// implemented. var promptCategories = push.SupportedCategoryIDs() // prompter is the narrow seam over the interactive library. Production diff --git a/internal/push/category.go b/internal/push/category.go index bfae858..eef4a2e 100644 --- a/internal/push/category.go +++ b/internal/push/category.go @@ -26,8 +26,8 @@ type CategorySpec struct { // never ships to the central backend by default. RegressionClass bool // CLISupported reports whether `dataset push` implements the category - // today. semantic_/instance_segmentation are known (the schema - // defines them) but not yet pushable. + // today. semantic_segmentation is known (the schema defines it) but + // not yet pushable. CLISupported bool // UnsupportedNote explains why a known-but-unimplemented category // isn't available yet; surfaced by the push gate. Empty when supported. @@ -49,11 +49,15 @@ const ( FamilyText ) -// categoryRegistry is the ordered, authoritative list of every category -// the ingest.v1 schema defines. Order is the display order for help text -// and the interactive picker (CLI-supported first, in workflow order; -// the not-yet-implemented ones last). Adding a category to the schema -// means adding it here — the parity test pins the set. +// categoryRegistry is the ordered list of every category the ingest.v1 +// schema defines — nothing more, nothing less. Order is the display order for +// help text and the interactive picker (CLI-supported first, in workflow +// order; the not-yet-implemented ones last). Adding a category to the schema +// means adding it here; TestRegistryCoversSchemaCategories + +// TestRegistryWithinSchema pin the set equal to the schema enum both ways, so +// it can neither fall behind (a schema category rejected as "unrecognized") +// nor carry an extra the ingestor won't accept (the instance_segmentation +// half-ingest class — data-ingestors #240/#99, #1005). var categoryRegistry = []CategorySpec{ {ID: "image_classification", Family: FamilyImage, Label: "Image classification", CLISupported: true}, {ID: "object_detection", Family: FamilyImage, Label: "Object detection", CLISupported: true}, @@ -66,8 +70,6 @@ var categoryRegistry = []CategorySpec{ {ID: "time_to_event_prediction", Family: FamilyTabular, Label: "Time-to-event prediction", RegressionClass: true, CLISupported: true}, {ID: "semantic_segmentation", Family: FamilyImage, Label: "Semantic segmentation", CLISupported: false, UnsupportedNote: "blocked on the ingestor's mask-sidecar support (data-ingestors#136)"}, - {ID: "instance_segmentation", Family: FamilyImage, Label: "Instance segmentation", CLISupported: false, - UnsupportedNote: "not implemented"}, {ID: "causal_language_modeling", Family: FamilyText, Label: "Causal language modeling", CLISupported: false, UnsupportedNote: "schema-recognized (data-ingestors#805); `tracebloc ingest` discover/build for its raw-.txt / prompt\\tcompletion `texts` layout is pending"}, {ID: "seq2seq", Family: FamilyText, Label: "Sequence-to-sequence", CLISupported: false, diff --git a/internal/push/category_registry_test.go b/internal/push/category_registry_test.go index eb38a40..3548a38 100644 --- a/internal/push/category_registry_test.go +++ b/internal/push/category_registry_test.go @@ -15,7 +15,7 @@ import ( func TestRegistryKnownCategories(t *testing.T) { want := []string{ "image_classification", "object_detection", "keypoint_detection", - "semantic_segmentation", "instance_segmentation", + "semantic_segmentation", "text_classification", "token_classification", "masked_language_modeling", "causal_language_modeling", "seq2seq", "sentence_pair_classification", "embeddings", @@ -45,9 +45,9 @@ func TestSupportedCategories(t *testing.T) { t.Errorf("SupportedCategoryIDs returned %q but IsCLISupported is false", id) } } - // segmentation + the self-supervised text categories (CLM, seq2seq) + - // token_classification are known but not yet pushable, and must explain why. - for _, id := range []string{"semantic_segmentation", "instance_segmentation", "causal_language_modeling", "seq2seq", "token_classification", "sentence_pair_classification", "embeddings"} { + // semantic_segmentation + the self-supervised text categories (CLM, seq2seq) + // + token_classification are known but not yet pushable, and must explain why. + for _, id := range []string{"semantic_segmentation", "causal_language_modeling", "seq2seq", "token_classification", "sentence_pair_classification", "embeddings"} { if !IsKnown(id) { t.Errorf("%s should be known", id) } @@ -87,16 +87,12 @@ func TestPredicatesDeriveFromRegistry(t *testing.T) { } } -// TestRegistryCoversSchemaCategories pins registry⇄schema parity: every -// category the ingest schema accepts must be known to the registry, or a -// schema-valid `dataset push --category=X` is wrongly rejected as -// "unrecognized" (the token_classification drift, Bugbot v0.4.0 RC). The -// existing tests only pin the registry against a hand-written list, which -// stays internally consistent while drifting from the schema — this closes -// that gap. The reverse direction isn't required: the registry may carry a -// known-but-unsupported alias the v1 schema doesn't list yet (e.g. -// instance_segmentation), which is gated out before schema validation. -func TestRegistryCoversSchemaCategories(t *testing.T) { +// schemaCategoryEnum returns the category enum from the embedded ingest.v1 +// schema — the single source of truth the registry is pinned against (#1005). +// The schema is vendored + drift-checked against data-ingestors by +// scripts/sync-schema.sh, so this ties the registry transitively to upstream. +func schemaCategoryEnum(t *testing.T) []string { + t.Helper() var doc struct { Properties struct { Category struct { @@ -110,7 +106,23 @@ func TestRegistryCoversSchemaCategories(t *testing.T) { if len(doc.Properties.Category.Enum) == 0 { t.Fatal("no category enum found in the embedded schema (parse path wrong?)") } - for _, id := range doc.Properties.Category.Enum { + return doc.Properties.Category.Enum +} + +// registryAliases are registry category IDs deliberately NOT in the ingest.v1 +// schema enum — declared placeholders. Empty today: instance_segmentation used +// to sit here unchecked, but it's dead (it half-ingested with no validators or +// file transfer — data-ingestors #240/#99) and was removed, not kept. A future +// known-but-unschema'd placeholder must be DECLARED here, so TestRegistryWithinSchema +// flags undeclared drift while allowing an intentional superset (#1005). +var registryAliases = map[string]bool{} + +// TestRegistryCoversSchemaCategories pins schema ⊆ registry: every category the +// ingest schema accepts must be known to the registry, or a schema-valid +// `dataset push --category=X` is wrongly rejected as "unrecognized" (the +// token_classification drift, Bugbot v0.4.0 RC). +func TestRegistryCoversSchemaCategories(t *testing.T) { + for _, id := range schemaCategoryEnum(t) { if !IsKnown(id) { t.Errorf("schema category %q missing from the registry — `dataset push --category=%s` "+ "would be rejected as unrecognized despite passing schema validation", id, id) @@ -118,6 +130,28 @@ func TestRegistryCoversSchemaCategories(t *testing.T) { } } +// TestRegistryWithinSchema pins registry ⊆ schema (+ declared aliases): the +// registry must not carry a category the ingest schema — and therefore the +// ingestor — doesn't accept. An undeclared extra is exactly the +// instance_segmentation half-ingest class: the backend/CLI would accept a +// `--category` the pipeline can't handle, and the config half-ingests (DB rows +// + API records, zero files staged; #1005, data-ingestors #240/#99). Together +// with TestRegistryCoversSchemaCategories this pins registry == schema, modulo +// explicitly declared placeholders in registryAliases. +func TestRegistryWithinSchema(t *testing.T) { + inSchema := make(map[string]bool) + for _, id := range schemaCategoryEnum(t) { + inSchema[id] = true + } + for _, id := range AllCategoryIDs() { + if !inSchema[id] && !registryAliases[id] { + t.Errorf("registry category %q is not in the ingest.v1 schema enum and not a declared "+ + "alias — add it to the schema (data-ingestors) if it's real, or declare it in "+ + "registryAliases if it's an intentional placeholder", id) + } + } +} + func equalSet(a, b []string) bool { if len(a) != len(b) { return false