Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
160 commits
Select commit Hold shift + click to select a range
2b2b82c
chore: open 0.8.0-alpha.1 on develop
dkackman Oct 2, 2026
d546d87
docs(stabilization): dw_mcp assessment and UI scope
dkackman Oct 2, 2026
b290dab
fix(mcp): stage 1 of the dw_mcp pass - fixes and pins
dkackman Oct 2, 2026
12aa920
docs(ui-stabilization): roadmap and Phase 0 plan
dkackman Oct 2, 2026
3122924
docs(ui-stabilization): record Don's rulings - freeze yes, Bits UI ap…
dkackman Oct 2, 2026
812df77
chore(ui): architecture ratchet over ui/src, baseline and CI check
dkackman Oct 2, 2026
e726f70
fix(stabilization): the gate report counts .svelte code lines
dkackman Oct 2, 2026
6400d29
fix(ui): every reference prefix is edited as text; references.ts owns…
dkackman Oct 2, 2026
549e083
fix(ui): the live reference check resolves names as the engine does
dkackman Oct 2, 2026
83412b4
fix(ui): workspace names follow the engine's pattern and length cap
dkackman Oct 2, 2026
c140301
fix(ui): the job page renders outputs by the server's media kinds, au…
dkackman Oct 2, 2026
6a0dbe6
fix(ui): offer every content type the engine writes; keep an unlisted…
dkackman Oct 2, 2026
44e55f2
test(ui): pin the UI's copies of engine vocabularies to their owners
dkackman Oct 2, 2026
a663269
docs: seam-map rows for the UI's owners and its ratchet
dkackman Oct 2, 2026
a6928db
fix(ui): final-review fixes for Phase 0
dkackman Oct 2, 2026
367bcb5
Merge branch 'ui-stabilization/phase-0' into develop
dkackman Oct 2, 2026
5972f8b
docs(ui-stabilization): gate 0 report
dkackman Oct 2, 2026
c1d04d6
docs(ui-stabilization): Phase 1 plan - one owner per rule, dw_mcp sta…
dkackman Oct 2, 2026
8257073
refactor(ui): reference prefixes are spelled only in references.ts
dkackman Oct 2, 2026
8bf04d3
fix(ui): one name-segment rule for workspaces, folders and prompts
dkackman Oct 2, 2026
2497e41
fix(ui): the dtype select keeps a dtype it does not list
dkackman Oct 2, 2026
57200d7
fix(ui): the live reference check and the engine run one case file
dkackman Oct 2, 2026
3dc93d3
test(ui): pin the remaining engine vocabularies; strengthen two weak …
dkackman Oct 2, 2026
d742d8a
refactor(mcp): get_server_info reads the workspace-scoped /api/server…
dkackman Oct 2, 2026
eecdf83
refactor(mcp): the server tolerates null downloads and sends the re-a…
dkackman Oct 2, 2026
8041dd4
feat(server): DELETE /api/jobs/{id}/run; delete_output(job_id=) calls it
dkackman Oct 2, 2026
46dbe9a
feat(server): PATCH a stored workflow atomically; save_workflow patch…
dkackman Oct 2, 2026
f594047
refactor(server): the metadata route reports level findings; dw_mcp s…
dkackman Oct 2, 2026
e52c5c4
refactor(server): one inline-media helper for frame tiles
dkackman Oct 2, 2026
0419ec8
chore(stabilization): raise the modules baseline 165 -> 166 for dw/se…
dkackman Oct 2, 2026
15378c1
feat(server): GET /api/gallery/{name}/image - cropped, fitted, within…
dkackman Oct 2, 2026
c24a2bf
test(server): the image route takes the query token, as an <img> needs
dkackman Oct 2, 2026
7d17e38
feat(server): /frames shrinks tiles together under max_total_bytes
dkackman Oct 2, 2026
cc07951
refactor(mcp): images and frames are fitted on the server; dw_mcp dro…
dkackman Oct 2, 2026
b95fb13
docs: stage 2 routes - release note, server guide, seam-map rows
dkackman Oct 2, 2026
399f06e
refactor(mcp): one confinement module for reads and writes
dkackman Oct 2, 2026
bb14a58
chore(stabilization): raise the modules baseline 166 -> 167 for dw_mc…
dkackman Oct 2, 2026
181a501
refactor(mcp): one home for the base64 size, projection, workflow-sou…
dkackman Oct 2, 2026
98c59b2
docs: seam-map rows for the editor's reference check and name segments
dkackman Oct 2, 2026
d46442e
fix: Phase 1 final-review fixes
dkackman Oct 2, 2026
8ca60a8
Merge branch 'ui-stabilization/phase-1' into develop
dkackman Oct 2, 2026
4d25dd5
fix(server): deleting a job's run twice says the run is already gone
dkackman Oct 2, 2026
59f64f8
Merge branch 'ui-stabilization/phase-1-gate' into develop
dkackman Oct 2, 2026
1ed0555
docs(ui-stabilization): gate 1 report
dkackman Oct 2, 2026
5a06228
docs(ui-stabilization): Phase 2 plan - primitives on Bits UI
dkackman Oct 2, 2026
8267f67
feat(ui): Bits UI behind src/lib/ui; the confirm dialog on AlertDialog
dkackman Oct 2, 2026
b049dc6
feat(ui): the keyboard help on the Modal wrapper
dkackman Oct 2, 2026
0262476
fix(ui): the status and token popovers are non-modal popovers anchore…
dkackman Oct 2, 2026
40f2e6a
refactor(ui): pages ask the overlay layer count; the hand-built focus…
dkackman Oct 2, 2026
8f6d0a8
feat(ui): Suggest replaces every datalist
dkackman Oct 2, 2026
b442bd9
docs: seam-map rows for the UI overlay wrappers and the layer count
dkackman Oct 2, 2026
9b233c7
fix(ui): Suggest keeps typed text on Enter, and takes the same sugges…
dkackman Oct 2, 2026
c3c0cd5
Merge branch 'ui-stabilization/phase-2' into develop
dkackman Oct 2, 2026
fee6ff0
docs(ui-stabilization): gate 2 report; the UI freeze lifts
dkackman Oct 2, 2026
ec9f543
docs(ui-stabilization): Phase 3 plan - structural moves
dkackman Oct 2, 2026
3189063
refactor(ui): drop scoped copies of global rules; one page-head class
dkackman Oct 2, 2026
b07cbd9
refactor(ui): one home for the GB and output-metadata helpers
dkackman Oct 2, 2026
a0e3655
refactor(ui): one polling helper
dkackman Oct 2, 2026
aaf8e2b
refactor(ui): split pipelineDigest and setupMonaco
dkackman Oct 2, 2026
21ff654
refactor(ui): workspace state in its own module; the api cycle is gone
dkackman Oct 2, 2026
6aa91eb
refactor(ui): api.ts has one scoping rule and one request path
dkackman Oct 2, 2026
c2d0b88
refactor(ui): the gallery's detail panel is its own component
dkackman Oct 2, 2026
c481229
refactor(ui): the job page's header is a component
dkackman Oct 2, 2026
6e8cf73
refactor(ui): the job page's progress is a component
dkackman Oct 2, 2026
0f62e5c
refactor(ui): the job page's results are a component
dkackman Oct 2, 2026
a53678a
refactor(ui): the assets page's shadowed entries are a component
dkackman Oct 2, 2026
4e6783e
refactor(ui): the assets page's library header and detail popout are …
dkackman Oct 2, 2026
543ed02
refactor(ui): a step's pipeline options and digest list are components
dkackman Oct 2, 2026
cff19a4
test(ui): pin the prompt editor's behaviour before it moves onto the …
dkackman Oct 2, 2026
f3814a2
refactor(ui): the prompt editor runs on a shared editor shell
dkackman Oct 2, 2026
d467774
refactor(ui): the prompt enhancer is a module and a panel
dkackman Oct 2, 2026
0be164a
refactor(ui): the workflow editor runs on the shared editor shell
dkackman Oct 2, 2026
8035045
refactor(ui): the workflow editor's validation panel and file bar are…
dkackman Oct 2, 2026
7d1710c
test(ui): an add, a move and a remove in the step list reach the JSON
dkackman Oct 2, 2026
f08ae88
refactor(ui): the workflow editor's step list and step modes are thei…
dkackman Oct 2, 2026
95134f1
docs: seam-map rows for the editor shell, UI polling and workspace state
dkackman Oct 2, 2026
4fbd0ea
refactor(ui): drop the job header's copy of the global .withicon
dkackman Oct 2, 2026
5ea428b
test(ui): wait for the gallery's second listing to land before assert…
dkackman Oct 2, 2026
0a0666e
test: the default-workspace twin pin reads the constant where it now …
dkackman Oct 2, 2026
0d9b624
style(ui): prettier on the prompt editor page test
dkackman Oct 2, 2026
329b7d0
Merge ui-stabilization/phase-3: split the UI's oversized pages and ed…
dkackman Oct 2, 2026
36c4628
docs: UI stabilization gate 3 report
dkackman Oct 2, 2026
5b2b88d
docs: UI stabilization Phase 4 plan - response contract and guardrails
dkackman Oct 2, 2026
4fca9f1
feat(server): ApiModel and the strict OpenAPI dump for the UI's respo…
dkackman Oct 2, 2026
17052d7
build(ui): generate response types from the server's OpenAPI document
dkackman Oct 2, 2026
a534492
feat(server): the system routes declare their responses; the UI's sys…
dkackman Oct 2, 2026
7b0accb
fix(server): the OpenAPI dump reads its own checkout and leaves ~ alone
dkackman Oct 2, 2026
c6b51db
Merge ui-stabilization/phase-4a: the UI's response contract, and the …
dkackman Oct 2, 2026
a622a01
feat(server): the job routes declare their responses; the UI's job ty…
dkackman Oct 2, 2026
e2f5402
feat(server): validate declares its answer; the UI's plan types are g…
dkackman Oct 2, 2026
d935deb
fix(server): validate answers for a gated model; runtime never 500s o…
dkackman Oct 2, 2026
9aecaef
Merge ui-stabilization/phase-4b: the job and validation routes under …
dkackman Oct 2, 2026
f713bdd
feat(server): the library routes declare their responses; the UI's li…
dkackman Oct 2, 2026
0226a5f
feat(server): the gallery and asset routes declare their responses; t…
dkackman Oct 2, 2026
f268a49
feat(server): the introspection routes declare their responses; the U…
dkackman Oct 2, 2026
fc1a88e
test: every JSON route api.ts calls declares its response model
dkackman Oct 2, 2026
f35c12d
fix(server): a non-finite parameter default is named, not nulled
dkackman Oct 2, 2026
4a9f037
Merge ui-stabilization/phase-4c: library, gallery, asset and introspe…
dkackman Oct 2, 2026
7842ed2
ci: run e2e before develop moves
dkackman Oct 2, 2026
c2993d4
build(ui): the metrics script reports rises in the engine ratchet's f…
dkackman Oct 2, 2026
93524e6
test(ui): where --live appears is a check; ui/CLAUDE.md points at its…
dkackman Oct 2, 2026
5f32cd9
fix(ui): the metrics script measures what a branch cannot switch off
dkackman Oct 2, 2026
c7a1f05
docs: the --live seam row points at its test; ui/CLAUDE.md gives both…
dkackman Oct 2, 2026
e578d7e
Merge ui-stabilization/phase-4d: e2e before develop moves, the UI rat…
dkackman Oct 2, 2026
2b8e94e
docs: UI stabilization gate 4 report
dkackman Oct 2, 2026
8e1ef40
ci: the response contract is generated under pinned FastAPI and Pydantic
dkackman Oct 2, 2026
cc08caa
test: the response coverage pin compares method and path
dkackman Oct 2, 2026
c8a1128
test: asset, prompt-card and workspace entries pin their exact keys
dkackman Oct 2, 2026
279dc89
ci: e2e runs once per develop push while the release PR is open
dkackman Oct 2, 2026
d556256
docs: Gate 4's deferred minors, as resolved after the gate
dkackman Oct 2, 2026
03d477f
Merge ui-stabilization/minors: Gate 4's deferred minors
dkackman Oct 2, 2026
352cf5b
Refactor code structure for improved readability and maintainability
dkackman Oct 2, 2026
4aa8e29
docs: #579 - upload route answers 201 with 'reference', not 'path'
dkackman Oct 3, 2026
9c708a1
fix(catalog): #580 - unquantized FLUX templates use sequential offloa…
dkackman Oct 3, 2026
924a973
feat(tasks): #499 - reland the H3 latent upscaler with the node's nor…
dkackman Oct 3, 2026
f01ae1f
Merge feat/471-a-h3-latent-upscale-rebuild: #499 H3 latent upscaler, …
dkackman Oct 3, 2026
d983feb
fix(security): GHSA-fwg5-jfjg-fxpf, GHSA-crqf-hw9p-r739 - no absolute…
dkackman Oct 3, 2026
7a0fdd5
fix(events): ensure watchdog measures silence from last event to prev…
dkackman Oct 3, 2026
ab2e4b4
fix(deps): bump ui transitive deps for open Dependabot alerts
dkackman Oct 3, 2026
2666c6f
feat(plugin): #546 - ltx-2.5 names the wait reply's applied and cappe…
dkackman Oct 3, 2026
5d48399
Merge feat/377-1-ltx-wait-fields: #546 ltx-2.5 names the wait reply's…
dkackman Oct 3, 2026
2737c73
Merge pull request #582 from dkackman/fix/dependabot-ui-deps
dkackman Oct 3, 2026
c352d19
Merge pull request #581 from dkackman/fix/ghsa-server-path-disclosure
dkackman Oct 3, 2026
b21b5c2
docs(proposals): #542 - upscale-clip design record
dkackman Oct 3, 2026
3926d43
Merge docs/542-upscale-clip-complete: #542 design record
dkackman Oct 3, 2026
436494c
fix(ui): #573 - render text outputs as text, keep audio/text out of w…
dkackman Oct 3, 2026
f24b440
feat(tasks): #499 - vendored upscaler docstring points at the caller'…
dkackman Oct 3, 2026
8a5136d
Merge feat/471-a-h3-latent-upscale-docfix: #499 arch-review docstring…
dkackman Oct 3, 2026
c1e8455
fix(docs): #584 - the documented H3LatentUpscalePreview declares the …
dkackman Oct 3, 2026
c249c01
docs(proposals): #377 - design record for the long wait_for_job; prop…
dkackman Oct 3, 2026
4c34987
Merge docs/377-long-wait-complete: #377 design record
dkackman Oct 3, 2026
f073a11
docs(plugin): #585 - minimax-h3 skill names the 4-step draft and stac…
dkackman Oct 3, 2026
d44622c
docs(proposals): #471 - design record for the H3 latent upscaler; gui…
dkackman Oct 3, 2026
b2f4714
Merge docs/471-h3-latent-upscale-complete: #471 design record
dkackman Oct 3, 2026
50d2fea
docs(specs): LoRA catalog and opt-in Hub recommender design
dkackman Oct 3, 2026
f305d25
docs(plans): LoRA catalog and recommender implementation plan
dkackman Oct 3, 2026
5577272
feat(library): the loras library kind and its validators
dkackman Oct 3, 2026
c5b1a4b
feat(loras): catalog entry schema, base resolution and exact matching
dkackman Oct 3, 2026
e320cba
fix(loras): restore description in score calculation, fix test fixture
dkackman Oct 3, 2026
08eb896
feat(loras): seed the shipped catalog from the templates and #585
dkackman Oct 3, 2026
1337474
fix(audio): #586 - mix_audio rate-mismatch warning no longer advises …
dkackman Oct 3, 2026
a735e81
feat(server): LoRA catalog routes - list by base or workflow, save, d…
dkackman Oct 3, 2026
5647697
fix(loras): resolve variable references through dw.references
dkackman Oct 3, 2026
ccbc832
feat(loras): Hub candidate search with header-based format check
dkackman Oct 3, 2026
b17577b
feat(server): GET /api/loras/recommend - catalog first, then Hub cand…
dkackman Oct 3, 2026
7a7b1f4
fix(audio): #587 - find_loop_bed no_loop_bed names the dominant in-sh…
dkackman Oct 3, 2026
4e663a1
test(audio): #587 - drop a brittle count comparison
dkackman Oct 3, 2026
409909f
fix(security): Request amplification and thread stacking in GET /api/…
dkackman Oct 3, 2026
3fba424
fix(security): Prevent lock deadlock if thread creation fails
dkackman Oct 3, 2026
543d38c
feat(mcp): list_loras, save_lora and the opt-in recommend_loras
dkackman Oct 3, 2026
5eb2a32
docs(loras): catalog and Hub-candidate guide, MCP table, architecture…
dkackman Oct 3, 2026
d39783a
chore(arch): raise the modules baseline 171 -> 175 for the LoRA catalog
dkackman Oct 3, 2026
70af2af
fix(loras): turbo keyframe entries cover t2va and fl2va; workflow acc…
dkackman Oct 3, 2026
a374e3d
fix(loras): warn on a pre-existing loras workspace, 404 a corrupt ent…
dkackman Oct 3, 2026
a9e1e72
fix(loras): kohya lora_down/up classification, cap Hub card text, doc…
dkackman Oct 3, 2026
780f1ce
Merge feat/lora-catalog: LoRA catalog, list/save tools and opt-in Hub…
dkackman Oct 3, 2026
d7ced91
Merge origin/develop into develop
dkackman Oct 3, 2026
f3f64ee
Refactor code for improved readability and consistency
dkackman Oct 3, 2026
a81086f
chore(releasing): update release notes for version 0.8.0 with new fea…
dkackman Oct 3, 2026
1c2eb7c
fix(security): hub_error names the exception type, never its text
dkackman Oct 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions .github/codeql/dw-security/DwPathSanitizers.qll
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ private predicate pathValidatorName(string name) {
name =
[
"validate_path", "validate_workflow_path", "validate_output_path",
"validate_prompt_path", "safe_join_path", "validate_media_path"
"validate_prompt_path", "validate_lora_path", "safe_join_path",
"validate_media_path"
]
}

