3lc-compute-plugin-sdk 0.3.0__tar.gz → 0.3.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {3lc_compute_plugin_sdk-0.3.0 → 3lc_compute_plugin_sdk-0.3.2}/.github/workflows/docs.yml +4 -3
- 3lc_compute_plugin_sdk-0.3.2/1.0-READINESS.md +434 -0
- {3lc_compute_plugin_sdk-0.3.0 → 3lc_compute_plugin_sdk-0.3.2}/CHANGELOG.md +51 -1
- {3lc_compute_plugin_sdk-0.3.0 → 3lc_compute_plugin_sdk-0.3.2}/CLAUDE.md +12 -13
- {3lc_compute_plugin_sdk-0.3.0 → 3lc_compute_plugin_sdk-0.3.2}/PKG-INFO +8 -7
- {3lc_compute_plugin_sdk-0.3.0 → 3lc_compute_plugin_sdk-0.3.2}/README.md +6 -5
- {3lc_compute_plugin_sdk-0.3.0 → 3lc_compute_plugin_sdk-0.3.2}/docs/plugin-guide.md +164 -106
- {3lc_compute_plugin_sdk-0.3.0 → 3lc_compute_plugin_sdk-0.3.2}/pyproject.toml +19 -24
- {3lc_compute_plugin_sdk-0.3.0 → 3lc_compute_plugin_sdk-0.3.2}/src/tlc_plugin_sdk/shared/data_source_routes.py +90 -9
- {3lc_compute_plugin_sdk-0.3.0 → 3lc_compute_plugin_sdk-0.3.2}/src/tlc_plugin_sdk/shared/data_source_ui.py +35 -4
- {3lc_compute_plugin_sdk-0.3.0 → 3lc_compute_plugin_sdk-0.3.2}/tests/test_data_source_routes.py +81 -0
- {3lc_compute_plugin_sdk-0.3.0 → 3lc_compute_plugin_sdk-0.3.2}/tests/test_import_light.py +2 -2
- {3lc_compute_plugin_sdk-0.3.0 → 3lc_compute_plugin_sdk-0.3.2}/uv.lock +3 -7
- {3lc_compute_plugin_sdk-0.3.0 → 3lc_compute_plugin_sdk-0.3.2}/.github/workflows/ci.yml +0 -0
- {3lc_compute_plugin_sdk-0.3.0 → 3lc_compute_plugin_sdk-0.3.2}/.github/workflows/release.yml +0 -0
- {3lc_compute_plugin_sdk-0.3.0 → 3lc_compute_plugin_sdk-0.3.2}/.gitignore +0 -0
- {3lc_compute_plugin_sdk-0.3.0 → 3lc_compute_plugin_sdk-0.3.2}/LICENSE +0 -0
- {3lc_compute_plugin_sdk-0.3.0 → 3lc_compute_plugin_sdk-0.3.2}/docs/api.md +0 -0
- {3lc_compute_plugin_sdk-0.3.0 → 3lc_compute_plugin_sdk-0.3.2}/docs/conf.py +0 -0
- {3lc_compute_plugin_sdk-0.3.0 → 3lc_compute_plugin_sdk-0.3.2}/docs/index.md +0 -0
- {3lc_compute_plugin_sdk-0.3.0 → 3lc_compute_plugin_sdk-0.3.2}/docs/package-lock.json +0 -0
- {3lc_compute_plugin_sdk-0.3.0 → 3lc_compute_plugin_sdk-0.3.2}/docs/package.json +0 -0
- {3lc_compute_plugin_sdk-0.3.0 → 3lc_compute_plugin_sdk-0.3.2}/docs/requirements.txt +0 -0
- {3lc_compute_plugin_sdk-0.3.0 → 3lc_compute_plugin_sdk-0.3.2}/src/tlc_plugin_sdk/__init__.py +0 -0
- {3lc_compute_plugin_sdk-0.3.0 → 3lc_compute_plugin_sdk-0.3.2}/src/tlc_plugin_sdk/asgi_app.py +0 -0
- {3lc_compute_plugin_sdk-0.3.0 → 3lc_compute_plugin_sdk-0.3.2}/src/tlc_plugin_sdk/contract/plugin-api.d.ts +0 -0
- {3lc_compute_plugin_sdk-0.3.0 → 3lc_compute_plugin_sdk-0.3.2}/src/tlc_plugin_sdk/contract.py +0 -0
- {3lc_compute_plugin_sdk-0.3.0 → 3lc_compute_plugin_sdk-0.3.2}/src/tlc_plugin_sdk/job_context.py +0 -0
- {3lc_compute_plugin_sdk-0.3.0 → 3lc_compute_plugin_sdk-0.3.2}/src/tlc_plugin_sdk/py.typed +0 -0
- {3lc_compute_plugin_sdk-0.3.0 → 3lc_compute_plugin_sdk-0.3.2}/src/tlc_plugin_sdk/shared/__init__.py +0 -0
- {3lc_compute_plugin_sdk-0.3.0 → 3lc_compute_plugin_sdk-0.3.2}/src/tlc_plugin_sdk/shared/alias_override_ui.py +0 -0
- {3lc_compute_plugin_sdk-0.3.0 → 3lc_compute_plugin_sdk-0.3.2}/src/tlc_plugin_sdk/shared/alias_ui.py +0 -0
- {3lc_compute_plugin_sdk-0.3.0 → 3lc_compute_plugin_sdk-0.3.2}/src/tlc_plugin_sdk/shared/aliases.py +0 -0
- {3lc_compute_plugin_sdk-0.3.0 → 3lc_compute_plugin_sdk-0.3.2}/src/tlc_plugin_sdk/shared/config_store.py +0 -0
- {3lc_compute_plugin_sdk-0.3.0 → 3lc_compute_plugin_sdk-0.3.2}/src/tlc_plugin_sdk/shared/config_ui.py +0 -0
- {3lc_compute_plugin_sdk-0.3.0 → 3lc_compute_plugin_sdk-0.3.2}/src/tlc_plugin_sdk/shared/generic_job.py +0 -0
- {3lc_compute_plugin_sdk-0.3.0 → 3lc_compute_plugin_sdk-0.3.2}/src/tlc_plugin_sdk/shared/images.py +0 -0
- {3lc_compute_plugin_sdk-0.3.0 → 3lc_compute_plugin_sdk-0.3.2}/src/tlc_plugin_sdk/shared/job_tracker.py +0 -0
- {3lc_compute_plugin_sdk-0.3.0 → 3lc_compute_plugin_sdk-0.3.2}/src/tlc_plugin_sdk/shared/labels.py +0 -0
- {3lc_compute_plugin_sdk-0.3.0 → 3lc_compute_plugin_sdk-0.3.2}/src/tlc_plugin_sdk/shared/modality.py +0 -0
- {3lc_compute_plugin_sdk-0.3.0 → 3lc_compute_plugin_sdk-0.3.2}/src/tlc_plugin_sdk/shared/model_storage.py +0 -0
- {3lc_compute_plugin_sdk-0.3.0 → 3lc_compute_plugin_sdk-0.3.2}/src/tlc_plugin_sdk/shared/naming.py +0 -0
- {3lc_compute_plugin_sdk-0.3.0 → 3lc_compute_plugin_sdk-0.3.2}/src/tlc_plugin_sdk/shared/ui_inject.py +0 -0
- {3lc_compute_plugin_sdk-0.3.0 → 3lc_compute_plugin_sdk-0.3.2}/src/tlc_plugin_sdk/shared/url_utils.py +0 -0
- {3lc_compute_plugin_sdk-0.3.0 → 3lc_compute_plugin_sdk-0.3.2}/src/tlc_plugin_sdk/worker.py +0 -0
- {3lc_compute_plugin_sdk-0.3.0 → 3lc_compute_plugin_sdk-0.3.2}/tests/test_job_context.py +0 -0
- {3lc_compute_plugin_sdk-0.3.0 → 3lc_compute_plugin_sdk-0.3.2}/tests/test_js_contract_surface.py +0 -0
- {3lc_compute_plugin_sdk-0.3.0 → 3lc_compute_plugin_sdk-0.3.2}/tests/test_url_utils.py +0 -0
- {3lc_compute_plugin_sdk-0.3.0 → 3lc_compute_plugin_sdk-0.3.2}/tests/test_worker_gpu_reclaim.py +0 -0
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
name: docs
|
|
2
2
|
|
|
3
3
|
# Build the Sphinx docs and publish to GitHub Pages.
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
# One-time setup: repo Settings -> Pages -> Source = "GitHub Actions".
|
|
4
|
+
# Deploys automatically on every release tag, or manually from the Actions tab
|
|
5
|
+
# (`gh workflow run docs`). One-time setup: repo Settings -> Pages -> Source = "GitHub Actions".
|
|
7
6
|
on:
|
|
7
|
+
push:
|
|
8
|
+
tags: ["v*"]
|
|
8
9
|
workflow_dispatch:
|
|
9
10
|
|
|
10
11
|
# Least-privilege token for the Pages deploy.
|
|
@@ -0,0 +1,434 @@
|
|
|
1
|
+
# 1.0 readiness report — 3lc-compute-plugin-sdk
|
|
2
|
+
|
|
3
|
+
*Audit of SDK `main` @ `576bcd5` (0.2.0, includes JS_CONTRACT 0.2 / PR #8), host `3lc-compute` @ `bbf2aed`,
|
|
4
|
+
Hub frontend, and all 9 first-party plugins (exporter, importer, merger, splitter, image-metrics,
|
|
5
|
+
table-statistics, timm, sam3, yolo). Line numbers reference those revisions. Uncommitted working doc.*
|
|
6
|
+
|
|
7
|
+
**Framing** (per the pre-1.0 window): all consumers are in-house, so breaking is cheap *now* —
|
|
8
|
+
grep-and-fix across our own repos, coordinated via editable overlays and the frontend contract test.
|
|
9
|
+
Every breaking proposal below states who migrates and roughly how.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## The shape of the problem (executive summary)
|
|
14
|
+
|
|
15
|
+
1. **The contract that's written down is healthy; the contract that's *lived* is bigger and undeclared.**
|
|
16
|
+
The `PluginApi` top level matches the d.ts exactly (the conformance test passes), but the surface
|
|
17
|
+
plugins actually depend on includes `openTablePicker` (7 of 8 UI plugins), 13 SDK-injected `_tlc*`
|
|
18
|
+
globals (6 of 8 plugins), `CancelJob`, bare `showToast`, `cssVar`, `plugin-common.css` classes, and
|
|
19
|
+
an `allowErrorStatus` fetch option the host itself uses 13×. None of that is in the d.ts. 1.0 should
|
|
20
|
+
be the moment the declared contract and the lived contract become the same thing.
|
|
21
|
+
2. **The job-reporting API is fought by its heaviest users.** `ctx.result(run_url=)` is called by only
|
|
22
|
+
2 of 7 job plugins — all three GPU trainers smuggle their result URL through `ctx.emit`/`ctx.metric`
|
|
23
|
+
instead; failure reporting is hand-rolled (`ctx.emit("job_failed", ...)` + `raise`) in duplicate;
|
|
24
|
+
the failure message rides `PluginJobUpdate.subtitle`. These are the exact warts that freeze at 1.0.
|
|
25
|
+
3. **The version story has three axes and one consumer.** `PY_CONTRACT`/`JS_CONTRACT` are read by
|
|
26
|
+
nobody (host compares `sdk_version` only; zero plugin references); the manifest `sdk_version` the
|
|
27
|
+
SDK docstring promises is read by nobody; and `PLUGIN_API.contractVersion` is `''` in every
|
|
28
|
+
production frontend container. Consolidate to one axis and make it actually reach production.
|
|
29
|
+
4. **Live pin defect, before anything else:** the host and the plugins umbrella pin
|
|
30
|
+
`3lc-compute-plugin-sdk[shared]>=0.1.0,<0.2.0` — which *excludes the current 0.2.0 release* — so
|
|
31
|
+
three shipped 0.2 features (host-mode GPU reclaim, `/reclaim`, `/health` skew detection) are inert
|
|
32
|
+
in the resolved lock (host `pyproject.toml:24`, acknowledged at `job_manager.py:59-71`).
|
|
33
|
+
5. **Ceremony is measurable.** 8/9 plugins repeat the same `_ui_cache` + `Path(__file__)` +
|
|
34
|
+
`read_text` block; 5/9 implement `compute()` as a dead stub; script injection exists in **four**
|
|
35
|
+
idioms (two of them the exact footgun `ui_inject.py` was written to eliminate, one injection dead
|
|
36
|
+
on both ends); the config-CRUD route quartet is copy-pasted 4×; yolo reimplements
|
|
37
|
+
`PluginConfigStore` wholesale.
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## A. Break at 1.0 (ranked by leverage)
|
|
42
|
+
|
|
43
|
+
### A1. Reshape job *reporting*: `result`, failure, and the error field
|
|
44
|
+
|
|
45
|
+
- **What/where:** `src/tlc_plugin_sdk/job_context.py:75-87` (`result(*, run_url)`),
|
|
46
|
+
`contract/plugin-api.d.ts` `PluginJobUpdate` (failure message documented as riding `subtitle`),
|
|
47
|
+
host translation `3lc-compute/src/tlc_compute/plugins/job_manager.py:461-479`.
|
|
48
|
+
- **Why it hurts:** the name `run_url` is wrong for half the producers (importers/exporters/mergers
|
|
49
|
+
produce *tables*), and the evidence says the API lost its users: only merger (`__init__.py:147`) and
|
|
50
|
+
importer (`:1553`) call `ctx.result`; timm ships the URL via `ctx.emit("job_completed", {..., "run_url"})`
|
|
51
|
+
(`timm/__init__.py:258-261`), yolo likewise (`:299-308`), and sam3 abuses `ctx.metric("run", str(run.url))`
|
|
52
|
+
(`:393-394`) — so the generic panel's "open result" link works for 2 of 7 job plugins. Failure has no
|
|
53
|
+
first-class API at all: timm `:266-269` and yolo `:311-313` both hand-emit `job_failed` + `raise`, and
|
|
54
|
+
the frontend reads the failure message out of `subtitle`. All of this freezes at 1.0 if untouched.
|
|
55
|
+
- **Proposed 1.0 shape:**
|
|
56
|
+
- `ctx.result(url: str, *, kind: str = "run")` (or just `url=`); keep emitting `run_url` on the wire
|
|
57
|
+
*or* rename the wire field to `result_url` in the same coordinated set.
|
|
58
|
+
- Add `error` to the generic job record/`PluginJobUpdate` (host sets it from the terminal `error`
|
|
59
|
+
event); keep `subtitle` as display text. Optionally add `ctx.fail(message)` sugar (equivalent to
|
|
60
|
+
raising, for validation paths) — that part is additive.
|
|
61
|
+
- Document `percent=-1` (indeterminate) in `JobContext.progress` itself — today it's only in the
|
|
62
|
+
guide (`docs/plugin-guide.md:530`), and importer relies on it (`:1578,1621`).
|
|
63
|
+
- **Additive or breaking:** breaking (kwarg rename + wire-field addition/rename).
|
|
64
|
+
- **Who migrates:** SDK (`job_context.py`, d.ts `PluginJobUpdate`); host (`job_manager.py:473-474`,
|
|
65
|
+
`_Job.to_generic` `:109-122`); frontend (queue-panel/cancel-job read of `run_url`/`subtitle`);
|
|
66
|
+
plugins: merger + importer one line each, then timm/yolo/sam3 *adopt* `ctx.result` and delete their
|
|
67
|
+
workarounds (a net simplification, not just a rename).
|
|
68
|
+
- **Effort:** M.
|
|
69
|
+
|
|
70
|
+
### A2. Collapse the three version axes to one, and make it reach production
|
|
71
|
+
|
|
72
|
+
- **What/where:** `src/tlc_plugin_sdk/__init__.py:39-56` (`PY_CONTRACT`, `JS_CONTRACT`),
|
|
73
|
+
`asgi_app.py:52-59` (`/health` reports all three), frontend `app.py:22-28` + `pyproject.toml:17-34`.
|
|
74
|
+
- **Why it hurts:** `PY_CONTRACT`/`JS_CONTRACT` have **zero consumers** — the host's skew check
|
|
75
|
+
compares only `sdk_version` (`3lc-compute/plugins/versioning.py:128-164`), no plugin reads them, and
|
|
76
|
+
the frontend's use of `JS_CONTRACT` is broken in production: the SDK is a dev-group-only dependency
|
|
77
|
+
(`frontend/pyproject.toml:33`), the Docker image installs only `[project.dependencies]`, so
|
|
78
|
+
**`PLUGIN_API.contractVersion === ''` in every shipped container** and all documented 0.2
|
|
79
|
+
feature-detection (`location`, `resolveLocation?`, `getLocations?`) reports "pre-0.1 host". Three
|
|
80
|
+
axes is bookkeeping we pay on every release for a distinction nothing exercises. Also: the SDK
|
|
81
|
+
docstring's claim that plugins declare `sdk_version` in their manifest (`__init__.py:32-33`) is
|
|
82
|
+
fiction — no manifest has it, no host code reads it.
|
|
83
|
+
- **Proposed 1.0 shape:** `SDK_CONTRACT_VERSION` is the one version. Delete `PY_CONTRACT`/`JS_CONTRACT`
|
|
84
|
+
exports; the d.ts header states the package version it shipped in. `/health` reports `sdk_version`
|
|
85
|
+
only. The frontend derives `contractVersion` from the *compute service* (it already calls
|
|
86
|
+
`GET /api/plugins/` and `/health`) instead of a locally-installed Python package — that fixes the
|
|
87
|
+
production hole structurally rather than by promoting the SDK to a frontend runtime dep. Fix or
|
|
88
|
+
delete the `sdk_version`-manifest docstring claim (see A6).
|
|
89
|
+
- **Additive or breaking:** breaking (removes two public constants and two `/health` fields).
|
|
90
|
+
- **Who migrates:** SDK (`__init__.py`, `asgi_app.py`, d.ts header, guide's "Two version axes"
|
|
91
|
+
section); frontend (`app.py`, `base.html:48`, `plugin-loader.js:210` → read from compute);
|
|
92
|
+
host: nothing (never consumed them). Plugins: nothing.
|
|
93
|
+
- **Effort:** M (the frontend sourcing change is the real work).
|
|
94
|
+
|
|
95
|
+
### A3. Bring the escaped browser surface into the contract; kill the bare globals
|
|
96
|
+
|
|
97
|
+
- **What/where:** frontend `static/js/table-picker.js:62` (`openTablePicker`, used by **7 of 8** UI
|
|
98
|
+
plugins), `cancel-job.js:29` (`CancelJob`, yolo+timm), `main.js:202/170` (bare `showToast`, `cssVar`);
|
|
99
|
+
SDK-injected `_tlc*` globals — 13 functions across `shared/alias_ui.py`, `alias_override_ui.py`,
|
|
100
|
+
`config_ui.py`, used by 6 of 8 plugins, none declared in the d.ts.
|
|
101
|
+
- **Why it hurts:** this is contract-shaped surface with no contract: no d.ts entry, no conformance
|
|
102
|
+
gate, no stability tier. `openTablePicker(targetInputId)` additionally couples via a global DOM id
|
|
103
|
+
(`table-picker.js:308` reaches out of the plugin container), and the host fires both `input` *and*
|
|
104
|
+
`change` events because five named plugins disagree on which to listen for (`table-picker.js:308-317`).
|
|
105
|
+
The underscore prefix on `_tlc*` says "private" while the usage pattern is "public API".
|
|
106
|
+
- **Proposed 1.0 shape:**
|
|
107
|
+
- `PLUGIN_API.pickers.table(options) → Promise<string|null>` (promise-returning, no DOM-id
|
|
108
|
+
coupling; frontend wraps the existing picker). There is no run picker today — declare only what
|
|
109
|
+
exists, add `pickers.run` later additively.
|
|
110
|
+
- `PLUGIN_API.jobs.cancelDialog(jobId, opts)` or declare `CancelJob` as-is in the d.ts; plugins
|
|
111
|
+
already use `PLUGIN_API.showToast`, so delete the bare-global usage (importer ×3).
|
|
112
|
+
- Rename the injected `_tlc*` families under one declared namespace (e.g. `window.TlcPluginUi.alias.*`,
|
|
113
|
+
`.aliasOverride.*`, `.configBar(...)`) with d.ts declarations; ship thin deprecated aliases during
|
|
114
|
+
the migration window, drop the bare `_tlc*` names at 1.0.
|
|
115
|
+
- **Additive or breaking:** additive to introduce, breaking to remove the old globals at 1.0 (that
|
|
116
|
+
removal is the point — otherwise the undeclared surface stays load-bearing forever).
|
|
117
|
+
- **Who migrates:** frontend (implement `pickers`, declare members, then delete the legacy event
|
|
118
|
+
double-fire once all plugins use the promise API); SDK (rename injected JS + d.ts declarations);
|
|
119
|
+
plugins: mechanical sweep — `openTablePicker` call sites in 7 plugins, `_tlc*` call sites in 5.
|
|
120
|
+
- **Effort:** M–L (mechanical but wide; the contract-test extension in C3 keeps it honest).
|
|
121
|
+
|
|
122
|
+
### A4. Make the SDK own the manifest schema — and fix the doc/code lies about it
|
|
123
|
+
|
|
124
|
+
- **What/where:** manifest parsing lives host-side (`3lc-compute/plugins/manifest.py:140-248`) with
|
|
125
|
+
no schema, while the SDK documents it (`docs/plugin-guide.md:104-157`). Confirmed divergences:
|
|
126
|
+
- `isolation` default: guide says `venv` is the default (`plugin-guide.md:130`); host code defaults
|
|
127
|
+
to **`host`** (`registry.py:392`) — the opposite, and the dangerous direction (in-process import).
|
|
128
|
+
- `socketio_namespace`: guide says declare-to-override (`:134-137`); host **hardcodes** `f"/{pid}"`
|
|
129
|
+
and never reads the key (`manifest.py:219-221`).
|
|
130
|
+
- `provision_extra`: guide says REQUIRED for venv (`:133,140`); host treats it as optional and
|
|
131
|
+
silently falls back to in-place resolution (`worker_spec.py:351-397`).
|
|
132
|
+
- Undocumented keys the host reads: `runtime.auth_exempt_paths`, `runtime.venv_python`,
|
|
133
|
+
`runtime.python`, `runtime.training`, `ui.min_input_count`, `ui.action_param_names`,
|
|
134
|
+
`ui.quick_action_label/description` (`manifest.py:191-216`).
|
|
135
|
+
- Documented keys nothing reads: `sdk_version`, `ui.fragment`, `display_mode = "hidden"` (no host
|
|
136
|
+
branch on it).
|
|
137
|
+
- No `max_input_count`: image-metrics encodes "1–10 tables" as `input_types = ["table"] × 10`
|
|
138
|
+
(`tlc_plugin_image_metrics/plugin.toml:13`).
|
|
139
|
+
- No reserved-id list: `id = "jobs"` (or `manifest`, `by-resource`, `install`, …) passes the id
|
|
140
|
+
pattern (`identity.py:52`) and is then unroutable under `/api/plugins`.
|
|
141
|
+
- A TOML parse error makes the plugin **vanish silently** (`manifest.py:115-121`) — contradicting
|
|
142
|
+
the stated design goal of "listed greyed-out, never vanishing".
|
|
143
|
+
- `contract.py:8-9` says the manifest is "`[tool.tlc-compute]` in plugin.toml" — all 9 real
|
|
144
|
+
manifests are bare top-level `plugin.toml`; the `[tool.tlc-compute]` form only applies to
|
|
145
|
+
`pyproject.toml`.
|
|
146
|
+
- **Why it hurts:** the manifest *is* contract surface — every key an author writes is a promise —
|
|
147
|
+
and right now the source of truth is a host file the SDK repo can't see, with drift in both
|
|
148
|
+
directions and no validation (string `priority` takes down `GET /api/plugins` wholesale,
|
|
149
|
+
`registry.py:725`).
|
|
150
|
+
- **Proposed 1.0 shape:** a typed `tlc_plugin_sdk.manifest` module (dataclass + `parse()` +
|
|
151
|
+
validation errors) that the **host imports** — SDK is already the root of the dependency graph, so
|
|
152
|
+
this is the natural home. In the same move: require `isolation` explicitly (all 9 manifests already
|
|
153
|
+
declare it — zero real migration); either implement `socketio_namespace` override or delete it from
|
|
154
|
+
the schema/docs (recommend delete — nothing has ever overridden it); make `provision_extra`
|
|
155
|
+
actually required for venv (validation error); add `max_input_count`; reserve the literal path
|
|
156
|
+
segments (`jobs`, `manifest`, `by-resource`, `venv`, …) in id validation; document the rest.
|
|
157
|
+
Also add `/venv` to the reserved-route list in `contract.py:105` (host owns
|
|
158
|
+
`GET /api/plugins/{id}/venv`; a plugin's `@get("/venv")` is silently shadowed today).
|
|
159
|
+
- **Additive or breaking:** breaking (required `isolation`, dropped dead keys, host adopts SDK parser).
|
|
160
|
+
- **Who migrates:** host (manifest.py becomes a consumer of the SDK schema — the big piece);
|
|
161
|
+
plugins: none in practice; docs: guide's manifest section becomes generated-from/checked-against
|
|
162
|
+
the schema.
|
|
163
|
+
- **Effort:** L (mostly host-side; the SDK module itself is S–M).
|
|
164
|
+
|
|
165
|
+
### A5. Prune and re-tier `shared.*` (the graduation question)
|
|
166
|
+
|
|
167
|
+
- **What/where & evidence (usage census across all 9 plugins + host):**
|
|
168
|
+
- `shared/modality.py` (551 lines — the largest shared module): plugins use exactly **one** of its
|
|
169
|
+
five public functions (`detect_modality_from_table`, 5 plugins). `detect_modality_from_url` has
|
|
170
|
+
**zero references anywhere**; `detect_modality_from_schema` and `classify_metrics_columns` are
|
|
171
|
+
internal/host-only. It is 551 lines of heuristics (7-step fallback chains, name conventions)
|
|
172
|
+
published as forever-contract.
|
|
173
|
+
- `shared/labels.py`: three plugins use one distinct function each, no overlap;
|
|
174
|
+
`candidate_label_paths`/`find_label_path`/`get_label_map` have no external callers.
|
|
175
|
+
- `shared/generic_job.epoch_progress`: 2 users (timm, yolo) — and **both** also maintain a private
|
|
176
|
+
`_timing` dict + hand-rolled elapsed/avg/ETA computation + a local `avg_epoch_s → avg_step_s`
|
|
177
|
+
remap (`yolo/__init__.py:184-191`, `timm/__init__.py:144-154`) because the helper only formats,
|
|
178
|
+
it doesn't *track*. The abstraction is at the wrong altitude.
|
|
179
|
+
- `shared/url_utils.normalize_url`: adoption ~25% (29 bypassing `tlc.Table.from_url` calls vs ~9
|
|
180
|
+
using it), and its heuristics are platform folklore — `url.startswith(("Users/", "home/"))`
|
|
181
|
+
re-adds a lost leading slash (`url_utils.py:37-39`), macOS/Linux-specific, silently wrong
|
|
182
|
+
elsewhere.
|
|
183
|
+
- `shared/model_storage.py`: 2 users (timm, yolo); `on_status: Any` where it means
|
|
184
|
+
`Callable[[str], None]`; contains an `except AttributeError` probe for a `tlc.Url` method.
|
|
185
|
+
- `shared/config_store.py`: healthy (3 users + yolo's clone proves demand) but leans on
|
|
186
|
+
`# type: ignore[attr-defined]` ×5 for the id/created/last_run envelope — the repo's own
|
|
187
|
+
CLAUDE.md bans that.
|
|
188
|
+
- **Proposed 1.0 shape:**
|
|
189
|
+
- **Graduate (stable core):** `ui_inject`, `job_tracker`, `config_store` (with a typed envelope —
|
|
190
|
+
a `PluginConfigBase` dataclass or Protocol killing the `type: ignore`s), `aliases`, `images`,
|
|
191
|
+
`labels` (public trio only), `naming`.
|
|
192
|
+
- **Trim:** delete `detect_modality_from_url` (zero users); underscore
|
|
193
|
+
`detect_modality_from_schema`, `candidate_label_paths`, `find_label_path` (internal helpers).
|
|
194
|
+
- **Mark experimental:** `modality` detection heuristics — either a `shared.experimental` namespace
|
|
195
|
+
or an explicit "best-effort, may change in minors" tier in the docstring + docs, mirroring the
|
|
196
|
+
`libs` tier precedent. Freezing 551 lines of heuristics at 1.0 is the single biggest frozen-wart
|
|
197
|
+
risk in the package.
|
|
198
|
+
- **Replace:** `epoch_progress` with an `EpochTimer` that owns the timing dict + ETA math both
|
|
199
|
+
trainers reimplement (additive add, breaking delete of `epoch_progress` — 2 trivial migrations).
|
|
200
|
+
`normalize_url` → either push proper resolution into `tlc` core or reduce it to the defensible
|
|
201
|
+
subset (absolute-ize + project-root resolution) and delete the "Users/" folklore.
|
|
202
|
+
- **Tighten:** `model_storage` signatures (`on_status: Callable[[str], None] | None`).
|
|
203
|
+
- **The `[shared]` extra:** keep the split. The pyproject "direction" note (`pyproject.toml:43`)
|
|
204
|
+
says `3lc` likely returns to base — but the frontend is a real bare-SDK consumer (it installs the
|
|
205
|
+
SDK purely for the d.ts). Recommend deleting that direction note and declaring the split
|
|
206
|
+
permanent at 1.0.
|
|
207
|
+
- **Additive or breaking:** mixed; the deletions/underscorings are breaking.
|
|
208
|
+
- **Who migrates:** essentially nobody for the deletions (zero external users, verified); timm+yolo
|
|
209
|
+
for `epoch_progress`; call-site sweep for `normalize_url` semantics (5 plugins, host insights
|
|
210
|
+
plugins).
|
|
211
|
+
- **Effort:** M.
|
|
212
|
+
|
|
213
|
+
### A6. Fix `contractVersion`'s meaning and the d.ts's own identity strings
|
|
214
|
+
|
|
215
|
+
- **What/where:** the d.ts references the **pre-rename distribution name** `3lc-plugin-sdk` in three
|
|
216
|
+
places (`plugin-api.d.ts:26, 353, 416`); the guide (`plugin-guide.md:313`) and `docs/api.md:32` say
|
|
217
|
+
`/// <reference types="3lc-compute-plugin-sdk/contract/plugin-api" />` while the d.ts header
|
|
218
|
+
(`:24-27`) explains at length why it must be the *import* name `tlc_plugin_sdk`. The guide's
|
|
219
|
+
version table (`plugin-guide.md:785-787`) still says JS_CONTRACT 0.1 / package 0.1.0. README says
|
|
220
|
+
"0.1 is the released contract line" and that the base deps include `3lc` (`README.md:46,52`) —
|
|
221
|
+
both wrong.
|
|
222
|
+
- **Why it hurts:** the contract document contradicting itself about its own name and version is
|
|
223
|
+
exactly the kind of thing a first external plugin author trips on day one.
|
|
224
|
+
- **Proposed 1.0 shape:** one pass fixing every self-reference; if A2 lands, the "Two version axes"
|
|
225
|
+
section collapses to one paragraph. Decide the `<reference>` path question once (test both forms in
|
|
226
|
+
an editor, document the winner, delete the loser).
|
|
227
|
+
- **Additive or breaking:** docs-only (bundled here because it must land *with* A2's reshape).
|
|
228
|
+
- **Effort:** S.
|
|
229
|
+
|
|
230
|
+
---
|
|
231
|
+
|
|
232
|
+
## B. Additive anytime (ranked)
|
|
233
|
+
|
|
234
|
+
### B1. Auto-inject the SDK client scripts at serve time (kill the injection ceremony)
|
|
235
|
+
|
|
236
|
+
- **What/where:** every job-using plugin must remember
|
|
237
|
+
`inject_scripts(raw, job_tracker_script())`. Reality check across 9 plugins: **four different
|
|
238
|
+
idioms** — `inject_scripts` (4 plugins); the literal `raw.replace("<script>", ...)` footgun that
|
|
239
|
+
`ui_inject.py:16-22` was written to eliminate (sam3 `__init__.py:50-55`); a brittle 3-line-exact
|
|
240
|
+
source-string match (importer `:1691-1694`); wholesale script-block prepending (merger `:104`,
|
|
241
|
+
splitter `:157`). Plus one **dead** injection: exporter injects `alias_override_ui_script()` into a
|
|
242
|
+
`ui.html` that never references any alias function (`exporter/__init__.py:381-386`).
|
|
243
|
+
- **Proposal:** `build_plugin_app`'s `/ui` handler (`asgi_app.py:64-66`) — and the host's in-process
|
|
244
|
+
ui path — injects `JOB_TRACKER_JS` automatically. The client is already idempotent
|
|
245
|
+
(`if (window.PluginJobs) return;`, `job_tracker.py:33`), so existing plugins keep working and can
|
|
246
|
+
delete their injection lines at leisure. Alias/config UI injection becomes a manifest opt-in
|
|
247
|
+
(`[ui] inject = ["alias", "alias_override", "config_bar"]`) or stays manual — either way documented.
|
|
248
|
+
- **Effort:** S–M. Migration: none required; cleanup sweep optional.
|
|
249
|
+
|
|
250
|
+
### B2. De-abstract `compute()`
|
|
251
|
+
|
|
252
|
+
- **What/where:** `contract.py:56-59` makes `compute()` abstract; 5 of 9 plugins implement it as a
|
|
253
|
+
dead stub (timm `:56` returns `{"status": "Use /api/plugins/timm/* endpoints"}`, etc.). The
|
|
254
|
+
`/compute` route also injects a magic `params.setdefault("url", "")` (`asgi_app.py:71`).
|
|
255
|
+
- **Proposal:** give it a default returning `{"error": "<id> does not implement compute()"}` —
|
|
256
|
+
mirroring `run_job`'s default — so `get_ui_fragment` is the only abstract. Drop the magic `url`
|
|
257
|
+
defaulting (let plugins read their own params). Deleting five stubs is optional cleanup.
|
|
258
|
+
- **Effort:** S. (The `url` defaulting change is technically behavior-breaking for any plugin relying
|
|
259
|
+
on the key existing — grep says none do.)
|
|
260
|
+
|
|
261
|
+
### B3. Ship the missing batteries the plugins keep hand-rolling
|
|
262
|
+
|
|
263
|
+
Evidence-ranked by duplication count:
|
|
264
|
+
|
|
265
|
+
| Gap | Duplication evidence | Proposal |
|
|
266
|
+
|---|---|---|
|
|
267
|
+
| HTML-escape helper in JS | identical `_esc` in **8/8** ui.html files | ship in the injected client (`TlcPluginUi.esc`) and declare it |
|
|
268
|
+
| Plugin-scoped fetch | 8/8 re-derive `getConfig('compute_service_url') + '/api/plugins/<id>/...'`; timm/yolo/sam3/image-metrics each wrap it 2–3× with per-plugin timeouts; sam3 hardcodes a `localhost:5020` fallback (`sam3/ui.html:296`) | `PLUGIN_API.pluginFetch(path, opts)` — pre-scoped to the mounted plugin's route root, with a venv-aware default timeout (timm's comment at `ui.html:474-477` documents why 10s is wrong for cold-start workers) |
|
|
269
|
+
| Config CRUD routes | the same `GET/POST /configs`, `GET /configs/{id}`, `POST /configs/{id}/delete` quartet 4× (image_metrics, timm, sam3, yolo), with **three different** dataclass↔dict marshalling answers | `shared.config_routes.make_config_routes(store) -> list[BaseRouteHandler]` — pairs with the graduated `config_store`; also removes yolo's incentive to keep its `ProjectStore` clone (97 lines) |
|
|
270
|
+
| Alias-override lifecycle | identical `apply_alias_overrides → try → finally: restore_aliases` 4×, with **three different** param keys (`_alias_overrides` popped / gotten, `alias_overrides`) | `with alias_overrides(params) as ...:` context manager + one blessed param key |
|
|
271
|
+
| Duration/ETA/log-pane UI | duration formatter 4×, timing-strip renderer 3×, log-pane appender 4×, config-picker-with-localStorage ~100 lines × 3 (only image_metrics uses `_tlcPluginConfig`) | fold into the declared injected client (per A3's namespace) |
|
|
272
|
+
| Table identity | `getattr(table, "project_name", "") or ""` ×6 across timm/yolo/image_metrics | tiny `shared.tables.table_identity(table)` helper (or a `tlc` core ask) |
|
|
273
|
+
| Task detection | `timm/task_detection.py` ≈ `yolo/task_detection.py` (same 60 lines, different tail) | `shared.modality.detect_task(table, mapping)` |
|
|
274
|
+
|
|
275
|
+
- **Effort:** M total; each row is S. All additive.
|
|
276
|
+
|
|
277
|
+
### B4. `PluginJobs` v2 — close the gap that makes GPU plugins hand-roll sockets
|
|
278
|
+
|
|
279
|
+
- **What/where:** all four plugins that need custom events (`timm`, `yolo`, `sam3`, `image_metrics`)
|
|
280
|
+
bypass `PluginJobs` entirely — hand-rolled `io(...)` connections with a copy-pasted options object
|
|
281
|
+
(`timm/ui.html:511-522`, `yolo:330-350`, `image_metrics:614-620`; sam3 connects to the **root**
|
|
282
|
+
namespace with no namespace at all, `sam3/ui.html:792-794`) and hand-rolled `POST .../run` +
|
|
283
|
+
`POST .../jobs/{id}/cancel`. The Python side achieved "a plugin never opens its own SocketIO
|
|
284
|
+
connection" (`job_context.py:97`); the JS side did not.
|
|
285
|
+
- **Proposal:** extend `PluginJobs.run(pluginId, params, handlers)` with `handlers.on = {eventName: fn}`
|
|
286
|
+
(custom `ctx.emit` events on the same managed socket, subscribed before `/run` fires — the ordering
|
|
287
|
+
footgun `PluginJobs.on` currently documents away). Add reconnection options matching what the
|
|
288
|
+
plugins standardized on. Then the four hand-rolled stacks collapse onto the client.
|
|
289
|
+
- Also: stop plugins re-stamping `job_id` into every `ctx.emit` payload (15 occurrences) — the sink
|
|
290
|
+
already `setdefault`s it (`job_context.py:115`); document that payloads arrive with `job_id`.
|
|
291
|
+
- **Effort:** M. Additive.
|
|
292
|
+
|
|
293
|
+
### B5. d.ts truth pass (declare what exists, decide what doesn't)
|
|
294
|
+
|
|
295
|
+
- Add `allowErrorStatus` to `PluginFetchOptions` (implemented at `api-client.js:172-183`, used 13×
|
|
296
|
+
by the host's own pages) — and note `authFetch`'s auth-blocking and Response-destroying error
|
|
297
|
+
behavior (`api-client.js:132-140, 182-194`).
|
|
298
|
+
- Complete `TlcLocationApi` (`rowsSpanLocations`, `shortLabel` — `location.js:38-63`) or explicitly
|
|
299
|
+
tier them out; reconcile ambient `TlcApi` (declares 6 members, implementation exports 20, and the
|
|
300
|
+
d.ts doc-comment references `encodeObjectUrl`, which it never declares).
|
|
301
|
+
- `libs.cytoscape` is **unconditionally `null`** for plugins — the vendor file is loaded only on
|
|
302
|
+
`project_detail.html:15`, never on `plugin_host.html`. It's best-effort tier, so: either load it on
|
|
303
|
+
the plugin page or delete the member at 1.0. Also decide `TlcPptx` (a full deck-builder global on
|
|
304
|
+
the plugin page with no tier at all) — declare best-effort or hide.
|
|
305
|
+
- Declare the fragment-shape contract prose in the guide: `.plugin-hero h2` rewriting
|
|
306
|
+
(`plugin-loader.js:244-254`), `[data-provisioning]` lifecycle (`:267-301`), and `plugin-common.css`
|
|
307
|
+
class stability.
|
|
308
|
+
- **Effort:** S–M. Additive.
|
|
309
|
+
|
|
310
|
+
### B6. Export the job-runner core to de-duplicate host↔worker terminal logic
|
|
311
|
+
|
|
312
|
+
- **What/where:** the terminal-event synthesis (`done/cancelled/error` + message format), GPU-reclaim
|
|
313
|
+
ordering, and the `job_update` reserved-name check exist **twice**: SDK `worker.py:158-171` /
|
|
314
|
+
`job_context.py:108-110` and host `job_manager.py:262-274, 83, 436-442` — byte-similar
|
|
315
|
+
reimplementations that must drift-proof by hand.
|
|
316
|
+
- **Proposal:** export `run_job_and_stream(plugin, job_id, params, state_dir, sink, cancel_event)`
|
|
317
|
+
from the SDK (the `_Job._run` body, made public); host mode calls it too. One implementation of the
|
|
318
|
+
terminal contract.
|
|
319
|
+
- **Effort:** M (host adopts). Additive.
|
|
320
|
+
|
|
321
|
+
### B7. Docs drift batch (everything not already inside A6)
|
|
322
|
+
|
|
323
|
+
- README: base-deps claim (`README.md:46-47` says `3lc` is base), Status section ("0.1 line").
|
|
324
|
+
- `contract.py:8-9` manifest-location claim; delete the orphaned `_ICON_SVG` (`contract.py:32` —
|
|
325
|
+
guide says "no `_ICON_SVG` import anymore", nothing imports it).
|
|
326
|
+
- `__init__.py:32-33` `sdk_version` manifest claim (per A2/A4 decision).
|
|
327
|
+
- Guide: document undocumented-but-real manifest keys (A4 list) in the interim, fix the
|
|
328
|
+
`min_input_count = 0` falsy-default footgun host-side (`manifest.py:210` — explicit 0 is silently
|
|
329
|
+
replaced), correct the isolation-default row until A4 decides it.
|
|
330
|
+
- `frontend/API.md:294-302` stale "add location in the same PR" instruction (already done).
|
|
331
|
+
- **Effort:** S.
|
|
332
|
+
|
|
333
|
+
---
|
|
334
|
+
|
|
335
|
+
## C. Process & CI
|
|
336
|
+
|
|
337
|
+
### C1. Fix the live pins — today, independent of 1.0
|
|
338
|
+
|
|
339
|
+
- Host + plugins umbrella: `3lc-compute-plugin-sdk[shared]>=0.1.0,<0.2.0` excludes 0.2.0
|
|
340
|
+
(`3lc-compute/pyproject.toml:24`, `3lc-compute-plugins/pyproject.toml:23`); resolved lock is 0.1.1,
|
|
341
|
+
so host-mode GPU reclaim, worker `/reclaim`, and `/health` skew-detection are all dead in practice.
|
|
342
|
+
Bump to `>=0.2.0,<0.3.0`, re-lock, verify the contract test.
|
|
343
|
+
- Frontend: decide the production `contractVersion` story (A2's compute-derived value is the fix; the
|
|
344
|
+
dev-group-only SDK dep is fine for the d.ts + test).
|
|
345
|
+
- **Effort:** S. Do this first — it's also the dry run for the 1.0 cross-repo bump.
|
|
346
|
+
|
|
347
|
+
### C2. Mechanical additive-only enforcement (the known wishlist item — confirmed absent)
|
|
348
|
+
|
|
349
|
+
- CI job in this repo that diffs, against the last release tag: `tlc_plugin_sdk.__all__` + public
|
|
350
|
+
module symbols (a simple import-and-introspect script), the d.ts interface member sets (extend the
|
|
351
|
+
frontend's regex parser or use a real TS parser), and — once A4 lands — the manifest schema fields.
|
|
352
|
+
Removals/renames fail unless the version bump is major. This is what makes "additive-only" and the
|
|
353
|
+
post-1.0 freeze a property of the repo instead of a promise in the README.
|
|
354
|
+
- **Effort:** M.
|
|
355
|
+
|
|
356
|
+
### C3. Deepen the frontend conformance test
|
|
357
|
+
|
|
358
|
+
- `frontend/test_plugin_contract.py` diffs **top-level `PluginApi` names only**. Confirmed blind
|
|
359
|
+
spots: nested `libs`/`context` shapes, `getConfig` key set, every referenced interface
|
|
360
|
+
(`TlcData`, `TlcLocationApi`, `TlcApi`, `PluginFetchOptions`, `PluginJobsApi`), signatures, and a
|
|
361
|
+
second `window.PLUGIN_API` assignment. Dropping `libs.io` — the one STABLE lib — would pass today.
|
|
362
|
+
Extend to nested object literals and the `getConfig` key union at minimum; `PluginJobsApi` needs a
|
|
363
|
+
gate on the SDK side (its implementation and declaration live in the same wheel — a unit test
|
|
364
|
+
comparing `JOB_TRACKER_JS`'s exported names to the d.ts is cheap).
|
|
365
|
+
- **Effort:** M.
|
|
366
|
+
|
|
367
|
+
### C4. Test the contract itself in this repo
|
|
368
|
+
|
|
369
|
+
Current suite: 2 files (import-light + GPU reclaim), both good, neither about the contract. Add:
|
|
370
|
+
- `JobContext` event golden-shapes test (each method → exact wire dict; `job_id` stamping; the
|
|
371
|
+
reserved-name rejection) — this is the schema the host, frontend, and plugins all parse.
|
|
372
|
+
- Worker NDJSON protocol test (start → events → terminal; cancel; the `error` message format) —
|
|
373
|
+
doubles as the conformance suite the host can run against its in-process runner (B6).
|
|
374
|
+
- `inject_scripts` edge cases; `config_store` round-trip/migration; manifest schema round-trip
|
|
375
|
+
(post-A4).
|
|
376
|
+
- CI: add a second job with `--extra shared` so mypy actually checks `shared.*` against a real `tlc`
|
|
377
|
+
(today `tlc`/`PIL`/`torch` are `Any` in CI, which is how `on_status: Any` survives --strict).
|
|
378
|
+
- **Effort:** M.
|
|
379
|
+
|
|
380
|
+
### C5. Write the release-train checklist
|
|
381
|
+
|
|
382
|
+
- The 0.2 pin miss (C1) happened because the bump dance spans four repos with no checklist. One page
|
|
383
|
+
in this repo: bump order (SDK tag → host pin+lock → plugins pin → frontend dev pin), the contract
|
|
384
|
+
test loop, and the editable-overlay verification recipe.
|
|
385
|
+
- **Effort:** S.
|
|
386
|
+
|
|
387
|
+
### C6. State the plugin trust model explicitly
|
|
388
|
+
|
|
389
|
+
- Fragments execute with **full host origin** — `innerHTML` + script re-execution
|
|
390
|
+
(`plugin-loader.js:198, 257-261`), no iframe/shadow-DOM/CSP anywhere, `TlcAuth` (raw JWT) reachable.
|
|
391
|
+
With a catalog/install flow shipping (guide's "Publishing via a Catalog"), 1.0 must say out loud:
|
|
392
|
+
*installing a plugin is executing trusted code in the Hub's origin* — in the guide and the catalog
|
|
393
|
+
docs. Actual sandboxing is a large frontend project; the honest documented stance is the 1.0
|
|
394
|
+
deliverable, sandboxing a post-1.0 decision.
|
|
395
|
+
- **Effort:** S (doc) / L (sandboxing, out of scope).
|
|
396
|
+
|
|
397
|
+
---
|
|
398
|
+
|
|
399
|
+
## Suggested one-week sequencing
|
|
400
|
+
|
|
401
|
+
Cross-repo sets are ordered so the contract test + editable overlays keep SDK, host, and frontend
|
|
402
|
+
green at every step. Breaking work lands in two coordinated waves, not a trickle.
|
|
403
|
+
|
|
404
|
+
**Day 1 — make the current state true (no breaks).**
|
|
405
|
+
C1 pin fixes across host + plugins umbrella (re-lock, contract test). B7 + A6 docs/d.ts truth batch.
|
|
406
|
+
B5 d.ts truth pass. Start C2 freeze-check script (it gates the rest of the week).
|
|
407
|
+
|
|
408
|
+
**Day 2–3 — breaking wave 1: the reporting/version core (SDK-led).**
|
|
409
|
+
A1 (`ctx.result`/error field) + A2 (version-axis collapse) + A5 prunes/underscorings + B2
|
|
410
|
+
(de-abstract `compute`) land in the SDK as `1.0.0rc1`. Same-day coordinated PRs: host
|
|
411
|
+
(`job_manager` translation + pin `>=1.0.0rc1`), frontend (error field in the panel,
|
|
412
|
+
compute-derived `contractVersion`), plugins (merger/importer `result` rename; timm/yolo/sam3 adopt
|
|
413
|
+
`ctx.result` + delete `job_failed` hand-rolls). Overlay-verify a GPU job end-to-end.
|
|
414
|
+
|
|
415
|
+
**Day 3–4 — breaking wave 2: the browser surface (frontend-led).**
|
|
416
|
+
A3: frontend implements `PLUGIN_API.pickers.table` + declares CancelJob/toast surface; SDK renames
|
|
417
|
+
`_tlc*` under the declared namespace (aliases kept for the sweep); B1 auto-injection + B4
|
|
418
|
+
`PluginJobs` v2 ship alongside. Then the mechanical plugin sweep (7 plugins × pickers, 5 × `_tlc*`,
|
|
419
|
+
4 × hand-rolled sockets → `PluginJobs`), deleting the four injection idioms and the dead exporter
|
|
420
|
+
injection as you pass through.
|
|
421
|
+
|
|
422
|
+
**Day 4–5 — the manifest + batteries.**
|
|
423
|
+
A4 `tlc_plugin_sdk.manifest` module; host adopts it (the week's biggest single PR — can trail into
|
|
424
|
+
the next week without blocking the tag if rc-gated). B3 config-route factory + alias context manager
|
|
425
|
+
+ `pluginFetch`; delete yolo's store clone and the CRUD quartets in the same sweep.
|
|
426
|
+
|
|
427
|
+
**Day 5 — lock it.**
|
|
428
|
+
C3 contract-test deepening + C4 golden-shape tests written against the *new* surface (cheaper than
|
|
429
|
+
writing them twice). C2 freeze-check turned on against `v1.0.0`. C5 checklist + C6 trust-model
|
|
430
|
+
paragraph. Tag `1.0.0`.
|
|
431
|
+
|
|
432
|
+
**Explicitly deferred (fine post-1.0, all additive):** run picker, sandboxing, `modality`
|
|
433
|
+
graduation into `tlc` core (the porting notes in `shared/images.py:13-37` are the plan of record),
|
|
434
|
+
B6 job-runner unification (safe any time).
|
|
@@ -7,7 +7,57 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
_Nothing yet._
|
|
11
|
+
|
|
12
|
+
## [0.3.2] - 2026-08-31
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
- Data-source browse fields with a comma-separated `accept` (e.g. `*.yaml,*.yml`, `*.pt,*.pth`)
|
|
16
|
+
now list matching files; previously they showed none. `/browse` was matching the whole
|
|
17
|
+
`accept` string as one literal glob pattern instead of any pattern in the list.
|
|
18
|
+
([#28](https://github.com/3lc-ai/3lc-compute-plugin-sdk/pull/28))
|
|
19
|
+
|
|
20
|
+
## [0.3.1] - 2026-08-27
|
|
21
|
+
|
|
22
|
+
A patch on the 0.3 line, additive and pin-compatible (`>=0.3.0,<0.4.0` resolves it). The SDK now
|
|
23
|
+
brings the `3lc` data plane as a base dependency, and the shared data-source browse widget gains an
|
|
24
|
+
input/output picker split.
|
|
25
|
+
|
|
26
|
+
### Added
|
|
27
|
+
- The shared data-source browse widget shows a small caption ("Showing files matching:
|
|
28
|
+
`<glob>`") when a field declares an `accept` glob, so users understand why some files in a
|
|
29
|
+
listed folder don't appear as selectable — folders themselves are never glob-filtered.
|
|
30
|
+
|
|
31
|
+
### Changed
|
|
32
|
+
- **`3lc[pandas]` is now a base dependency.** Installing `3lc-compute-plugin-sdk` brings the 3LC
|
|
33
|
+
data plane, so a plugin pins the SDK alone and gets `tlc` and every `shared.*` helper with it.
|
|
34
|
+
The former `[shared]` extra is kept as an empty, deprecated alias — existing
|
|
35
|
+
`3lc-compute-plugin-sdk[shared]` pins keep resolving — and will be removed at 1.0. Drop the
|
|
36
|
+
suffix when you raise your floor to a release carrying this change. Nothing changes at import
|
|
37
|
+
time: `tlc` is still loaded lazily, only by the helpers that use it.
|
|
38
|
+
- **Docs:** clarified `runtime.provision_extra` in the plugin guide — it names the
|
|
39
|
+
optional-dependency group the host installs into your plugin's venv (keeping the base light for
|
|
40
|
+
discovery and letting one distribution carry several plugins), not an "umbrella-only, required"
|
|
41
|
+
key. Restated that isolation is venv-only: every plugin runs from a host-built managed venv,
|
|
42
|
+
with no in-process/host mode and no `.venv` beside the source. No contract or code change.
|
|
43
|
+
- **Data-source browse widget:** the widget now takes a `purpose` config axis (`'input'`, the
|
|
44
|
+
default, or `'output'`), mirrored by a `purpose` query param on `/browse`. Input pickers now
|
|
45
|
+
**hide** folders the compute process cannot open (rather than showing them disabled), so the
|
|
46
|
+
listing only offers places you can actually descend into. Output pickers show **all** folders
|
|
47
|
+
and instead flag the ones that are not writable — dimmed with a lock glyph and a tooltip, but
|
|
48
|
+
still navigable so you can descend to a writable subdirectory — and disable "Select This
|
|
49
|
+
Folder" when the current directory is not writable. In output mode `/browse` returns a
|
|
50
|
+
`writable` flag on each directory entry and for the listed directory itself (computed only in
|
|
51
|
+
output mode; an older host that omits the flag is treated as writable).
|
|
52
|
+
|
|
53
|
+
### Fixed
|
|
54
|
+
- **Data-source browse widget:** folders the compute process cannot actually open (e.g. macOS
|
|
55
|
+
TCC-protected packages like `Photos Library.photoslibrary`) are now marked inaccessible and
|
|
56
|
+
rendered disabled — dimmed, with a lock glyph and a tooltip — instead of listed as navigable
|
|
57
|
+
and then failing with a red "Permission denied" panel when clicked. The `/browse` route probes
|
|
58
|
+
each directory's openability with `os.scandir` (not `os.access`, which TCC packages spuriously
|
|
59
|
+
pass) and tags directory entries with an `accessible` flag; an older host that omits the flag
|
|
60
|
+
keeps working (entries are treated as accessible).
|
|
11
61
|
|
|
12
62
|
## [0.3.0] - 2026-08-25
|
|
13
63
|
|
|
@@ -18,16 +18,16 @@ in a separate repository and is **not** a dependency of this SDK.
|
|
|
18
18
|
host-side). The SDK is the root of the dependency graph; the host depends on the SDK, never the
|
|
19
19
|
reverse. The whole point — a plugin built against just this wheel runs in its own isolated venv.
|
|
20
20
|
2. **Import-light.** Importing `tlc_plugin_sdk` must not eagerly pull the server stack *or the data
|
|
21
|
-
plane*. `litestar` and `
|
|
22
|
-
|
|
23
|
-
host-only SocketIO server must not be importable from here at all.
|
|
24
|
-
enforces this (guards `litestar`/`socketio`/`uvicorn`/`tlc`) —
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
21
|
+
plane*. `litestar`, `uvicorn`, and `tlc` are all base deps but are imported **lazily** — the
|
|
22
|
+
server stack only when a worker actually serves, `tlc` only inside the `shared.*` helpers that
|
|
23
|
+
use it; and the host-only SocketIO server must not be importable from here at all.
|
|
24
|
+
`tests/test_import_light.py` enforces this (guards `litestar`/`socketio`/`uvicorn`/`tlc`) —
|
|
25
|
+
keep it green. Import-light is about worker startup time, not about what is installed.
|
|
26
|
+
3. **Dependencies stay minimal.** Base = `uvicorn` + `litestar` + `3lc[pandas]`, nothing else.
|
|
27
|
+
`3lc` is deliberately a base dep, not an extra: every real plugin uses the data plane, and making
|
|
28
|
+
it optional only created a way to build a venv that imports fine and fails on first use. (The
|
|
29
|
+
`[shared]` extra it used to live behind is an empty, deprecated alias — gone at 1.0.) Adding a
|
|
30
|
+
base dep widens what every plugin venv must install — justify it.
|
|
31
31
|
4. **The contract is published — every public symbol is forever-ish.** The 0.x line is
|
|
32
32
|
additive-only (see README → Status): additions are the safe move; reshaping
|
|
33
33
|
`JobContext`/`ComputePlugin` waits for a major bump. The version is the contract version
|
|
@@ -50,9 +50,8 @@ and how to port an existing plugin.
|
|
|
50
50
|
|
|
51
51
|
## Dev setup
|
|
52
52
|
|
|
53
|
-
`3lc` resolves from
|
|
54
|
-
|
|
55
|
-
path source.
|
|
53
|
+
`3lc` resolves from public PyPI. To develop against an unreleased `3lc`, override locally
|
|
54
|
+
(uncommitted) with an editable path source in `[tool.uv.sources]`.
|
|
56
55
|
|
|
57
56
|
```bash
|
|
58
57
|
uv sync # installs the SDK + dev tools (ruff, mypy, pytest)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: 3lc-compute-plugin-sdk
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.2
|
|
4
4
|
Summary: Public Python SDK for building 3LC compute-service plugins.
|
|
5
5
|
Project-URL: Homepage, https://github.com/3lc-ai/3lc-compute-plugin-sdk
|
|
6
6
|
Project-URL: Repository, https://github.com/3lc-ai/3lc-compute-plugin-sdk
|
|
@@ -18,10 +18,10 @@ Classifier: Programming Language :: Python :: 3.11
|
|
|
18
18
|
Classifier: Programming Language :: Python :: 3.12
|
|
19
19
|
Classifier: Typing :: Typed
|
|
20
20
|
Requires-Python: >=3.10
|
|
21
|
+
Requires-Dist: 3lc[pandas]<4.0.0,>=3.0.0
|
|
21
22
|
Requires-Dist: litestar<3.0.0,>=2.22.0
|
|
22
23
|
Requires-Dist: uvicorn>=0.34.0
|
|
23
24
|
Provides-Extra: shared
|
|
24
|
-
Requires-Dist: 3lc[pandas]<4.0.0,>=3.0.0; extra == 'shared'
|
|
25
25
|
Description-Content-Type: text/markdown
|
|
26
26
|
|
|
27
27
|
# 3lc-compute-plugin-sdk
|
|
@@ -72,11 +72,12 @@ stance.
|
|
|
72
72
|
|
|
73
73
|
## How it fits
|
|
74
74
|
|
|
75
|
-
This SDK sits at the root of the plugin dependency graph. Its
|
|
76
|
-
`
|
|
77
|
-
|
|
78
|
-
on the compute service itself or on any plugin.
|
|
79
|
-
therefore portable across service versions and
|
|
75
|
+
This SDK sits at the root of the plugin dependency graph. Its dependencies are `uvicorn`,
|
|
76
|
+
`litestar`, and the `3lc` data plane (`3lc[pandas]`) — so installing the SDK is all a plugin needs
|
|
77
|
+
to use `tlc` and the `shared.*` helpers built on it. All three are imported lazily; `import
|
|
78
|
+
tlc_plugin_sdk` stays cheap. It **never** depends on the compute service itself or on any plugin.
|
|
79
|
+
A plugin built against `tlc_plugin_sdk` alone is therefore portable across service versions and
|
|
80
|
+
runs in its own isolated environment.
|
|
80
81
|
|
|
81
82
|
## Status
|
|
82
83
|
|
|
@@ -46,11 +46,12 @@ stance.
|
|
|
46
46
|
|
|
47
47
|
## How it fits
|
|
48
48
|
|
|
49
|
-
This SDK sits at the root of the plugin dependency graph. Its
|
|
50
|
-
`
|
|
51
|
-
|
|
52
|
-
on the compute service itself or on any plugin.
|
|
53
|
-
therefore portable across service versions and
|
|
49
|
+
This SDK sits at the root of the plugin dependency graph. Its dependencies are `uvicorn`,
|
|
50
|
+
`litestar`, and the `3lc` data plane (`3lc[pandas]`) — so installing the SDK is all a plugin needs
|
|
51
|
+
to use `tlc` and the `shared.*` helpers built on it. All three are imported lazily; `import
|
|
52
|
+
tlc_plugin_sdk` stays cheap. It **never** depends on the compute service itself or on any plugin.
|
|
53
|
+
A plugin built against `tlc_plugin_sdk` alone is therefore portable across service versions and
|
|
54
|
+
runs in its own isolated environment.
|
|
54
55
|
|
|
55
56
|
## Status
|
|
56
57
|
|