Draw, type, or upload a signature and place resizable/rotatable copies across PDF pages; output flattened server-side with PyMuPDF. Visual electronic signature, not cryptographic. New interactive-sign display mode (pdf.js + Konva) and a custom docs-pool route.
PR #364 only changed en/nl; the appDescription and rotating-phrase tool count was still '240' (translated) in the other 19 locales (ar, de, es, fr, hi, id, it, ja, ko, pl, pt-BR, ru, sv, th, tr, uk, vi, zh-CN, zh-TW). Replaces the number in each; surrounding translations untouched. My earlier English-only grep missed these.
* fix(landing): correct PDF tool count to 28 in alternatives copy
The pdf section has 28 tools (section.test.ts asserts bySection('pdf')=28). PR #363 corrected the docs breakdown but the alternatives pages still said 40 PDF tools (the document-modality count, not the pdf section) with 200 for the rest. Update to 28 PDF tools and 212 for the non-PDF remainder.
* chore: use "200+ tools" for the tool-count claim across public surfaces
Replaces the exact '240 tools' count (which drifts as tools are added) with the stable '200+ tools' on README, the Docker Hub overview, landing pages, the docs site (meta, homepage, search), the API self-description, the demo OG tag, llms.txt, package.json, the branding readme, and the en/nl app strings. Per-modality breakdown tables stay exact. Leaves the architecture doc's technical 'tool routes' figure, an internal vitest comment, and a QA report line unchanged. Updates the two tests that assert the docs strings.
Lands five integrated branches: pipeline templates (#355), analytics opt-out (#354), 83 conversion presets bringing the catalog to 240 tools (#356), self-hosted positioning (#353), and e2e modernization (#351).
Integration fixes: aligned stale web analytics tests with the opt-out/allow-list model, closed 3 CodeQL incomplete-sanitization alerts in the i18n generator, resolved settings/index/docs/format-matrix conflicts, and corrected tool counts to 240.
* fix(enterprise): ship enterprise pkg in prod image, full license features, tracing key fallback
docker/Dockerfile: COPY packages/enterprise manifest+src into the production stage.
Without it, apps/api's workspace link to @snapotter/enterprise dangles and every
import() throws (silently caught), so all 19 enterprise features failed closed
(enterprise.active=false) regardless of a valid license.
scripts/generate-license.mjs: sync PLAN_FEATURES with packages/enterprise/src/license.ts
so a --plan enterprise license unlocks all 19 features (was 8) and team unlocks 8.
apps/api/src/tracing.ts: accept SNAPOTTER_LICENSE_KEY as a fallback to LICENSE_KEY so
distributed_tracing activates with the same key as the rest of the app.
* fix(docker): keep scripts/bake-analytics.mjs in build context
.dockerignore excluded the whole scripts/ dir (PR #82, V1 hardening), but
docker/Dockerfile later added 'COPY scripts/bake-analytics.mjs' for the analytics
bake step. A clean production image build therefore fails with
'scripts/bake-analytics.mjs: not found'. The published image build is gated off in
CI so this latent break went unnoticed. Exclude scripts/* but re-include the one
file the Dockerfile needs.
* fix: S3 upload stream, analytics bake reaches API, dedupe retention field, reconcile orphan jobs
storage-s3.ts: wrap the upload AsyncIterable in Readable.from() so @aws-sdk/lib-storage
accepts it. STORAGE_MODE=s3 file uploads failed with 'Body Data is unsupported format'
for every tool because a bare async generator is not a Readable.
docker/Dockerfile: COPY the builder-baked analytics baked.ts into the API runtime stage.
The API re-copied the committed (off) baked.ts from the build context, so the
SNAPOTTER_ANALYTICS build arg had no effect on the API -- and since the SPA reads
/api/v1/config/analytics, analytics was off everywhere regardless of the arg.
settings-dialog.tsx: remove the duplicate tempFileMaxAgeHours control under Data
Retention; it bound the same setting key as the File Management control with a different
default, so editing either silently overwrote the other.
apps/api/src/index.ts: reconcile orphaned job rows (empty tool_id, never enqueued to
BullMQ) at boot so they don't sit in processing/queued forever and inflate the per-user
concurrent-job count and the upgrade-check in-flight gate.
* fix(web): style the SSO login buttons (they referenced undefined theme tokens)
The OIDC/SAML 'Sign in with <provider>' buttons used bg-secondary /
text-secondary-foreground, which the web theme never defines (it has primary,
background, foreground, muted, border, card, primary-subtle). Those classes resolved
to nothing, so the buttons rendered as bare unstyled text on the login page.
Restyle: the optional (non-enforced) buttons become white-card outline buttons with a
key icon and an orange hover tint, secondary to the primary Login button; the
SSO-enforced buttons become solid primary with the icon.
* fix: gate S3 behind license, custom-role enterprise perms, wire retention UI, cleanup
S3 is a licensed feature, but shipping packages/enterprise in every image removed the
implicit gate, so STORAGE_MODE=s3 worked without a license. Enforce
isFeatureEnabled('s3_storage') at boot and fail fast if unlicensed.
Custom roles can now be granted security:manage / compliance:manage / webhooks:manage
(roles.ts ALL_PERMISSIONS + the Roles UI) so admins can build least-privilege
compliance/security roles instead of only the built-in admin role.
retentionSweep now reads the jobsRetentionDays / auditRetentionDays DB settings the
System Settings UI writes (env vars become the fallback default), mirroring how the
temp-file sweep reads tempFileMaxAgeHours. Previously those two UI controls were no-ops.
Cleanup: drop the never-set snapotter_storage_bytes gauge and the unused
MAX_WORKSPACE_SIZE_GB env var; emit tool_client_error to PostHog from the web
ErrorBoundary (client crashes were not reaching analytics); add the Python
OpenTelemetry packages so the innermost sidecar.<script> span exports; fix the stale
'only local storage' line in the docs; delete two e2e-analytics specs that tested the
removed consent UI.
* fix(env): restore MAX_WORKSPACE_SIZE_GB default
security-auth-hardening.test.ts asserts env.MAX_WORKSPACE_SIZE_GB defaults to 10, so
the var is an intentional (tested) default, not dead code. Removing it in the cleanup
commit broke that unit test. Keep the declaration.
* fix(docker): pin CUDA base to 12.6 so the GPU image starts on R560+ drivers
The amd64 base nvidia/cuda:12.9.2-cudnn-runtime bakes a cuda>=12.9 driver gate enforced by nvidia-container-toolkit at container start, so the image fails to launch on common production drivers (e.g. 570.x / CUDA 12.8). The AI bundles are all cu126 wheels and the image installs libcublas-12-6, so 12.9 was misaligned with the workload. Pin to nvidia/cuda:12.6.3-cudnn-runtime-ubuntu24.04 to match the wheels and lower the driver floor to R560+.
* fix(ai): broaden OOM detection so the rembg lighter-model fallback fires
onnxruntime/CUDA allocation failures surface as 'Failed to allocate memory for requested buffer', CUBLAS_STATUS_ALLOC_FAILED, or bad_alloc, not just 'out of memory'. The background-removal and transparency-fixer fallback-to-lighter-model paths only matched the literal 'out of memory', so the fallback was dead code and transparency-fixer (default birefnet-hr-matting) always failed with an allocation error. Add isMemoryAllocError() and use it in both checks.
* fix(ai): use bundled PaddleOCR models so OCR runs offline
ocr.py passed no model dirs to PaddleOCR, so PaddleX resolved models from ~/.paddlex and downloaded them from HuggingFace at runtime (slow first use, broken air-gapped), ignoring the models the OCR bundle ships in MODELS_PATH; it also pulled doc-orientation/unwarping models that are not bundled. Pin detection, recognition and textline models to the bundled dirs in MODELS_PATH (per language) and disable use_doc_orientation_classify / use_doc_unwarping, with per-component fallback when a model is absent. Verified: OCR runs with zero HuggingFace requests.
* fix(docker): add CAP_KILL so container shutdown is graceful
cap_drop: ALL without re-adding KILL meant tini (PID 1, root) could not forward SIGTERM to the gosu-dropped snapotter process (root minus CAP_KILL cannot signal a different UID). docker stop logged '[FATAL tini] forwarding signal: Operation not permitted', never delivered the signal, and fell back to SIGKILL after the 10s timeout. Add KILL to cap_add in both compose files. Verified: docker stop completes in 0s with SIGTERM delivered (exit 143) and no FATAL tini.
* fix(ai): serialize bundle installs against AI jobs to prevent sidecar segfault
A feature bundle install rewrites the shared Python venv (pip + copytree of site-packages/*.so) as a background subprocess, with no coordination against AI tool jobs that dlopen native libs (torch / onnxruntime CUDA) from the same venv; a job loading a shared object while it is overwritten segfaults the sidecar. Add a process-wide async mutex (venv-lock.ts): bridge.run() acquires it before every AI script and the install route holds it across the installer subprocess. Both run in the same Node process so a module-level lock suffices. Verified: concurrent install + AI job produces zero segfaults and the job serializes behind the install.
* fix(ai): make the venv lock read/write so concurrent AI jobs are not serialized
The first cut used an exclusive mutex, which (a) deferred the dispatcher spawn by a microtask and broke unit tests that synchronously drive the mocked spawn, and (b) serialized AI jobs against each other, removing the dispatcher's by-id request multiplexing. Make it a writer-preferring read/write lock: AI jobs are shared readers (with a synchronous fast path so spawn still happens in-tick) and a bundle install is the exclusive writer. Verified: all 764 AI unit tests pass.
* fix(ai): degrade OCR to Tesseract on CPU-only hosts instead of segfaulting
The amd64 AI bundle ships paddlepaddle-gpu, whose native libs dlopen
libcuda.so.1 at import and segfault on a host without a GPU (libcuda is the
driver lib, injected only by nvidia-container-toolkit on GPU hosts). The
segfault crashed the shared long-lived AI dispatcher and, after a few attempts,
tripped the bridge crash-recovery permanent-disable, wedging all AI until a
container restart. The standalone ocr tool defaults to quality=balanced
(PaddleOCR), so it hit this on every CPU-only deployment; ocr-pdf already
hardcoded Tesseract and was unaffected.
ocr.py now gates the PaddleOCR tiers on gpu_available(): balanced/best
transparently fall back to fast (Tesseract, CPU-capable) when no usable GPU is
present, and run_paddleocr_v5/run_paddleocr_vl refuse before importing paddle so
the GPU build is never dlopen'd on CPU. GPU hosts are unchanged.
Verified on a CPU-only Windows/WSL2 box: ocr returns Tesseract text across
repeated runs with the dispatcher staying healthy (no wedge).
The persistent Python dispatcher rejects scripts whose feature bundle is not
installed, but the per-request fallback (used when the dispatcher is down, e.g.
restarting right after a model repair) spawned scripts directly and bypassed
that gate. Behavior was therefore inconsistent: a gated script would fail under
the dispatcher but run under the fallback -- the "works once after a repair"
symptom from the original report.
- add packages/ai/src/feature-gate.ts: SCRIPT_BUNDLE_MAP + missingBundleForScript,
mirroring TOOL_BUNDLE_MAP in dispatcher.py, reading the same installed.json and
failing closed exactly like dispatcher._get_installed_bundles()
- runPerRequest now rejects with "feature_not_installed" (the same message the
dispatcher path surfaces) when a gated script's bundle is not installed
- unit tests for the gate, plus a drift test pinning the TS map to dispatcher.py
Closes#327
* fix(passport-photo): require the face-detection bundle, not just background-removal
Passport Photo runs face-landmark detection (face_landmarks.py, gated to the
face-detection bundle) before background removal (background-removal bundle),
but it was only declared under and guarded against background-removal. A user
who installed only Background Removal passed every JS-side check, then hit a
late "feature_not_installed" from the Python dispatcher gate when the analyze
step ran face landmarks, and the UI never told them Face Detection was needed.
- shared: add TOOL_EXTRA_BUNDLES + getRequiredBundlesForTool so a tool can
declare more than one required bundle (passport-photo needs background-removal
and face-detection). enablesTools is untouched, so the one-tool-per-bundle
invariant still holds.
- api: isToolInstalled() now checks every required bundle; add
getFirstMissingBundleForTool() so the analyze and base routes, pipeline (both
guards) and batch report the bundle the user actually still needs.
- web: the proactive install prompt (tool-page) and features-store treat a tool
as installed only when all required bundles are present, and point the prompt
at the first missing one (sequential install, no new UI).
Refs #327
* test(passport-photo): deterministic integration coverage for the two-bundle guard
Boots the real API with an isolated DATA_DIR and controls installed.json to
prove the HTTP route behavior end-to-end:
- nothing installed -> 501 naming background-removal
- only background-removal installed -> 501 naming face-detection (issue #327)
- both installed -> guard passes (not 501)
- base route reports face-detection too
Refs #327
* feat(web): add pure zoom/pan math module with unit tests
* feat(i18n): add a11y.pan key across all locales (English, matching adjacent zoom labels)
* feat(web): add useZoomPan hook (state + gestures over pure math)
* feat(web): add ZoomToolbar component
* feat(web): zoom & pan in the object eraser canvas
* feat(web): zoom & pan in the split tool preview
* fix(web): synchronous pan-mode refs so drag-pan is race-free under fast input
* test(e2e): zoom & pan acceptance (split always-on, eraser bundle-gated)
Translate the ~950 previously-untranslated Swedish UI strings (tool names,
descriptions, settings labels, dialogs) that were still showing English, and
restore the {size} placeholder dropped from settings.aiFeatures.diskUsage.
Machine-translated and verified: full key parity with en.ts, all {placeholders}
preserved, passes tsc and Biome. Native-speaker review welcome.
Translate the ~950 previously-untranslated German UI strings (tool names,
descriptions, settings labels, dialogs) that were still showing English, and
restore the {size} placeholder dropped from settings.aiFeatures.diskUsage.
Machine-translated and verified: full key parity with en.ts, all {placeholders}
preserved, passes tsc and Biome. Native-speaker review welcome.
Translate the ~950 previously-untranslated Brazilian Portuguese UI strings (tool names,
descriptions, settings labels, dialogs) that were still showing English, and
restore the {size} placeholder dropped from settings.aiFeatures.diskUsage.
Machine-translated and verified: full key parity with en.ts, all {placeholders}
preserved, passes tsc and Biome. Native-speaker review welcome.
Translate the ~950 previously-untranslated French UI strings (tool names,
descriptions, settings labels, dialogs) that were still showing English, and
restore the {size} placeholder dropped from settings.aiFeatures.diskUsage.
Machine-translated and verified: full key parity with en.ts, all {placeholders}
preserved, passes tsc and Biome. Native-speaker review welcome.
Translate the ~950 previously-untranslated Spanish UI strings (tool names,
descriptions, settings labels, dialogs) that were still showing English, and
restore the {size} placeholder dropped from settings.aiFeatures.diskUsage.
Machine-translated and verified: full key parity with en.ts, all {placeholders}
preserved, passes tsc and Biome. Native-speaker review welcome.
The auth.rotatingPhrases and features.progressMessages arrays were skipped by
the main Italian pass (#298) and still had a few missing accents (e.g. verb
e -> è). Diacritic-only fix; wording, placeholders, and key parity unchanged.
Translate the ~950 previously-untranslated Thai UI strings (tool names,
descriptions, settings labels, dialogs) that were still showing English, and
restore the {size} placeholder dropped from settings.aiFeatures.diskUsage.
Machine-translated and verified: full key parity with en.ts, all {placeholders}
preserved, passes tsc and Biome. Native-speaker review welcome.
Translate the ~950 previously-untranslated Indonesian UI strings (tool names,
descriptions, settings labels, dialogs) that were still showing English, and
restore the {size} placeholder dropped from settings.aiFeatures.diskUsage.
Machine-translated and verified: full key parity with en.ts, all {placeholders}
preserved, passes tsc and Biome. Native-speaker review welcome.
Translate the ~950 previously-untranslated Vietnamese UI strings (tool names,
descriptions, settings labels, dialogs) that were still showing English, and
restore the {size} placeholder dropped from settings.aiFeatures.diskUsage.
Machine-translated and verified: full key parity with en.ts, all {placeholders}
preserved, passes tsc and Biome. Native-speaker review welcome.
Translate the ~950 previously-untranslated Hindi UI strings (tool names,
descriptions, settings labels, dialogs) that were still showing English, and
restore the {size} placeholder dropped from settings.aiFeatures.diskUsage.
Machine-translated and verified: full key parity with en.ts, all {placeholders}
preserved, passes tsc and Biome. Native-speaker review welcome.
Translate the ~950 previously-untranslated Turkish UI strings (tool names,
descriptions, settings labels, dialogs) that were still showing English, and
restore the {size} placeholder dropped from settings.aiFeatures.diskUsage.
Machine-translated and verified: full key parity with en.ts, all {placeholders}
preserved, passes tsc and Biome. Native-speaker review welcome.
Translate the ~950 previously-untranslated Arabic UI strings (tool names,
descriptions, settings labels, dialogs) that were still showing English, and
restore the {size} placeholder dropped from settings.aiFeatures.diskUsage.
Machine-translated and verified: full key parity with en.ts, all {placeholders}
preserved, passes tsc and Biome. Native-speaker review welcome.
Translate the ~950 previously-untranslated Ukrainian UI strings (tool names,
descriptions, settings labels, dialogs) that were still showing English, and
restore the {size} placeholder dropped from settings.aiFeatures.diskUsage.
Machine-translated and verified: full key parity with en.ts, all {placeholders}
preserved, passes tsc and Biome. Native-speaker review welcome.
Translate the ~950 previously-untranslated Polish UI strings (tool names,
descriptions, settings labels, dialogs) that were still showing English, and
restore the {size} placeholder dropped from settings.aiFeatures.diskUsage.
Machine-translated and verified: full key parity with en.ts, all {placeholders}
preserved, passes tsc and Biome. Native-speaker review welcome.
Translate the ~950 previously-untranslated Russian UI strings (tool names,
descriptions, settings labels, dialogs) that were still showing English, and
restore the {size} placeholder dropped from settings.aiFeatures.diskUsage.
Machine-translated and verified: full key parity with en.ts, all {placeholders}
preserved, passes tsc and Biome. Native-speaker review welcome.
Translate the ~950 previously-untranslated Dutch UI strings (tool names,
descriptions, settings labels, dialogs) that were still showing English, and
restore the {size} placeholder dropped from settings.aiFeatures.diskUsage.
Machine-translated and verified: full key parity with en.ts, all {placeholders}
preserved, passes tsc and Biome. Native-speaker review welcome.
Translate the ~950 previously-untranslated Korean UI strings (tool names,
descriptions, settings labels, dialogs) that were still showing English, and
restore the {size} placeholder dropped from settings.aiFeatures.diskUsage.
Machine-translated and verified: full key parity with en.ts, all {placeholders}
preserved, passes tsc and Biome. Native-speaker review welcome.
Translate the ~950 previously-untranslated Japanese UI strings (tool names,
descriptions, settings labels, dialogs) that were still showing English, and
restore the {size} placeholder dropped from settings.aiFeatures.diskUsage.
Machine-translated and verified: full key parity with en.ts, all {placeholders}
preserved, passes tsc and Biome. Native-speaker review welcome.
Translate the ~950 previously-untranslated Traditional Chinese UI strings (tool names,
descriptions, settings labels, dialogs) that were still showing English, and
restore the {size} placeholder dropped from settings.aiFeatures.diskUsage.
Machine-translated and verified: full key parity with en.ts, all {placeholders}
preserved, passes tsc and Biome. Native-speaker review welcome.
Translate the ~950 previously-untranslated Simplified Chinese UI strings (tool names,
descriptions, settings labels, dialogs) that were still showing English, and
restore the {size} placeholder dropped from settings.aiFeatures.diskUsage.
Machine-translated and verified: full key parity with en.ts, all {placeholders}
preserved, passes tsc and Biome. Native-speaker review welcome.
Around 35% of Italian strings (903 of 2583) were still English while the
file silently typechecked (key parity was already correct). This translates
them and fixes quality issues in the existing Italian:
- Translate untranslated tool names, descriptions, categories and UI labels;
keep legitimate English terms (formats, URL, Team, Pipeline, device presets)
- Fix accent errors (qualita->qualità, piu->più, Si e->Si è, and similar)
- Restore the {size} placeholder dropped from settings.aiFeatures.diskUsage
- Make role labels consistent Italian: Utente / Editore / Amministratore
- Normalize stray curly apostrophes to the file's straight-quote convention
Based on the Italian translation contributed by @albanobattistella (the issue
author), reconciled against the current en.ts (e.g. the Data->Files rename)
and corrected for accents and a structural error in the source.
Closes#231
* fix(deps): patch gray-matter onto js-yaml 4.2.0 (close js-yaml DoS alert)
js-yaml 3.14.2 (quadratic-complexity DoS in merge-key handling, GHSA
patched only in 4.2.0) was kept in the tree by a scoped pnpm override
"gray-matter>js-yaml": "^3.14.1" that exempted gray-matter from the
global js-yaml>=4.2.0 override. gray-matter is a build-time-only
transitive dep of the docs site (vitepress-plugin-llms,
@sugarat/theme-shared) and pinned 3.x because it calls the removed
yaml.safeLoad / yaml.safeDump APIs.
Remove the exemption so gray-matter resolves js-yaml 4.2.0, and add a
pnpm patch renaming safeLoad->load / safeDump->dump (the 4.x
equivalents; load is safe by default). js-yaml 3.x is now gone from the
lockfile.
Verified: gray-matter parse+stringify smoke test passes on 4.2.0; full
VitePress docs build green (177 pages, llms plugin parses all tool
frontmatter with no safeLoad/safeDump error).
* docs(ai): document rembg 2.0.69 pin and advisory non-reachability
The patched rembg 2.0.75 pulls a numpy 2.x closure (numpy>=2.3,
scipy>=1.16, scikit-image>=0.26) that is incompatible with the
numpy==1.26.4-locked AI stack (realesrgan 0.3.0 and codeformer-pip 0.0.4
break on numpy 2.x). Both open rembg advisories are unreachable in this
codebase: rembg is used purely as a library (never the `rembg s`
server), and new_session() only receives allowlisted model names
(remove_bg.py ALLOWED_MODELS), never user-controlled paths. Record this
rationale next to the pin; the Dependabot alerts are dismissed as
not_used.
- svg-sanitize.ts: strip each dangerous element repeatedly until stable with
whitespace-tolerant end tags, defeating nested/overlapping tags (closes 5
incomplete-multi-character-sanitization + 1 bad-tag-filter; the prior
single-pass regex could leave a residual <script>/<iframe>).
- file-preview.ts: add a resolve()+containment barrier (the path-traversal
guard CodeQL recognizes) on top of the id charset check (closes 9
path-injection).
- metadata.ts: bound the XMP namespace:name key segments so parseXmp cannot
backtrack polynomially (closes js/polynomial-redos).
- analytics-disabled.spec.ts: match analytics by URL host, not substring
(closes 4 incomplete-url-substring-sanitization).
typecheck + lint green; svg (119), preview (22), metadata (164) tests pass.
rembg 2.0.75 requires a numpy incompatible with the pinned numpy==1.26.4
that the rest of the ML stack (onnxruntime etc.) depends on, making
pip-audit's resolution impossible. The rembg <2.0.75 advisory (medium) is
accepted as a residual: it only affects the on-demand background-removal AI
bundle (publishing currently paused) and can't be patched without a numpy
2.x migration across the whole Python sidecar.
The fifth user-facing group is now Image, Video, Audio, PDF, Files
(internal modality id stays "file"; section.ts "files" was already
"Files"). Updates modality.ts label + comment, all 21 i18n locales
(categories.data "Data Files"->"Files", modalities.documentsAndFiles
"PDF & Data"->"PDF & Files", dead homePage.data), landing cards/hero
search/tools filter, docs headings, and e2e modality-tab assertions
(/^Data/ -> /^Files/, which had been failing).
Four launch gates for v2.0.0:
1. Catalog integrity (catalog-integrity.test.ts): asserts every TOOLS entry
is fully wired end to end (API route + frontend registry + display mode +
process fn or REGISTRY_EXEMPT). Count checked dynamically against
TOOLS.length. All 157 tools pass.
2. i18n cross-locale parity (i18n-parity.test.ts): asserts every locale in
SUPPORTED_LOCALES has the same key set as en.ts. Found and fixed a real
bug: zh-CN and pt-BR exported only a camelCase named export (zhCN, ptBR)
with no default export, so loadTranslations silently fell back to English
for Chinese Simplified and Brazilian Portuguese users. Fixed by adding
export default to both files. All 20 non-en locales now pass parity.
3. Cross-modality smoke (cross-modality-smoke.test.ts): one fast tool per
modality (rotate/image, mute-video/video, convert-audio/audio,
rotate-pdf/document, csv-json/data) plus an auth gate. Tools needing
ffmpeg or qpdf are gated with skipIf. Ship/no-ship signal.
4. Migration launch gate: extended migrate-from-sqlite.test.ts with a
representative 1.x SQLite database (3 users, 3 teams, 3 settings,
2 roles, 2 sessions, 2 API keys, 2 pipelines, 4 jobs, 4 audit entries,
4 user files) covering boolean/timestamp/JSON/NULL type conversions,
column remapping (input_files->input_refs, progress real->jsonb), and
multi-row round-trip verification. 9 new test cases.
Parity: 13260 passed, 0 dropped.
rembg 2.0.70+ requires numpy>=2.3.0, but the AI bundle pins numpy==1.26.4
(mediapipe, realesrgan/basicsr, codeformer, paddle all need numpy<2). The
unresolvable rembg==2.0.75 + numpy==1.26.4 combination broke pip-audit's
dependency resolution (CI red) and the background-removal bundle build. 2.0.69
is the newest rembg with an unconstrained numpy requirement. Verified: pip-audit
resolves with no unignored vulnerabilities on Python 3.11.
Bump all workspace package versions and APP_VERSION to 2.0.0, marking the
official 2.0 release. Removes the stale 1.x .release-notes.md artifact
(semantic-release regenerates release notes). The 2.0/multimodality docs
and rebrand already landed on main via #254 and #261, so this carries only
the version designation forward from the rebrand branch.
BREAKING CHANGE: SnapOtter 2.0 - the platform re-architecture (Postgres 17 +
Redis 8 + BullMQ durable jobs, 157 tools across five modalities) is the 2.0
release line, replacing the 1.x SQLite single-container architecture.