Expand All @@ -56,7 +57,8 @@ private predicate pathValidatorName(string name) {
private predicate nameValidatorName(string name) {
name =
[
"validate_workspace_name", "validate_prompt_reference", "validate_asset_reference",
"validate_workspace_name", "validate_prompt_reference", "validate_lora_name",
"validate_asset_reference",
"validate_output_reference", "validate_variable_name", "validate_commit_hash"
]
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ extensions:
- ["dw.security", "Member[validate_workflow_path].ReturnValue", "path-injection"]
- ["dw.security", "Member[validate_output_path].ReturnValue", "path-injection"]
- ["dw.security", "Member[validate_prompt_path].ReturnValue", "path-injection"]
- ["dw.security", "Member[validate_lora_path].ReturnValue", "path-injection"]

# The name validators are regex whitelists that raise InvalidInputError:
# a workspace name is one path segment (^[\w][\w.-]*\Z - no separator,
Expand All @@ -20,4 +21,5 @@ extensions:
- ["dw.security", "Member[validate_asset_reference].ReturnValue", "path-injection"]
- ["dw.security", "Member[validate_output_reference].ReturnValue", "path-injection"]
- ["dw.security", "Member[validate_prompt_reference].ReturnValue", "path-injection"]
- ["dw.security", "Member[validate_lora_name].ReturnValue", "path-injection"]
- ["dw.security", "Member[validate_variable_name].ReturnValue", "path-injection"]
30 changes: 23 additions & 7 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ jobs:
# goes on afterwards so the resolver doesn't replace it with a
# release
run: |
pip install -r requirements.txt -r requirements-test.txt
pip install -c constraints-openapi.txt -r requirements.txt -r requirements-test.txt
pip install git+https://github.com/huggingface/diffusers
- name: Format check
run: ruff format --check dw dw_mcp tests scripts
Expand All @@ -68,10 +68,19 @@ jobs:
cache: npm
cache-dependency-path: ui/package-lock.json
- run: npm ci
# The UI's response types are generated from the server's OpenAPI
# document (scripts/dump_openapi.py writes it; the backend job's
# tests/test_api_contract.py fails when it is stale). A stale
# api-schema.ts fails here; a field the UI reads that the server no
# longer sends fails the type check below
- name: Response contract
run: npm run gen:api && git diff --exit-code src/lib/generated
- name: Format check
run: npx prettier --check src e2e *.ts *.js
run: npx prettier --check src e2e scripts *.ts *.js
- name: Lint
run: npm run lint
- name: Architecture ratchet
run: npm run metrics -- --check ../docs/stabilization/ui/baseline.json
- name: Type check
run: npm run check
- name: Unit tests
Expand All @@ -80,10 +89,17 @@ jobs:
run: npm run build

e2e:
# Playwright against a real dw.serve, on PRs into master only - the
# release PR is where a stale spec has to be caught, and the job carries
# the backend install as well as the browser
if: github.event_name == 'pull_request' && github.base_ref == 'master'
# Playwright against a real dw.serve before develop moves - on PRs into
# develop or master, and on pushes to develop, since the agent loop
# pushes develop directly with no PR. The release PR (this repo's
# develop into master) is skipped: its every commit is a develop push,
# already run. The job carries the backend install as well as the browser
if: >-
(github.event_name == 'pull_request' &&
(github.base_ref == 'develop' || github.base_ref == 'master') &&
!(github.head_ref == 'develop' &&
github.event.pull_request.head.repo.full_name == github.repository)) ||
(github.event_name == 'push' && github.ref == 'refs/heads/develop')
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
Expand All @@ -100,7 +116,7 @@ jobs:
run: pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cpu
- name: Install dependencies
run: |
pip install -r requirements.txt -r requirements-test.txt
pip install -c constraints-openapi.txt -r requirements.txt -r requirements-test.txt
pip install git+https://github.com/huggingface/diffusers
- uses: actions/setup-node@v7
with:
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ once; without it the run fails partway through with a 401/403 from the Hub.

## Drive it from an agent

Then just ask. The agent has 59 tools covering the whole surface — the
Then just ask. The agent has 62 tools covering the whole surface — the
workflow catalog, the real diffusers pipeline signatures, the job queue, the
gallery, the model cache:

Expand Down
8 changes: 8 additions & 0 deletions constraints-openapi.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# The FastAPI and Pydantic that ui/src/lib/generated/openapi.json is
# generated under. Either one's release can change the document with no
# change here, so CI installs these exactly (pip -c) and
# scripts/dump_openapi.py refuses to write under anything else. To move
# them: bump both lines, install them, run the dump and `npm run gen:api`,
# and commit the four files together.
fastapi==0.142.2
pydantic==2.13.5
24 changes: 23 additions & 1 deletion docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ one to open.
| Reference name shape | `dw/reference_names.py`: `reference_name_errors` | The shape of every `asset:`, `prompt:` and `output:` name is checked before the queue, and existence is checked later. | `tests/test_reference_names.py::TestTheValidationPass::test_a_malformed_reference_is_refused_before_the_queue` |
| Library reads | `dw/library.py`: `LibraryPath` | Reads go through the roots front to back, so an earlier name shadows a later one. | `tests/test_library_path.py::TestResolution::test_the_front_of_the_path_shadows_the_rest` |
| Library writes | `dw/library.py`: `LibraryPath` | Writes go only to the front root, so saving something opened from a read-only root writes a copy. | `tests/test_library_path.py::TestConstruction::test_the_writable_root_comes_first`, `tests/test_library_sources.py::TestServer::test_saving_an_example_prompt_writes_a_copy` |
| LoRA catalog matching | `dw/lora_catalog.py`: `matches`, `workflow_bases` | An entry fits a base only by exact repo id (and H3 partition); no alias or family match. | `tests/test_lora_catalog.py::TestMatching::test_the_base_must_match_exactly` |
| Hub search | `dw/lora_hub.py`: `search_hub` | The Hub is searched only by `GET /api/loras/recommend`, never raises, and offers no pickle-only repo. | `tests/test_lora_hub.py::TestCandidates::test_a_pickle_only_repo_is_dropped`, `tests/test_lora_hub.py::TestFailure::test_a_hub_error_is_returned_not_raised` |
| Packaged builtins | `dw/library.py`: `builtin_root`, `resolve_sub_workflow_reference` | `builtin:` names the packaged `dw/workflows/`, not the top-level `workflows/` examples. | `tests/test_sub_workflow_resolver.py::TestTheResolver::test_a_builtin_resolves_in_the_packaged_root` |
| Sub-workflow paths | `dw/library.py`: `resolve_sub_workflow_reference`, `resolve_sub_workflow` | A sub-workflow path is confined to the root it resolves in. The search order is in the docstring of `resolve_sub_workflow`. | `tests/test_sub_workflow_resolver.py::TestTheResolver::test_a_path_escaping_its_root_is_refused` |
| Workspace | `dw/workspace.py`: `resolve_workspace`, `set_workspace` | `--workspace` beats `DW_WORKSPACE`, which beats the setting, which beats a working directory that looks like a workspace, which beats `~/diffusers-workspace`. | `tests/test_workspace.py::TestResolution::test_a_flag_wins_over_everything`, `tests/test_workspace.py::TestResolution::test_a_bare_working_directory_falls_back_to_the_home_workspace` |
Expand Down Expand Up @@ -99,6 +101,7 @@ one to open.
| Worker protocol | `dw/worker_protocol.py`: `parse_reply` | Every command and reply is a frozen dataclass that travels as a wire dict. | `tests/test_worker_messages.py::test_from_wire_inverts_to_wire`, `tests/test_worker_messages.py::test_an_unknown_reply_type_is_kept_whole_rather_than_raised` |
| Persistent worker | `dw/worker.py`, `dw/worker_manager.py`, `dw/serve.py` | Jobs run in one spawned worker that keeps models loaded, so a change to engine code needs a server restart. | — |
| Failed-run reporting | `dw/worker.py`, `dw/worker_protocol.py`: `Failed`, `Cancelled` | A failed or cancelled run's reply still carries the manifest of the steps that ran. | `tests/test_worker_execute.py::test_failure_carries_the_manifest_of_the_steps_that_ran`, `tests/test_worker_execute.py::test_cancellation_carries_the_manifest_too` |
| Failure path redaction | `dw/path_redaction.py`: `redact_paths`, called by `dw/worker.py` | A failed run's message and traceback name a file under the job's asset roots or output directory by its `asset:`/`output:` reference, never its absolute server path. | `tests/test_worker_execute.py::test_a_failure_names_an_asset_by_reference_not_by_server_path`, `tests/test_path_redaction.py` |
| Run-time warnings | `dw/events.py`: `emit_warning` | A warning found at run time is emitted as an event, so it reaches the caller and not just the log. | `tests/test_concat_videos.py::TestWarningsReachTheCaller::test_the_level_spread_warning_is_emitted_as_an_event` |

## Media and DSP
Expand Down Expand Up @@ -140,11 +143,30 @@ one to open.
| `dw_mcp` stays torch-free | `dw_mcp/` | `dw_mcp` reaches `dw.serve` over HTTP and imports no `dw` module, because `dw/__init__.py` pulls in torch. | `tests/test_mcp_server.py::TestStartupWeight::test_the_server_starts_without_importing_the_engine` |
| Tool surface | `dw_mcp/server.py`, `dw_mcp/tools_*.py` | Only these modules import the MCP SDK, and each tool body is a one-line call into a handler. | `tests/test_mcp_server.py::test_the_wiring_table_covers_every_registered_tool`, `tests/test_mcp_server.py::test_the_stated_tool_count_is_the_registered_one` |
| Surface text budget | the tool docstrings in `dw_mcp/tools_*.py`, the instructions in `dw_mcp/server.py` | The instructions and each tool description stay at or under 2,048 characters, and the whole surface stays within its token budget. | `tests/test_mcp_server.py`: `SURFACE_BUDGET`, `CLIENT_TEXT_LIMIT`; `tests/test_mcp_server.py::test_no_text_the_agent_reads_is_cut_off_by_the_client`, `tests/test_mcp_server.py::test_the_tool_surface_fits_the_budget` |
| Spending needs consent | `dw_mcp/diagnose.py` | `run_workflow` and `rerun_job` refuse until `acknowledged_cost` is set. | `tests/test_mcp_diagnose.py::test_run_refuses_without_an_acknowledged_cost`, `tests/test_mcp_diagnose.py::test_rerun_refuses_without_an_acknowledged_cost` |
| Spending needs consent | each handler behind a tool that takes `acknowledged_cost` (`dw_mcp/diagnose.py`, `models.py`, `prompts.py`, `workspaces.py`) | Every tool that takes `acknowledged_cost` refuses until it is set; `delete_workspace`'s refusal is the server's 409. | `tests/test_mcp_twins.py::test_every_tool_that_takes_acknowledged_cost_refuses_without_it` |
| Copies of engine rules | the constants and word lists in `dw_mcp/` and `dw/run.py` | `dw_mcp` cannot import its owners, so each copy equals its owner; a copy that needs no twin is deleted, not pinned. | `tests/test_mcp_twins.py` |
| The CLI is a client of the server | `dw/run.py` | `dw.run` queues on `dw.serve` through `dw_mcp.client`, so `dw` imports `dw_mcp` and never the reverse. | `tests/test_mcp_server.py::TestStartupWeight::test_the_server_starts_without_importing_the_engine`, `tests/test_mcp_twins.py` |
| Inline images | `dw/server/inline_media.py` | Fitting an image to a longest side and halving it under a base64 budget happens on the server, for `/image` and `/frames` alike; `dw_mcp` forwards the size and budget and imports no Pillow. | `tests/test_server_gallery_image.py`, `tests/test_mcp_server.py::TestStartupWeight::test_the_server_starts_without_importing_the_engine` |
| Workflow patch | `dw/library.py`: `merge_patch`; `dw/server/routes/library.py`: `patch_workflow` | A merge patch is applied on the server under the lock `PUT` takes; no client reads, merges and writes back. | `tests/test_server_library_path.py::TestPatchWorkflow::test_patch_merges_onto_the_stored_definition` |
| Deleting a job's run | `dw/server/jobs.py`: `JobManager.run_location`; `dw/server/outputs.py`: `delete_run_directory` | The server resolves a job's run directory against the root the job ran in, and refuses a job still queued or running. | `tests/test_server_jobs.py::TestDeleteAJobsRun::test_a_running_job_is_409` |
| Level findings | `dw/server/assess.py`: `level_findings` | Gallery metadata reports level problems from `dw/audio_qc.py`'s thresholds; no client restates a threshold. | `tests/test_server_assess.py::TestMetadataLevelFindings::test_level_findings_read_audio_qc_thresholds` |
| API errors | `dw_mcp/client.py` | An API failure becomes a message a person can act on, here and nowhere else. | `tests/test_mcp_client.py::test_a_400_surfaces_the_servers_detail_verbatim` |

## UI

| Concept | Owner | Rule | Enforced by |
| --- | --- | --- | --- |
| The UI reads engine fields | `ui/src/lib/plan.ts`: `describePlan`, `ui/src/lib/results.ts`: `sectionBySubfolder` | The UI reads `plan`, `version` and `subfolder` as fields the server sends and derives nothing of its own, and it never sends `acknowledged_cost`. | `ui/src/lib/plan.test.ts`, `ui/src/lib/results.test.ts` |
| Reference prefixes in the UI | `ui/src/lib/references.ts` | The only module in `ui/src` that spells a reference prefix; each equals `dw/references.py`'s. | `tests/test_ui_twins.py::test_the_ui_spells_every_reference_prefix_the_engine_does`; `prefix_literals` in `ui/scripts/arch-metrics.mjs` |
| Output kinds on the job page | `dw/server/outputs.py`: `MEDIA_KINDS`, `output_kinds` | The page renders an output by the `output_kinds` the job detail carries, never by its extension. | `tests/test_server.py::test_a_job_reports_each_output_files_media_kind`, `ui/src/lib/pages/JobPage.test.ts` |
| The editor's live reference check | `ui/src/lib/flow.ts`: `danglingReferenceDetails`, owned by `dw/previous_results.py` and `dw/for_each.py` | The editor warns as the author types, so it keeps a copy of the reference rules; one case file is run through both, the engine deciding each case. | `tests/test_ui_twins.py::test_the_engine_decides_each_shared_reference_case`, `ui/src/lib/flow.test.ts` |
| Name segments in the UI | `ui/src/lib/names.ts`: `isNameSegment` | Workspaces, folders and stored names follow `dw/security.py`'s `WORKSPACE_NAME_PATTERN` and length cap, counted in code points. | `tests/test_ui_twins.py::test_the_engine_decides_each_shared_workspace_name_case`, `ui/src/lib/names.test.ts` |
| UI copies of engine rules | the constants `tests/test_ui_twins.py` reads | A list the UI must hold equals its owner, read from the TS source; a rule tested on both sides reads one case file in `tests/fixtures/`. | `tests/test_ui_twins.py` |
| UI overlays and suggestions | `ui/src/lib/ui/`: `ConfirmDialog`, `Modal`, `Popover`, `Suggest` | Bits UI is imported only here; pages and components use the wrappers, which own focus, Escape, outside presses and scroll lock. Overlay styles and the stacking scale (`--layer-*`) live in `ui/src/app.css`. | `ui/eslint.config.js` (`no-restricted-imports`), `ui/src/lib/ui/*.test.ts`, `ui/e2e/chrome.spec.ts` |
| Escape on a page under an overlay | `ui/src/lib/ui/layers.svelte.ts`: `holdLayer`, `overlayOpen` | Every open overlay holds a layer; a page's own Escape handling acts only when none is open. | `ui/src/lib/pages/GalleryPage.test.ts` (an Escape that closes a confirm leaves the selection) |
| The editors' document | `ui/src/lib/editorShell.svelte.ts`: `DocumentEditor` | The workflow and prompt editors keep one rule each for the view (remembered per editor), the JSON draft (a failed parse pins it), dirty against the last load or save, the save path, and the tab-close guard. | `ui/src/lib/editorShell.test.ts`, `ui/src/lib/pages/PromptEditorPage.test.ts`, `ui/src/lib/pages/EditorPage.test.ts` |
| UI polling | `ui/src/lib/poll.ts`: `poll`, `sleep` | A page that refreshes on a timer starts it through `poll` and stops it with the function `poll` returns. | `ui/src/lib/poll.test.ts` |
| Workspace state in the UI | `ui/src/lib/workspaceState.svelte.ts`: `workspace` | The current workspace is one `$state`; `api.ts` reads it from here, so the request layer imports no page state. | `import_cycles` in `ui/scripts/arch-metrics.mjs` |
| The UI's response contract | `dw/server/api_models.py`: `ApiModel`, `send_rejected_responses`; `scripts/dump_openapi.py` | Every JSON route `api.ts` calls declares a response model, and `types.ts` re-exports the types generated from it (`ui/src/lib/generated/`). The payload does not change: an absent key stays absent (`response_model_exclude_unset`), a sometimes-sent key is `sometimes()`. Runtime is lenient - an undeclared key passes and a rejected response is logged and sent as built; tests, the e2e server and the dump run strict (`DW_STRICT_RESPONSES=1`). The document is generated under the FastAPI and Pydantic `constraints-openapi.txt` pins: CI installs them, and the dump refuses to write under others. | `tests/test_api_contract.py`, `tests/test_ui_twins.py::test_every_json_route_the_ui_calls_declares_its_response`, CI's "Response contract" step |
| Where `--live` appears | `ui/src/app.css` (its header) | The state colour marks machine state only, in the files `ui/scripts/design-rules.test.ts` lists. | `ui/scripts/design-rules.test.ts` |
| UI architecture ratchet | `ui/scripts/arch-metrics.mjs` | No metric rises above `docs/stabilization/ui/baseline.json`. | the `ui` job in `.github/workflows/ci.yml`; `npm run preflight` |
Loading
Loading