Skip to content

feat(media): plugin-declared model capabilities drive the console - #13

Merged
nextroad-dev merged 3 commits into
mainfrom
feat/video-generation-admin-settings
Sep 20, 2026
Merged

nextroad-dev merged 3 commits into
mainfrom
feat/video-generation-admin-settings

Conversation

@nextroad-dev

Copy link
Copy Markdown
Owner

Summary

  • Media model capabilities are now declared by the provider plugins themselves and flow through GET /api/models as parameter descriptors; the console renders, validates and submits from that metadata with no provider/model special-casing anywhere in apps/web-next.
  • One validator in @musecanvas/contracts is shared by the browser, the API, the domain normalizer and the plugin adapters, so a rendered control and an accepted value are the same set by construction. Adds image-size presets + custom-size bands, dependsOn, cross-field rules and capability flags; extends the existing ParameterDescriptor union rather than creating a parallel type.
  • Closes three real defects: the console offered 1024x768/768x1024, which no configured model accepts; route.ts validated image size/quality as {type:'text',maxLength:32} so 9999x9999 reached the vendor and failed there; and quality had no UI at all (its setter had zero call sites).
  • apps/web-next no longer hand-copies the descriptor types from contracts, and publicModelDto is now contract-typed, so frontend/backend drift is a compile error instead of a silent cast.
  • Bundles in-flight work from concurrent sessions (region-select image editing, the plugin-upload catalog, and the route.ts -> modules/router split). Those hunks are interleaved inside shared files and the tree does not compile without their untracked modules.

Test plan

  • pnpm typecheck — 0 errors repo-wide
  • pnpm test — 429 passing, 0 failing
  • tests/integration/*.test.ts — 23 passing, 0 failing (CI step changed from a single filename to a glob so these actually run)
  • next build — succeeds, /generate 22.7 kB
  • Browser parameter engine exercised directly against the real plugin manifests: 24 assertions covering model-switch reconciliation, custom-size rejection, transparent+jpeg, compression visibility, and undeclared-model refusal
  • Untracked-set secret scan before committing; .env confirmed ignored
  • L3 deep security review on the committed change — no findings
  • Migration against a real Postgres — migrate.ts section 10b has never been executed; verify before deploying
  • Live provider calls — no vendor requests were made; the capability matrix was taken from the product spec because OpenAI's docs return 403 from this environment

Notes

  • Behaviour change to surface in release notes: 1280x720 on Seedream 4.5 previously passed the API and failed at the plugin; it is now rejected at submit with a named-parameter business error.
  • dall-e-3 is marked deprecated and sorts last; gpt-image-1/gpt-image-1-mini are deliberately absent because no authoritative parameter table could be confirmed.

nextroad-dev and others added 3 commits September 20, 2026 07:55
packages/database/src/migrate.ts had grown into one 1143-line script that
mixes the original CREATE TABLE baseline with twenty-one feature eras of
incremental ALTERs, data backfills and destructive drops, and re-executes
all of it on every deploy. Nothing could tell an operator which parts had
actually been applied, and nothing guarded a future edit from losing a
statement.

This commit adds the sequential migration set and its runner without
switching anything over: migrate.ts still runs exactly as before, so the
change is inert.

- migrations/0001..0026.sql reproduce the historical statement stream
  verbatim and in order. Cuts never separate an unguarded DROP from the
  ADD that depends on it, and the ALTER TYPE ADD VALUE stays in its own
  committed transaction because a later CHECK uses the new enum value.
- src/migrations/loader.ts normalizes line endings before hashing, so the
  CRLF working tree and the LF container agree on a checksum.
- src/migrations/runner.ts holds one session advisory lock for the whole
  run and applies each file in its own transaction.
- migrations.test.ts proves the split is lossless against a frozen golden
  master of the old statement stream, with no database required, so a
  dropped statement fails CI in milliseconds.
… from them

Media parameters were invented by the host instead of by the plugins. The API
synthesized size/quality descriptors from flat model_configs columns, route.ts
validated image requests against a `{type:'text',maxLength:32}` stub, and the
console hardcoded five size options -- two of which no configured model accepts.
`size:"9999x9999"` therefore passed the boundary, queued a job, and failed at the
vendor, so users saw a dead task instead of a form error naming the field.

Provider manifests are now the single source of truth. Each media model declares
its parameter descriptors, geometry limits, dependencies and edit capabilities,
and one validator in @musecanvas/contracts is shared by the browser, the API, the
domain normalizer and the plugin adapters -- so a rendered control and an
acceptable value are the same set by construction rather than three lists that
can disagree.

- image-size descriptors carry presets plus a custom-size band; the picker
  validates live and blocks submit before a 400 comes back
- dependsOn and cross-field rules hide output_compression for lossless formats
  and reject background=transparent with jpeg, in the API and adapter too
- model switches reconcile to the new contract instead of sending stale values
- retries re-validate against the revision the job is pinned to
- a model with no declared contract is refused, never guessed at
- migrate.ts appends manifest-declared revisions; historical jobs keep rendering
  the parameters they were created with

apps/web-next also stopped hand-copying the descriptor types from contracts, and
CI's integration step now takes a glob instead of one filename, so new tests under
tests/integration/ actually run.

This commit bundles in-flight work from concurrent sessions (region-select image
editing, the plugin-upload catalog, and the route.ts -> modules/router split).
Their hunks are interleaved with these changes inside shared files and this tree
does not compile without their untracked modules, so the threads could not be
separated; that work is not verified by me beyond the suite passing as a whole.

Co-Authored-By: Qoder <noreply@qoder.com>
…eout

`importBundle` unrefs its timeout on purpose, so a bundle that never settles
leaves the test file holding no pending handle at all: the never-resolving import
promise owns none and the unref'd timer refuses to own one. The loop drains, Node
exits, and every in-flight test is reported as "Promise resolution is still
pending but the event loop has already resolved".

libuv kept lingering fs handles alive on Windows, which masked it; on Linux CI
four loader tests were cancelled by the parent process exiting. A ref'd interval
cleared in `after` reproduces the invariant the real worker has -- something
always owns the loop -- without weakening the production unref that stops a
wedged plugin from holding the process open.
@nextroad-dev
nextroad-dev merged commit 676cdc1 into main Sep 20, 2026
6 checks passed
@nextroad-dev
nextroad-dev deleted the feat/video-generation-admin-settings branch September 21, 2026 01:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant