2026 Achievements Log
The completed-achievements log for SveltyCMS 2026 — dated, self-measured engineering milestones that shipped, moved here from the roadmap when done.
On this page
A Comparative Analysis of Architecture, Performance, and Enterprise Readiness
Methodology: All SveltyCMS metrics are self-measured via reproducible benchmark suites (bun test tests/benchmarks/). Competitor data is derived from publicly available documentation, CVE databases (NVD, GitHub Advisory DB), and published architecture analyses as of June 2026. We encourage independent verification. This evaluation is prepared in accordance with EU Directive 2006/114/EC on comparative advertising — all comparisons are based on objectively verifiable features.
At a Glance
SveltyCMS is a self-hosted, headless and API-first content management system built on Svelte 5 (runes) and SvelteKit 3, with TypeScript end to end, a native Svelte UI kit (no client-side UI framework dependency), and first-class adapters for SQLite, PostgreSQL, MariaDB and MongoDB. Its 2026 engineering record — dated, reproducible and self-measured — is summarised below; every number names the harness it came from and the host conditions, and none of it is a third-party certification or a global ranking.
Headline results (self-measured, latest recorded runs on 2026-10-10)
| Area | Result | How it is measured |
|---|---|---|
| Read-path latency (SQLite) | Warm HTTP end-to-end 0.393–0.486 ms (p95 0.481–0.620 ms), 2 042–2 527 RPS; cold, cache-busted 0.458–0.508 ms, 1 957–2 171 RPS — two same-code runs, recorded 2026-09-26 baseline 0.911 ms / 1 093 RPS | BENCHMARK_RECORD=1 … --only=truth-latency (SQLite, local host) |
| Write-path throughput (PG) | Warm HTTP concurrent 8c create 2 950 RPS (+220 %, baseline 920 RPS); update 3 678 RPS (+121 %, baseline 1 665 RPS); HTTP seed burst 13 120 docs/s (9.2× gain, 500 docs in 38 ms) | tests/benchmarks/competitive-workload-replica.test.ts (PostgreSQL) |
| Distance to the driver | Read findById at 114 %, listPlain at 213 % of bare-HTTP floor; Write create at 47 %, update at 57 % of bare-HTTP floor (and 35 % of raw in-process driver ceiling at 100k rows) |
scripts/benchmark-matrix/raw-db-ceiling.ts --db=postgresql --scale=100000 |
| Test estate | 5 749 unit tests / 625 files in 27.3 s; full 4-DB contract bundle green on SQLite, PostgreSQL, MariaDB and MongoDB | bun run test:unit · bun run test:integration · bun run test:matrix |
| Static gates | check (format + lint + dead-class + design-token + shade + lockfile), lint:docs (263 files) and risk:audit (CVE, secret-misuse, slop, backdoor probe) all clean; project diagnostics 0 errors / 0 warnings |
bun run check · bun run lint:docs · bun run risk:audit |
Verified gains (before → after, same harness)
| Stream | Recorded change |
|---|---|
| Write Performance | PG/MariaDB native socket pipelining (opt-in coalescing === "1"); create 920 → 2 950 RPS (+220 %), update 1 665 → 3 678 RPS (+121 %), seed burst 1 421 → 13 120 docs/s (9.2× speedup) |
| Driver Parity | All 4 DBs synchronized: PG POSTGRES_SYNCHRONOUS_COMMIT, MariaDB skipReturning without RETURNING *, MongoDB MONGO_WRITE_CONCERN + direct doc return on skipReturning, SQLite SQLITE_SYNCHRONOUS=NORMAL |
| Security | Read-lane enforcement points 1 → 5 (publication clamp, endpoint parity, field-level-access exemption, grant epoch, tenant resolution); leak/bypass paths 3 → 0; stale-grant window until TTL → 0 |
| Performance | Warm read latency −47 % to −57 %; throughput +87 % to +131 %; cold cache-busted latency −61 % to −64 % |
| Efficiency | Unit-suite wall time for the widget group 21.90 → 3.07 s (−86 %), media 1.73 → 0.86 s (−50 %), utils 4.62 → 3.52 s (−24 %); total unit suite 5 749 tests in 27.3 s |
| Quality gates | Project diagnostics 42 → 0; build-time warning classes (undefined named import, non-handler +server.ts export, alias in the config graph) turned into test failures instead of surprises |
How to read these numbers. They are self-assessments produced by this repository’s own harnesses on a single developer host; run-to-run spread on that host is up to ±10 % for single runs and up to 24 % between two identical runs, and every table above names the conditions. CI — not this document — is the source of truth for pass/fail counts. Where a claim carries an attribution caveat or an unmet acceptance criterion, the caveat is stated with the number rather than omitted.
1. Introduction: The Post-Monolithic Era of Content Management
The content management system (CMS) landscape of 2026 is defined by a rigorous bifurcation between legacy monolithic architectures and modern, composable, headless solutions. As enterprises increasingly demand sub-millisecond latency, edge-ready deployment, and “developer-first” ergonomics, the market has opened for frameworks that leverage the compilation-centric performance of next-generation JavaScript libraries.
1.1 The Definition of “State of the Art” in 2026
To accurately assess SveltyCMS, one must establish the baseline for SOTA technology in the current year. The benchmarks for 2026 include:
-
Zero-Runtime Overhead: The shift away from heavy client-side JavaScript bundles toward compiled, lightweight output.
-
Ultra-Fast Developer Feedback: Sub-1.2s Hot Module Replacement (HMR) for content structure changes, a 60% improvement over legacy patterns.
-
Native Svelte 5 UI Library (March 2026): Zero-dependency, native Svelte 5 UI architecture, achieving <150 requests per page and solving the “ESM Storm” overhead.
-
Ultra-High Performance Toolchain: Rust-powered linting and formatting (oxlint/oxfmt).
-
Linting (Oxlint): Project-wide checks in 47ms (1000+ files).
-
Formatting (Oxfmt): Full project formatting in ~2.2s.
-
Production Builds (Vite 7): Fast builds using Vite’s native bundler.
-
Zero-Warning Codebase: Automated and manual audit cleared all 84 legacy warnings.
-
Automated Doc Integrity (March 2026): Native
lint-docs.tsutility achieving 100% link integrity and Path Alignment Strategy (filesystem matching) across 158 files. -
True Database Agnosticism: The ability to run on NoSQL (for scale), SQL (for relations), or Embedded DBs (for edge) without code changes.
-
-
Edge Compatibility: The ability to deploy logic and content delivery to the network edge (e.g., Cloudflare Workers, Vercel Edge).
-
Type Safety: End-to-end TypeScript integration from the database schema to the frontend component.
-
Infrastructure as Code (IaC): The ability to define content schemas, permissions, and configurations as version-controlled code.
-
Structural Consolidation (5-Pillar Architecture): Moving beyond file-fragmented systems to a unified, 5-pillar reactive core.
-
Enterprise SEO Excellence: Sub-millisecond redirect lookups and dynamic, multi-tenant sitemap generation with automated indexing pings.
-
Typed Component Loaders: Moving beyond brittle string-based component resolution to type-safe, tree-shakeable
import()closures.
This report describes self-measured SveltyCMS behavior and publicly documented competitor capabilities as of mid-2026. It does not claim global ranking, external security certification, or same-harness superiority over all commercial CMS products.
2. The Architectural Foundation: SvelteKit and the Compiler Paradigm
2.1 The “No-Runtime” Advantage
SveltyCMS leverages Svelte’s compiler to generate highly optimized vanilla JavaScript during the build process.
- Middleware pipeline (April 2026, self-measured): Consolidated security handlers and
/apifast-paths; documented ~71% reduction in hook overhead on the measured path (~12 µs p95 micro-bench — not full HTTP E2E). - API fast-path skips: Omitting locale/theme/content-init on pure API routes saves on the order of tens of µs per request when those hooks would otherwise run (depends on path and cache state).
- Throughput: REST/dispatcher benches have recorded high thousands–~14k+ RPS on specific SQLite/local scenarios. Not a claim of global product speed leadership; networked adapters and full auth stacks are lower.
2.2 Performance & Security Infrastructure (New)
In March 2026, SveltyCMS implemented several enterprise-grade infrastructure optimizations:
-
Single-Trip Mutations: Standardized
.returning()(SQL) andreturnDocument: 'after'(Mongo) across all adapters, eliminating redundantSELECTqueries and reducing mutation latency by ~40%. -
Prepared / hot-path SQL (April 2026): Cached prepared statements and related hot-path work on SQLite, PostgreSQL, and MariaDB reduce per-query compile overhead. Sub-ms reads are typical on embedded SQLite under our harness; networked SQL still pays RTT (~0.5–2 ms class on local Docker, 2026-08-04).
-
Memory Stability & Allocation Efficiency (April 2026): Resolved significant allocation overhead in the
LocalCMSSDK by consolidating transient caches into a sharedstaticLRU engine. This refactor neutralized a 524 MB/min memory leak, achieving a STABLE rating with < 1MB/min growth under sustained 3,400+ RPS loads. -
Negative Caching & Bloom Filter Protection: Implemented a high-performance negative caching engine with an in-memory Bloom filter to track missing keys/documents. When a 404 cache miss is recorded, subsequent requests for the same missing key are resolved immediately in the current microtask tick, bypassing the database driver.
Negative cache / Bloom miss path is orders of magnitude faster than a full DB miss on repeated absent keys (self-measured hot-miss micro-benches). It reduces miss storms; it does not “immunize” the system against all DoS classes (rate limits, body limits, and ops still apply).
-
Zero-Tax LocalCMS SDK Bridge: Hardened the
LocalCMSserver-to-server bridge to bypass SvelteKit’s HTTP middleware pipelines entirely. Combined with hot-swappable getter structures and microtask batching, this achieves internal query resolution in <0.05ms for server-side loading and internal API interactions with 0% runtime overhead. -
Enterprise Redis L2 Cache Optimization (October 2026, self-measured):
- TCP Socket Performance (
TCP_NODELAY+ Keep-Alive): Eliminated Nagle packet coalescing delays on small JSON payloads (<1.5KB) by pinningnoDelay: trueandkeepAlive: 5000on the Redis L2 connection pool, suppressing 5–40ms tail latency spikes. - Non-Blocking Evictions (
UNLINK): Migrated cache invalidations and tag set drops from blockingDELto asynchronousUNLINKexecuted insidemulti.exec()batches, preventing Redis main-thread stalls during high-concurrency collection updates. - Batch
mGetRelation Probing: Wired bulk RedismGetlookups intopopulate-resolver.tsto resolve missing relational entity IDs in a single round-trip before issuing SQL/NoSQLfindManyqueries. - Zero-Dependency Optionality: Maintained strict architectural optionality — Redis remains 100% optional, seamlessly falling back to in-memory L1 cache with sub-µs hits when disabled.
- TCP Socket Performance (
-
Active Driver Validation (Anti-False Positive): Implemented mandatory
SELECT 1verification in the MariaDB and PostgreSQL adapters. This ensures that the Setup Wizard provides immediate, clear feedback for missing or incorrect credentials, eliminating “lazy connection” false positives found in legacy ORM implementations. -
Micro-Telemetry: Native per-operation timing (
performance.now()) is bubbled up from the DB adapter to the APImetafield, providing high-resolution observability without external dependencies. -
Automated Bottleneck Detection: Heap Stability, Pool Contention & P95/P99 Variance (2026-09-26):
- Heap Stability Regression Guard (
tests/unit/benchmarks/heap-stability.test.ts): Three automated tests that fail fast when a code change causes unbounded heap growth: (1) 1 k-doc transformation burst asserts <25 MB retained delta post-GC; (2) 3-burst asymptotic leak check asserts <15 MB gradient across 500-doc waves — catches commitment-ratchet leaks that only converge over many rounds; (3) 256-item LRU eviction cap asserts <10 MB post-GC after 2,000 inserts of 8 KB payloads, matching the internalBoundedCachecontract. - Connection Pool & Mutex Contention Test (
tests/integration/databases/pool-contention.test.ts): Fires 20 concurrent background-worker-pattern tasks (outbox/audit/status-poll simulation) against 20 concurrent foreground CRUD writes and asserts: zero deadlocks, all 40 operations succeed, sub-50 ms post-contention query latency. Runs on SQLite locally, switches to PostgreSQL in CI viacurrentDbType(). - P95/P99 Latency Variance Report (
scripts/verify-benchmark-local.ts):generateLatencyVarianceReport(db)grades the last recordedcompetitive-workload-replicarun — rows selected bytestFileand grouped byrunId, never by filename — using the run’s own recorded p50/p99 percentiles, the sample count behind them, and the absolute tail excess (p99 − p50). Each workload is classifiedEXCELLENT(<2× tail ratio) /ACCEPTABLE(≤3×) /WATCH(≤4.5×) /HIGH_VARIANCE(>4.5×); a row whose percentiles are absent, or backed by fewer than 100 samples (where “p99” is the maximum), is reportedUNMEASUREDwith the reason instead of an estimated band. Aggregate rows (p50 = 0 ms, e.g. the seed burst) are listed as excluded, not as unmeasured, so--strictstays passable.printLatencyVarianceReport()renders the table (naming the graded run and its age) andlatencyVariancePasses()is the strict verdict — a 🔴 tail or an unmeasured row fails it, 🟡/🔵 stay informational. Superseded (see §3.34): an earlier revision reconstructed p50/p99 fromavg/p95/cvwhen the JSON lacked them and graded those estimates, and read ten hardcoded filenames from a metric-naming scheme the harness had stopped writing. - Background Worker Stability (
src/services/background/background-runtime-env.ts): The standalone background worker was crashing and restarting in production because its esbuild bundle could not resolve$app/env(a SvelteKit virtual module). The fix: a lightweight runtime replacement (browser=false,building=false,dev=NODE_ENV!==production,version=npm_package_version) aliased in vite.config.ts so the worker never tries to load SvelteKit’s virtual module loader. A companion esbuild plugin now strips TypeScript and compiles.svelte.tsrune modules ($state/$derived) inside the bundle so runes never leak into the Node child as undefined globals. Verified intests/unit/services/background-supervisor.test.ts. - RFC 7240
Prefer: return=minimalFast Path (create + update):handleCollectionCreateandhandleCollectionUpdatenow detect thePrefer: return=minimalrequest header (or?return=minimalquery param) and passskipReturning: trueto the adapter, then return a lean{"_id":"..."}response (~70 B) instead of the full merged document (~3.5 KB). Removes the fullRETURNING *PostgreSQL round-trip on the happy path for callers that only need confirmation. Measured: update +4.3% RPS paired median on PostgreSQL 5k docs 8c. - PostgreSQL Schema Migration Guard (
adapter-core.ts):addedColumnsSet tracks which columns were actuallyADD COLUMNed in the current migration run. The subsequentCREATE INDEXloop now skips columns that already had indexes — eliminates redundantCREATE INDEX IF NOT EXISTScalls on large schemas with many existing materialized fields. - Tests:
tests/unit/benchmarks/heap-stability.test.ts(3) ·tests/integration/databases/pool-contention.test.ts(2) ·tests/unit/services/background-supervisor.test.ts(5, extended). Pre-commit gate: 6/6 checks pass in 2 m 2 s.
- Heap Stability Regression Guard (
-
Streamed collection export (September 2026):
GET /api/collections/{id}/exportstreams NDJSON, CSV, or chunked JSON fromstreamMany(Mongo cursor, PostgreSQL driver stream, SQLite/MariaDB 500-row pages).streamingExportResponseencodes one record at a time with backpressure. Admin import/export downloads the stream URL instead ofJSON.parseof the full payload. Tests:tests/unit/utils/export-encode.test.ts. Docs: Collections API. -
Collection Weak ETags & DB-free 304 (September 2026):
GET /api/collections/{id}emitsW/"cv1|{tenant}|{collection}|{epoch}|{reprHash}".If-None-Matchis compared to the in-memory collection epoch (bumped on write with L1 eviction) inhandleTurboGetandhandleApiRequestsbefore the collections handler — no DB, no JSON. Representation hash covers path, query, and user id. Tests:tests/unit/services/collection-etag.test.ts. Docs: Collections API. -
Native Field-Level Encryption at Rest (September 2026): Collection fields flagged
encrypt: trueare transparently encrypted inwrite-pipeline.tsand decrypted inread-pipeline.tsusing AES-256-GCM envelopes (v1:iv:tag:ciphertext) keyed fromENCRYPTION_KEYwith HKDF domain separation (sveltycms-field-at-rest) and AAD binding (tenantId:collectionId:fieldName). Physical adapters store only ciphertext; L1/L2 caches keep ciphertext (decrypt clones on the way out). Filter/sort on encrypted fields fail closed (ENCRYPTED_FIELD_NOT_QUERYABLE). Unique indexes and SQL materialization are skipped. Tests:tests/unit/utils/field-encryption.test.ts,tests/unit/services/field-encryption-pipeline.test.ts. Docs: Field-Level Encryption. -
Interactive Admin Session Console (September 2026): Shipped a comprehensive user session management modal (
modal-user-sessions.svelteandDELETE /api/user/sessions) inside User Management. Administrators can inspect concurrent active sessions per user account, complete with client metadata (OS, browser type, IP address, and current active session indicator) and execute one-click remote session revocation with instantaneous authorization cache invalidation. Tests:tests/unit/auth/sessions/session-manager.test.ts. -
GDPR Article 17 Right to Erasure & Benchmark Hardening (September 2026): Implemented permanent cascading user erasure (
eraseUser) inGDPRServiceand exposed authenticatedaction: "erase"andaction: "wipe"on/api/gdpr. Cascading deletion purges associated sessions, active tokens, audit logs, and account records with 0 orphans across all 4 database adapters. Migratedtests/benchmarks/right-to-be-forgotten-audit.test.tsfrom the legacy/api/testingbackdoor to the authentic production endpoint withbenchmarkAuthHeaders(), unblocking the test to execute inside the production benchmark matrix with<1msaverage wipe latency and 900+ wipes/s. Tests:tests/unit/api/gdpr.test.ts,tests/unit/services/gdpr-service.test.ts,tests/benchmarks/right-to-be-forgotten-audit.test.ts. Docs: ADR: Testing 2026. -
Point-Lookup Slab & Hot-Path Serialization (September 2026):
- Random-Access Slab: Implemented a dedicated 2,000-slot FIFO cache (
randomAccessSlab, 30s TTL) for high-frequency point lookups, elevatingfindByIdRandomthroughput by +196% to match the hot-pathfindByIdbaseline. - LRU Protection: Applied
updateAgeOnGet: falseonENTRYandCONTENTlookups, preventing cold random reads from displacing hot cache working sets. - Pattern Invalidation Acceleration: Refactored pattern-based cache clearing to reduce invalidation latency by −87% (4.11ms → 0.53ms), achieving +676% RPS in mutation benchmarks.
- O(N) Fast JSON Serialization: Replaced rope concatenation with pre-allocated buffer joins in
serializeArrayFast(fast-json.ts), eliminating GC pressure duringlistLargequeries. - Pool & SMTP Hardening: Elevated hardware-profile connection pool floors to 32 on 8c+ hosts, and hardened transactional SMTP connection pooling (
maxRecipients: 1,maxMessages: Infinity,connectionTimeout: 10s,socketTimeout: 30s).
- Random-Access Slab: Implemented a dedicated 2,000-slot FIFO cache (
-
Store Architecture Modernization & Svelte 5 Runes Consolidation (September 2026):
- Lean Store Footprint: Consolidated the global store directory from 28 files down to 18 single-purpose domain singletons.
- Monolith Elimination: Removed the legacy
store.svelte.tssingleton and compatibility bridges in favor of direct imports from specialized modules (locale-store.svelte,validation-store.svelte,toast.svelte). - State Absorption: Absorbed satellite stores (
status-store,data-change-store,user-prefs-overlay) into cohesive domain singletons (collection-store,theme-store). - Domain Colocation: Relocated single-consumer stores (
consent-store,settings-state,dashboard-preferences,image-editor-store,help-state) directly into their respective feature and component directories. - Widget & Shell Decoupling: Updated all 72 widget inputs, displays, schemas, and app shell routes to import directly from domain singletons, eliminating dozens of unused legacy imports and standardizing on Svelte 5 fine-grained reactivity (
$state(),$derived()). Tests:tests/unit/stores/,tests/unit/components/image-editor/.
-
Write-Path Acceleration & Single-Pass Field Preparation (August 2026):
- Deduplication of Sanitization Passes: Eliminated redundant recursive
sanitizeObjecttree traversal inprepareWritePayload, relying on the pre-compiledPrepPlaninprepareCollectionFieldsto perform targeted HTML/text sanitization per schema field. - Pre-Compiled Number & DateTime Plans: Pre-compiled DateTime and Numeric constraint plans in
SchemaHotFlags(_dateTimeFieldNames,_numberFields), eliminating runtimeschema.fieldsiteration in the hook pipeline and achieving zero field-iteration loops on write mutations. - Behavioral Mutation Self-Learning: Integrated
recordWriteAccessdirectly intoschedulePostWriteand commerceaddCartItem/decrementStock, feeding mutation signals (weight: 2) into the in-memory decaying heat map without blocking response latency. - Canonical Keyspace Cache-Warming Alignment: Aligned
CacheWarmingServicekeyspace withCollectionsNamespace(${tenant}:collection:${id}:${entry}:publishedand${tenant}:collection:${id}:find:default_50:published), ensuring pre-warmed entries and lists produce 100% cache hits on subsequent SDK reads. - PostgreSQL Parameter Coercion: Hardened
bindPgParamto coerceundefinedtonullacross all prepared statement queries, eliminating driver parameter rejection.
- Deduplication of Sanitization Passes: Eliminated redundant recursive
-
Security Hardening (API & Multi-Tenancy):
-
Universal Tenant Context: Implemented a rigorous middleware-driven
tenantIdpropagation system, ensuring every database mutation and query is explicitly scoped to the correct tenant. -
IDOR Prevention: Hardened all 40+ API endpoints with role-based access control and explicit owner verification.
-
GraphQL Isolation: Eliminated schema leakage by deploying isolated per-tenant Yoga server instances.
-
Tenant-Aware Uniqueness: Implemented granular control for unique field constraints. The
tenantScopedUniqueflag ensures that identifiers like slugs (e.g.,/about) remain unique within a tenant but can safely overlap across different tenants, preventing cross-tenant namespace collisions. -
SSE Hygiene: Resolved a critical memory leak in the real-time event stream and implemented tenant-aware real-time filtering.
-
Encrypted Data Portability: Standardized AES-256-GCM encryption for all sensitive fields during tenant-scoped data exports.
-
Automated Security Response (v2026): Implemented a unified
SecurityResponseServiceproviding:- Structured Payload Scanning: JSON and Form-data aware parsing, avoiding the overhead of raw string scanning on binary/multipart payloads.
- Bounded ReDoS Protection: All security regex patterns now use strictly bounded quantifiers ({0,500}), effectively eliminating the risk of accidental or malicious Regular Expression Denial of Service.
- Distributed Redis State: Security incidents, IP throttles, and blocks are distributed via Redis, ensuring immediate global protection across multi-node clusters.
- Performance Early-Exits: Sub-microsecond
Content-Lengthchecks and a 32KB processing cap for unknown payload types ensure high-volume protection without performance degradation.
-
Hardened Authentication Core:
-
OAuth State Integrity: Native HMAC-SHA256 signing and verification of OAuth states, neutralizing authorization code replay attacks and CSRF during external identity provider flows.
-
Compartmentalized Rate Limiter Secrets: Decoupled RateLimiter cryptographic keys from the primary
JWT_SECRET_KEY, utilizing a dedicatedRATE_LIMIT_SECRETfallback architecture. This containment strategy prevents a leaked rate-limit key from compromising the core authentication perimeter. -
Strict IP Resolution: Implemented
getClientIp(event)across all authentication mutation flows (Login, Sign-Up, Forgot Password), replacing generic “N/A” placeholders in Audit Logs to guarantee high-fidelity forensic traceability. -
RFC 6585 Compliance: Enforced strict
Retry-After: 60HTTP headers for all429 Too Many Requestsresponses generated by the authentication firewall, ensuring respectful client backoff protocols. -
Setup Wizard Gating (June 2026): Hardened setup route handlers (
setup.ts) to query the database and check for registered admin accounts, blocking setup hijacking attempts (403 SETUP_ALREADY_COMPLETE) even if the local config fileconfig/private.tsis deleted or corrupted. -
Stateless CSRF Exemption (June 2026): Excluded Bearer-authenticated API key and Website token requests from CSRF checks in
+server.tsto allow secure external client mutations without blocking browser-based cookie-authenticated sessions.-
Demo Tenant Capacity Guard: Integrated a hard-coded 100-user capacity limit for demo tenant signups, utilizing high-performance
getUserCountadapters to protect the system from automated resource exhaustion attacks. -
L0 Session & Cache Stability (August 2026): Refactored the
InMemorySessionManagerto utilize a bounded directMapwith capacity caps (MAX_SESSIONS = 10000) and proactive timestamp eviction, eliminating GC drop hazards while preventing memory leaks. Additionally, added process-local_clientNodesCachetocontentStoreand_clientSchemamemoization, eliminating repetitive layout JSON serializations.
-
-
TOTP Secret Encryption: TOTP secrets are now encrypted at rest using AES-256-GCM under the instance
-
HKDF + size-aware compression (August 2026): Passphrase-shaped
ENCRYPTION_KEY/SECRET_ENCRYPTION_KEYvalues (not 64-char hex) derive AES-256 keys with HKDF-SHA-256 (cached once per process, domain-separated per subsystem). Decrypt still accepts SHA-256(raw) envelopes. Hex keys are unchanged (AES-NI, no KDF on the request path). Compression negotiation now prefers gzip below 4 KiB and skips zstd below 32 KiB;compressSyncrefuses output that is not smaller than the input. No ML-KEM on the request path. -
SQL PK upsert + bulk import (August 2026):
crud.upsert/upsertManyon SQLite, PostgreSQL, and MariaDB are oneINSERT … ON CONFLICT (_id)/ON DUPLICATE KEYwhen the filter is a primary-key lookup (status-pinned queries still use findOne). Collection import (sync LocalCMS and theimport-datajob) sharesbulkImportCollectionDocuments:findByIds+insertMany/upsertManyper 100-row chunk. -
Automation event→flow index (August 2026): Automation dispatch indexes active event-triggered flows by event name so content mutations resolve matching flows with a single Map lookup instead of scanning every flow. Unmatched events exit before flow iteration.
-
QueryBuilder list + Mongo 1-RT upsert (August 2026): Admin collection lists (
queryBuilder.execute) convert rows in place with registered schema maps on SQLite/PostgreSQL/MariaDB;exists()is LIMIT 1. Mongo upsert is onefindOneAndUpdate(filter carries_id) instead of update-then-create. -
Plugin state + relation batch (August 2026): Extensions uses the layout plugin-state map (one
findMany+ 15s L1) instead of NgetPluginState; config reusesparent().pluginStates. Relation Input/Display issue one_id $inlist request; collection lists hydrate relation labels in onefindByIdsper related collection so table cells skip extra HTTP. -
Remotes skip HTTP-to-self (August 2026): User profile, sessions, tokens, settings groups, and collection save/delete remotes call LocalCMS in-process (
getRequestLocalCMS) instead ofevent.fetch("/api/..."). Dashboard health usescms.db.isConnected()instead of fetching/health. Same privilege strips as the REST handlers. -
SQLite write templates + GraphQL fast path (August 2026): SQLite INSERT synthesizes the row (no
RETURNING *) with a cached SQL template; UPDATE SQL is cached per column-set. GraphQLcontentSystemHealth/allCollectionsskip Yoga/JIT after auth (in-memory catalog/health).
-
-
-
TOTP Replay Protection: A consumed-codes registry using an adapter pattern (in-memory, swappable to Redis/SQLite) prevents TOTP code reuse within the 90-second validity window. Fail-closed design — registry errors reject the code.
-
TOTP Timing Attack Mitigation: Replaced standard string comparisons in the 2FA layer with
crypto.timingSafeEqual, mathematically neutralizing nanosecond-level side-channel attacks on TOTP code verification.- Trusted Devices: HMAC-SHA256 signed __Host-2fa-trusted-device cookie (30-day TTL) enables “Remember this device” — subsequent logins skip 2FA on recognized devices. Forgery-proof design using the instance’s ENCRYPTION_KEY.
-
Plugin Auth Hooks: New afterAuthenticate lifecycle hook allows plugins to deny logins or enforce 2FA gating on any authentication method (password, OAuth, passkey, token, magic link). Fires after credential verification but before session cookie issuance.
-
Pending 2FA Setup: Encrypted TOTP secrets are persisted immediately during setup with twoFactorPending: true — interrupted enrollment is automatically resumed on re-initiation.
-
Configurable TOTP Window: TOTP_WINDOW env var (default 1, max 5) for clock-drift tolerance without code changes.
-
Fallback Garbage Collection Resilience: Hardened the session cleanup loop to guarantee that in-memory fallback caches are continuously pruned even when a
RedisSessionManageris actively managing the primary connection, ensuring zero memory leaks during Redis outages.-
May 2026 AI Bot Defense & Crypto Hardening:
-
AI Bot Detection & Blocking: Proactive User-Agent fingerprinting detects and blocks 28 known AI crawler patterns (GPTBot, Claude, Perplexity, CommonCrawl, Bytespider) and reconnaissance tools (Nmap, SQLMap, Nikto, Burp Suite, Zgrab, Masscan, Nessus).
-
Multi-Layer Honeypot Grid: 45+ decoy routes covering WordPress, Drupal, Joomla, AWS metadata endpoints, config/backup file probes, and common exploit paths. Any probe triggers immediate IP flagging/blacklisting.
-
Honeypot IP Flagging & Response Poisoning: Honeypot probes get an immediate decoy 200 (no 5–15s socket hold — that was a Slowloris/DDoS vector) while the IP is flagged for firewall rejection on the next request. Bots receive fake JSON payloads (phantom users, false config) designed to corrupt scraper datasets rather than revealing real system information.
-
Cross-Origin Isolation Headers: All API responses enforce
Cross-Origin-Opener-Policy: same-origin,Cross-Origin-Embedder-Policy: require-corp, andCross-Origin-Resource-Policy: same-origin, preventing Spectre/Meltdown side-channel attacks.- Zero-Bias Token Generation:
generateRandomTokennow uses rejection sampling to eliminate ~3.125% modulo bias, guaranteeing uniform CSPRNG distribution for session tokens and API keys.
- Zero-Bias Token Generation:
-
Setup Cookie Prefix Consistency: Setup handler now uses consistent
__Host-cookie prefix logic, closing the gap between runtime and initialization session cookie naming.
-
-
June 2026 Comprehensive Security Audit & Hardening: Full-stack codebase audit against dependency stack, security architecture docs, and CVE landscape. Closed 6 gaps:
-
SVG Polyglot Upload Sanitization: Server-side
sanitizeSvg()strips scripts, foreignObject, event handlers,javascript:/data:URIs, CDATA, XML PIs, and DOCTYPE from uploaded SVG files. -
S3 Endpoint SSRF Prevention:
validateS3Endpoint()blocks private IPs (127.0.0.0/8, 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 169.254.0.0/16), cloud metadata hosts, and non-HTTP protocols before S3Client creation.- Ollama RAG Prompt Injection Defense: Remote knowledge-base context relocated to
usermessage role with<rag_context>XML delimiters and control-character stripping.
- Ollama RAG Prompt Injection Defense: Remote knowledge-base context relocated to
-
GraphQL Introspection Explicit Blocking:
NoSchemaIntrospectionCustomRuleapplied in production, preventing unauthenticated__schemaenumeration. -
Tiptap Link Protocol Hardening: Protocol allowlist
["http", "https", "mailto", "tel"]blocksjavascript:anddata:URI injection at editor level.- Rate Limiter Dependency Hygiene:
sveltekit-rate-limitermoved fromdevDependenciestodependenciesto guarantee production build inclusion. All 64 defense-in-depth security regression tests pass. 0 lint errors. 0 type errors.
- Rate Limiter Dependency Hygiene:
-
-
4-Layer Defense-in-Depth Authorization: Most CMS platforms (Payload, Strapi, Directus) rely on a perimeter security model: authenticate at the gate, then assume internal trust. This creates dangerous gaps where an authenticated user with low privileges can exploit deeper vulnerabilities. SveltyCMS eliminates this assumption through 4-layer zero-trust re-validation:
-
Layer 1 — Middleware:
__Host-cookie prefix enforcement (RFC 6265bis), setup completion gating, CSRF protection. -
Layer 2 — Dispatcher: Fail-closed
hasApiPermission()checks; unmapped namespaces return 403 by default. -
Layer 3 — Handler: Admin verification for system mutations,
media:write/media:deletepermission checks. -
Layer 4 — Page Action: Centralized permission guards (e.g.,
requireCollectionBuilderPermission). Each layer re-validates permissions independently, and the system fails safely if any check fails. This “zero trust within the application” architecture is rare among open-source CMS platforms and makes entire classes of vulnerabilities impossible — a single compromised perimeter layer does not grant full access.
-
-
ETag Conditional Requests: Implemented HTTP ETag support in the central API dispatcher. Every GET 200 response receives a SHA-256-based ETag header (first 16 hex chars of the response body hash). The dispatcher checks the If-None-Match request header and returns 304 Not Modified with zero body when content has not changed. This reduces bandwidth consumption and server processing for cache-valid clients, with sub-microsecond hash computation overhead per response.
-
API Versioning: Added X-API-Version: 1 header to all API responses. The dispatcher automatically strips /v1/ path prefixes for backward-compatible routing (e.g., /api/v1/collections/posts to /api/collections/posts). This enables future /api/v2/ endpoints without breaking existing clients, while existing /api/ paths continue working unchanged.
-
Vectorized Audit Chaining: Moved Audit Log SHA-256 hashing to a dedicated Node.js
Worker Threadpool. This offloads cryptographic overhead from the main event loop, preventing API starvation during high-volume bulk imports and deletions. -
Middleware AST Pre-compilation: Introduced a compiled “Auth-Path” cache in the SvelteKit
handleSetuphook. Pre-evaluating complex AST route bypasses reduced total middleware latency by ~40%. -
Unified Gatekeeper Consolidation (April 2026): Purged 13 redundant API route directories and consolidated entire REST surface into a single, high-performance
[...path]dispatcher. This reduced AST parsing overhead in SvelteKit by 80% and minimized cold-start times for serverless environments. -
Enterprise “Immunity” Layer (April 2026):
- Global Security Guard: A top-level wrapper ensuring HSTS, CSP, and X-Frame-Options are injected into every response, including error boundaries and redirects.
- Self-Healing Load Shedding: Integrated a memory-pressure monitor (90% heap threshold) that automatically rejects mutation traffic with compressed 503 responses, protecting read-availability during spikes.
- Graceful Shutdown Instrumentation: Instrumented
SIGTERM/SIGINThandlers to drain in-flight requests and cleanly disconnect DB/Cache pools, ensuring zero-restart integrity. - Timing-Safe Cryptography: Protected CI/CD test bypasses using
crypto.timingSafeEqualto neutralize side-channel attacks on administrative bypass secrets. - Request Tracing: Automated
X-Request-IDandrequestIdpropagation across the entire stack viaAsyncLocalStorageand the middleware pipeline.
-
Lazy Relation Hydration: Eliminated Depth-3 query lag on strict SQL engines (PostgreSQL/MariaDB) by utilizing Svelte 5 snippets (
{#snippet}) and theIntersectionObserverAPI. This “Ghost Relation” pattern delays complex sub-graphJOINexecution until the specific relation field natively scrolls into the viewport. -
L1/L2 cache (v0.0.7+): Hybrid LRU + optional Redis. Hot-path hits bypass the DB driver; LocalCMS / L1-hot internal timings can land in the sub-0.1 ms class in our micro-benches. Percent reductions vs a specific prior baseline are scenario-dependent — reproduce with
tests/benchmarks/cache-*.test.ts/ LocalCMS benches.Furthermore, batched Ghost Relations mathematically eliminate the N+1 query bottlenecks and relational choking that plague competitors like Strapi and Directus under heavy nested population.
-
Surgical Field-Level Caching: Enhanced the Ghost Caching engine with
tagMapsurgical invalidation. Single-field updates (e.g.,viewCount) now trigger targeted purges of specific document keys instead of flushing entire collections, maintaining “Hot L1” status for 99% of unrelated traffic. -
Distributed Edge Sync & Real-Time Invalidation: Introduced a Redis Pub/Sub invalidation bridge (
svelty:cache:invalidation). This ensures sub-millisecond global state consistency across multi-node edge clusters, with immediate local-memory purging upon remote invalidation events. -
Geographic Read-Replica Awareness: Implemented a context-aware
ReplicaPoolManagerin the PostgreSQL adapter. By parsing thex-svelty-regionheader, the CMS automatically routes read traffic to the nearest global database replica (e.g., US users tous-east-1), while keeping all mutations on the primary instance.- Optional Edge CDN Invalidation: Integrated a background
CdnServicefor Cloudflare. Automatedpurge_by_tagscalls are fired upon content updates, ensuring global Edge consistency without manual cache management. This service is 100% optional and fails gracefully if credentials are not provided.
- Optional Edge CDN Invalidation: Integrated a background
-
Hardened Telemetry Protocol: HMAC-SHA256 registration handshakes for lifecycle reporting to make spoofed heartbeats harder (not a formal PKI guarantee).
-
Persistent DoS Protection (April 2026): Implemented stateful rate-limiter persistence using
dump()andrestore()mechanisms inSecurityResponseService. This ensures that active IP blocks and throttles survive graceful shutdowns and node restarts, providing continuous protection across deployments. -
SQLite Multi-Tenant Index Optimization (April 2026): Hardened the SQLite schema with compound
(tenantId, field)indexes acrossauthUsers,authTokens, andcontentNodes. This eliminated table scans for multi-tenant lookups, achieving sub-millisecond query performance (0.000ms - 0.007ms) even under high concurrency. -
Connection Resilience Hardening (June 2026): Fixed undici pool hangs when rejected HTTP response bodies were left unconsumed (brownout simulations). Chaos-resilience benches pass after consuming failed bodies; re-run
tests/benchmarks/chaos-resilience.test.tsfor current numbers.
-
2.2.1 Scaling benches (April 2026, self-measured)
Selected high-load results from our suites (not “zero overhead” as a universal claim):
-
Hot REST/read path: On the order of sub-ms entry retrieval when schema/cache is warm (scenario-dependent).
-
Content scanning: Worker-pool scan of large collection sets measured in sub-ms–low-ms class under our content-scan bench.
-
Bulk ingestion: Order of ~10k entries / ~1s class in migration-scale style benches (machine- and DB-dependent).
-
Revision-heavy docs: Lookup degradation under many revisions constrained by indexing strategy in revision benches (see benchmark ledger for latest %).
-
Write-path span attribution (2026-09-22, self-measured):
PROFILE_WRITE=1spans now cover the whole create/update pipeline (ns:prep:*,ns:beforeSave,ns:persist:adapter-write,ns:persist:outbox,db:ins:*,db:upd:*) with correct span boundaries — the previousns:sanitize+validatespan closed after the DB write, which inflated validation to 1.14 ms and made it look like the dominant write cost. Real split at 1c:ns:persist:adapter-write0.24 ms (of which the bare statement call is 0.11–0.18 ms),ns:sanitize+validate0.035 ms,ns:beforeSave0.013 ms, row normalization 0.009–0.011 ms, widgets/encryption/outbox ≈ 0. Driver choice is not a factor (node:sqlite 37.8 µs vs bun:sqlite 35.4 µs per INSERT in a like-for-like probe). -
Middleware attribution for the write lane (2026-09-22, self-measured):
hooks-performance.test.tsnow reports per-scenario pipeline costs, and the measured middleware-only delta between the light turbo lane and the full auth+security lane is +0.01–0.09 ms — i.e. the middleware chain is not where the mutation row’s cost sits (mutation 1.99 ms vs warm reads 0.40–0.47 ms, audit disabled). -
Rejection-path cost parity (2026-09-22, self-measured, fixed): a 401 used to cost ~2× a valid read in
tests/benchmarks/auth-performance.test.ts(every rejected flavour 0.84–1.00 ms vs 0.40–0.46 ms authenticated; SQLite, warm). The root cause was outside the middleware: the production 401/403 fast-path envelopes (src/utils/error-handling.ts) were the only API responses built without acontent-length, sohandleCompressionread them as “unknown size”, skipped both the <1 KiB skip-gate and the buffered tier, and negotiated the streaming tier — a fresh zstd stream per reject (~0.45 ms for a 64-byte body;Accept-Encoding: identityrestored the cheap path, which isolated it). Thesequence()spans also nest, so “every phase wrapper inflates” was a nesting artifact rather than evidence of a per-stage cost. After declaring the length, the reject rows land at 0.35–0.39 ms — parity with the authenticated rows (0.33–0.36 ms turbo-served, 0.29–0.33 ms full chain; five runs each side, 2026-09-22). The harness now prints a live lane/cache/hook-count table per row, becauseAuth Validation @ 1cis turbo-cache served (4 hooks) while a rejection runs the full chain (7), andAuth (full chain) @ 1cis the like-for-like authenticated row (10 hooks). Supersedes the earlier “1.85x Fast-Fail Efficiency” figure (pre rate-limit/AMR/WAF consolidation) and the “cause is an open roadmap item” note — see Server Hooks. -
Monitoring Tax Elimination: Reduced health-probe latency to 0.45ms via intelligent caching, protecting production CPU cycles from monitoring overhead.
-
Cold point-read response audit (2026-09-22, self-measured, fixed + refuted): the “unknown size” trap behind the rejection fix was swept across every hand-built response. Fixed: the API cache-hit path (
src/hooks/handle-api-requests.tsserveCachedEntry) deleted its inheritedcontent-length, so every L1/L2 cache hit — 0.1 KB envelope to 250 KB list — was negotiated into the streaming compression tier instead of the<1 KiBskip-gate or the ≤ 64 KiB buffered tier; it now declares the length of the body it actually sends (computed, never the stale cached value), withtests/unit/hooks/api-requests.test.tspinning the contract. Fixed the same way: the 62 B503 Database adapter unavailableenvelope inhandle-local-context.ts. Already compliant (verified, not changed):buildJsonResponse/fastSuccessResponse(computed length), the error envelopes, the rebuilt token-resolution response,/files(declared from the file stat), and every reject fromhandleSecurity/handleRateLimit/handleTurboPipeline— those return upstream of the compression stage, so no fix applies. The competitivefindByIdRandomrow itself is not affected by any of this: a warm session’s point read is served by the collection read lane, which returns fromhandlebefore the middleware chain and never reaches compression. Its cost was attributed end-to-end instead (laneHYPER_TURBO/MISS, 676 B body + 26 headers = 1279 B on the wire; 10 k-doc replica, 8 c, n = 6790 misses): SDKfindById55 µs (SQLite driver 27 µs, row/date conversion 12 µs, JSON + point-cache insert 25 µs), response construction 69 µs, SDK facade + session/tenant prep 20 µs — against a raw-HTTP floor of 28 µs (raw-db-ceiling.ts --db=sqlite) and ~10 % of server CPU in undici header/body plumbing vs ~4.5 % in all bundled CMS chunks. No lane-local step is provably unnecessary, and none is big enough to close a ~70 µs gap — the row’s headroom is framework/driver/transport, tracked in the roadmap rather than “fixed” with a speculative change. Three fingerprint-pinned runs per side: warm sequential 0.430–0.515 ms before vs 0.356–0.469 ms after (medians 0.451 vs 0.443 ms), warm 8c 2.215–2.273 ms before vs 2.266–2.698 ms after (medians 2.236 vs 2.384 ms) — ±13–20 % machine noise that swallows the difference both ways, as expected for a fix that is off this row’s code path. Details and the per-site table: Server Hooks. -
Simulated Form Vitality: Proved a sub-0.05ms server prep time for complex 50-field forms, ensuring the Admin UX feels instantaneous regardless of schema complexity.
-
Transparent AI Integration: Reduced CMS-side AI logic tax to 1.3ms, ensuring the platform adds zero perceived latency to generative workflows.
-
High-Throughput Large-Payload & Concurrency Scaling (September 2026):
- Single-Pass V8 JSON Serialization: Replaced intermediate string template allocation in
successResponsewith direct V8 streaming serialization, reducing 225 KB payload serialization time from 1.47s to 650ms (55.7% latency reduction) in micro-benchmarks. - SIMD-Accelerated ByteLength Calculation: Fast-paths payloads $\ge$ 1 KB to AVX2/SSE4.2 SIMD
Buffer.byteLength(), eliminating JavaScript character-by-character loops on large responses. - L1 Query Cache Retention: Kept
CacheCategory.CONTENTwithupdateAgeOnGet: true(restrictingcoldCategorytoENTRY), ensuring high-traffic collection queries and pagination pages stay hot in L1 cache under concurrent workloads. - Adaptive Brotli Quality Scaling: Lowered Brotli compression quality for dynamic API responses between 32 KB and 256 KB from quality 6 to 2 (4.4× faster compression with <0.1% ratio difference), preventing threadpool saturation under multi-worker spikes.
- Hardware-Concurrent Libuv Threadpool: Automatic boot-time scaling of
UV_THREADPOOL_SIZEup to 32 worker threads prior to module initialization in the shared server entry (index.server.mjs), on both Node.js and Bun.
- Single-Pass V8 JSON Serialization: Replaced intermediate string template allocation in
2.3 The Typed Widget Revolution (March 2026)
One of the most significant architectural leaps in 2026 was the migration of the “Three Pillars” widget system from string-based paths to Type-Safe Component Loaders.
- The Problem: Legacy CMS architectures (including early SveltyCMS) relied on strings like
inputComponentPath: './MyWidget.svelte'. This prevented IDE refactoring, required giantimport.meta.globscans, and led to runtime “module not found” errors. - The Solution: Redesigned the
createWidgetfactory to accept typedComponentLoaderclosures:() => import('./input.svelte'). - Result:
- Zero Runtime Surprises: Broken paths are caught at compile-time by TypeScript and Svelte Check.
- Perfect Tree-Shaking: Vite no longer bundles unused widgets; only the components for enabled widgets are loaded.
- AI Integrity: The
jsonRendercatalog validates referenced components at load time to fail closed on missing widgets (not a cryptographic proof system).
2.4 Core Utilities Standardization (March 2026)
To support the UI migration and global dynamic content, SveltyCMS standardized its core utility belt:
cn: High-performance Tailwind class merging (powered bytailwind-mergeandclsx) resolving “ESM Storms” in UI components.pluralize: A dynamic, runtime counterpart to Paraglide JS, leveragingIntl.PluralRulesto accurately pluralize Arabic, Russian, and English database content (which cannot be pre-compiled).slugify: Advanced Unicode normalization engine capable of safely trans-literating complex diacritics into URL-safe identifiers.
3. Performance & Competitive Analysis
3.1 Competitive Feature Matrix (2026)
The following matrix evaluates core engineering implementations where SveltyCMS provides a distinct technical advantage over legacy and contemporary competitors.
| Feature | SveltyCMS (v2026) | Strapi 5 | Payload 3.0 | Directus 11 | Sveltia CMS |
|---|---|---|---|---|---|
| Core Framework | Svelte 5 (Runes) | React | Next.js / React | Vue / Node.js | Svelte (SPA) |
| Architecture | Server-First (SSR) | Node.js / React | Next.js / React | Node.js / Vue | Git-based (Local) |
| Multi-Tenancy | Native Adapter-Level | Paid (Enterprise) | Plugin-based | Native | N/A |
| Slug Uniqueness | Tenant-Scoped (Native) | Global Only | Global Only | Manual Config | Locale-Agnostic |
| AI Architecture | Local (Ollama) + Cloud + Client‑Side (LiteRT.js) | Cloud Only (Paid) | MCP Native (Cloud) | Cloud Only (Paid) | N/A |
| Dual-Runtime | ✅ Dual-Adapter (Node.js + native Bun.serve) | Node.js only | Node.js only | Node.js only | Browser Only |
| Real-time Sync | SSE + Yjs (adapter-node compatible) | WebSockets | None / Custom | WebSockets | Git Pull/Push |
| Feature | SveltyCMS (v2026) | Strapi 5 | Payload 3.0 | Directus 11 | Sveltia CMS |
|---|---|---|---|---|---|
| Cache Repair | Smart / Incremental | Manual Clear | Manual Clear | Manual Clear | Browser Only |
| SDK Latency | Zero (Local Bridge) | 2–5ms (Local HTTP) | ~1ms (Direct) | 3–8ms (Local HTTP) | N/A |
| Negative Caching | Bloom Filter (100,000x) | None / DB query | None / DB query | None / DB query | N/A |
| SEO Redirects | 0.92 ms (Sub-MS) | ~45 ms (Plugin) | ✅ Plugin | ⚠️ Basic | ⚠️ Basic |
| Toolchain | Vite + oxlint/oxfmt | Vite (JS/TS) | Vite (JS/TS) | Vite (JS/TS) | Rolldown/Vite |
| Feature | SveltyCMS (v2026) | Strapi 5 | Payload 3.0 | Directus 11 | Sveltia CMS |
|---|---|---|---|---|---|
| Scheduled Publishing | ✅ Adaptive Job Scheduler | ⚠️ Cron Plugin | ✅ Job Queue | ✅ Flows | ❌ |
| Content Versioning | ✅ Revisions + Diff + Restore | ⚠️ Basic | ✅ Versions | ✅ Revisions | ❌ |
| Per-Field Localization | ✅ Record |
⚠️ Separate Entries | ✅ | ✅ | ❌ |
| Full-Text Search | ⚠️ FTS adapters on all 4 engines (not wired) | ⚠️ Plugin | ⚠️ Plugin | ⚠️ Plugin | ❌ |
| Crypto-Chained Audit | ✅ SHA-256 Tamper-Evident | ❌ | ❌ | ❌ | ❌ |
| Smart CMS Migration | ✅ AI-Smart 5-Format Importer | ❌ | ❌ | ❌ | ❌ |
| Cold Start UX | ✅ Progressive / gated init (self-measured) | ⚠️ Spinner | ⚠️ Spinner | ⚠️ Spinner | ✅ Local |
| Quick-Start Templates | ✅ 7 Presets (Website Starter default) | ❌ | ❌ | ❌ | ❌ |
| In-Repo Site Frontend | ✅ SvelteKit + Svedit at / |
❌ (bring your own) | ❌ | ❌ | ⚠️ Git-only |
| Behavioral Learning | ✅ Server-side, zero PII | ❌ | ❌ | ❌ | ❌ |
| AI Widget Scaffolder | ✅ LLM-ready code generation | ❌ | ❌ | ❌ | ❌ |
| Token Security | ✅ SHA-256 all adapters | ⚠️ | ⚠️ | ⚠️ | ❌ |
3.2 Competitive Benchmark Analysis (2026)
See Competitive Comparison for the full benchmark matrix — every registered module across all 4 databases under extreme concurrent load (source of truth for the matrix: Benchmarks), with per-adapter throughput, Local SDK, and competitor comparisons (Payload, Directus, Strapi, Drupal, WordPress).
What our benchmarks actually show (not a global ranking)
We do not claim global product speed leadership. Public competitor numbers rarely use the same hardware, dataset, or middleware stack. Below is self-measured behavior only.
| Layer | Self-measured order of magnitude (2026) | Caveat |
|---|---|---|
| LocalCMS / in-process SDK | Sub-0.1 ms class internal calls when L1-hot | Not comparable to another product’s HTTP API RPS |
| SQLite adapter FIND ONE | ~0.05–0.08 ms avg (2026-08-04 re-bench) | Embedded; no network RTT |
| Networked adapter FIND/INSERT | ~0.5–2+ ms avg on local Docker (PG/Maria/Mongo) | Dominated by loopback RTT + engine |
findPage vs list+count |
~1.6–5.7× fewer ms on dual-query path (same re-bench) | Product API change; call sites must use findPage |
| Count L1 cache hit | ~0.024–0.029 ms all engines | count only, not full list JSON |
| Count estimate (unfiltered) | ~3–10× vs exact when mode allows | Never used for tenant-scoped exactness |
Full tables: Performance Architecture · matrix: Benchmarks · buyer framing: Competitive Comparison.
Understanding Executive Latency Uniformity Across Adapters
It is entirely normal and expected for raw CRUD execution matrices to look nearly identical for individual operations within a single database dialect (e.g., PostgreSQL FIND ONE at ~0.85ms vs UPDATE at ~2.15ms across runs). This occurs because:
- Network RTT & Loopback IPC Overhead: Under local containerized testing (Docker), loopback socket traversal and TCP handshake serialization impose a fixed 0.4–0.8ms latency floor regardless of query complexity.
- Standardized Drizzle SQL Generation: The unified Drizzle AST compiler emits identical SQL query structures for single-record lookups across all relational adapters.
- Hardware Engine Isolation: Below the
hooks.server.tsmiddleware pipeline, raw adapter performance reflects the underlying database engine’s storage locks (e.g. WiredTiger in Mongo, InnoDB in MariaDB, WAL in SQLite).
Reproduce locally:
bun test tests/benchmarks/database-performance.test.ts BENCHMARK_RECORD=1 bun test tests/benchmarks/database-performance.test.ts bun run scripts/benchmark-matrix/index.ts --sql8 database reports available at
docs/project/benchmarks/benchmark_<db>.mdxwith educational context (Measures/Budget/Code/Why), trend labels, and root cause insights.
Earlier 2026 (condensed archive: June–August)
Condensed 2026-09-27: the headline results, measurements and their conditions are retained in full; only narrative prose was removed. The September entries below (3.15 onwards) remain in full detail.
3.3 June 2026 Enhancement Sprint — Closing the Feature Gap
- Scheduled publishing (production-ready): adaptive job scheduler (
src/services/scheduler.ts) polls every 1-second when jobs are pending and every 30-second when idle, at sub-0.1ms CPU per poll cycle; automatic draft→publish, publish→unpublish and scheduled-delete transitions; failures retry with 2^n-second backoff (max 3 attempts) and every execution gets a crypto-chained audit entry; CRUD viaPOST/DELETE /api/system-jobs; the right-sidebar “Schedule publication” creates real, cancellable jobs. - Per-field content localization: translated fields store all language variants in one document (
Record<Locale, string>, e.g.{ en: "Hello", de: "Hallo", fr: "Bonjour" }), with an inline[EN ▼]locale switcher,{#key fieldName:locale}scoped remounting, one-click AI translation (caching, rate limiting, audit logging), automatic migration of legacy single-string fields, and per-field per-localetranslationProgress. - Crypto-chained audit logs (SHA-256): every entry stores
previousHash(SHA-256 of the prior entry) andchainHash(SHA-256 of self + previousHash), generated with the CSPRNGglobalThis.crypto.subtle.digest('SHA-256')(no Math.random() fallback);verifyChain()detects tampering, deletion or reordering; a newAuditHistorycomponent shows a visual chain indicator with a “Verify Chain” button; the schema was extended across all 4 database adapters; SOC 2, ISO 27001 and GDPR Article 30 ready. - Database-native full-text search (implemented, not yet wired as of 2026-09): zero additional npm packages — PostgreSQL
tsvector/tsquerywith weighted column ranking (title:A, content:B, description:C), MariaDBMATCH...AGAINST IN BOOLEAN MODEwith FULLTEXT indexes, SQLite FTS5 virtual tables with BM25 ranking, MongoDB$textwith field weights, andILIKE/LIKEfallback; the live?search=path still uses schema-drivenLIKE/regex matching. - Smart AI-Driven Migration Pro v2.1 (June 2026): 40+ CMS platforms through 40 parsers (12 PHP CMS + 4 headless SaaS + SveltyCMS native) plus 9 universal formats (CSV, Markdown, SQL, API, JSON, Airtable, Notion, Firebase, MongoDB); 5-step wizard (
?plugin=smart-importer,TransformationTree, SSE/api/migration/import) with auto-scaffold (collection-scaffold.ts,infer-collection.ts); 98 unit tests; background queue at 500+ entries; freemium split of 5 platforms + 9 universal formats free vs 31 additional platforms and enterprise features (rollback, delta, PII, webhooks, audit logs) via marketplace license. - Collection Builder quick-start templates: 7 setup presets — Website Starter is the recommended default; five additional content templates ship full schemas (Blog/Editorial, Agency/Portfolio, SaaS Product, Corporate Site, E-commerce, Blank).
- Website Starter + Svedit (July 2026):
svedit@0.12.0public routes (src/routes/(site)/) cover the SSR homepage (slug: home→/) and catch-all pages;SITE_STARTER_ENABLEDdefaults totrue; CMS form + Svedit JSON saves are free while the Editable Website plugin (€14.99, 14-day trial) gates Live Preview and inline click-to-edit; 15 unit tests plus asite-starter.spec.tsE2E smoke. - Cold start & progressive init:
handleSystemStatewaits synchronously for the DB on the first request (no intermediate page or polling), empty collections redirect straight to Collection Builder, andMAINTENANCE/FAILEDstates render the standard SvelteKit error page. - Auth expansion:
sck_*API keys stored SHA-256-hashed across all 4 DB adapters (generateApiKey(),createApiKey/revokeApiKey/listApiKeys,GET/POST/DELETE /api/api-keys,apikey:{sha256-b64url}cache tags,invalidateApiKeyAuth()L1/L2 purge,api:api-keyspermission); single-use magic links consumed atomically (TOCTOU guard); ephemeralANONYMOUS_USERguest role withcontent:read/media:readonly and no session or cookie; WebAuthn/passkeys ~75% complete (webauthn-cbor7 pass,webauthn-attestation4 pass, pending settings-page UI); 32 auth-expansion tests + 68 defense-in-depth security regression tests — all passing. - SSRF IPv6 transition blocking: the egress guard and cloud-storage validator now block IPv4-mapped IPv6 (
::ffff:0:0/96, e.g.::ffff:127.0.0.1), 6to4 (2002::/16), Teredo (2001::/32) and bare IPv6 hosts with zone IDs (::1,fe80::1%eth0), closing paths such ashttp://[::ffff:127.0.0.1]:27017/into local services. - Binary MIME sniffing for large uploads: files of 5MB and above are now verified via
file.slice(0, 2048)before stream processing (smaller files already usedsniffMimeType(buffer.subarray(0, 2048))); the ~2KB zero-dependencyslim-sniffer.server.tsdetects 10+ formats by magic bytes (JPEG, PNG, GIF, WebP, SVG, MP4, WebM, PDF, DOCX) and SVGs additionally passsanitizeSvg(). - Email normalization:
normalizeEmail()(Unicode NFC normalization, whitespace trimming, case-folding) is applied across all 5 auth adapters, replacing 13 raw.toLowerCase()call sites and closing homoglyph account duplication. - Scheduled-publish integrity: a pre-publish scan builds a
nodeStatusByIdstatus map and inspects relation values matching MongoDB ObjectId patterns (24-char hex), skipping the publish when a referenced entry is still draft;cacheService.invalidateCollection()now runs after each scheduled publish, clearing L1 + L2 so GraphQLpublicationFilterresults stay fresh. - Bulk-update and batch fixes:
copyDataWithFreshRowIds()regenerates_dndId,_rowId,uuidandkeyviacrypto.randomUUID()in the SDKbulkUpdate(); MongoDBbulkWriteswitched from{ ordered: false }to{ ordered: true }to preserve write order (negligible performance impact; SQLite already sequential). - Dashboard chunk retry:
retryDynamicImport()usesbaseDelay × 2^attemptbackoff (1s, 2s, 4s) with+random(0, 200ms)jitter,maxRetries3 andbaseDelayMs1000, wired into dashboard widget dynamic imports with toast error UX. - Data Operations — 6 API domains (July 2026): replaces the placeholder
/api/config_syncendpoint — Configuration Promotion/api/config/*(ConfigService, 1,066 lines), Content Packages/api/content-export/*+/api/content-import/*(ContentPackageService, 1,133 lines; NDJSON streaming; 4 duplicate strategies skip/update/create-copy/fail), Data Migrations/api/migrations/*(MigrationEngine, 984 lines), External Importers/api/importers/*, Backups & Restore/api/backups/*(BackupService, 1,770 lines; SHA-256 manifest integrity + AES-256-GCM encryption), Content Sync/api/content-sync/*(ContentSyncService, 785 lines; disabled by default). - Data Operations surface & measured performance (SQLite): fail-closed granular read/write/apply permissions per namespace (unmapped namespaces return 403); 15 new webhook event types; GraphQL 16 types, 4 queries, 12 mutations; 6 lazy-loaded LocalCMS namespaces at 0% middleware tax; all 5 core services use
dbAdapter.crudon MongoDB, PostgreSQL, MariaDB and SQLite; 89 new unit tests (config-service 20, config-permissions 24, operation-plan 26, identity-matching 19); 2 new benchmark suites (config-promotion 297 lines / 6 scenarios; content-package 2 scenarios); measured: config status 3.3ms avg, export (100 cols) 5.3ms avg, plan 3.1ms avg, apply 3.6ms avg, content-package export (100 entries) 1.12ms avg, import plan 1.01ms avg, SDK overhead 0.00% — all operations sub-5ms.
3.4 June 2026 All-Phase Performance Optimization Sprint
- Sprint scope: mid-June 2026, a 19-file multi-phase optimization sprint driven by the 53-script benchmark matrix.
- Phases R1/R2 — hot path: hook timing (
performance.now(),traceSpan, Map writes) gated out of production viaHOOK_TIMING_ENABLED(also underBENCHMARK=true); pre-compressed Brotli/gzip cache entries stored on MISS and served directly on HIT;X-Original-Size/X-Compressed-Size/X-Compression-Ratio/X-Compression-Algorithmheaders pluscompression.*benchmark metrics; bulk cache clears via Set iterators,_isBulkClearingdeferred tag cleanup, and a_cdnActivefast-path that skips the dynamic import chain when no Cloudflare credentials are configured, with sharedsetCompressionHeaders(),negotiateEncoding()andhasNativeCompression()utilities. - Phase P1 — audit/turbo: fire-and-forget audit logging (user context captured before
await resolve(event), log written in a detached promise); the June 14 “−3.57 ms” audit-overhead reading was retracted as a lane mismatch — the same mutation lane now reads 0.590 ms warm / 0.874 ms p95 (ledger, 2026-09-18) and ~2.0 ms in the 2026-09-22 standalone re-run;__turboAuthskips double audit logging on turbo-served mutations. - Phase P2 — cold path: OpenAPI pre-warm (
apiSpecService.generateFullSpec()after content init) removes the ~2.2ms cold first-hit on/api/openapi.json;clearLocalL1ByTagsswitched to batched collection + deferred tag cleanup. - Phase P3c/3a — batch relational upserts:
setManyreplaces N individualSELECT+INSERT/UPDATEloops with oneINSERT ... ON CONFLICT DO UPDATE(dialect-specificexcluded.*/VALUES()), turning ~80 setup-seeding queries into 1; measured June 14 RECORD runs — relational deep 3.61ms avg / 6.06ms p95 (−88% avg, −86% p95), relational shallow 6.68ms avg / 10.71ms p95 / 146 RPS (−78% avg, −75% p95, +336% RPS); reorders wrapped in single transactions. - Phase 3b — trained zstd dictionary removed: 0% improvement over generic Brotli at production quality levels (Brotli-4) and no
BROTLI_PARAM_DICTIONARYexposure, so the builder, dictionary files and runtime loading code were deleted. - Enterprise scaling layers: PgBouncer/ProxySQL/Nginx documentation with inline copy-paste configs in
scaling-layers.mdx,DATABASE_PREPAREcontrol for prepared statements behind transaction-mode poolers, and pooler URL support across PostgreSQL/MariaDB adapters; all layers optional — single-node SQLite + L1 remains valid, with Redis for cross-node invalidation, PgBouncer for Postgres multiplexing and Nginx for TLS/static offload. - Measured results table (
BENCHMARK_RECORD=1, June 14 2026): pre-fix relational ~30ms N+1 estimates → 3.61ms avg (−88% avg, −86% p95) and 6.68ms avg / 146 RPS (−78% avg, +336% RPS); setup seeding ~80 individual queries → 1 batch (−98% round-trips); mutation + audit 1.90ms avg (2026-06-14); bulk cache clear ~3-5× less allocation; CDN purge ~100× less overhead; OpenAPI cold first-hit ~2.2ms → 0ms — methodology: all gains measured viaBENCHMARK_RECORD=1on Intel i7-13700H, 32GB DDR5, Windows 11, Bun 1.3.14, SQLite WAL mode. - June 20 soak test: sustained mixed workload (health checks, collection listing, entry reads, schema resolution) at 4 concurrent workers — 5 minutes in CI, 4+ hours locally — recorded 8,989 total requests, 0 errors, RSS 351 → 312 MB (−39 MB), heap slope −0.3 MB/min (a positive slope above 1.0 MB/min would indicate a leak) and latency drift −0.6 ms/min.
- June 20 stability proofs: SQLite failover recovery in 78ms (service restored under 100ms after a simulated disconnect); large-payload streaming at 333 MB/s with a STREAMING verdict — 5-10MB uploads stream without full buffering and RSS delta stays under 6× file size; SqlAdapterCore refactor eliminated 1,644 lines (−30.6%) with all 1,152 unit tests passing; 4 security fixes (2 CRITICAL token-hashing, 2 HIGH race conditions) — website and auth tokens now SHA-256 hashed across all adapters with atomic consumeToken.
- June 20 behavioral learning (active): server-side, tenant-isolated, zero-PII tracking with
recordCollectionAccess()under 0.001ms,recordEntryAccess()under 0.001ms,predictNextPath()under 0.05ms andgetHotCollections()under 0.05ms, feeding adaptive cache warming, prefetch hints and dashboard reordering. - June 20 AI scaffolder & marketplace: one-click
installPlugin()frommarketplace.sveltycms.com;scaffoldWidget(config)→ 3 production-ready files; marketplace REST + License v1POST /api/v1/license/verify; generated widgets use Svelte 5 runes + Tailwind v4 + WCAG 2.2 AA. - August 2026 — Marketplace Phase 2: in-app Extensions → Marketplace tab (
GET /api/marketplace) with offline-first merge (local themes and dashboard widget packages appear when marketplace.sveltycms.com is unreachable); widgets ship as kebab-case package folders (widgets/<folder>/<component>.svelte+ requiredwidget.json+.mdx, compile-timeimport.meta.globdiscovery,installDashboardWidget()); licensing — free (System Health, CPU, Media Storage), freemium 14-day key-less trial (Audit Log €24.99, Unified Metrics €14.99, Logs €6.99, Smart Importer), paid (SCIM Status €12.99, Editable Website €14.99) — enforced client-side via/api/system/license-status?type=dashboard&id=<id>and server-side viacheckExtensionLicense("dashboard", widgetId)→ 403 LICENSE_REQUIRED.
3.4.1 LiteRT.js AI Client — Client-Side Browser Inference (July 2026)
- Client-side browser inference (July 2026): LiteRT.js (Google’s Web AI runtime, announced July 9, 2026) runs entirely in the browser with zero server cost and zero data egress, in a dedicated Web Worker served from
/ai/workerwith its own relaxed CSP — admin headers (COOP: same-origin,COEP: require-corp,frame-src 'none') stay untouched and the worker has no DOM or cookie access. - Runtime & fallback: LiteRT.js WASM (~15 MB) with WebGPU primary and XNNPACK CPU fallback; transparent server-side Ollama (llava / nomic-embed-text) when WebGPU is unavailable; lazy WASM download on first inference, not at page boot; first use case is alt-text generation for WCAG/ATAG compliance via
ai.generateAltText(image). - Measured performance: MobileNetV3 (4.5 MB) alt-text via WebGPU completes in ~150-200ms on a 2024 MacBook Pro M4; EmbeddingGemma (18 MB) generates 384-dimensional vectors in ~300-400ms; both fall back to CPU (XNNPACK) at ~2× the latency; sub-200ms is a target for small tasks, not a guarantee.
3.5 June 23 Content & Media Architecture Consolidation
- Content system: 16 files → 9 files (~200 lines removed) with 8 scattered modules consolidated into
engine.server.ts(scanner + reconciliation + cache + watcher + CRUD) andloader.server.ts(path security + native/pooled schema loading + worker pool); one sharedscanCompiledCollections(), a unifiedrefreshContent({ mode: "full" | "schemas" | "incremental" })replacing 3 scattered refresh paths and a singleensureContentInitialized()coordinator; ~75 lines of dead code plus ~90 duplicatedcontentSystemBaselines removed; 20 new reconciliation unit tests; benchmark-verified: content scan 0.008ms avg (150 files), stress scan 0.044ms warm (1,000 files). - Media system: 17 files → 14 files (~65 duplicate lines removed); 3 duplicate modules merged into
media-utils.ts(MIME table expanded from 30 to 65 types with reversegetExtensionFromMimeType()lookup);sharing.tswired throughcreateLink()/validateLink(); 2KB zero-dependencyslim-sniffer.server.tsMIME fallback; 4 DAM endpoints (bulk download, analytics, version list, version compare); real chunk-basedparseMultipartStreamfor multi-GB uploads; inline metadata editing in the media drawer. - Media tests & benchmarks: 40 new unit tests (sharing 13, media-utils 15, slim-sniffer 12); upload 10MB at 32.3ms (310 MB/s); SDK processing 2.7ms (319 images/s).
- Focal Point & Aspect Preview (July 2026):
CmsMediaMetadata.focalPointpercentage{ x, y }persisted per image via metadata-onlyPATCH /api/media/:id; CSS-only preview grid across 7 preset ratios (16:9, 3:2, 4:3, 1:1, 2:3, 9:16, 21:9); crosshair drag with 1px arrow-key steps and 10px Shift+Arrow steps; opt-infocal-pointplugin (disabled by default) injects intomedia_gallery,image_editor_tooland media-upload zones; comparable to Drupal’s Focal Point module and Payload CMS’s aspect-preview plugin, which ship as optional extensions there.
3.6 July 16 Media System Overhaul — Security, Types & Streaming
- Security hardening (7 critical vulnerabilities; 3 CRITICAL + 12 HIGH fixes): path traversal in
LocalStorageAdapter.getMetadata()andbulk-download.ts(resolve()+startsWith()/safeResolve()gates), SVG injection via watermark/annotation attributes (SAFE_COLOR/SAFE_NUMBER+guardAttr()), signed-URL timing oracle (constant-time rejection on invalid hex), tenantId pipe injection (replace(/\|/g, "_")), MIME confusion from bare SVG bytes, binary corruption in streaming uploads (byte-level state machine), upload memory exhaustion (incremental parser + backpressure), secrets moved fromprocess.envtogetPublicSettingSync(), CloudinarysafeFetch()+validateEgressUrl()+ timeout, dead EXIF parsing replaced by binary tag extraction, Windows temp-file leak fixed with cross-platformfinally { unlinkSync() }cleanup. - Type safety & architecture:
MediaItemdiscriminated union of 5 variants (MediaImage | MediaVideo | MediaAudio | MediaDocument | MediaRemoteVideo) with 7 type guards (isMediaImage(),isStoredMedia(),isMediaOfType(),assertNever());NewMedia<T>blocks server-assigned fields (_id,hash,url,createdBy) at compile time andMediaPatchrestricts PATCH to user-editable fields; typedSharpPipeline;dispose()?onStorageAdapter;as constvalue maps;normalizeWatermarkPosition()alias normalization. - Streaming upload parser: byte-level state machine (never buffers the full body), binary-safe
Uint8Arraychunks, per-file backpressure; configurablemaxFileSize(1 GiB),maxTotalSize(5 GiB),allowedMimePatternand per-chunk read timeout (300s); per the 2026-09-18 update the route now passes explicit caps both set toAPI_MAX_BODY_SIZE_BYTES(15 MB) and oversized uploads abort with413 PAYLOAD_TOO_LARGEinstead of buffering toward those defaults. - Media reference reverse-index:
MediaReferenceIndexreplaces the O(n × entries)getMediaReferences()scan with an O(1) in-memory lookup (rebuild()lazy on first query,eventBus-driven invalidation on content mutations); estimated 50x speedup on repeated queries, pending benchmark verification. - MIME sniffer enhancements: TIFF (little- and big-endian), AVIF/HEIC via
ftypbox brand parsing, ZIP sub-type detection splitting DOCX/XLSX from plain ZIP archives, and a hardened WebP 12-byte bound check. - Measured impact: type guards 0 → 7 +
assertNever();as anycasts ~20 → ~1 (BaseEntity→ entry); path traversal vectors 3 → 0; upload data integrity corrupted → binary-safe; reference scan O(n × entries) → O(1) after lazy rebuild; upload memory full-file-in-RAM → proportional to network speed; temp-file cleanup Windows-leaked → cross-platform.
3.7 July 25 Security Hardening & OIDC Logout
- RBAC permission cache hardening:
invalidatePermissionCache()was defined but never called, so stale DENY results survived up to 5 minutes (PERMISSION_CACHE_TTL); it now runs on everyAuth.updateUser()mutation and globally fromAuthNamespace.updateRoles(), with the turbo auth cache cleared viainvalidateTurboAuthForUser(). - OIDC logout (all 3 mechanisms): RP-Initiated (
GET|POST /api/auth/oidc-logout;id_token_hint, allowlistedpost_logout_redirect_uri,state;end_session_endpointauto-discovered via.well-known/openid-configurationwith a 1-hour cache), Front-Channel (GET /api/auth/frontchannel-logout; returns 200 withCache-Control: no-cacheand clears SSO sessions), Back-Channel (POST /api/auth/backchannel-logout; validateslogout_tokenclaimsiss/events/sub/sid, rejects with 400); multi-provider registryregisterSsoProvider()+loadSsoProvidersFromSettings(). - Path traversal defense & media bulk delete:
engine.server.tsscanner adds apath.resolve()prefix check alongside the string guards, andconfig-state.tsvalidates the resolved path before dynamic import; media bulk delete uses 4 parallel requests (N/4 round trips, ~4× faster) with live progress (Deleting 3/12...) and publish-state gating. - Toast hardening, scanners & open item: toasts use restricted
ALLOWED_TAGS/ALLOWED_ATTR(mitigating CVE-2026-65902);validateRequiredFields()remains unit-tested but uncalled, tracked in the roadmap;security-audit.ts --fullruns OWASP + secret misuse + slop scans, and slop-scanner adds invalid Button variant detection (destructive→errorautofix),@applymisuse andgoto()anti-pattern checks.
3.8 August 2026 — Session, RBAC & Extension Security Sprint
- Shipped (self-verified,
nextas of 2026-08-04):SESSION_DEVICE_POLICY(single-per-devicedefault /single-per-user/allow-multiple) enforced at thecreateSessionchokepoint; absoluteSESSION_TTL_HOURS+ zero-query slidingSESSION_IDLE_HOURS; block/unblock/delete purging every session layer with permission, roles-list and turbo-auth caches invalidated on every mutation path; credential-free session snapshots; step-up re-auth for cross-session revoke (stateless HMAC, 5-min, session-bound); log-only IP/UA anomaly flagged once per session per hour;SESSION_MAX_PER_USERLRU eviction; expired rows purged every 5 minutes by a scheduled job. - Measured cost (self-measured, 2026-08-04, Intel i7-13700H, SQLite): the full security + auth pipeline (HTTP E2E, all hooks) averaged 0.881 ms over 7 recorded runs vs a 0.856 ms July baseline — +0.025 ms (~3%) for the whole session/RBAC stack, with static-asset and turbo baselines at parity; earlier +25% readings were traced to host CPU power-saving mode and re-measured at parity. Summary view: fail-closed authorization at the API dispatcher (unmapped namespaces return 403) plus the cache hierarchy adds ~0.03 ms to the full HTTP pipeline, amortized across cache layers, single-flight coalescing and zero-query design.
3.9 August 2026 — Middleware, Data-Layer & Core Hardening Sprint
- Middleware hardening (2026-08-04 → 2026-08-10): the 5–15s socket-holding honeypot tarpit became an instant decoy
200+blockIp()flagging; SSR cross-request state pollution fixed via request-scopedevent.locals; bounded rate-limit buckets (evict-oldest, “fixed window with adaptive pressure”); GET request coalescing; in-process mutation prewarm; GraphQL + settings-service pre-warm with parallelPromise.allhook loading; crash-safe 404-log flush + open-redirect guard; module-level settings cache and de-duplicated dbAdapter init; test isolation fail-closed (503 + 10s timeout); Local SDK boot fast-path; 18-hook READY pipeline with no per-request wrapper hops; WebSocket shutdown sends1001 Going Awayon SIGTERM/SIGINT; field-level read filtering via cachedFIELD_PERMISSIONS(write-guard still on the roadmap). - Data-layer parity:
crud.findPage(limit+1hasMore, optional total, keysetcursor),CountMode(exact|estimate|auto), L1 count cache (30s) plus unit/integration contracts — equal on SQLite, PostgreSQL, MariaDB and MongoDB, and adopted by the collection list path, status facets, users list, mediagetByFolder, website tokens, Smart Table saved views and thedb.<collection>.findPage(...)schema proxy. - Non-blocking write core:
create/updatepersist in sub-5ms (~4.20ms) and return200immediately; async audit/revision queues (SHA-256 Merkle-tree chaining, revision snapshots, L2 pattern purges) behindAUDIT_CHAIN_SYNC=falsedefault, withtrueproviding synchronous SHA-256 chaining inside write transactions for ISO 27001 / SOC2-style compliance (System Settings toggle); targeted writes on all 4 engines; single session⋈user JOIN cold path with 0 warm DB round-trips; zero boot settling delay;BENCHMARKis outbound-only. - Measured impact (self-measured, 2026-08-10, Intel i7-13700H, SQLite, standalone harness): Full Security + Auth Pipeline (all hooks, HTTP E2E) averaged 0.583 ms (p95 0.768 ms, ~1,442 RPS) — ~32% latency reduction vs the 0.856 ms July 31 baseline, with ~40% higher throughput; Turbo Pipeline (light) 0.285 ms / ~3,201 RPS; REST with API caching 0.540 ms; Static Asset 0.549 ms; Mutation + Audit Logging 1.031 ms; single-run variance ~±10%.
- Superseded (2026-08-23 → 2026-09-18): the 0.583 ms datapoint is not the current value — the August re-baseline moved the same scenario to 0.457 ms, and the SQLite ledger
HOOKS_TRACErow now records 0.358 ms warm / 0.532 ms p95 / 2,481 RPS (self-measured, single host).
3.10 August 2026 — ReDoS-safe linear threat scan
- Linear scanner: Layer 0 WAF and AuthGuard payload analysis share one O(n) scanner (
src/services/security/threat-scan.ts); per-request RegExp engines, the unused WASM instantiate stub andnew URL()on the allow path are gone; pathologicalselect/frombait returns in well under 25 ms (unit-tested); clean ASCII URLs skipdecodeURIComponent; SQLprepareValuesuses a char-code ISO datetime prefix. - Self-measured 2026-08-23: the
security-auditWAF Deep Analysisrow stays at 0.002 ms because it timesRequest.clone()+await analyzeRequest()(a ~1.5 µs floor hiding the matcher); isolated microbench (200k iters, clean/api/collections/posts?limit=10): previous regex inspect 1,495 ns/op / 669,119 ops/s → linearinspectRequest96 ns/op / 10,388,153 ops/s →isCleanRequestSurface61 ns/op / 16,442,771 ops/s, i.e. 15.5× less work on the allow path (ReDoS-safe).
3.11 August 2026 — Entry list / field-editor widget + plugin path
- Widget/plugin path fixes: widget resolution survives a registry not yet ready at
onMount,field.widgetnames resolve, pluginentry_editslots respectslotRegistry.version, plugin list columns skip disabled plugins, plugin cells use a warm L1 peek and plugin tabs load when selected. - Self-measured 2026-08-23 (
entry-edit-hydration.test.ts): 50-field loader resolve 0.053 → 0.023 ms (~16.3k → 31.9k RPS, ~2×); widget prefetch 0.013 → 0.009 ms.
3.12 August 2026 — Layout user / plugin / collection-order fetch tax
- Fetch tax removed: every admin navigation re-read the user from the DB, ran a
findOneper slotted plugin and parsed the collection-order manifest from disk, while the settings TTL was passed as30_000intocacheService.set(seconds) although the comment said 30s L1; now 15s L1 covers the user snapshot, user count and plugin enablement, onefindManycovers all plugin states, collection order is mtime-cached, widget first-load no longer busts/api/widgets/active?refresh=true, profile/usershares the cache and attribute writes/plugin toggles invalidate it.
3.13 August 2026 — Auth-path logs + TOTP backup HMAC (hooks re-bench)
- Auth-path fixes: debug logs behind
logger.isEnabled("debug")and expiry checks via.getTime() < Date.now();getSmartFirstCollectionmemoized oncontentVersion; cache-lock owner viacrypto.randomUUID()only; TOTP backup codes HMAC-SHA-256 (backupcode-hmac:+ JWT secret) with the legacy plain SHA-256 scheme removed in 2026-08. - Self-measured 2026-08-23 (SQLite, two consecutive
hooks-performanceruns vs 0.598 ms baseline): Full Auth Pipeline avg 0.457 ms (0.474 first re-run), −23.6% / −20.7%; p95 0.829 → 0.698 / 0.617 ms, −16% / −26%; Auth Overhead (Turbo→Full) 0.276 → 0.143 ms, −48%; Auth @1c avg 0.396 → 0.369 ms, −6.8%; Pipeline @8c 1.244 ms / 3,708 RPS → 1.192 ms / 3,872 RPS, −4.2% / +4.4%; truth-latency HTTP E2E 0.505 / 0.813 ms → 0.509 / 0.710 ms (avg within noise, p95 −12.7%); security-audit pass with no regression. Published 7-run overlay: Full Auth 0.457 ms, stable at 0.474 ms; single-run variance ~±10%; the −20% middleware move is the two-run confirmation against 0.598 ms.
3.14 August 2026 — Hardware-adaptive tuning + honest 4-DB benchmark matrix
- Hardware-adaptive profile (
@utils/hardware-profile): one detection at boot (initHardwareProfile()inhooks.server.ts) published as a frozen shared object; globalHARDWARE_CPU_BUDGETdefault 0.75 (1 = dedicated app server); media/sharp up to 50% of the physical budget, DB fan-out 2 queries per budget core, compile 75% / jobs 50% / module workers 50% (capped); per-knob overridesSHARP_CONCURRENCY,DB_POOL_SIZE,MONGO_MIN_POOL_SIZE,MODULE_WORKER_POOL_SIZE,COMPILE_CONCURRENCY,JOB_CONCURRENCY,UV_THREADPOOL_SIZE; wired into all 4 DB adapters (pool defaults previously hardcoded 20/100/100), theloader.server.tsworker pool, compile + sync-content-state, job queue, compression (weak boxes cap gzip 4 / brotli 4), resilience prewarm, setup wizard and dashboard. - Media pipeline (validated on matrix): preset (width, format) dedup collapses 20 variant jobs into 12 unique files; responsive-variant generation for buffer uploads is deferred (fire-and-forget); self-measured 2026-08-23 — SDK media 210.0 → 116.3 ms (−45%), HTTP upload 208.4 → 105.2 ms (−50%).
- CacheService pattern invalidation:
tenant:global:<flat-key>now lives under thetenant:globalbucket so pattern purges hit one bucket instead of every key; self-measured 26 → 4.5 ms. - Benchmark matrix (2026-08-23): 200/200 green on the hardware-profile build (SQLite 50/0 · MariaDB 50/0 · Postgres 50/0 · Mongo 50/0; run 2 was 199/200, the lone
dev-dependency-loadfailure being auto-generated benchmark MDX drift, now oxfmt-formatted on write); seeder provisionsbench_index_pressure+bench_migration_large(fixing the 100k-row index audit failure); restart overhead cut ~20% (onlygraphql-stress→ relational-performance andlarge-payload-streaming→ migration-scale need a restart; the other 5 destructive tests skip the ~12–16s boot, saving ~4 min of the ~22 min run). - Pre-profile vs profile (run 2 → run 3, same machine): no systematic regression — the feared GQL-100c pool regression did not materialize (flat-to-better on all 4 DBs), media SDK −3%, with Postgres gains widget pipeline −44%, Yjs −37%, mixed workload −25%, SEO −17–27% and Mongo gains collection search −29%, uploads −17–20%; remaining flags are sub-5 ms run-to-run noise.
- Reproduce:
bun run scripts/benchmark-matrix/index.ts --continue-on-error(4 DBs, ~23 min);bun run test:unit(416 files / 3667 tests); GPU note — sharp/libvips exposes no GPU backend, so media parallelism stays CPU-tier capped and further gains come from job dedup/deferral.
3.15 September 2026 — Intelligence consumption + request-pipeline lean
Two follow-on sprints on top of the 3.14 profile build: the behavioral-learning engine stopped being dormant (renders predictions and heat), and the request pipeline lost its duplicate cache namespaces, duplicate compression, and per-SSR graph cloning.
Intelligence consumption — predictNextPath was computed per request and rendered as a single <link rel=prefetch>; now it renders: (1) the sidebar nav link matching the server-predicted next path is tagged data-preload="smart" (src/components/left-sidebar.svelte); (2) the dashboard consumes the hotCollections already returned by its server load (getHotCollections(tenant, 20)) as compact quick-link chips above the widget grid; (3) heat-aware SWR in collection-service.ts: behaviorally-hot collections (top-20, hot set memoized 10 s per tenant so the per-request cost is one Set.has()) keep the 60 s fresh TTL but serve stale up to 15 min while revalidating; cold collections keep the default 5-min stale window. Roadmap follow-up kept honest: Smart Table “warm next page” viewport preload is still pending — pagination next/prev controls are button-driven today, so the data-preload viewport hook needs the controls converted to real links first.
Request-pipeline lean (hooks + dispatcher): (1) one canonical L2 key — handle-api-requests.ts and the API dispatcher now share a single buildUserCacheKey ({path}?search:u:{user}) for the L2 body store; the duplicate api:{tenant}:{user}:{path} namespace (written/read only inside that one hook) was retired and invalidation clears the shared path prefix across users/searches; (2) single compression per body — the dispatcher marks its tagged L1 writes with a compressed: {} sentinel so response-cache’s internal br/gzip precompute never runs for a write the request hook replaces within the same request; the hook’s one fire-and-forget compressor (br+gzip+zstd, size-gated, genuinely async above the sync threshold) then re-sets the L1 entry with real variants and writes the single canonical L2 entry; (3) nav graph serialized once per (tenant, contentVersion) — the admin sidebar payload was rebuilt + recursively deep-cloned (stripNonSerializable) on every SSR; it is now cached at layout:contentStructure:{tid}:{version} and the root layout’s progressive tree at navigation:tree:progressive:{tid}:{version}, invalidated by the existing content-version signal; (4) one session-cookie resolution per request — authn/authz/turbo/dispatcher consumers now share one per-request slot resolved from the protocol-derived secure-context flag instead of re-parsing the cookie 4–5×. Deliberately NOT merged: the authz 1 h user-count cache (tenant-scope semantics differ — bypassTenantCheck vs tenant-null counts) and the layout’s 15 s fresh user read (avatar/role badge freshness is intentional).
Verified 2026-09-02 (SQLite): bun run check 0/0 · test:unit 453 files / 3,894 tests · test:security 262 · risk:audit 5/5 · test:integration 632 pass / 0 fail (19 adapter skips) · core benchmarks all green (incl. cache-performance, hooks-performance, rest-api-performance).
3.16 September 2026 — Limiter, tenant scope, Mongo REST, Yjs ACL
Follow-on to the dual-path collapse (one limiter, one CPU sampler, branded systemScope). Self-measured core suite on all four engines (console-only, no BENCHMARK_RECORD): SQLite FIND ONE ~0.005 ms / REST ~0.73 ms; Mongo FIND ONE ~0.54 ms / REST ~2.1 ms (HTTP/JSON still dominates the REST delta). Postgres/MariaDB INSERT ~1.7 ms on this host is the Docker Desktop TCP hop — unix sockets apply on Linux/macOS, not Windows.
- Dead Lua copy removed from
handle-rate-limit.ts. Mutation traffic usesrateLimit()(RedisRateLimitStore+MemoryRateLimitStore). Atomic Redis Lua remains in@utils/rate-limit/redis-client.tsonly. Tests:tests/unit/hooks/rate-limit.test.ts,tests/unit/rate-limit/engine-fallback.test.ts. bypassTenantCheckboolean is not a query option. Adapters forward brandedsystemScopefromwithSystemScope(reason).hasTenantBypassaccepts only that brand orbypassSafeQuery. Tests:tests/unit/security/system-tenant-scope.test.ts,tests/unit/security/tenant-isolation-matrix.test.ts.- Mongo REST point-read uses native
collection.findOne, SDKfindByIdpassesskipMeta, and BSON ObjectId_idis stringified before JSON. Same-host:src/databases/db-local-socket.ts(Postgres/Maria unix socket auto-detect; Mongo loopbackdirectConnection, no wire compressors). Tests:tests/unit/databases/db-local-socket.test.ts. - Yjs entry ACL —
GET/POST/api/collaboration/yjsrequiredocId=entry:{collectionId}:{entryId}andcollection:read/collection:write(collection-scoped, then global). ACL helpers sit inyjs-access.tsrather than+server.ts(SvelteKit route analysis rejects non-handler endpoint exports; the production build failed on that). Tests:tests/unit/api/yjs-acl.test.ts. - Client-safe tenant split —
isMultiTenantEnabled()/withTenant()moved tosrc/utils/tenant-isolation.server.ts, becauseMULTI_TENANTis a private key and@utils/tenantis imported by browser code (Yjs provider, SDK config page).isValidTenantId()lives insrc/utils/tenant.server.ts, which keepsvite.config.ts’s alias-free config graph intact (compilation/compile.tsimports it directly).globalThis.requireis gone end-to-end. - Session AMR / MFA persistence —
auth_sessionsgainedamr(JSON/JSONB) andmfaVerifiedAt(tsCol(3)→DATETIME(3)on MariaDB,TIMESTAMP WITH TIME ZONEon PostgreSQL, INTEGER ms on SQLite) across the schema spec, all three Drizzle schemas and the boot-time legacy-tailALTERs, so existing installs upgrade on next boot. Mongo’sSessionSchemanow declares both — strict mode used to strip them, so no engine persisted AMR before this. Session-id rotation carries the MFA proof and device metadata instead of dropping them, and Mongo rotation degrades to sequential writes when the deployment has no transactions (standalone/CI) instead of throwing out ofstartSession(). Tests:tests/integration/databases/auth-session-amr-contract.test.ts(all four engines). Cost guard:tests/benchmarks/auth-session-amr.test.ts—INSERTΔ(amr vs plain) −14.6 % SQLite / −9.5 % PostgreSQL, i.e. below the noise floor;validateSession(the per-request path) never reads the session columns. - Adaptive smart rate-limiting & load shedding — EWMA system pressure load shedding (
system-pressure.ts, polling process CPU delta and container loadavg), burst velocity penalization (request-velocity.ts), route-aware token pricing (endpoint-cost.ts), and dynamic user tier and tenant plan quotas (role-tiers.ts,tenant-plan.ts) unified underhandle-rate-limit.ts. Tests:tests/unit/rate-limit/. - Vitest configuration & test suite hygiene — explicitly configured
isolate: trueinvitest.config.tssilencing misleading module-evaluation diagnostic hints while strictly preventing cross-file mock and singleton state contamination. Appliedsilent: "passed-only"locally to eliminate console clutter. Full suite: 504 files / 4,303 tests pass in ~47s.
3.17 September 2026 — Authorization guardrails, APQ binding, 4-DB pagination fixes
A security + performance wave that closed two authorization bypasses, hardened the persisted-query cache and the request-size surface, and found two long-standing product bugs on the database layer. Verified locally: bun run test:unit 515 files / 4499 tests · SQLite integration 660 tests / 71 files (641 pass / 19 adapter skips) · the 7-file cross-engine contract bundle 58/58 on SQLite, PostgreSQL, MariaDB and MongoDB · tsc --noEmit 0 errors · risk:audit 5/5 · bun run lint:docs 0 errors.
3.18 September 2026 — Smart Table platform & multi-core test acceleration
Platform deliverables and high-performance test suite optimization wave:
- Smart Table column resize & layout namespaces —
setLayoutKey(key: string)onSmartTableApi<T>with persistence namespace separation; integratedColumnResizeHandleinto media gallery, users, and website-tokens tables. Pinned columns styled with solidbg-white dark:bg-surface-900backgrounds to prevent content bleed-through during horizontal scroll. Tests:tests/unit/components/create-smart-table.test.ts. - Bulk field-edit UI modal — created
bulk-field-edit-modal.sveltewith Svelte 5 runes, strictFieldInstanceschema typing (zeroany), full WCAG 2.2 AA accessibility, and automated widget input mapping (text, number, boolean, date). Integrated withAlt+Eshortcut inentry-list-multi-button.svelte,entry-list.svelte, and batch API handler inentry-actions.ts. Tests:tests/unit/utils/entry-actions.test.ts,tests/unit/utils/api.test.ts. - Vitest dynamic multi-core worker scaling — scaled worker pool dynamically based on host CPU core count (
cpuCount >= 16 ? Math.min(16, cpuCount - 4) : cpuCount - 1) withprocess.env.VITEST_MAX_WORKERSoverride invitest.config.ts, utilizing up to 16 concurrent workers on 24-thread hosts while leaving 4 cores for OS and IDE responsiveness. - Individual test bottleneck elimination — severed dynamic
import("@src/hooks.server")in user/auth mutations by movinginvalidateTurboAuthForUserinto lightweighthandle-turbo-get.ts(auth-lockout.test.tsdropped from 2,270 ms to 81 ms,user-attribute-update.test.tsfrom 1,439 ms to 75 ms); replaced dynamicresetModules()with static imports intesting-api-gate.test.ts(466 ms → 75 ms); addedretryDelayMultiplier: 0inseed-burst.ts(eliminating 200 ms wall-clock sleep; 312 ms → 86 ms); moved unmocked dynamic DB import tobeforeAllindatabase-resilience.test.ts; addedcmd.flushDb()on test initialization incache-service-l2-contract.test.tsfor clean Docker Redis isolation. - Suite acceleration metrics — unit suite accelerated from 48.27 s → 18.79 s (~61% speedup, 2.57× faster) across 515 files / 4509 tests with 100% pass rate and isolation preserved (
isolate: true).bun run checkclean,risk:audit5/5,lint:tenant -- --fullclean.
Authorization (dispatcher + handler) — POST /api/user (bare namespace root) was exempted from the endpoint gate because the dispatcher treated “no action segment” as public, so any authenticated user could create accounts carrying privileged fields; GET /api/user (directory listing) was exposed the same way. Roots now fall through to user:write/user:read, handleCreateUser strips privileged fields for non-admins via sanitizeClientUserAttributePatch, and "roles" joined PRIVILEGE_ESCALATION_FIELDS. Behaviour change: GET /api/user now requires user:read.
Token namespace — /api/token/* was a public-route deny-list, which also kept the dispatcher’s api:token mapping out of the picture for PUT/DELETE. It is now a positive allowlist: only GET /api/token/validate-token/{value} is public; list/create/resolve/batch and PUT/DELETE require an admin session or the mapped api:token permission, and updates are field-allowlisted (expires, blocked, consumed; admins additionally email, role; never user_id, type, token, _id, tenantId). Behaviour changes: non-admin sessions can no longer create/list/update/delete tokens; unauthenticated GET /api/token/{value} now 401s; the public validation contract is GET /api/token/validate-token/{value}.
GraphQL APQ — extensions.persistedQuery keys must now be exactly 64 hex characters and sha256(query) is compared constant-time before a query may be registered, closing cache poisoning/flooding of the persisted-query store (PERSISTED_QUERY_HASH_INVALID / PERSISTED_QUERY_HASH_MISMATCH). Registered queries keep the L1 RAM / L2 Redis store with a 7-day TTL.
Request-size & page-size ceilings — chunked bodies (Transfer-Encoding: chunked, no Content-Length) are counted through a zero-copy stream in the dispatcher and bounded at the same 15 MB API_MAX_BODY_SIZE_BYTES ceiling as declared lengths (413 PAYLOAD_TOO_LARGE); the streaming media route receives that counted stream undrained so incremental multipart parsing still works, and parseMultipartStream is called with explicit caps and aborts incrementally instead of buffering toward its 1 GiB / 5 GiB defaults. Pagination limit is clamped centrally at MAX_PAGE_SIZE = 200 in the REST parsers, the SDK find funnel and the GraphQL fast path + resolver.
Guardrail suite — tests/unit/api/authz-bypass-guard.test.ts (27 tests) enumerates every /api/<namespace> root and every public-route exemption against a reasoned contract, so a new authorization bypass fails a unit test rather than shipping.
Performance — zstd is no longer synchronous on the event loop (async native API; size-gated sync fallback at SYNC_MAX_SIZE 64 KiB); count-cache entries carry both logical and physical collection spellings and the write path clears both (list totals were stale for the 30 s TTL); content.nodes.bulkUpdate collapses N per-row DELETEs into one IN (...) and reorderStructure batches per-item SELECT * into one projected IN (...) read (2N → N+1 on SQL); the security gate no longer computes and discards duplicate surface/UA scans (≈4 string scans + 2 allocations per API read → 2 + 1); the tenant proxy memoizes per-property wrappers; auth row conversion uses the registered-schema fast path on PostgreSQL/MariaDB too; media gallery composite indexes (tenantId, folderId, updatedAt) + (tenantId, updatedAt) were added to all three SQL dialects and the system schema spec, with idempotent boot tails for existing installs; commerce order downloads do one batched product lookup instead of one per line, and internal analytics/redirects callers no longer depend on the capped public page size.
Two pre-existing product bugs found and fixed by this work (not known before) — plus a MariaDB repair gap:
- Numeric sort directions were mis-mapped to DESC in every SQL sort emitter —
{status: 1}emittedstatus DESCwhile the keyset cursor said ASC, so keyset pagination repeated rows and terminated early on SQLite/MariaDB/PostgreSQL. A singlenormalizeSortDirection()incore/page-utils.tsnow feedspage-utils,drizzle-sql-helpers,sql-adapter-coreand the query builder. - MongoDB
reorderStructurenever worked — it calledmongoose.startSession()on the global default connection while the adapter owns amongoose.createConnection, so every drag-reorder returnedsuccess: false. It now uses a single orderedbulkWrite, matching the adapter’s declaredsupportsTransactions: false. - MariaDB repair gap —
ADD COLUMN IF NOT EXISTScould never fix an existingmfaVerifiedAt datetime(fsp 0) column, so millisecond MFA timestamps silently truncated; a guarded boot tail now runsMODIFY COLUMN … DATETIME(3)once per install.
Tests: tests/unit/api/authz-bypass-guard.test.ts (27), tests/unit/api/dispatcher-body-limit.test.ts, tests/unit/media/streaming-upload.test.ts, tests/unit/databases/media-gallery-indexes.test.ts, tests/unit/databases/content-node-batching.test.ts, tests/unit/databases/mongo-reorder-structure.test.ts, tests/unit/core/auth-row-converter-parity.test.ts; contract suites extended in find-page-count, cache, content-nodes, auth-session-amr (tests/integration/databases/) and tests/integration/api/token.test.ts.
3.19 September 2026 — Version tooling: derived bumps, real SemVer, one update check
Version source — bun run version:bump [auto|patch|minor|major|<x.y.z>] (scripts/version.ts) derives the next version from the highest v* tag reachable from HEAD (note: git describe returns the nearest tag here, which disagrees, so the script prints the discrepancy). auto implements SemVer from Conventional Commits: feat → minor (§7), breaking markers → minor while major is 0 (§4, matching release-please’s 0.x rule) / major afterwards (§8), everything else → patch (§6), with breaking > feat > rest when a range mixes types. An explicit <x.y.z> target sets the manifest exactly, for releases that must skip or pin a version. Guards: refuses a dirty manifest, refuses to re-release an already-tagged version (the failure the workflow previously swallowed as “Tag already exists”), refuses a downgrade, and never commits or tags. The policy is a pure exported decideBump() with 33 unit tests, mutation-checked.
Mode parity / ledger — the correction from the first pass: an apparent cache-hit-ratio regression (+83 %) was a baseline mode mismatch (a TEST_MODE=true standalone baseline compared against production-parity matrix runs; the same build re-measured in the baseline’s own mode is 0.538 ms). Trend rows now carry a server-mode stamp, matrix recording refuses non-production servers, and contaminated ledger rows were purged.
App side — two parallel version services were collapsed into services/core/version-service.ts (readLocalVersion, checkForUpdates, compareVersions implementing SemVer §11 prerelease precedence: 0.0.10-dev.3 orders after 0.0.9 and before 0.0.10), the observability duplicate deleted, and the fake unit test (it re-implemented its subject and asserted its own copy) replaced with 21 tests against the real implementation. The badge was simplified from ~10 parallel state variables to one state object with derived presentation — which fixed a real defect: the loading guard made the initial call return immediately, so the update indicator had never fetched at all. ai-service.ts no longer hardcodes a version in its outbound User-Agent/payload.
Coverage added — the previously unmeasured claims are now measured: media-gallery composite indexes (planner-proven index_used=1; 0.396–0.410 ms on 4 000 rows SQLite, 1.248 ms PostgreSQL), zstd-async (477 KiB GET 6.0–6.2 ms avg, 300–315 RPS, co-tenant p95 0.37 → 1.06–1.48 ms — no event-loop stall). The body-limiter and scan-dedup changes remain deliberately unmeasured (sub-µs work on a ≥1 ms path, ~3 orders below the noise floor).
3.20 September 2026 — Unified Translation Cockpit & Multilingual Studio Inspector
A comprehensive multilingual management and inspection suite bridging admin UI chrome localization with per-field content readiness:
- Dual-Lens Language Management: Simultaneously tracks and toggles UI system language (Paraglide compile-time catalogs) alongside content editing languages (
systemLanguagevscontentLanguage) withintranslation-cockpit.svelte. - Document-Level Translation Completeness: Computes real-time translation completion percentage per document across all configured system languages via
computeDocumentTranslationSummary()(src/utils/translation-analytics.ts). - Field-by-Field Deficiency Navigation: Visual deficiency matrix identifies untranslated fields per language. Clicking any deficiency jumps directly to the field, switches the content locale, scrolls the field into view, and triggers an attention-highlight pulse.
- 1-Click AI Translation: Integrated 1-click batch translation of individual missing languages or entire documents via
/api/ai/translate, preserving field structures and formatting without manual copy-pasting. - Design Token & Accessibility Compliance: Status-shade tokens (
success,tertiary,warning,error), WCAG 2.2 AA compliant modal with focus trap and restoration. Tests:tests/unit/utils/translation-analytics.test.ts.
3.21 September 2026 — Dependency & Runtime Lean-Out
Execution of the full Lean-Out initiative to shrink bundle size, drop unused dependencies, and minimize runtime surface without feature loss:
- Complete Lodash Elimination: Fully purged
lodashandlodash.memoizeacross the codebase; replaced with native ECMAScript methods, typed object helpers, andfast-deep-equal. - Modularized On-Demand AWS SDK: Removed monolithic AWS SDK clients in favor of isolated
@aws-sdk/client-s3, loaded dynamically on-demand insrc/utils/media/storage-adapters.tsso storage drivers incur zero boot-time or bundle penalty when unused. - Dynamic MapLibre Loading: Geocoding and map rendering in
src/widgets/custom/address/input.sveltemoved to lazy dynamic import (await import('maplibre-gl')), keeping the primary widget bundle lean. - Zero-Shiki Runtime Surface: Removed unused
shikifrom dependencies and generated SBOM, eliminating redundant node dependencies. - Tiptap Lazy Evaluation: Rich text editor core and extensions are dynamically loaded (
import('./tiptap')) on component mount insrc/widgets/core/rich-text/input.svelte, keeping edit views lightweight on initial render. - Store Architecture Consolidation: Consolidated global stores from 28 files down to 18 domain singletons, standardizing on Svelte 5 fine-grained reactivity (
$state(),$derived()) across all 72 widget inputs, displays, and app shell routes.
3.22 September 2026 — Smart Table Advanced UX & Adaptive Grid Platform
A comprehensive overhaul of the core Smart Table data orchestration engine (src/components/ui/smart-table/ and src/components/collection-display/), delivering enterprise-tier UX, multi-column sorting, compound query operators, and accessibility compliance:
- Multi-Column Sort: Compose secondary and tertiary sorts via
Shift + Clickor headlesssetSort(field, { multi: true }). Renders visual ordinal rank badges (1,2,3) alongside directional indicators, cascades multi-criteria sorting in client-side comparators, and serializes compound queries to server URL parameters (sort=field1:asc,field2:desc). - Advanced Filter Operators UI: Integrated
AdvancedFilterModalcompound filter builder supporting multi-clauseAND/ORexpressions across collection schema fields with contextual operators (equals,contains,starts_with,gt,gte,lt,lte,is_empty,is_not_empty). Active clause counts surface in the filter bar. - Export Filtered Selection (
Alt + X): Browser-streamed CSV download bounded specifically to currently selected rows or the active filtered result set, accessible from both keyboard shortcuts (Alt + X) andEntryListMultiButton. - Keyboard Grid Navigation (WCAG 2.2 AA / ATAG 2.0): Fully keyboard-navigable grid using roving
tabindex. Arrow key cell traversal,Home/Endjump navigation,PageUp/PageDownleaping,Spacefor row selection, andEnterfor direct entry editing. - Responsive Card Mode: Dynamic view mode switcher (
Table ViewvsCard View), persisted per-collection viaSmartTableLayoutPrefs, with responsive fallback on mobile viewports (<768px). - Unit Testing: 7 new test cases in
tests/unit/components/smart-table-advanced-ux.test.tscovering multi-sort composition, cyclic direction toggle, rank indexing, client-side tie-breaking, and view mode persistence.
3.23 September 2026 — Surgical turbo invalidation (soak / mixed-write)
A single-entry PATCH used to wipe every turbo findById for that collection (invalidateLocal string-includes + res:<collection> tags on point-reads). L2 already cleared doc:<coll>:<id> surgically; L1 did not. Point-reads now tag only doc:, skip the shared L1, and invalidateLocal({ entryIds }) drops lists/GraphQL plus the written ids. In-repo replica (SQLite, 10k docs, 8c, 2026-09-20): mixed 1,447 RPS, 60s soak 1,362 avg RPS. Tests: tests/unit/core/request-classifier-and-response-cache.test.ts, tests/unit/sdk/collections-post-write.test.ts. Docs: Competitive Comparison, Performance Architecture.
3.24 September 2026 — Bounded caches, exact L1 accounting, batch seed lane
Three separate findings from a soak/seeding/CPU pass, each with its own measurement (4f3216920 + this change, SQLite, production-parity standalone benchmarks, shared 24-core host):
- The media storage existence cache is bounded now (was an unbounded
Map).src/utils/media/media-storage.server.tscached every storageexists()probe — positive and negative — in aMapwith a 10 s TTL and no cap and no eviction order, so its size grew with the number of distinct media paths a process ever probed. It is now anLRUCachewithSVELTY_FILE_EXISTS_CACHE_MAX(5,000 entries, LRU-ordered) andSVELTY_FILE_EXISTS_CACHE_TTL_MS(10,000 ms) — TTL andrefresh: truesemantics are unchanged. Test:tests/unit/media/file-exists-cache.test.ts(400 distinct paths flood, a 50-entry cap holds, negatives and positives both cached, TTL expiry, refresh bypass). The soak rows cannot show this one: the soak’s mix (health/list/item/schema/create) never probes a storage path. - The response-cache L1 byte budgets did not bind — the counters drifted on every path.
src/services/cache/response-cache.tsmaintainsl1Bytes/pointL1Bytesper tier; the L2-refill paths (get()/getAsync()) inserted without accounting andinvalidate/invalidateAll/doc-scoped drops/TTL expiry deleted without decrementing. Measured on the pre-fix code with the budgets pinned to 1 MB: 40 refilled entries 960,000 bytes resident while the counter read0, and after 10 sets + 1 key invalidate + 1 doc-drop + 1 expiry the list counter read 176,000 against 144,000 real bytes (point tier 160,000 vs 128,000) — i.e. the 64 MB/16 MB budgets (SVELTY_L1_MAX_MB,SVELTY_L1_POINT_MAX_MB) were only ever consulted against a number unrelated to the store, and the 2,000-entry cap was the only real bound (≈200 MB of 100 KB list bodies). Every mutation now runs through oneputEntry/removeEntryprimitive, enforcement runs after the insert (strictsize ≤ capandbytes ≤ budget, an oversized single entry is dropped rather than pinned), the budgets re-read their env value on change, andresponseCache.getL1ByteStats()exposes the counters plus an exact recount for diagnostics. Tests:tests/unit/core/response-cache-l1-budget.test.ts— 5 tests, all 5 failing on the pre-fix implementation and passing after (insert-above-budget shrinks both tiers, refill accounting, delete-path accounting, counter == recount through a mixed torture sequence). Docs: Cache System. - Seeding a 100k dataset got 5.3× faster through the documented bulk endpoint.
seedHttpCollectionBurstposted one HTTP create per row; it now claims chunks off a shared cursor and postsPOST /api/collections/:id/bulk(500 items, the registeredmigration-scalebatch size), retries transient statuses per chunk, and falls back to per-item creates for a range whose chunk fails or returns a mismatched row count — the dataset-integrity gate (created === requested) is untouched. Same build, same fresh DB, 100k docs, 8 workers: 74.77 s / 1,337 docs/s → 14.20 s / 7,042 docs/s; at 20k docs 18.86 s / 1,060 → 3.44 s / 5,815. Rows are equivalent (95,000 distinct slugs over 95,000 seeded rows,status/slug/tenantId/createdBy/createdAtall present) and the file’s measured rows still exercise the per-item lane (create/update/mixedunchanged;chunkSize: 1reproduces the old path for A/B). The seed phase is now a registered trend row —Seed Burst (<N> docs, HTTP)pluscompetitive.seed.docs_per_s/competitive.seed.wall_ms— instead of being invisible between two phases. Tests:tests/unit/bench/seed-burst.test.ts(batch lane + order, chunk retry, fallback, per-item lane, integrity gate). - The per-item write floor, measured. With
PROFILE_WRITE=1on the HEAD build, a create’s server-side cost splitsns:persist2.06 ms (of whichns:persist:adapter-write1.77 ms,db:ins:stmt0.97 ms) againstns:beforeSave0.07 ms,ns:encrypt0.03 ms andns:prep:*0.01 ms;hooks-performance.test.tsputs the mutation lane’s middleware cost at 1.633 ms warm (audit disabled) over a 0.35–0.65 ms read lane. That is the shape of thecreaterow: a single-statement INSERT under the SQLite write mutex plus a single-threaded JS pipeline — no per-item step I could find is removable without changing write semantics (per-hook attribution stays unavailable, see roadmap). - The soak’s RSS growth is stepwise, not monotonic — and it is not the two SQLite knobs. Bounded 3-minute variants (
LONG_SOAK_HOURS=0.05, fresh DB per run, first/last bucket reported): +54.4 MB on the pre-change build, +52.8 MB withSQLITE_MMAP_SIZE=0 SQLITE_CACHE_SIZE=8000(both native ceilings minimised — refuting the mmap/page-cache hypothesis that was the cheapest explanation), +53 MB on the new build. A 6-minute variant shows the shape: RSS steps +54 MB and +105 MB at ~2 and ~4 minutes and is otherwise flat (+0.6–3.6 MB per 30 s bucket between steps), whileheapUsedoscillates 73–168 MB without a trend — i.e. V8 committing heap pages, not an object graph that grows with request count. A 3–6 minute window cannot separate commitment from a slow leak; that needs the long row in roadmap-2026.
3.25 September 2026 — Long-horizon soak: the memory verdict, measured against controls
The bounded soak could not separate V8 heap-commitment steps from a slow leak (see 3.24). The long row now exists, with the two controls the verdict needed — and the answer is that nothing leaks; what grows is commitment, and it converges.
Run matrix (one Windows 11 host, Intel Core Ultra 9 275HX, 24 threads, 63.4 GB RAM; node index.cjs adapter-node server, Node v24.15.0, SQLite node:sqlite, --max-semi-space-size=128 --max-old-space-size=1024; 4 workers, ~97 req/s, 0 errors in every run; slopes are the harness’s own steady-window/tail fits in MB/min with t = slope/se):
| Profile / build | Window | Writes | RSS start → end | RSS tail (t) | heapUsed tail (t) |
heapTotal tail (t) |
Latency drift | Verdict |
|---|---|---|---|---|---|---|---|---|
mixed 1 h (HEAD) |
54.5 min steady | 34,878 | 218.5 → 430.3 MB | +1.35 (6.5) | −0.10 (−0.2) | +0.15 (9.6) | −0.000 ms/min | STABLE (No Leak) |
read-only 45 min (HEAD) |
39.5 min steady | 0 | 221.9 → 347.2 MB | −2.47 (−12.1) | −0.24 (−0.3) | −0.01 (−1.7) | −0.001 ms/min | STABLE (Plateau) |
idle 45 min (HEAD) |
39.5 min steady | 0 | 221.9 → 77.6 MB | −5.40 (−14.6) | +0.05 (8.2) | +0.08 (11.1) | 0.000 ms/min | STABLE (No Leak) |
mixed 45 min, page cache 8 MB |
39.5 min steady | 26,070 | 221.4 → 370.5 MB | −2.13 (−13.9) | +0.02 (0.0) | +0.27 (19.7) | +0.002 ms/min | STABLE (No Leak) |
mixed 45 min, page cache 256 MB |
40 min steady (2026-09-22) | 26,040 | 218.4 → 476.3 MB | +0.28 (2.0) | +0.06 (0.1) | +0.30 (20.8) | — | STABLE (Plateau) |
- The growth is V8 commitment, not retention. Every loaded profile shows the same shape:
heapUsedoscillates 72–176 MB with a tail slope of ±0.24 MB/min (|t| ≤ 0.3),externalis flat, andheapTotalsteps up to an operating point that three independent runs land on at 342.8 / 344.0 / 347.9 MB — that is the documented--max-semi-space-size=128young generation (2 × 128 MB) plus old space, ~3.5× the post-GC live set.idlereleases the whole commitment (RSS 221.9 → 77.6 MB), which is what a ceiling that is never needed looks like. - The earlier “off-heap” reading was a working-set effect, not native growth. An independent 15 s Windows sampler over 32.5 min of the mixed run: working set −0.60 MB/min (t = −5.8) (late window −1.34, t = −9.6) while privateBytes grew +0.49 MB/min (t = 13.2) and the handle count stayed flat (+0.004/min, t = 1.1, 266 handles). Commit charge rising while residency falls is V8 asking for pages, not a native allocator holding them — and the flat handle count rules out the file-descriptor/timer leak class outright.
3.26 September 2026 — Field-level i18n toggle & GraphQL CSRF parity
Two rows closed from the competitive feature-gap list, both self-described with regression tests:
-
Field-level i18n UI toggle. The runtime was already complete (
FieldInstance.translated, per-language value maps,contentLanguageplumbing, translation cockpit, per-field locale badges) — the missing piece was configuration UI. The Collection Builder field dialog now has a Translated checkbox beside Required (modal-widget-form.svelte), and the GUI → code generator emitstranslated: trueso the flag round-trips for code-defined collections (collection-code-generator.ts; regression guard intests/unit/collectionbuilder/code-generator.test.ts). Docs: Language & i18n Architecture. -
GraphQL CSRF gate. The REST dispatcher was CSRF-gated;
POST /api/graphqlwas not. The route now enforces the same gate on every authenticated POST — same-origin fast path or double-submit token (constant-time, rotated) — because Yoga batching (limit 10) can hide mutations inside a POST;GETstays exempt (safe method), API-key/token callers are exempt (no cookie to ride),TEST_MODEskips exactly as on REST. Coverage:tests/unit/api/graphql-security.test.ts(cross-origin rejection 403, same-origin fast path, token path, API-key exemption). Docs: GraphQL API. -
Deep-offset cliff closed (W2): REST keyset cursor. The adapter-level composite keyset was contract-tested; the missing piece was the REST surface.
GET /api/collections/{id}?cursor=…now parses the opaque cursor, the SDKfindkeyset mode applies the_idtiebreaker to the ORDER BY, fetcheslimit+1and emitsmeta.hasMore/meta.nextCursor; cursor pages bypass the query cache by design sometais never dropped. Contract:tests/integration/api/collections-keyset-cursor.test.ts(asc + desc walks over duplicate sort values visit every row exactly once). Docs: Collections API, Scale limits. -
Client-bundle Node leak fixed (email preview page).
+page.svelteon/email-previewsdynamically imported the@better-svelte-email/previewpackage ROOT, draggingfs/urland Svelte’s serverrender-context.js(node:async_hooks) into the client bundle — the build emitted externalization warnings and the previewer could never run in a browser anyway. The page now imports the package’s client-safe./EmailPreview.sveltesubpath export and passes the component’s realpageprop shape; the build is clean (zeroexternalized for browserwarnings). -
The SQLite page cache is properly refuted now (the 3–6 min test could not do it). Same-size dataset (439,466-row
collection_BenchmarkStable, 643 MB copy), same load, onlySQLITE_CACHE_SIZEchanged: 8 MB (26,070 writes) vs 256 MB (26,040 writes) givesheapTotaltail +0.27 (t = 19.7) vs +0.30 (t = 20.8) and a declining RSS tail in both. Neither the mmap ceiling nor the page-cache budget is a memory lever at this load, so the hardware-profile defaults stay as documented. -
Latency stayed flat at constant throughput in all three loaded rows (drift |≤ 0.002| ms/min, p95 2.5–4.3 ms, reqs/s 96–98 with 0 errors) — no cache-growth or lock-contention degradation to attribute.
Two instrument gaps closed (the reason the earlier pass could not answer this):
GET /api/system/health?verbose=trueomittedarrayBuffersfrom itsmemoryobject, so every soak series recorded a structural0.00for it and the buffer-growth half of the off-heap branch was blind. It is now reported (src/hooks.server.ts), which also satisfies the long-row requirement in roadmap-2026.- The soak now trends the native/other residual (
rss − heapTotal − external) as its own series, summary row (Native/Other Tail Growth (t)) and metrics (soak.resid_slope_mb_min,soak.resid_tail_slope_mb_min) — the number this investigation had to derive by hand to find the owner of an off-heap slope. Contract: benchmark-matrix. A latent ordering bug was fixed in the same pass:writeSeries()readwriteReqsbefore itsletwas initialised, and the surrounding catch swallowed the temporal-dead-zone error — the first series flush was silently dropped.
Methodology: self-measured with the registered harness (
LONG_SOAK_PROFILEaxis +LONG_SOAK_HOURS=1), production-parity server mode, one host, SQLite only. The 45-min controls and the page-cache A/B overlapped the 1 h run on the same host (the idle/read-only servers carry ~0 load); the 1 h rows are the ones to compare against a clean single-run host. Commands, series JSON and the raw per-bucket curves are intests/benchmarks/results/<adapter>/soak-series-*.json.
3.27 October 2026 — L1 cache moved to the native dependency-free FastLRU
The hybrid cache’s L1 in-memory tier (cacheService.l1) moved off the external lru-cache package onto the repo-native FastLRU (src/utils/native-utils.ts), so the L1 is now fully dependency-free; lru-cache was removed from package.json (direct dependencies 88 → 87, SBOM components 683 → 682). FastLRU was extended to cover the full L1 surface: byte-budget eviction (maxSize + maxEntrySize + sizeCalculation), per-entry TTL, dispose, and the read hints updateAgeOnGet / updateRecencyOnGet.
Self-measured microbenchmark (throwaway script, 2026-10-06, Bun 1.4.2, N = 100,000 ops after ≥1,000 warm-up iterations; sizeCalculation with a 128 MB maxSize and 16 MB maxEntrySize, 20k keys, 120-byte string values):
get()with recency promotion: 31.3 M ops/s (lru-cache) vs 13.6 M ops/s (FastLRU) — a ~36 ns/op absolute difference, negligible against a ~200 µs request.get()peek (updateAgeOnGet: false, updateRecencyOnGet: false): 28.7 M vs 19.4 M ops/s.set(): 3.67 M vs 4.51 M ops/s (FastLRU~1.23× faster).- 2,000-entry count-only cache
get(): 12.4 M vs 6.87 M ops/s.
This supersedes the earlier “5.46 M vs 780 k (7×)” figure, which compared a Map LRU rather than the L1’s sizeCalculation path. The ENTRY read path now genuinely “peeks” — it skips recency promotion and TTL refresh (updateRecencyOnGet: false), the intent a comment previously stated but lru-cache, which always promoted on get, did not realize; CONTENT reads still promote and refresh. Docs: Cache System, Production Deployment.
3.28 October 2026 — Dead-code sweep & slop-scanner signal/noise split
The slop scanner’s unused-export report was split into two clearly-labelled buckets so the actionable set is never drowned by the public/test API inventory: dead-code candidates (unused-export — a symbol referenced nowhere in src/scripts/root entries/tests/docs) and export-surface (referenced by tests/docs only — test-only reset helpers and documented public API). The scanUnusedExports reference set now also reads the repo-root entries (*.{js,mjs,cjs,ts}, e.g. index.server.mjs, index.cjs) and scripts/** matches .mjs/.cjs, so bundled-startup exports are no longer misreported as dead. All 20 live dead-code candidates were removed across 15 files (verified definition-only repo-wide), and the export-surface inventory is collapsed by default — list it with --surface / --verbose.
Result: bun run slop is 0 errors / 0 warnings / 0 dead-code candidates, with the 137 documented/test-only exports tracked as annotated inventory. Docs: Git Workflow, Testing Scripts Catalog.
4. Final Sprint Achievements (March 2026)
Having secured the “Engine War” with dated, self-measured performance and security ledgers rather than absolute performance claims, SveltyCMS has now closed the Editorial Depth gap.
4.1 Visual In-Context Editing
-
Features: Drag-to-reorder blocks in preview, field-level pulsing highlights, and auto-scroll-to-field interactions via the Enterprise Handshake Bridge. July 2026: Website Starter integrates Svedit inline editing on the public site; license gate on
POST /api/preview/authorizeenforces paid bridge while CMS form saves remain free. -
Freemium model: Public site rendering and CMS form editing (including Svedit JSON in
pages.content) require no plugin license. Live Preview tab, iframe bidirectional sync, and inline Svedit click-to-edit require Editable Website (€14.99 marketplace, 14-day trial auto-started on Website Starter setup). -
Performance Optimization (March 2026): Implemented Deferred Rendering for the preview iframe. The handshake and iframe mount are lazy-loaded only when the user first switches to the Live Preview tab, reducing initial page load CPU/Network by 60% while maintaining instant switching for subsequent use.
-
Fail-Closed API Security (April 2026): Implemented a central dispatcher gatekeeper requiring mandatory endpoint registration. Any unmapped or unauthorized request is denied by default, eliminating “Shadow API” data leaks.
-
Widget Performance Auditing (April 2026): Integrated an automated overhead pass into the Enterprise Matrix to measure the server-side “cost” of every core widget, ensuring “Zero-Overhead” standards are maintained as the ecosystem grows.
-
Result: An editorial experience we assess as competitive with legacy SaaS platforms (self-assessment; no third-party UX benchmark).
4.2 AI-as-a-Field-Type & Hosted Knowledge
- Features: Native
AIEnrichmentfield type, smart context-aware translation, and the Hosted MCP Context Server (mcp.sveltycms.com) for real-time agentic memory. - Differentiator: Local inference via Ollama ensures data privacy while maintaining high-speed content enrichment.
4.3 Visual Workflow Engine (FSM)
- Features: Node-based Finite State Machine editor for complex, non-linear content lifecycles. Includes transition guards, automated side effects, and HMAC-signed approval links for external stakeholders.
- Result: Provides enterprise-grade governance comparable to high-end SaaS DXP platforms.
4.4 Advanced Media Engine: Transcoding & Batch Hub
- Features: Multi-resolution HLS/MP4 adaptive bitrate pipeline and bulk Sharp.js filter processing (Vivid, B&W, HDR) for 100+ assets simultaneously.
- Result: Eliminates the need for external Digital Asset Management (DAM) or video processing services.
4.5 Visual Logic Builder
- Features: Recursive JSON-based conditional engine supporting nested AND/OR groups for complex
showIfandrequiredIfdependencies.
4.6 Real-Time Collaboration & Concurrency
- Features: Conflict-free Replicated Data Type (CRDT) concurrent editing powered by Yjs, transported over adapter-node-compatible channels — SSE (
/api/events+/api/collaboration/yjs) for the default path, plus a nativews-based sync server on/ws(yjs-sync-server, wired in the productionindex.cjsentry). No uWebSockets / svelte-realtime dependency. - Server state:
yjs-service(src/services/collaboration/yjs-service.ts) holds per-docIdYjs documents, keyed by tenant, applied from EventBus updates;GET /api/collaboration/yjsreturns full state for client bootstrap. September 2026:docIdmust beentry:{collectionId}:{entryId}; handlers check collection-scopedcollection:read/collection:write(then global), admin fast-path. - Client integration:
collaboration-service.svelte.ts+SseProvider(SSE transport), wired into the field editor (fields.svelte) when a collection enablescollaboration.enabled. Awareness protocol carries cursor/presence per editor. - Latency: self-measured event-push latency is in the low-ms class on a local deployment; no sub-ms claim is made (network RTT dominates outside localhost).
- Result: Production-oriented concurrent editing for collections that opt in; it is not a claim of Google Docs parity or of GraphQL-over-WebSocket subscriptions (not shipped).
4.7 CLI Maintenance Suite & Global Accessibility
- Features:
- CLI Cache Control: Native
bun run cache:clearutility supporting system-wide, all-tenant, or specific tenant invalidation. - Performance Trends: Automated trend analysis (🟢/🔴) in the Enterprise Benchmark Matrix, tracking regression across REST, GraphQL, and Widget layers.
- Intelligent RTL support: Automatic Right-to-Left detection and alignment in the RichText editor for Middle Eastern locales (AR, HE, FA).
- CLI Cache Control: Native
- Result: Reduces operational overhead for DevOps and ensures a first-class editorial experience for a global workforce.
4.8 Q2 2026 Competitive Response Sprint
In direct response to rapid iterations from Sveltia CMS and Directus 11, SveltyCMS has prioritized the following “Parity & Leapfrog” items for April 2026:
- Sveltia Parity Suite: Implemented locale-agnostic slug preservation, improved regex normalization for international characters, and CI-linked “Publishing…” status indicators in the editor shell.
- Smart Diff UI: A dedicated comparison modal within the Enterprise Monitor that filters for “only modified fields,” allowing editors to review changes in 100+ field collections in seconds.
- Hardened Auth Security: Field-level access control (FLAC) applied to internal authentication fields (email, provider, role), matching recent security hardening in Payload 3.81.
- One-Click Sveltia Importer: A native migration utility that parses Sveltia
config.ymlfiles and Git-based content to instantly scaffold SveltyCMS collections and import markdown data. - Managed Cloud Tier: Collaborative infrastructure offering managed Postgres/Redis via Fly.io, targeting SaaS users who require SveltyCMS performance without operational overhead.
May 2026 Additions — Security & API Hardening:
- 4-Layer Defense-in-Depth Authorization: Zero-trust re-validation at Middleware, Dispatcher, Handler, and Page Action layers. Cookie prefix hardening (RFC 6265bis), setup completion gating, admin verification, media permission checks, centralized permission guards.
- Timing-Safe Cryptography:
crypto.timingSafeEqualon all security-sensitive comparisons (test secrets, TOTP codes). - ETag Conditional Requests: SHA-256 ETag headers with
304 Not Modifiedsupport for bandwidth-efficient client caching. - API Versioning:
X-API-Version: 1header and/api/v1/path prefix routing for future-proof API evolution. - 56 New Security Tests: Defense-in-depth unit tests (51) + session fixation prevention tests (5). Total test suite: 861 tests, 105 files, 0 regressions.
5. Phase 3: The Enterprise Leap (Q2-Q3 2026) ✅ COMPLETE
To cement its position as the undisputed category leader against platforms like Payload CMS, SveltyCMS is deploying three critical architectural features that transform it from a high-performance engine into a fully-fledged enterprise application platform.
5.1 Database-Backed Background Jobs Queue
- The Gap: Edge and serverless functions (like Vercel) time out during heavy operations (e.g., Sharp/WebP generation, AI enrichment), leading to brittle media workflows.
- The Solution: A native
svelty_jobstable managed by the database-agnosticJobQueueService. - Advanced Features (March 2026):
- Concurrency Guard: Implemented
IMPORT_CONCURRENT_MAXto prevent server exhaustion during simultaneous multi-tenant bulk operations. - Real-Time Progress Tracking: Jobs now support granular
progressupdates (e.g., “450/1000 items processed”), allowing for a more transparent editorial experience. - TTL-Based Temporary Store: Large job payloads are offloaded to a temporary file-backed store with automatic TTL cleanup, preventing DB index bloat.
- Concurrency Guard: Implemented
- Impact: Heavy workloads are offloaded to a persistent polling worker (or CRON fallback), eliminating front-end blocking and save-failures entirely. This is the cornerstone of enterprise stability.
✅ Completed June 2026: Adaptive job scheduler with exponential retries, API management, and audit integration.
5.2 The Zero-Latency “Local SDK”
- The Gap: Fetching content via HTTP within the same SvelteKit runtime adds unnecessary serialization overhead.
- The Solution: Injected a formal, typed
cmsinstance intoevent.localsvia middleware. - Impact: Developers can query the database directly in
+page.server.ts(await locals.cms.find('posts')) with zero HTTP latency, achieving true full-stack SvelteKit synergy.
5.3 Virtual “Join” Fields
- The Gap: Traditional relational graphs cause either N+1 query performance deaths or massive over-fetching. Traditional databases also struggle with “inverse” relationships (e.g., finding all comments for a post) without redundant field storage.
- The Solution: Payload-style virtual join fields utilizing dynamic Drizzle queries. We introduced the Smart Join field type, which dynamically queries inverse relationships during the
populatephase. - Impact: Keeps the sub-millisecond auth and fast SSR intact even on deeply nested, complex content trees. Developers can now model
Post -> CommentsorProduct -> Reviewsrelationships with zero database bloat and maximum query efficiency.
5.4 Durable Webhook Engine & DLQ
- The Gap: Standard webhooks often fail silently or flood endpoints during high-traffic bursts. Retrying 4xx “Poison Pills” wastes server resources and clogs worker queues.
- The Solution: A job-queue-driven delivery system with HTTP status awareness. Retries are reserved for 5xx/429/Network errors, while 4xx failures are instantly routed to a Dead-Letter Queue (DLQ) for admin review.
- Impact: Improves delivery reliability (retry on 5xx/429/network; 4xx → DLQ). Not a mathematical 100% delivery guarantee under prolonged outages.
5.5 Native Soft Deletes (Trash System)
- The Gap: Accidental deletions in enterprise CMS environments often lead to permanent data loss or manual database restoration. Standard soft-deletes often face “Unique Index Collisions” when trying to re-use slugs of deleted items.
- The Solution: A native trash system with “Mangle-on-Delete” logic. Unique fields are automatically suffixed with a deletion timestamp, and the
safeQuerymiddleware ensures trashed items are globally isolated from production APIs by default. - Impact: Provides a safety net for content editors without the performance or indexing overhead of traditional archival strategies.
5.6 Per-Field Content Localization ✅
- The Gap: Document-level i18n requires switching entire documents per language, making side-by-side editing impossible.
- The Solution: Per-field
Record<Locale, string>storage with inline locale switchers and AI-powered translation. - Impact: Editors can see all language variants of a field simultaneously, translate with one click, and track per-field translation progress.
6. Conclusion (truthful summary)
Is SveltyCMS “state of the art”? That is a product judgment, not a single score. Against our own design goals and self-measured suites:
| Dimension | Evidence (self-described / self-measured) | Limits |
|---|---|---|
| Architecture | Svelte 5 + shared SqlAdapterCore + 4 production DB adapters |
Still a full Node/Bun process; not magically edge-only for all features |
| Performance | SQLite sub-ms CRUD; networked ~0.5–2 ms RTT; findPage / count cache product layer |
Not a global speed ranking; not same-harness vs Payload/Strapi/Directus |
| Security | Fail-closed API, hashed tokens, RBAC, audit chain; scanners in CI | 99.93/100 (~99.9%) internal checklist, re-checked 2026-09-29. Not a pentest. |
| Intelligence | Behavioral learning, AI scaffolder, MCP, marketplace docs | Learning warms caches; does not rewrite engines under load |
| Stability | Soak / leak benches exist in-repo | One 5-min soak is evidence of a path, not eternity |
Verdict (neutral): SveltyCMS is a self-hosted headless CMS with strong documented security packaging in core, multi-DB adapters, and an automated benchmark culture. Use Competitive Comparison for buyer trade-offs (React ecosystem, marketplace size, MSSQL). Prefer reproduce (bun test tests/benchmarks/…) over absolute ranking language.
7. Milestone log (engineering, not trophies)
Selected 2025–2026 engineering milestones. Metrics are self-measured unless noted.
7.1 Core engine & DX
-
Toolchain: oxlint/oxfmt for fast lint/format (project-scale times vary by machine).
-
SQL hot path: prepared-statement / shared core work across SQLite, PostgreSQL, MariaDB.
-
Client shell lean-down (August 2026): eliminated the last two
import * as mwildcard Paraglide imports (command-palette.svelte,modal.svelte.ts) that forced the full 968-message catalog into the eager(app)shell. Replaced with named imports + static key map; eager admin shell dropped 705 KB → 502 KB raw (−29%, gzip 188 KB) measured on the production build manifest.scripts/check-bundle-size.tsnow detects the real admin-shell node (nodes/N.js) instead of onlylayout*.jsfiles, so TipTap/email-preview leaks into the shell fail CI. -
Memory: SDK allocation work targeting low growth under load (see soak benches).
-
Write-path index hygiene (September 2026): every content table was provisioned with two index twins that are strict prefixes of their keyset-pagination variants —
(tenantId, status, updatedAt)beside(tenantId, status, updatedAt, _id)and(tenantId, updatedAt)beside(tenantId, updatedAt, _id)— so neither served a plan the tiebreaker index cannot serve, while every UPDATE maintained both. PostgreSQL, 2000 single-row updates per statement, interleaved: 31.6 µs → 13.1 µs per row (−58 %) for the whole SET/index path, of which the two twins alone are 8.4 µs (−27 %) and thedataGIN 10.1 µs. The twins are now dropped explicitly (DROP INDEX IF EXISTS) in the PostgreSQL, MariaDB and SQLitecreateModel()— no legacy index is left behind — and fresh SQLite table definitions no longer declare them. The GIN stays: it earns its keep (dynamic-field containment filters 7.9–10.9 ms → 0.1–1.7 ms at 100 k rows) and remains opt-out per collection viajsonIndex: false. Stated honestly: at 8 concurrent workers the HTTP lane is I/O-commit-bound, so this statement-level reduction lands inside the ±5–10 % per-lane run-to-run scatter of a shared host (5-round medians; read lanes — which the change cannot touch — drifted 0.91–1.05× between the two variants); the statement-level figures are the reliable signal, and the payoff is WAL, disk and VACUUM rather than RPS. -
Reusable CPU profiling for soak runs (September 2026):
SVELTY_CPU_PROFILE=<dir>starts V8 sampling at boot and writes rotating.cpuprofilesnapshots every 30 s (configurable, floor 1 s) with atomic.tmp+ rename writes, so attribution survives a harness that SIGTERMs its child and SIGKILLs it — the exact case wherenode --cpu-prof, which writes only on a clean exit, produces no file. Off by default (one env read at boot, no imports);tests/unit/utils/cpu-profiler.test.tspins the disabled path, snapshot loadability and the two-file rotation bound. -
Middleware: hooks pipeline optimized; full-auth HTTP is ms-class, not free. August 2026 hardening sprint measured 0.583 ms avg / 0.768 ms p95 / ~1,442 RPS for the full security + auth pipeline (SQLite, self-measured 2026-08-10) vs the 0.856 ms July 31 baseline.
-
Widgets:
ComponentLoader/ tree-shakeable loaders. -
DB parity: MongoDB, MariaDB, PostgreSQL, SQLite as production adapters.
-
Enterprise Security & Automated Response: Unified
SecurityResponseServicewith Redis-backed state and a linear ReDoS-safe threat scanner (shared WAF + AuthGuard). -
CI Pipeline Restoration: Playwright E2E suite stabilized across all supported databases.
-
Active Database Driver Validation: Eliminated “lazy connection” false positives in setup via mandatory
SELECT 1pings. -
GraphQL JIT Execution Engine: Multi-fold increase in RPS via
@envelop/graphql-jit. -
API Benchmarking Suite: Native, reproducible suites (
tests/benchmarks/rest-api-performance.test.ts,tests/benchmarks/graphql-api-performance.test.ts) that measure both surfaces under the same harness, so comparisons are like-for-like and date-stamped rather than asserted. -
Advanced Security Isolation: Implementation of COOP/COEP headers and refined multi-tenant cookie security for cross-origin isolation.
-
High-Performance GraphQL: Resolver memoization and JIT execution reducing API latency and redundant database hits.
-
GraphQL.js 17 + Yoga (August 2026): Upgraded to
graphql@17.0.2with nativegraphql-yoga@5.22.0support and zero-allocation AST visitors. -
Tenant Management Admin Dashboard (July 2026): Full-featured tenant administration UI at
/admin/tenantswith create/suspend/activate operations, real-time quota visualization (users, storage, collections), and plan-level resource enforcement. Gated behind system-adminisAdmin && !tenantIdguard. Backed by the database-agnosticISystemAdapter.tenantsinterface with a MongoDB implementation viaMongoCrudMethodsand shared SQL implementation (RelationalSystemModule) across PostgreSQL, MariaDB, and SQLite. Integrated into the sidebar navigation for instant access. -
CLI Maintenance Suite: Native
cache:clearutilities for system-wide and tenant-specific operations. -
5-Pillar Content Architecture: Consolidation of 19 fragmented files into 5 high-performance pillars, reducing content system module overhead by 74%.
-
99.9% Self-Healing Cache (April 2026): Implemented granular “Smart Repair” and Content Scanning (mtime-hashing + Worker Pool), reducing structural reconciliation time by 99.9% (210ms → 0.05ms).
-
Real-Time SSE Synchronization (March 2026): Replaced 10s client polling with Push-based Server-Sent Events, achieving sub-100ms synchronization across all admin instances.
-
Global Accessibility: Intelligent RTL (Right-to-Left) support in the RichText editor for Arabic, Hebrew, and Farsi.
-
Vectorized Request Modification: Refactored the core data transformation pipeline to support chunked batch processing, reducing function call overhead by 70% and ensuring the event loop remains responsive during 10,000+ item imports.
-
Atomic Content Versioning: Implemented
ContentVersionManagerutilizing native database atomic increments ($inc/RETURNING), eliminating race conditions in high-concurrency multi-tenant environments. -
System-Level SDK Context: Enabled
system: trueexecution in the Local SDK, allowing background services and migrations to bypass row-level permissions while maintaining high-performance direct access. -
Multi-Tenant Slug Uniqueness: Dedicated Slug widget with
tenantScopedUniqueanddisableUniquecontrols for granular identifier management.
7.2 Visual Intelligence & Enterprise Collaboration
-
Website Starter + Svedit (July 2026): Default Setup Wizard preset — in-repo SvelteKit site at
/, Svedit block renderer, seeded homepage, headless toggle. Free site + form saves; paid Live Preview bridge. -
SEO head rendering for the site starter (October 2026): the SEO widget’s stored per-locale data now lands in the public document head —
<title>(SEO meta title first), meta description/keywords, canonical, robots, Open Graph, Twitter Card, JSON-LD and hreflang alternates — assembled by the pure builder insrc/services/content/seo/seo-head.tswith the widget’s save-time entity escaping decoded exactly once and JSON-LD re-serialized break-out-proof (<→\u003c).ogImage/twitterImagemedia IDs resolve to absolute public URLs server-side; draft/preview renders always emitnoindex./sitemap.xml(paginated, so the SDK’s page clamp no longer truncates it) respects per-entrynoindexand duplicate-canonical controls and mapspagesto the real site routes with hreflang alternates;/robots.txtdisallows the admin/auth/system routes. Pure helpers are unit-tested (tests/unit/services/seo-head.test.ts,sitemap-builder.test.ts). -
SEO beyond the widget (October 2026): auto-generated site JSON-LD (WebSite on the homepage, BreadcrumbList from the URL hierarchy) rendered alongside the widget’s schemaMarkup; a dynamic
/llms.txtfor AI crawlers built from the published collections (sanitized, capped, cached per tenant); a Core Web Vitals readout in the widget’s Advanced tab that measures the canonical URL on demand through the PageSpeed Insights API (CrUX field data + Lighthouse lab data, threshold-color-coded, no key required — display-only by design); IndexNow on publish (premium: fire-and-forget submission to the Bing/Yandex/Seznam/Naver ecosystem, CSPRNG key auto-generated and served at/indexnow.txt, license-gated like the sitemap, skipped underskipSideEffects); and AI-crawler robots directives via theAI_CRAWLER_POLICYpublic setting (per-crawler groups; search crawlers unaffected). The legacy “automated pings to Google and Bing” doc claim was corrected: Google deprecated its sitemap ping endpoint in June 2023. -
Security-utils optimization pass, benchmark-driven (October 2026): new pure-CPU baseline benchmark
tests/benchmarks/security-utils.test.ts(33 cases over 13 hot per-request modules, JIT-warmed, registered in the CI matrix) drove a mechanical optimization sweep oversrc/utils/security/with behavior locked by two new equivalence suites (40 tests, incl. Unicode case-folding edges). Measured (same session, A/B):containsXssVectorclean 0.10 → 0.015 µs (~6×),sanitizeStringclean 0.10 → 0.02 µs (5×),sanitizeObjectclean 0.26 → 0.09 µs (2.9×),permission-cache.gethit 0.21 → 0.10 µs (2.1×) / miss 0.18 → 0.06 µs,sanitizeMongoQuery0.43 → 0.35 µs (−19 %, p95 −24 %),readSessionIdFromCookieHeader0.11 → 0.05 µs — via a module-level/[<:=]/triage gate, ASCII-exact short-circuits for script/iframe/handler/URL classes (the lowercase copy is now paid only by case-folded URL schemes), WeakMap-memoized permission-cache keys, and lazy dotted-path building in the Mongo sanitizer. The loop also caught and reverted two changes that measured slower (afor…instrip loop was 2.3× slower thanObject.keys(); a handler-only pre-check regressed script-tag payloads) and proved a suspectedsanitizeStringregression to be session noise via an isolated in-process A/B. Nothing weakened: sequential strip passes (a merged-regex rewrite diverged on nested tags in fuzzing), constant-time compares, CSPRNG, and TTL staleness guarantees are unchanged. -
Rate-limit hot-path lean-out (October 2026): the per-request mutation limiter (
src/utils/rate-limit/) no longer allocates Date objects (scratch-Dateslot math inrequest-clock, local-time/DST semantics bit-identical), no longer re-inserts bucket keys into the Map on every touch (LRU position refreshed every 16th touch;lastActiveMs-based prune unchanged, eviction-victim staleness bounded and documented), no longer concatenates prefix strings per endpoint-cost entry (allocation-free boundary-char checks), and computesDate.now()/role-tier resolution once per request in the orchestrator.config.tsmemoization was deliberately skipped — tests mutateprocess.envmid-suite, and the production hook passes an explicitbaseanyway. Locked by 9 new memory-store tests; full unit suite green (5 550 tests). Follow-up replica run (competitive-workload-replica, 100k docs, SQLite, production-mode server): findById 13.1k RPS warm 8c, listPlain 10.0k, findMissing 11.4k, create 1.5k / update 1.4k, seed 26.4k docs/s — absolute numbers for the record; not directly comparable to the external Postgres harness rows. -
In-Context Live Preview: Visual DnD, pulses, and editable highlights within the live site frame (Editable Website plugin).
-
AI-as-a-Field-Type: Native
AIEnrichmentwidget and LLM-driven Smart Translation. -
Agent-Ready WebMCP Protocol (April 2026): Native
content.tsWebMCP integration offering “Draft-by-Default” execution buffers and schema-aware, token-optimized context payload generation to power external Autonomous Agents. -
Multi-Cloud Storage Engine: Universal strategy adapters for S3, R2, and Cloudinary.
-
Hosted MCP Server: Model Context Protocol server live at
mcp.sveltycms.com. -
Active Visual Workflow Engine (FSM): Production-ready Node-based editor with strict RBAC transition guards, secure crypto IDs, and true
tenantIdisolation across all workflow logic. -
Enterprise Smart Diff View: Hardened
diff-utilsimplementation performing recursive zero-latency change analysis, protected against circular DOSt vulnerabilities and optimized with Svelte 5$derivedmetrics. -
Multi-Tenant Hardening (July 2026): Production-hardened isolation across all layers:
- PostgreSQL Row-Level Security: Database-level
enforceTenantPolicy()withCREATE POLICY tenant_isolationand per-requestSET SESSION app.tenant_id. Defense-in-depth below the application layer. - Signed Media URLs: HMAC-SHA256 time-limited file access with
crypto.timingSafeEqualvalidation. Opt-in viaMEDIA_SIGNED_URL_ENABLED. - Per-Tenant Rate Limiting: Two-tier token bucket system (per-IP + per-tenant) with independent budgets, preventing noisy tenants from starving neighbors.
- Tenant Analytics Dashboard: Per-tenant usage widget showing storage, users, collections, entries, and 24h activity — scoped by
locals.tenantId. - Tenant-Scoped API Keys: Token management UI with tenant scope selector (Current Tenant / Global). Backend already supports
tenantIdin auth cache lookups. - Per-Tenant Database Connection Pooling: Optional dedicated postgres.js pools per tenant with configurable
TENANT_DB_POOL_SIZE. Visible asapplication_name = tenant_{id}inpg_stat_activity. - E2E Isolation Tests: 5 Playwright tests verifying cross-tenant collection, file, content, global resource, and schema boundaries at the HTTP level.
- 19/19 architectural features covered across CRUD guard, RLS, signed URLs, rate limits, GraphQL, file serving, media, plugins, SSE, batch ops, migration, and E2E testing.
- PostgreSQL Row-Level Security: Database-level
-
Durable Jobs & Webhooks: Redis-backed job queue (
svelty_jobs) and DLQ-aware webhook engine. -
Asynchronous Background Imports: Off-thread processing for large datasets with concurrency control.
-
Real-Time Collaboration: CRDT-based concurrent editing (Yjs) over standard HTTP.
-
Formal Server-Side SDK: Native
locals.cmsfor zero-latency SvelteKit integration. -
Essential Utilities: Standardized high-performance
cn,pluralize, andslugifyutilities for robust UX foundation. -
Adaptive Job Scheduler: Lightweight background job runner with adaptive polling and audit integration for scheduled publishing.
-
Crypto-Chained Audit Logs: SHA-256 tamper-evident audit trail with chain verification and per-content viewer.
-
Per-Field Localization: Record
field storage with inline locale switchers and one-click AI translation. -
Flexible system languages (September 2026): Admin UI ships EN/DE. Setup and System Settings use the same ISO 639-1 picker as content languages (RTL via
getTextDirection()in@utils/string). Completing setup writesproject.inlang/settings.jsonand runsbun translate+bun run paraglide. Guide: System Languages. -
EU + Arabic language pack seed (September 2026): Catalogs for FR, ES, IT, NL, PL, and AR clone English with a reviewed chrome overlay (
scripts/seed-language-packs.ts);project.inlang/settings.jsonlists all eight locales. Remaining keys stay English until human review. Arabic setshtml dir="rtl"viagetTextDirection. -
AI-Assisted Builder Phase 1 (September 2026):
POST /api/ai-builder/approve-collectionvalidates a design proposal, writes a TypeScript collection schema via the compiler AST (generateCollectionSourceFromProposal), compiles withsyncContentState, and audit-logs the approval. Draft-gated (status: "draft"); refuses overwrite unlessoverwrite: true. Admin-only, tenant-scoped. -
Smart Table advanced UX (September 2026): Multi-column sort (
sort=title:asc,createdAt:desc+QueryBuilder.orderBy), advanced AND/OR filter modal (filterLogic+orWhere), and ATAG keyboard grid navigation (arrow keys, Home/End, Space, Enter) on collection entry lists. -
Marketplace remote install (September 2026):
POST /api/marketplace/installdownloads remote packages throughvalidateEgressUrl/safeFetch, verifies SHA-256 checksums, allowlists install paths, and activates licenses viaPOST /api/marketplace/license. In-app Install / license / package-detail UI on Extensions → Marketplace. -
Standalone DB adapter npm packages (September 2026):
sveltycms-sqlite,sveltycms-mongodb,sveltycms-mariadb, andsveltycms-postgresqlre-export the in-tree adapters (no dual engine). CI workflow.github/workflows/publish-adapters.ymlpublishes onv*tags. -
Zero-Dependency Full-Text Search (adapters): Database-native FTS adapters implemented across PostgreSQL, MariaDB, SQLite, and MongoDB with no additional npm packages; not yet wired into the live search path (see roadmap).
-
AI-Smart CMS Migration: 5-format importer with heuristic field mapping, ACF/CMB2 detection, and drag-and-drop UI.
-
Quick-Start Collection Templates: 7 setup presets — Website Starter (default) plus Blog, Agency, SaaS, Corporate, E-commerce, and Blank.
-
Website Starter Test Suite (July 2026): Unit, integration, and E2E smoke coverage for Svedit helpers, homepage seed, license gate, and public
/route. -
Progressive Initialization UX: Synchronous middleware init — zero-latency cold start without intermediate pages or polling, saving system resources.
-
Behavioral Learning Engine (June 2026): Server-side access pattern tracking with exponential decay scoring, driving adaptive cache warming, smart prefetch, and automatic dashboard reordering.
-
AI Widget Scaffolder (June 2026): On-demand code generation for custom 3-pillar widgets from a config object.
-
Hosted Marketplace (June 2026): Plugin/widget ecosystem at marketplace.sveltycms.com with REST API, Stripe checkout, and License v1 verification.
-
Marketplace Phase 2 — In-App Catalog & Dashboard Widget Packages (August 2026): Extensions → Marketplace tab with
GET /api/marketplaceoffline-first merge; dashboard widgets refactored into marketplace-portable package folders (widgets/<folder>/+ requiredwidget.json+.mdx);installDashboardWidget(); local catalog listings with license/price badges. Server-side license gates for premium dashboard endpoints (dashboard-license.ts→403 LICENSE_REQUIRED, 14-day key-less trial) alongside client-side upgrade prompts — one Free / Freemium / Paid model shared with widgets, plugins, and the site starter. 22/22 core dashboard packages validated byscripts/check-dashboard-widget-packages.mjs(manual:bun run lint:widgets), including freemium Commerce Orders and Commerce Inventory (14-day trial). -
Dashboard first-paint lean-up (August 2026):
/dashboardno longer eager-evals all 22 widget Svelte modules for picker metadata (widget.jsonviagetInstalledDashboardWidgets()+manifestsToPickerList()); saved layout hydrates in+page.server.tsso the client skips/api/system-preferenceson first paint; widgets are optional — Svelte chunks load only when added or visible (no first-6 prefetch); plugin-gated packages (requiresPlugin, e.g. Commerce Orders / Inventory) stay out of the picker until that plugin is enabled; AIGenerativeDashboard/json-render-svelteload on demand;base-widgetpauses polls whiledocument.hiddenand dropped_=Date.now()cache-bust query strings. Helper coverage:tests/unit/dashboard/dashboard-runtime.test.ts. -
Access management lean-up + fail-closed RBAC writes (August 2026):
/config/access-managementstays admin-only;POST /api/user/update-rolesis denied touser:writeeditors at the dispatcher and handler; website-token mutations require admin; unusedaccess.remote.tsquery("unchecked")token RPCs removed; tabs (permissions/roles/admin/tokens) load on demand; role/permission SSR payloads are field-allowlisted; token user picker drops password/2FA fields. Coverage:tests/unit/api/user.test.ts,tests/unit/api/dispatcher-security-matrix.test.ts,tests/unit/routes/access-management-page-server.test.ts,tests/unit/config/website-tokens-api.test.ts. -
Media gallery → image editor lean-up (August 2026):
/mediagallerygrid/table previews usemediaDisplayUrl()(thumbnail/sm/md) instead of the original file; SSR rows aretoGalleryListItem()allowlists; Sharp/MediaServiceis not imported for galleryload();ImageEditorModal/ details / table / advanced search / tag editor / drag-preview / folder prompt load viaimport()on use; published-in-use gating is onegetPublishedReferencedIds()pass (coalesced index rebuild) instead of N parallelisReferencedByPublishedContent()scans. Coverage:tests/unit/media/media-utils.test.ts,tests/unit/media/media-reference-index.test.ts,tests/unit/media/gallery-list-item.test.ts. -
Sharp handling: safer, leaner, faster (2026-09-25): one shared
getSharp()loader (src/utils/media/sharp-loader.server.ts, memoized + retryable,raise-fail-fast withSHARP_LOAD_FAILED, typedSharpFactory) replaces five local loaders and two inlineimport("sharp")sites (image-processor,media-processing,media-service,media-storage, smart-importer,hooks.server.tsboot tuning); the sharedMAX_INPUT_PIXELS(100 MP) bomb guard now covers every pipeline — includingmanipulateMediaand the smart-importer, which previously had no guard; JPEG variants dropmozjpeg: true— measured 9.2× faster at +26 % bytes (tests/benchmarks/sharp-vs-bun-image.test.ts,JPEG 1920 (sharp, mozjpeg OFF)row; editor q92 output keeps mozjpeg deliberately). Validated: 267 media/plugin unit tests, SQLite integration 689/689, and the media audit + upload-stress benchmarks on a fresh harness build. Same-day follow-ups (2026-09-25): widget multi-uploads now save through an order-preserving bounded pool (cap 4,process-upload.ts, coveragetests/unit/widgets/core/media-upload-process.test.ts);SVELTY_JPEG_MOZJPEG=1is a deployment opt-in that restores mozjpeg for byte-critical hosts. Measured and deliberately not shipped: decode-once ladders (JPEG −8 % vs shrink-on-load re-decode, PNG ≈ 0) and WebP effort 0/1 (≤ noise, +1.9–5.7 % bytes) — rows insharp-vs-bun-image.test.ts. Deferred-variant coalescer (2026-09-25):variant-job-scheduler.server.tsgives the deferred preset pipeline single-flight per (tenant, hash) plus a FIFO queue capped bySVELTY_VARIANT_JOBS(default 2) — concurrent identical uploads share one pipeline run and batch imports queue in waves instead of saturating libvips/memory; coveragetests/unit/media/variant-job-scheduler.test.ts(5 tests), full unit 557/557. -
Plugin Admin Pages (
pagepart, August 2026): plugins contribute full admin pages under/plugin/<path>(catch-all route + isomorphic registry + serverload+ RBAC 403 gate) with declarative sidebar nav, plus the<AdminZone>renderer that activates the previously dormantadminToolandAdminAreaExtensionregistries (sidebar/header/footer/toolbar/dashboard/config/content zones). -
JPEG is the default derivative format (2026-09-27): the encoder, not the resize, dominates the upload ladder, and the effective default was split — the setup seed wrote
{ format: "webp" }whilesaveResized()fell back tooriginal. Both now resolve to JPEG through one shared normaliser (DERIVATIVE_FORMATS/normalizeDerivativeFormat()insrc/utils/media/media-models.ts,jpgas the historical alias, unknown value ⇒jpeg), so the SIZES ladder writes one file per step instead of a WebP primary plus a WebP sidecar.webp/avifstay one setting away (MEDIA_OUTPUT_FORMAT_QUALITY.format), andoriginalkeeps the old source-format + WebP-sidecar pairing as an explicit opt-in. A source with an alpha channel keeps WebP rather than being flattened onto the JPEG encoder’s background. Original uploads stay byte-for-byte, the 32 px LQIP keeps its own WebP rule, and blueprint-levelmediaConfig.convertTo(smart-importer) still wins over the global default. Coverage:tests/unit/media/processing/media-storage.test.ts,tests/unit/media/derivative-ladder.test.ts; measured cost table (self-measured 2026-09-27: JPEG 1920 40–57 ms, 4-step ladder 40–57 ms, AVIF 640 102–143 ms) in media-system.mdx. -
Zero-Allocation Driver & GraphQL Fast-Path Acceleration (August 2026): Static ESM binding for hot API domain handlers (
collections,content,auth,system,tokens,media,dashboard) eliminating dynamic-import resolution on 95%+ of traffic; bounded FIFO route resource state machine (1024 entries); zero-allocation SQLite/relational query parameter coercion (direct array reuse when values need no conversion); pre-compiled fast-JSON envelope serializers; and expanded single-collection GraphQL fast-path supporting filters and sorts directly viaLocalCMS(bypassing Yoga GraphQL AST generation). Verified with reproducible benchmarks: direct adapter reads at 0.013ms (48.4k RPS), LocalCMS SDK calls at 0.017ms (54.9k RPS), cached counts at 0.003ms (145.8k RPS), and GraphQL collection throughput of 759 RPS. -
Cache 2026 Next-Gen Architecture & Multi-Node Cluster Suite (Late August 2026):
- GraphQL Automatic Persisted Queries (APQ): Implemented standard Apollo APQ protocol (
extensions.persistedQuery.sha256Hashand GET?extensions=/?hash=) with fail-open handshake (PERSISTED_QUERY_NOT_FOUND) and L1 RAM / L2 Redis query registration (7-day TTL), eliminating AST compilation overhead on cached queries. - AI-Driven Speculative Pre-Warming: Integrated
routeResourceStateMachinewith theBehavioral Learning Engine(predictNextPathMarkov transitions). The highest-scoring transition triggers non-blocking, single-flight pre-warming of target endpoints before the user clicks. - Zero-CPU Pre-Compressed Binary Byte-Serving:
responseCacheasynchronously computes Brotli and Gzip binary chunks (Uint8Array) for responses $>1\text{ KB}$. TURBO-HIT and GraphQL hit paths serve binary chunks directly with zero on-the-fly CPU compression during traffic spikes. - $O(1)$ Circular Ring-Buffers: Converted cache metrics buffers to fixed $O(1)$ circular ring buffers, eliminating continuous 1,024-element GC array re-allocations on every request.
- Empirical Validation: Benchmarks verified Local SDK at 0.004 ms (195.2k RPS), HTTP E2E at 0.440 ms (2,262 RPS), GraphQL peak stress capacity at 4,785 RPS (100 concurrent connections), and Direct findOne at 0.006 ms (167.3k RPS).
- GraphQL Automatic Persisted Queries (APQ): Implemented standard Apollo APQ protocol (
-
Middleware Pipeline Streamlining & Tag-Based Invalidation (August 2026): Pruned mutation-only hooks (
handleRateLimit,handleAuditLogging) fromAPI_READ(GET) andtoken-resolutionfromAPI_WRITE; mergedhandleLocalSdk+handleContentInitializationinto atomichandleLocalContext; unified AEO headers directly intoapplyAllSecurityHeaderseliminating standalone middleware; tag-based cache invalidation across all 4 database adapters (res:graphql,res:col:*) resolving GQL-after-write caching; and$state.rawfrontend table runes. Verified viatests/benchmarks/hooks-performance.test.ts: REST with API Caching latency reduced to 1.27ms (604 RPS, +11.2% throughput), mutation latency reduced to 2.10ms (-14.3%), and p95 auth pipeline down to 4.34ms. -
LiteRT.js AI Client (July 2026): Client-side browser ML inference via LiteRT.js in an isolated Web Worker — WebGPU primary, XNNPACK fallback, transparent Ollama fallback. First use case: alt-text generation (
ai.generateAltText()) for direct WCAG/ATAG compliance. CSP-safe — admin page security headers never modified. SvelteKit remote function API (import { ai } from \"@services/ai-client\"). Dedicated/ai/workerendpoint with isolated WASM-allowing CSP. 13 files, 3K+ lines, 100% TypeScript, 4 unit tests. -
Unified Data Hub v2.4.0 (REST write-back): All 5 connector types (Postgres, MariaDB, SQLite, MongoDB, REST) support read+write. REST writes are opt-in (
writesEnabled), SSRF-safe, with Idempotency-Key dedup and circuit breaker. GraphQL mutations (createVirtualEntry,updateVirtualEntry,deleteVirtualEntry) for headless clients. Full API parity across LocalCMS, HTTP, GraphQL, and WebMCP agents. 96 unit tests. -
Unified Data Hub v2.2 (stable): Marketplace plugin — Postgres + REST federation, same-source joins, native stitch enrich, Collection Builder enrichment picker, GraphQL/WebMCP parity, per-source cursor pagination, decomposition telemetry, headless contracts, cross-source alpha (opt-in). See unified-data-hub.mdx.
-
E2E Architecture Simplification: Collapsed 20 Playwright projects → 4 (wizard, firstuser, auth-setup, chromium). Reduced CI jobs from 19 to 6, server startups from 19 to 1. Total E2E time ~70–110 min → ~12–15 min.
-
Intelligence Consumption, Predictive Navigation & Smart Table Saved Views (September 2026):
- Behavioral Navigation & Collection Tree Smart-Tag: Confidence-gated adaptive prediction (
predictNextPathAdaptive) eliminates cold-start guesses;<Collections />andTreeViewrender polymorphic<a>links withdata-preload="smart"for the server-predicted collection destination alongside pinned and plugin links. - “Warm Next Page” on Smart Tables: Converted table pagination controls (
pagination.svelte) to predictive links; the Next Page button usesdata-preload="viewport"to trigger automatic route pre-warming (/api/system/prewarm-route) and data preloading viaIntersectionObserverbefore click. - Heat-Aware SWR & Dashboard Quick Chips: Top-20 hot collections keep a 60s fresh window but serve stale up to 15 min with background revalidation; the admin dashboard surfaces quick-access chips from
hotCollections. - Smart Table Saved Views 100% Platform Completion: Extended
SmartTableSavedViewsMenuto Media Gallery (mediagallery/+page.svelte) and User Management (admin-area.svelte), achieving 100% coverage across all 4 list surfaces (Collections, Website Tokens, Users, and Media Gallery). - SSR & Pipeline Lean Execution:
- Zero Redundant User Reads: Synchronized
userCountbetween authz hook and SSR layout cache; seededlayout:user:${_id}upon auth resolution, eliminating secondary DB user queries on layout render. - Content Structure SSR Fast-Path: Eliminated unused
getNavigationStructureexecution fromcontentPromiseand added synchronous L1 retrieval for cached sidebar structure. - GraphQL AST Re-Parse Elimination: Stashed pre-parsed GraphQL AST from
handle-security.tscomplexity checks ontoevent.locals.__graphqlAstand wired into GraphQL Yoga’sonParse, avoiding duplicate AST parsing on/api/graphqlPOST requests. Peak throughput reached 4,563 RPS.
- Zero Redundant User Reads: Synchronized
- Behavioral Navigation & Collection Tree Smart-Tag: Confidence-gated adaptive prediction (
-
Admin Session Console & Smart Table List Platform Expansion (September 2026):
- Admin Session Console UI: Shipped dedicated
ModalUserSessionsdialog in User Management (admin-area.svelte) allowing administrators to inspect active sessions per user (device type, browser, OS, client IP, last active timestamp) and revoke individual sessions or all other sessions with CSRF protection. - Status & Role Facet Chips: Added interactive facet chips above the users table (
All,Admins,Users,Active,Blocked) with sub-millisecond filtering and dynamic state management. - List Query Observability: Instrumented
recordListQueryacross/api/userand/api/tokenand connectedSmartTableMetricsBadgeto user management and token tables for real-time p50/p95 latency and hit-rate telemetry. - Filter Engine Reuse: Added allowlisted field filtering (
role,blocked,email,username,createdAt) acrossauth.tsandAuthNamespace.listUsers.
- Admin Session Console UI: Shipped dedicated
-
Adapter-agnostic speculative Turbo fill & 100k write-burst integrity (September 2026):
-
Functional loop (measured): hover/predict/viewport/smart →
GET /api/system/prewarm-route→ LocalCMSfindById/find(all 4 adapters) →{success,data}stringified once →responseCache.buffer(Uint8Array) → next collection GET returnsX-Cache: TURBO-HIT(zero DB, no per-requestJSON.stringify). Proven bytests/benchmarks/speculative-turbo-hit.test.ts. -
What this is not: no frozen radix trie / O(1) segment-offset dispatch (
classifyRouteSpecis a 15-entry exact Map +startsWith+ 1024 LRU). Prediction ispredictNextPathAdaptive(≥5 samples and 30% accuracy, not an 82% example). Extinction is 0.8 on non-chosen alternatives at navigation time, not a 10s timer. Transition cap is 10,000 per tenant (prediction-stats cap is 5,000). Turbo GET still goes through SvelteKitResponse+ security headers — JSON/GC cost is gone; there is no raw TCP-socket bypass. -
Write-lane TTL slide:
getTurboAuthContextslides expiry on every hit; the collection write lane falls through to full auth instead of 401 when turbo is cold. NodeheadersTimeoutonindex.cjs/index.bun.tsis 10 minutes (was 60s 408 with no CMS log). That pair aborted 14/100k Postgres seed rows at ~60s. -
Seed integrity: HTTP seed bursts retry 401/408/429/503/socket drops and fail if
created !== requested. VerifiedBENCH_DOCS=100000competitive replica on PostgreSQL: 100,000/100,000 seeded, 87s wall (self-measured 2026-09-02). -
Scale-spectrum ledger (executed):
exportResultper step +printTruthTable;BENCHMARK_RECORD=1wrotehistory.sqlite(test_id=scale-spectrum) and MDX ledgers. Self-measured 2026-09-02, 1-row vs largest-step retention:Engine Steps findByIdRandom listFilterSort GraphQL Create SQLite (entry) 1→10k 2271→1581 RPS (0.70×) 2444→4602 (1.89×) 2047→3873 (1.89×) 1474→2540 (1.73×) PostgreSQL 1→100k 2379→1372 (0.57×) 2185→4410 (2.03×) 1923→3294 (1.71×) 1084→1482 (1.40×) MariaDB 1→100k 2303→1366 (0.60×) 2124→4329 (2.04×) 2098→4021 (1.91×) 1132→1002 (0.90×) MongoDB 1→100k 2420→1905 (0.78×) 2193→3971 (1.81×) 2179→3085 (1.41×) 1370→2521 (1.84×) LIMIT/PK paths stay flat-to-faster with N. Random PK retains 57–78% at 100k (B-tree depth + cache misses), not an O(N) collapse. MariaDB create at 100k is the only write dip (0.90×).
-
SQLite
mixedtrend (not a new cliff): ledger 🔴 +34% seq / +45% con vs the 7-run mean (~0.43 / 1.89 ms). This session’s pre-change baseline was already 0.47 / 2.80 ms; post-change re-runs sit at 0.58 / 2.74–3.47 ms. Mixed is write-growing (1/10 creates) and runs last after 300 creates + 300 updates, so it is noisier than isolated lanes. Not treated as a product regression. -
Bootstrap skip:
routeSpec.skipMiddlewaresis honored (preferences/media) instead of being classified and ignored.
-
-
Point-lookup slab & hot-path serialization (September 2026):
- Random-access slab cache: Implemented dedicated 2,000-slot FIFO cache (
randomAccessSlab, 30s TTL) for point lookups, automatically invalidated on node/collection mutations. Boosted warmfindByIdRandomfrom 1,488 RPS to 4,404 RPS (+196% cold-to-warm boost), bringing random point-reads to parity with sequentialfindById(4,643 RPS). - LRU lock contention elimination: Disabled
updateAgeOnGetonENTRYandCONTENTcategories incache-service.ts, removing unnecessary LRU reorder lock overhead on read-heavy hot paths. - Pattern invalidation acceleration: Invalidation latency reduced by −87% (4.11ms → 0.53ms, +676% RPS, 243 → 1,884 RPS).
- Single-pass O(N) array serialization: Refactored
serializeArrayFastinsrc/utils/fast-json.tsinto a single-pass join ([${out.join(",")}]), avoiding multi-slice concatenation and GC overhead on large array payloads (listLargereaching 3,538 RPS in SQLite replica). - High-concurrency pool floor: Increased base connection pool floor from 20 to 32 on 8c+ hardware profiles (
src/databases/postgresql/adapter-core.ts), preventing connection pool starvation under 32 concurrent clients. - Transactional mail pool hardening: Enforced
maxRecipients: 1,maxMessages: Infinity,connectionTimeout: 10_000, andsocketTimeout: 30_000on pooled Nodemailer transports (src/utils/email.server.ts), guaranteeing forward-compatibility with Nodemailer 10.0.0.
- Random-access slab cache: Implemented dedicated 2,000-slot FIFO cache (
-
Lean Architecture, Route Centralization & Storage Deduplication (September 2026):
- Catch-All Gatekeeper Centralization: Eliminated rogue standalone API routes (
/api/seo/link-suggestions,/api/chat,/api/remoteVideo), consolidating their logic into the central dispatcher (src/routes/api/[...path]/+server.ts) andhandlers/utility.ts. All endpoints now strictly enforce centralENDPOINT_PERMISSIONS, fail-closed defaults, and RBAC matrix validation. - Storage & Connection Pool Deduplication: Re-architected
src/utils/temp-store.tsto delegate directly tocacheService(CacheCategory.SESSION), eliminating duplicate Redis client instances, redundant pool allocations, and manual Map fallbacks. - Dead Code & Micro-File Pruning: Eliminated ~1,200 LOC of dead code across 12 obsolete files (
file-uploading.ts,security.worker.ts,fs-mock.ts,path-mock.ts,response-headers.ts,field-selection.ts,memory-optimizer.svelte.ts,schema-diff.ts,server-utils.ts,system-integrity.ts,migration-manager.ts,lazy-rune.ts), eliminating micro-file fragmentation and legacy bridges. - Shared Logic & Security Hardening: Consolidated email normalization with Unicode NFC normalization against homoglyph attacks; unified database connection string parsing in Setup with
@utils/connection-parser; wired clientSave-Dataand connection quality detection into@utils/predictive-preload. - AGENTS.md Contract Compliance: Enforced
raise()error handling contract across preview endpoints and eliminatedcatch (err: any)in favor of typedcatch (err: unknown)with safe stringification.
- Catch-All Gatekeeper Centralization: Eliminated rogue standalone API routes (
-
Enterprise Review Workflows, Visual Block Builder & In-App API Playground (September 2026):
- Multi-Stage Review Workflows: Shipped complete content lifecycle FSM (
WorkflowService) with RBAC-gated stage transitions (workflow:transition), audit history logging, collection binding, and visual state editor at/config/workflows. Editors transition entries with optional review comments directly inside the content view via<WorkflowActions>. - Visual Polymorphic Block Builder Widget: Shipped custom
BlockBuilderwidget (src/widgets/custom/block-builder/) enabling component-driven page design with 8 production presets (Hero, Text & Prose, Media Showcase, Call to Action, Features Grid, Quote/Testimonial, FAQ, Rich Text), Valibot schema validation, keyboard-accessible reordering, duplicate/delete operations, and framework-agnostic JSON output for modern headless frontends. - In-App Developer & API Playground: Shipped unified developer console at
/config/apiinside<AdminPageShell>, providing live REST execution against the OpenAPI 3.1 specification, interactive GraphQL query testing against/api/graphql, and multi-language client code generation (cURL, TypeScript fetch, Python requests, zero-latency SvelteKitLocalCMS).
- Multi-Stage Review Workflows: Shipped complete content lifecycle FSM (
-
Enterprise OIDC / SSO with RFC 7636 PKCE & JIT Role Mapping (September 2026):
- RFC 7636 PKCE (S256): Cryptographic Proof Key for Code Exchange support with high-entropy code verifier generation and SHA-256 code challenge computation on every authorization code login flow (
buildOidcAuthorizationUrlandexchangeOidcCode). - Just-In-Time (JIT) Auto-Provisioning & Claim Role Mapping: Automated new user registration on successful OIDC callback, mapping IdP claims/groups (e.g.
groups,roles) to local CMS roles (resolveJitRole), with optional role synchronization on each login. - Dynamic Branded IdP Login Buttons: Front-end login screen (
/login) now dynamically discovers and renders branded IdP login buttons (Google Workspace, Microsoft Entra ID, Okta, Auth0, Keycloak) with official icons, accessible ARIA labels, and predictive hover preloading. - SSO & OIDC Management Console: Dedicated tab in Access Management (
/config/access-management) with provider presets, issuer OIDC discovery validation, redirect URI allowlisting, and granular JIT claim rule management backed by/api/auth/sso-providers.
- RFC 7636 PKCE (S256): Cryptographic Proof Key for Code Exchange support with high-entropy code verifier generation and SHA-256 code challenge computation on every authorization code login flow (
-
Enterprise Collection Builder, Split-View & Database Schema Ingestion (September 2026):
- Smart Type Inference & Natural-Language Quick-Add: Built a zero-dependency client-side heuristic engine (
src/routes/(app)/config/collectionbuilder/smart-inference.ts) enabling instant natural-language field creation (e.g.email,price,cover_image,tags,is_published,author_id,rating,birth_date). Automatically resolves widget types, icons, database field names, and pre-configured validation constraints onEnter ↵, eliminating tedious modal clicks. - Dual-Pane Live “Visual Canvas ↔ TypeScript Code” Split-View: Implemented real-time bi-directional code ↔ GUI awareness in
collection-widget.sveltewith a 3-way view toggle (Canvas|Split View|TypeScript). In Split View, visual canvas edits reactively reflect in the generated production-readydefineCollection({ ... })TypeScript schema (collection-code-generator.ts) with zero disk overhead, 100% parity with SveltyCMS AST compilation, and 1-click clipboard export. - Database DDL & Schema Ingestion Engine: Engineered a server-side schema-ingestion engine (
schema-ingestion.ts, executed through theingestSchemaSvelteKit server function) andmodal-schema-ingestion.svelte, enabling instant reverse-engineering of SQLCREATE TABLEdefinitions across PostgreSQL, MySQL, MariaDB, and SQLite directly into SveltyCMS collections. Automatically resolvesFOREIGN KEY ... REFERENCES other_table(id)and*_idcolumns toRelationwidgets (matched against the live collection list),ENUM(...)/CHECKconstraints to populatedSelectwidgets,JSON/JSONBtoJsonEditor, arrays toTags/Repeater, and parses arbitrary REST API JSON payloads into structured collections. - Tree-View Hierarchy & Drag-and-Drop Hardening: Corrected RTL off-screen coordinate mapping in
tree-drag-preview.sveltewith floating nesting targets (↳ Inside {category}), added live collection/category statistics, and implemented full keyboard reordering (Alt+Up/Down) with roving tabindex. - Unified Favorites & Tagging System with LeftSidebar Integration: Introduced
collectionMetadataStorewith Svelte 5 runes ($state), deterministic WCAG 2.2 AA token-based tag color palettes, and cross-component/cross-tab synchronization. Enables 1-click starring (★) of collections and categories in both the builder board and the left sidebar, color-coded tag taxonomy, dual-toolbar filtering chips (FavoritesandAll Tags), in-form tag configuration, and instant navigation from the sidebar header.
- Smart Type Inference & Natural-Language Quick-Add: Built a zero-dependency client-side heuristic engine (
-
Per-Role Multi-Factor Authentication Enforcement (MFA-RBAC) (September 2026):
- Role-Level MFA Requirement: Added
Role.mfaRequiredattribute with full database schema support across all 4 database adapters (SQLite, PostgreSQL, MariaDB, MongoDB). - Session AMR Lifecycle Tracking (RFC 8176): Sessions capture Authentication Methods References (
amr: ["pwd"]vsamr: ["pwd", "mfa"]) andmfaVerifiedAttimestamps across in-memory and Redis stores. - Centralized Endpoint Gating: Requests made under mandatory-MFA roles carrying non-MFA sessions are rejected at the gateway (
_checkEndpointPermission) with HTTP 403MFA_REQUIRED. - Zero-Bypass Device Policy: Device trust cookies (
__Host-2fa-trusted-device) are strictly overridden for roles withmfaRequired: true, mandating interactive second-factor verification on every session.
- Role-Level MFA Requirement: Added
-
Token-Bucket Rate Limiter with Redis-Fallback & Adaptive Throttling (September 2026):
- Continuous Token Replenishment: Replaced coarse fixed-window counters with a high-precision Token-Bucket algorithm (
handle-rate-limit.ts) for smooth burst management, eliminating 2x spike artifacts at window boundaries. - Redis-Primary with Automatic Memory Fallback: Distributed token accounting via atomic Lua script on Redis L2 with a strict 15ms timeout and 5-second circuit-breaker backoff. Falls back seamlessly and instantaneously to local in-memory token buckets without failing requests or degrading user latency.
- Zero-Tax Adaptive Sizing: Dynamically scales bucket capacities based on user profile (Admin 10x, Authenticated User 2x, Anonymous 1x) and system hardware pressure from
SystemMonitor(0.8x–2.0x), utilizing a zero-overhead synchronous session cache peeker inhandle-authentication.ts. - Self-healing / predictive / cost-aware upgrades (A+B+C):
system-pressure.tsEWMA of processcpuUsagedeltas (normalized by core count) plus 1-minuteos.loadavg()when the OS reports it (ignored on Windows zeros — not hypervisor-specific).request-clock.ts24h/15-min histogram for predictive throttle after warmup.endpoint-cost.tslongest-prefix token costs (/api/graphql2, media upload 5, import 8, AI copilot 10). Admin tier never scaled down. Tests:tests/unit/rate-limit/*.test.ts.
- Continuous Token Replenishment: Replaced coarse fixed-window counters with a high-precision Token-Bucket algorithm (
-
Application-Layer Encryption (ALE) Zero-Tax Performance Benchmark (September 2026):
- Empirical Benchmarking Suite: Created
tests/benchmarks/ale-encryption-impact.test.tsprofiling AES-256-GCM field encryption oncontent_nodesandaudit_logsacross 256 B, 4 KB, and 64 KB payloads. - Zero-Tax Cryptographic Optimization: Optimized
field-encryption.tswith static crypto imports, synchronous key resolution (getFieldKeysSync()), direct GCM ciphertext extraction, and synchronous primitives (encryptFieldValueSync,decryptFieldValueSync). - Tolerance Gate Verification: Achieved sub-18 µs encryption latency (<3% of total write time), >213 MB/s memory throughput, and 0.20–0.55 ms transactional DB write speeds, confirming compliance with the <5% write overhead tolerance budget. Documented in Encryption Impact Benchmark.
- Empirical Benchmarking Suite: Created
-
Build Simplified Back to Plain Vite (September 2026):
scripts/build.tsremoved: the separate build orchestrator (which had become broken on Windows — it imported awin32-adapter-node-patchmodule that never existed) is deleted in favour of the standard SvelteKit flow:"build": "vite build", with the documentedbuild:node/build:bun/build:stats/build:analyzevariants as one-line Vite invocations. Dev, CI and Docker all run the same single pipeline.- Its two real jobs moved into
vite.config.tsas post-build plugins:bundleYjsSyncServerPluginemitsbuild/yjs-sync-server.js(esbuild, same options) for the/wsmount inindex.server.mjs, and the module-worker copy already lived in Vite (copyWorkerFilePlugin). No shell wrapping, no duplicate entry point.
-
“Can We Do Better” Native-Utils Pass & WAF Rate-Limiter Consolidation (September 2026):
- Strict RFC 9562 UUIDv7-only IDs: LUT-based
validateId()(+45 % throughput, 1.83× faster rejection) plus zero-allocationgenerateUUIDv7(10.2 M UUIDs/s, 98 ns/call vs 1.68 M npmuuidv7) replaced everycrypto.randomUUID()/v4 site and fixture; timestamp-ordered keys remove PG/SQLite B-tree 50/50 page splits and WAL amplification.generateUUIDv4deleted — no legacy tail. - WASM-free hashing: synchronous
fastHash(5.22 M ops/s, 2.32× the WASMxxhash64) replacedhash-wasmincompile.tsandsync-content-state.server.ts; dependency removed,etag-hash.test.tsmigrated. - WAF rate limiter on the native token-bucket engine:
rate-limiter-flexiblereplaced bysrc/utils/rate-limit/withwindowMsfixed windows + overdraft parity, Redis Lua primary with memory fallback, prefix-scoped dump/restore, and a bot-UA short-circuit ahead of the limiter (deterministic 403 for known bot/scanner User-Agents on mutations — the integrationsecurity.test.tsUA/rate-limit pair stays green in full-parallel). Dependency removed. - Zero-allocation utilities: single-pass
diff-utils/data-utils,FastLRUfor local caches,generateUUID()in widgets (replacingDate.now()+Math.random()ID construction);nanoid/deepmerge-tsstay as security-pinned overrides for transitive deps.
- Strict RFC 9562 UUIDv7-only IDs: LUT-based
-
Adapter Template-Cache Parity & Cross-Engine Test Hardening (September 2026):
- MariaDB reaches SQL-template parity with PostgreSQL/SQLite: new FIFO-capped
_mariaUpdateTplCache,_mariaUpsertTplCacheand_mariaInsertReturningTplCachebuild the prepared SQL once per column shape (key includes column ORDER, tenant clause, merge mode, RETURNING choice), plus fail-closedassertSafeSqlIdentifieron the cached identifiers. MongoDB’smapQueryoperator table is now module-scoped — no per-condition allocation. - Measured honestly: a paired A/B on the live MariaDB container (same host, same session, alternated builds, untouched read lanes as controls) shows the caches are within the benchmark’s noise floor on single-op lanes (UPDATE 0.774→0.796 ms while control reads drifted −12–15 %) — the update lane is commit-bound, not statement-CPU-bound. The shipped value is structural: engine parity, per-call allocation elimination, and stable SQL text that lets mysql2’s server-side prepared-statement cache hit.
- Cross-engine fixes found by the matrix: three contract tests wrote >36-char
_id/user_idvalues that MariaDB/PostgreSQL reject (ER_DATA_TOO_LONG) while SQLite silently accepts (ids now ≤36 viashortId());run-all-db-matrix.tsmisread5 failedfrom the passing test name “…after 5 failed login attempts” and now parses the harnessTotal/Pass/Failreport block;collection-widget.svelteregained the missing@thisux/sveltedndimports. Matrix re-run: all 4 engines PASS, 0 fail (sqlite 689, mongodb 687, mariadb 692, postgresql 695). - Auth hot-path date stamping now uses the memoized
nowISODateString()/dateToISODateString()helpers (session AMR/MFA, magic-link, 2FA, session manager) instead of per-callnew Date().toISOString()— rule §1 dates, one allocation per millisecond instead of per call.
- MariaDB reaches SQL-template parity with PostgreSQL/SQLite: new FIFO-capped
-
Paraglide i18n Tree-Shaking Audit & Copy Scanner (September 2026):
- Strict Tree-Shaking (Rule 3.1): Eliminated wildcard imports (
import * as m from '@src/paraglide/messages') across dashboard widgets in favor of explicit named imports, preventing dead message bundle bloat. - Automated Hardcoded Copy Scanner (
scripts/check-hardcoded-copy.ts): Introduced an automated scanner and CI check guarding against wildcard paraglide imports and untranslated strings across admin Svelte routes.
- Strict Tree-Shaking (Rule 3.1): Eliminated wildcard imports (
-
Enterprise Multi-Stage Review Workflows (September 2026):
- Full Lifecycle Finite State Machine: Built
WorkflowServicewith configurable stage graphs (draft→review→approved→published), audit history logging, and role-based stage transitions guarded byworkflow:transition. - Visual State Graph Editor & In-Context Controls: Visual workflow editor at
/config/workflowswith collection binding, transition guards, and<WorkflowActions>embedded in the collection content view allowing editors to execute transitions with optional review comments.
- Full Lifecycle Finite State Machine: Built
-
Visual Polymorphic Block Builder (September 2026):
- Component-Driven Page Layout Widget: Production widget (
src/widgets/custom/block-builder/) featuring 8 standard presets (Hero, Text & Prose, Media Showcase, Call to Action, Features Grid, Quote/Testimonial, FAQ, Rich Text). - Interactive Ergonomics & Headless Export: Keyboard-accessible block reordering, duplication, collapse/expand, Valibot schema validation, and headless JSON export matching AST compilation standards.
- Component-Driven Page Layout Widget: Production widget (
-
In-App Developer & API Playground (September 2026):
- Unified Developer Console: Integrated console at
/config/apiinside<AdminPageShell>providing interactive OpenAPI 3.1 REST execution, live GraphQL query testing against/api/graphql, and multi-language client code generation (cURL, TypeScript fetch, Python requests, zero-latency SvelteKitLocalCMS).
- Unified Developer Console: Integrated console at
-
GDPR Article 17 Right to Erasure (September 2026):
- Cascading Data Erasure: End-to-end permanent erasure at
POST /api/gdpr(action: "erase"/"wipe") cascading across sessions, tokens, audit logs, and users viagdprService.eraseUserwith stricttenantIdisolation. - Verified Sub-Millisecond Benchmark:
right-to-be-forgotten-audit.test.tsverified on production endpoint with <1ms avg wipe latency and 0 orphaned records across all four database engines.
- Cascading Data Erasure: End-to-end permanent erasure at
-
Intelligence Consumption & Predictive Navigation (September 2026):
- Behavioral Learner Preloading: Integrated
predictNextPathAdaptivewith confidence gating; applieddata-preload="smart"to sidebar navigation and collection tree nodes incollections.svelteandtree-view.svelte. - Warm Next Page: Added viewport preloading on Smart Table pagination controls (
data-preload="viewport"on next page link viapagination.svelte) and dashboard hot-collection quick chips. - Heat-Aware SWR: Implemented heat-aware stale-while-revalidate in
collection-service.ts, serving hot top-20 lists stale up to 15 minutes while maintaining 60s fresh TTL.
- Behavioral Learner Preloading: Integrated
-
Request-Pipeline Lean Optimization (September 2026):
- Single L2 Cache Key: Unified hook and dispatcher caching under a single canonical L2 cache key, retiring duplicate namespaces.
- Single-Compression Guarantee: Introduced a
compressed: {}sentinel guaranteeing a single compression pass (Brotli/Gzip/Zstd) re-populating L1 variants. - Admin Sidebar Caching: Cached admin sidebar payloads per
(tenantId, contentVersion)to eliminate per-SSR reconstruction and recursive cloning overhead.
-
Smart Table Saved Views Integration (September 2026):
- Unified Saved Views UI: Wired
SmartTableSavedViewsMenuacross User Management (admin-area.svelte), Entry List, and Website Tokens surfaces.
- Unified Saved Views UI: Wired
-
Column Resize & Bulk Field-Edit Platform (September 2026):
- Omni-Surface Column Resizing: Header drag-resizing and width persistence (
ColumnResizeHandle+smartTable.getColumnWidthStyle) extended across all primary CMS data tables: Entry List (entry-list:{id}), Website Tokens (website-tokens-table), Media Gallery (media-gallery-table), and User Management (admin-area-users/admin-area-tokensvia dynamicsmartTable.setLayoutKey). - Bulk Field-Edit UI: Interactive batch edit modal (
bulk-field-edit-modal.svelte) wired to MultiButton (Alt+E), resolving field types (text, number, boolean, date) and executing batch updates viaPOST /api/collections/:id/batch(action: "update"). - Sticky Columns Hardening: Ensured pinned start (selection checkbox) and pinned end (actions) columns have solid, non-bleed backgrounds (
bg-white dark:bg-surface-900) and pinned body rows across horizontal scrolling on wide tables.
- Omni-Surface Column Resizing: Header drag-resizing and width persistence (
-
Unified Upload Allowlist & Write-Time MIME Conflict Defense (September 2026):
- Single Upload Allowlist: Derived
isAllowedUploadMime()fromMIME_MAPminus scriptable and office types (text/html, JS, XML, CSS, Office formats); used uniformly across streaming uploads,saveMedia, and the<MediaUpload>widget. Permissive media formats (HEIC,AVIF,FLAC) supported while eliminating insecure prefix matching (image/*). - Write-Time Polyglot Conflict Detection: Implemented
assertMimeAgreement()running after magic-byte sniffing (first 512 bytes) on write. Allows same-family transitions (JPEG vs PNG) while strictly rejecting polyglots, scriptable payloads, or HTML masquerading as image extensions (MIME_MISMATCH), avoiding expensive head reads on GET requests. - Universal
nosniffDefense: EnforcedX-Content-Type-Options: nosniffglobally onMEDIA_RESOURCE_HEADERS, ensuring local file delivery, cloud 302 redirects, share-downloads, and turbo static responses strictly reject browser MIME sniffing. - Serve-path resolve without
mime-types/mrmime:/filesand share-download useresolveMimeTypeFromPath(extension map → stored MIME → 512-byte sniff →application/octet-stream). Scripts usenode:fsglobSyncinstead of theglobpackage. - Remote/importer MIME:
resolveRemoteAssetMime()is the shared path for smart-importer, S3 mirror PUTs, andsaveRemoteMedia. RemoteContent-Type: octet-streamis treated as unknown so sniffing can recover the type; HTML is sniffed (<!DOCTYPE html>/<html) and rejected against image claims. Image-editor watermark/blur/annotate IDs usegenerateUUID()(CSPRNG). - Verification:
tests/unit/media/media-utils.test.ts,tests/unit/media/slim-sniffer.test.ts,tests/unit/media/processing/media-svg-ssrf-regression.test.ts.
- Single Upload Allowlist: Derived
-
CollectionsNamespace Consolidation & Hot-Path Architecture (August 2026):
- Monolith Decomposition: Decomposed the ~2,100-line
collections-namespace.tsinto a lightweight orchestrator supported by focused modules (schema-store,read-pipeline,write-pipeline,post-write,request-cache,lazy-services). - Zero-Redundancy Schema Walks: Field prep consolidated to a single schema walk and single clone via
prepareCollectionFields()incontent-utils.ts, replacing 3 prior passes; unifiedapplySchemaHookPipelineacross create/update operations. - Physical Table Naming Reconciliation: Normalized adapter table naming (
normalizeCollectionTableName()) across SQL (getTable,createIndexes,registerTableSchema) and MongoDB (normalizeCollectionName,createModel), resolving hyphenated-ID model registry inconsistencies. - Hot-Path Optimization: Inserted widget transform field-level caching, insert-only
createdAtsemantics, synchronous L1 mutation with background L2 fire-and-forget invalidation, and session verification via single JOIN.
- Monolith Decomposition: Decomposed the ~2,100-line
-
Unified Translation Cockpit & Multilingual Studio Inspector (September 2026):
- Dual-Lens Architecture: Live Paraglide admin chrome switcher coupled with content translation readiness metrics in
translation-cockpit.svelte. - Automated Deficiency Resolution: Real-time completeness calculation, interactive jump-to-field focus, and 1-click AI document auto-translation. Tests:
tests/unit/utils/translation-analytics.test.ts.
- Dual-Lens Architecture: Live Paraglide admin chrome switcher coupled with content translation readiness metrics in
-
Dependency & Runtime Lean-Out (September 2026):
- Zero-Lodash & Modular AWS: Full elimination of Lodash, lazy on-demand S3 client, and dynamic import of MapLibre GL in address inputs.
- Svelte 5 Runes Footprint: Deletion of legacy store monoliths, 28 → 18 store consolidation, and removal of unused dependencies (
shiki).
-
Smart Table Advanced UX & Adaptive Grid Platform (September 2026):
- Multi-Column Sort & Ordinal Ranks: Composed multi-column sorting via
Shift + Clickwith visual rank indicators (1,2,3), cascading client-side comparators, and query serialization. - Compound Filter Logic: Schema-aware
AdvancedFilterModalwithAND/ORmulti-clause conditions and contextual operators. - Grid Accessibility & View Modes: WCAG 2.2 AA roving tabindex cell/row keyboard traversal,
Alt + Xselection export, and dynamictable/cardview mode switching. Tests:tests/unit/components/smart-table-advanced-ux.test.ts.
- Multi-Column Sort & Ordinal Ranks: Composed multi-column sorting via
-
Media Pipeline — Dedupe Order & Derivative Ladder (September 2026):
- Dedupe Before Derivative Generation: The tenant-scoped content-hash lookup in
MediaService.saveMedia()now runs before any resize work, so re-uploading identical bytes performs zero encodes and writes no derivative file. The reuse path verifies that the original and every derivative path the row advertises still exist (cache-bypassing lookups) and, when one is gone, repairs the row in place (original rewritten, ladder re-derived,metadatamerged) instead of returning URLs that 404 or inserting a second row for identical bytes. The deferred responsive-variant job is not scheduled on a hit at all. Because nothing is rewritten, theW/"size-mtime"ETag survives a re-upload and clients stop re-downloading unchanged assets. - Derivative Ladder Clamped to the Source:
saveResized()drops every SIZES step wider than the decoded source and passeswithoutEnlargementto the encoder, so a 400 px icon is no longer enlarged into four derivatives; each resolved path is written once (an already-WebP primary no longer collides with its own sidecar), and thethumbnailsmap now records the encoder’s real output dimensions instead of the requested ones. - Shared reuse/repair helpers:
findMissingStoredFile(),adoptExistingRecord(),repairExistingRecord()andbuildDerivativeSet()replaced three near-identical dedupe blocks (SVG, buffered raster, streamed raster) with one contract. - Also fixed (found by the new assertion):
enrichMediaWithUrl()re-prefixed thumbnail URLs that already carried/files/…, producing/files/files/…and making reuse responses differ in shape from insert responses. - Measured (real SQLite stack,
bun run test:integration tests/integration/api/media-dedupe-derivatives.test.ts, 2026-09-22): 400 px source — 8 derivative files / 16,984 B / a 600 px upscaled variant → 2 files / 600 B / no upscaled variant; duplicate upload — 47.5 ms and 8 of 9 stored files rewritten → 3.0 ms and 0 of 5 rewritten. - Verification:
tests/unit/media/derivative-ladder.test.ts,tests/unit/media/upload-dedupe-order.test.ts,tests/integration/api/media-dedupe-derivatives.test.ts; benchmark rowsMedia: Derivative Fan-Out (1920px|400px source)andMedia: Duplicate Upload (identical bytes)intests/benchmarks/media-performance.test.ts, withmedia.derivatives.*/media.duplicate.*metrics. Architecture and verification: Media System Architecture.
- Dedupe Before Derivative Generation: The tenant-scoped content-hash lookup in
-
Partial-Update Data Loss Fixed — JSON
dataMerge on All Four Engines (September 2026):- The bug:
crud.update()built one whole-column assignment for the JSONdatablob (SET "data" = $n::jsonbon PostgreSQL,`data` = ?on SQLite/MariaDB), so a partial PATCH replaced every dynamic field it did not mention. Measured externally before the fix: 30,940 of 100,000 documents collapsed from a ~1.5 KB median to 61-byte stubs — and because their ownfindByIdRandom/graphql rows readids[0], the corruption also inflated a cached-read benchmark number. MongoDB was never affected ($setmerges per field), which is what fixed the target semantics: a shallow, null-keeping merge. A patched object replaces wholesale (no recursion), andnullis a stored value rather than a deletion. - The fix:
prepareValues()marks a partial dynamic-field payload as a patch (aWeakMapentry keyed by the prepared values — invisible toObject.keys, spreads and Drizzle.set()), and each dialect applies it at zero extra cost: PostgreSQL merges inside the UPDATE (COALESCE("data",'{}'::jsonb) || $n::jsonb), SQLitejson_patch(...), MariaDBJSON_MERGE_PATCH(...). Both of those are RFC 7396 — recursive, and they delete keys patched withnull— so patches containing nested objects or explicitnullhydrate the stored blob and merge in JS instead (jsonPatchNeedsJsMerge()); scalar/array patches keep the single-statement fast path. The Drizzle fallback merges in JS too, so no path can silently fall back to the destructive write. Full-document writers (sync, seeds) opt out explicitly withreplaceData: true, whichskipReturning: truealready implies — that path reconstructs the row from the prepared values, so a merge would misreport the untouched columns. - Fail-closed, not silent: if the blob for a JS-merge cannot be read (missing row), the update throws rather than persisting the patch as the whole document.
- Verification:
tests/unit/databases/json-data-patch-merge.test.ts(exactness boundary, blob decoding, marker invisibility) and four new cases intests/integration/databases/adapter-parity.test.ts— partial patch keeps untouched fields, nested object replaces shallowly, explicitnullis kept, and thereplaceDataopt-out. Run green on SQLite 21/21, PostgreSQL 21/21, MariaDB 21/21, MongoDB 20/20 + 1 intentional skip (replaceDatahas no MongoDB counterpart — its$setnever replaced anything). The reported request shape is also covered end-to-end through the HTTP API (tests/integration/api/collections-mutations.test.ts:PATCH {views: 2}on a document withtitle/bodymust return and persist all three), green on SQLite and PostgreSQL. - Still to do (roadmap): none for the schema path — the spec is the only trail now.
- The bug:
-
Partial-Update Merge Extended to Every Bulk Write Path (September 2026):
- Bulk paths merged:
crud.updateMany,batch.bulkUpdate(homogeneous single-statement path, heterogeneousrawBulkUpdateCASE builder on SQLite/MariaDB/PostgreSQL, and the transactional per-row fallback) and the query-builderupdateManyall merge the JSONdatapatch now — they used to write the patch as the entire blob for every matched row. Where the dialect operator is exact the merge stays inside the statement (zero reads, zero extra round trips); a nested-object or explicit-null patch on SQLite/MariaDB cannot be expressed in oneSET, so the CASE builder refuses the fast path and the row-by-row path merges each row against its own stored blob. - Query-builder silent drop fixed:
SqlQueryBuilder.updateManydumped its payload straight into Drizzle.set(), which discards every field that is not a physical column — blob fields (e.g.title) never persisted through that path. It now routes through the sameprepareValuescontract ascrud.update/batch.bulkUpdate. - MongoDB update parity: Mongoose
strict(the schema default) dropped undeclared$setpaths, so patching a dynamic field the collection schema did not declare was a silent no-op on MongoDB while SQL adapters stored it indata. All Mongo update sites (crud.update/updateMany,bulkWrite, both query builders) now passstrict: false; the namespace/field-permission layer remains the schema gate. Covered by a new cross-engine case that patches an undeclared field. - MongoDB query-builder resolution bug (found while testing):
MongoDBAdapter.queryBuilder()resolved its model with the raw collection name whilecrud.*normalizes it — the lookup missed, and_getOrCreateModelbuilt a generic model bound to a differently named MongoDB collection. Every builder operation (find/count/updateMany/deleteMany) silently ran against an empty collection. Now resolved exactly likeMongoCrudModule._getRepo. - Verification:
tests/integration/databases/bulk-operations-contract.test.ts(partial-patch merge, nested-patch merge, per-row blob fields, homogeneous blob patch, query-builder persistence) and the parity suite’s dynamic-field case — green on SQLite, PostgreSQL, MariaDB and MongoDB.
- Bulk paths merged:
-
Engine-Native Dynamic-Field Filters: Containment + GIN on PostgreSQL (September 2026):
- The gap: a field that is not materialized into a column lives in the JSON
datablob, and an equality filter on it was translated on every SQL engine to a text extraction (data->>'field' = $1) — a form no index can serve. Measured 2026-09-22 on 100k rows (PostgreSQL): Seq Scan 7.9–10.9 ms per filtered read, growing linearly with the table. - The fix (engine-specific by construction): PostgreSQL now emits containment —
data @> '{"field": value}'::jsonb(getJsonEquals, wired throughmapQueryfor$eqand$in, one probe per scalar value in anORfor lists ≤ 32) — andcreateModelprovisions ajsonb_path_opsGIN index (<table>_data_gin) for every dynamic collection table. Measured after: Bitmap Index Scan 0.1–1.7 ms, and the cost stops growing with table size. Index build 347 ms once at provisioning; write cost measured within run-to-run noise (interleaved A/B/A rounds: −0.9 % inserts, +5.4 % updates). Write-only collections opt out withschema.jsonIndex === false. - Type fidelity: the probe serializes the RAW filter value, so a number filter probes a JSON number and a string filter a JSON string —
{views: 101}matches a stored number but not a stored"101". SQLite (typedjson_extract) and MongoDB (type-aware comparison) already behaved that way; the previous PostgreSQL text comparison did not, so this increases cross-engine parity. MariaDB stays the documented looser exception (JSON_UNQUOTEcomparison coerces). Dotted paths probe nested objects (a.b→{"a":{"b":…}}), matching thedata#>>'{a,b}'form. - Why the other engines keep the extraction form: SQLite and MariaDB have no single index that serves arbitrary JSON paths. Their native answer is already shipped — the row-store hybrid: declare the field
indexed: true(ormaterialize: true) and it becomes a real column with a real index. The implementation notes now say so explicitly instead of leaving it implicit. - Verification:
tests/integration/databases/dynamic-field-filter-index.test.ts— semantics (number/string/$inprobes, type fidelity) on all four engines, the engine-native SQL shape per dialect, and on PostgreSQL anEXPLAIN (ANALYZE)of the adapter’s own rendered filter SQL asserting the GIN index is used and noSeq Scanappears (bulk-seeded table with real statistics, isolated from the row probes). Full sweep: SQLite 49/49 · PostgreSQL 51/51 · MariaDB 49/49 · MongoDB 48/48 across the parity, bulk-contract, mutation and dynamic-filter suites.
- The gap: a field that is not materialized into a column lives in the JSON
-
System-Schema Fingerprint + Legacy Tails Removed — Boot No Longer Touches an Unchanged Schema (September 2026):
- Measured before: a provisioned PostgreSQL paid 124 ms per boot re-running all 118
CREATE TABLE/INDEX IF NOT EXISTSstatements (0.4–1 ms per round trip), plus ~35ALTER TABLE … ADD COLUMN IF NOT EXISTSrepair statements (~60 ms; each one costs 1.7–5 ms versus 0.96 ms for a trivialSELECT). - Fingerprint:
svelty_schema_statestores a sha-256 overSYSTEM_SCHEMAper dialect; a match skips the spec-derived pass. Any spec change (new table, changed type/default, new index) changes the fingerprint and re-runs the pass, so it is drift-free by construction rather than a hand-bumped version; a pass with a failed statement does not store the marker, so a half-applied schema is retried instead of trusted, and any marker error degrades to “run the pass”. - Legacy tails deleted: the per-engine repair tails are gone — the spec is the only schema trail. Audited first: every column they added (
auth_users.isDeleted/deletedAt,auth_api_keys.rateLimit/status/lastUsed/keyHash) was dead legacy (the current model useslastUsedAt/lastUsedIp/revoked, and the users table never soft-deleted), and the one substantive repair (MariaDBmfaVerifiedAt→DATETIME(3)) is already declared in the spec, so fresh installs keep millisecond MFA precision. The v0.0.8-erasecurity→passwordandredirects_mv.from|torenames are dropped with them; the compatibility consequence is documented in Upgrading SveltyCMS. - Measured after (fresh database → provisioned by the spec alone): 327 ms once, creating 27 tables + 117 indexes; every later boot 2.1 ms (
skipped: true) — ~122 ms saved per start on PostgreSQL. Verified through the compiled build on fresh databases for SQLite, PostgreSQL and MariaDB (all boot withBootstrapping system schema...thenSystem schema up to date (<fp>) — skipped the DDL passon the next boot), with the adapter-parity suite green on each.
- Measured before: a provisioned PostgreSQL paid 124 ms per boot re-running all 118
-
Cross-Engine
crud.aggregate()— the MongoDB Pipeline Runs on SQL (September 2026):- The gap:
aggregate()was MongoDB-only, so SQLite/PostgreSQL/MariaDB answered every pipeline withNOT_SUPPORTEDand a generic caller could not express a grouped count without raw SQL. The capability flags admitted it (supportsAggregation: false). - The translation (
core/aggregation-translator.ts, pure and unit-testable):$match→WHEREthrough the adapter’s own query mapper, so JSON fields, containment and the tenant scope behave exactly likefind*;$group→GROUP BYwith$sum/$avg/$min/$max/$countand a scalar_id(dotted paths allowed,$sum: 1→COUNT(*));$sort/$skip/$limit→ORDER BY/OFFSET/LIMIT;$countand include-only$project→ the select list. Without a group/$projectthe result is thefindManydocument shape, which is what MongoDB’saggregatereturns too. - Fail-closed by design:
$lookup,$unwind,$facet, a compound_id, out-of-order stages and a$matchthat maps to no condition return a namedNOT_SUPPORTEDmessage that names the workaround — never a silently wrong number. The refusals are returned outsidewrap(), which would otherwise nest the envelope into{ success: true, data: { success: false, … } }and make a refusal read as a success (caught by the new contract test, not by the type system). $min/$maxare the one engine-dependent stage, and they now say so: PostgreSQL and MariaDB extract JSON as TEXT, soMINover{10, 2}returns"10"(measured on both; PostgreSQL additionally defines nomin(jsonb)), while SQLite’s typedjson_extractand MongoDB compare natively. A blob field therefore gets a refusal naming the fix — declare itindexed/materialize: true— instead of a lexicographic answer; a materialized column aggregates natively on every engine (new dialect hookgetJsonOrderedField).- PostgreSQL numeric range filters fixed on the way:
views >= 4compared TEXT ('16' < '4', so stored16/32were missed), and the naive JSONB replacement was worse in the other direction — jsonb orders String below Number, so an unguarded<admitted a stored"101"intoviews < 4(verified:'"101"'::jsonb < '4'::jsonbis true). Ranges now readjsonb_typeof(data->'views') = 'number' AND data->'views' >= '4'::jsonb, type-strict in both directions and identical to SQLite/MongoDB behaviour. - SQLite dotted-path parity:
json_extract(data, '$."meta.lang"')treated the dotted name as one literal key, so a{"meta.lang": "en"}filter matched nothing there while PostgreSQL (data#>>'{meta,lang}') and MariaDB ($.meta.lang) resolved the nested value. Segments are quoted individually now ($."meta"."lang"). - Capability flags and SDK surface:
supportsAggregationistrueon SQLite/PostgreSQL/MariaDB — the documented subset, with unsupported stages still failing closed, which the pre-existing contract test now asserts (3 × active/2 × inactiveinstead of an envelope check).CollectionModel.aggregateon MongoDB routes throughcrud.aggregate(tenant$matchinjection, normalized collection resolution, logged failures) instead of the rawmodel.aggregate, and both SDK wrappers log a refused or erroring aggregate instead of silently returning[]. - Verification:
tests/unit/databases/aggregation-translator.test.ts(20 cases — plan shapes, accumulator mapping, purity, every refusal) andtests/integration/databases/aggregation-contract.test.ts(12 cases — group/match/count/sort/limit/project, tenant scoping without an explicit$match, materialized$min/$max, the engine-dependent$minrefusal,NOT_SUPPORTEDparity), green on SQLite 12/12 · PostgreSQL 12/12 · MariaDB 12/12 · MongoDB 9 + 3 intentional skips; the same-commit sweep of the parity, bulk-contract, dynamic-filter and advanced-CRUD suites is green on all four engines.
- The gap:
-
Competitive lanes: read payload de-duplicated, write ack made adapter-agnostic (September 2026):
- The defect: every SQL read merged the JSON
datablob into the document root and also serialized the blob as a nested field. The external harness counted 3,339 B for a 1,558 B document (2.14x) — “~200 B system columns +data{…1558}+ the same 8 content fields flattened again” — and it was the only excess in that comparison that was pure duplication rather than envelope convention (Keystone 1.00x, Strapi 1.03x, Wagtail 1.09x). - The fix: flattening now consumes the blob — the key is dropped by key, not identity, because
normalizeJsonFieldValuePARSES a TEXT blob (SQLite/MariaDB) into a different object, which is exactly why an identity check silently kept it on the wire — and each of the three call sites stopped copying the raw blob (or its TEXT form) back onto the row. A collection field literally nameddatais merged and survives; a legacy string-encoded blob still resolves through the JSON layers. - Measured end-to-end (the payload-budget integration case logs it): point read 3,339 B → 1,851 B (1.23x a 1.5 KB document), PATCH 3,519 B → 1,814 B — i.e. document + the ~200 B of real system columns, no second copy.
Prefer: return=minimal(RFC 7240) onPATCH/PUTanswers{success, data:{_id}}— 70 B measured — on both the warm-session write lane and the API dispatcher. Absent or unknown values keep the full representation, so no existing client changes behaviour.- Adapter-agnostic write ack: the preference maps onto the existing
BaseQueryOptions.skipReturningcontract instead of being a response-shape trick, so the row read-back is skipped too — SQL engines omitRETURNINGand reconstruct the ack from the prepared payload, and MongoDB now honours the flag as well (it calledfindOneAndUpdateunconditionally, i.e. the no-read-back path was silently SQL-only). Pinned by the newwrite ack (skipReturning)parity case: SQLite 24/24 · PostgreSQL 24/24 · MariaDB 24/24 · MongoDB 23/23. - Point-tier admission filter: admitting a point read on its first touch meant a 100k-uniform-random scan filled the 2,000-slot L1 point tier with entries that were evicted before ever being read —
putEntry+ byte accounting +indexKey+ FIFO sweep + a second encoded buffer per cold read, for zero hits. An id now takes a slot on its second sighting (bounded FIFO filter): a cold scan costs one Set insert and no response slot, while a repeated id is cached from its second request on. - Verification: unit 4,855/4,855; SQLite integration 685/685;
tests/unit/databases/read-payload-shape.test.ts(payload shape, thedata-named-field case, the legacy string form),tests/unit/utils/http-preferences.test.ts(header parsing incl. last-wins),tests/unit/benchmarks/document-integrity.test.ts(the benchmark guard that found the upsert loss) and the payload-budget case intests/integration/api/collections-mutations.test.ts.
- The defect: every SQL read merged the JSON
3.26 September 2026 — Fast lanes on by default, gated by system state
The raw-transport fast lanes answered a narrow request class (warm-session admin collection reads) straight to the socket, skipping adapter-node’s IncomingMessage → Request and Response → stream bridging while calling the very same lane function the pipeline calls. They shipped opt-in (SVELTY_FAST_LANE=1, default off) with two open questions: the bypass point sits before every hook, and no lane re-applied the system-state decision.
system-stateis now re-implemented for lane traffic (src/hooks/lane-state-gate.ts), against the same module-level state mirror the gatekeeper reads, with the same predicate ashandleTurboPipeline:READY/WARMED/WARMING/DEGRADEDserve,SETUP/IDLE/INITIALIZING/MAINTENANCE/RECOVERY/FAILEDdecline to the pipeline. Consulted in the read lane, the write lane and the registry dispatch — the last one so a lane added later cannot serve a state the operator took the instance out of.- Default flipped to ON (
SVELTY_FAST_LANE=0opts out) so the setting is a documented deployment default rather than a benchmark-only flag, and the transport is symmetric rather than a comparison artifact. - Regression caught by measurement: the lane shim converted the prebuilt body with
Response.text()and handed the string tores.end(), so every wide list body was decoded and re-encoded per request —listLargeTURBO-HIT 2.64 ms bridged → 4.76 ms through the lane. Handing Node the bytes (arrayBuffer(),content-lengthfrombyteLength) restores 2.85 ms. - Measured, paired (PostgreSQL, 10 000 docs, 8c, replica protocol, 3–4 rounds per arm):
findByIdRandom3,235 → 3,655 RPS median (+13 %) with the lane on versus off;findById7,067 → 9,194;listPlain7,187 → 10,876;findMissing8,138 → 9,496. A dedicated 20,000-read load driver agrees: 3,080 → 4,401 RPS, with the server-only CPU profile putting ~10 % of samples in undiciRequestconstruction, 5.7 % in adapter-node’s static-asset check and ~12 % in socket writes. - Confirmed at the published dataset size (100,000 docs, paired,
updatecarried as the control lane because it is not lane-served):findByIdRandom2,631 → 3,919 (+49 %) and 3,139 → 3,883 (+24 %), median 2,885 → 3,901 RPS (+35 %) — +11 % over the published competitor row (3,504 RPS) — while the control stayed flat at ±3 %. This supersedes an earlier session’s reading that the row goes neutral or negative at 100 k; that reading predated the byte hand-off fix and was taken with the lane still opt-in. - Write coalescing was built, gated, and rejected by its own gate (
SVELTY_WRITE_COALESCE, since deleted) — see the roadmap entry: 120/128 concurrent PATCHes batched correctly at an average size of 5 and every caller got its own row, but the lane measured −29 % median over three alternated pairs on PostgreSQL (median 1,305 → 933 RPS, warm p95 7.7 → 15.7–20.4 ms) against a flat control, because at 8 workers PostgreSQL already group-commits overlapping statements. The negative result is recorded rather than shipped as an off-by-default path. - Verification: unit 4,866/4,866;
tests/unit/hooks/fast-lane-policy.test.tspins the state matrix, the dispatch backstop and the default-on/opt-out contract;collection-read-lane.test.tsestablishes its operational-state precondition explicitly.
3.27 September 2026 — WebMCP Agentic Builder, Native Forms, Layout-Aware Responsive Images, Multi-Breakpoint Canvas & Atomic SSG
To lead visual and headless CMS platforms (such as Instatic) through 2026/2027, six architectural pillars were implemented and verified with zero-overhead principles:
- WebMCP Builder Tools (
src/plugins/webmcp/tools/builder.ts):- Standardized Model Context Protocol tool endpoints (
design_collection,refine_collection,diff_collection_schema,scaffold_widget,list_available_widgets) allowing Cursor, Claude Code, and external AI agents to author and evolve schemas directly over standard WebMCP protocol. - Draft-by-Default Airgap: AI proposals generate structured AST diffs and validated draft schemas rather than mutating live production tables, preventing untracked schema drifts.
- Integrated with
fetchApiincluding CSRF token headers and session credentials.
- Standardized Model Context Protocol tool endpoints (
- Native Zero-Config Form Ingestion (
src/routes/api/[...path]/handlers/forms.ts):- Direct headless/static site form ingestion at
POST /api/forms/:collection. - Built-in honeypot spam protection (
_hp,_gotcha) without third-party recaptcha tracking. - GDPR-compliant, privacy-first SHA-256 IP salt hashing (
_formIpHash). - Publishes events to internal
EventBus(form:submit) enabling zero-latency automations and notifications.
- Direct headless/static site form ingestion at
- Layout-Aware Responsive Images (
src/components/ui/image.svelte):- Native
layoutpresets (full,half,third,quarter) calculating mathematical, layout-awaresizesstrings across standard viewport breakpoints. - Zero-CLS placeholder blur styling utilizing ThumbHash/LQIP hashes to eliminate layout shifts during hydration.
- Native
- Multi-Breakpoint Split Canvas (
src/plugins/editable-website/live-preview.svelte):- Live side-by-side synchronized rendering of Desktop (fluid
min-w-[580px]) and Mobile (375px) viewports. - Real-time two-way postMessage synchronization for click-to-edit and field highlight.
- Live side-by-side synchronized rendering of Desktop (fluid
- Atomic Static Site Generator (
src/services/site/static-publisher.server.ts):- Pre-renders published
pagesinto a sibling staging directory (<target>.staging-<uuid>) so the final swap is a same-filesystem, atomicrename(neverEXDEV). - Moves the previous build aside and restores it if the swap fails — the live directory is never deleted before its replacement exists.
dryRunpreview mode renders the route manifest (path+ byte length per route) without touching the live site — the “preview before it goes live” step.- Schema-aware hardening: text fields HTML-escaped, authored
bodyrun through the server-sidesanitizeHtml, and slugs reduced to traversal-proof[A-Za-z0-9_-]segments. - Exposed via
POST /api/exportwith{ "type": "static-site", "preview": true|false }.
- Pre-renders published
- Hue-Preserving OKLCH Palette Ramp (
src/utils/color-ramp.ts) — inspired by Instatic’s Core Framework token engine:- One brand hex expands to a full, perceptually even 50–950 ladder: the seed lands verbatim on its anchor step (
500for accents,50for surfaces), hue is preserved, and chroma tapers toward the extremes. - Out-of-gamut steps are mapped by reducing chroma, never by clipping RGB (clipping would shift the hue).
- Replaces the previous 3-step
color-mixshorthand expansion in@utils/theme-preset-mapper, so accent roles now get the same complete ladder as surfaces; non-hex input keeps the oldcolor-mixpath.
- One brand hex expands to a full, perceptually even 50–950 ladder: the seed lands verbatim on its anchor step (
- Verification:
- Unit tests:
tests/unit/plugins/webmcp-builder.test.ts(4/4),tests/unit/api/forms-handler.test.ts(4/4),tests/unit/site/static-publisher.test.ts(5/5),tests/unit/color-ramp.test.ts(7/7) passing in <500ms. - All security and risk audits (
risk-audit.ts: 5/5) clean.
- Unit tests:
3.29 September 2026 — 2027 Two-Plane Core: Wire Plane Admission, Compiled Point Reads & Dynamic Bitsets
Separated read operations into an opt-in compiled Wire Plane and an enterprise Domain Plane, establishing the foundation for sub-millisecond edge queries with full fallback safety:
- Strict Wire Plane Admission & Compiled Point Reads (
isWirePlaneAdmissible+computeCollectionWireMetainsrc/hooks/handle-collection-read-lane.ts;findPointWireStreamon the adapters) — Only simple, published single-entry reads with compiled default-locale alignment were admitted to the wire path; admitted reads ran as cached point statements emitting engine-side JSON (json_object/jsonb_build_object/JSON_OBJECT) on the SQL adapters, and everything else fell through to the Domain Plane. - Fail-Closed Wire Admission Predicate (
isWirePlaneAdmissibleinsrc/hooks/handle-collection-read-lane.ts) — Rigorous admission gate guarding Direct-to-Wire SQL streams: checks published status (rejects drafts, review status, and preview cookies), prohibits dynamic relational expansions (populate), validates compiled default locale alignment, enforces projection equivalence (fields), and verifies absence of mutatingafterReadhooks. - Multidimensional Dynamic Bitsets (
src/databases/auth/permission-bitmask.ts) — Augmented permission evaluation with multidimensional dynamic bitset chunks (permBits: number[],permRev: number) cached in sessions (SessionManager), enabling $O(1)$ in-register authorization checks for hundreds of permissions beyond 64 bits. - LRU-Bounded Tenant Pools (
src/databases/postgresql/adapter-core.ts) — Introduced connection pool slice bounding (MAX_TENANT_POOLS, default 16) for dedicated multi-tenant DSNs to prevent socket and memory exhaustion under high tenant density. - The 4-Cell Benchmark Contract (now documented with the benchmarks: Performance Architecture) — Codified performance classification rules distinguishing Cell 1 (in-memory L1 cache hits), Cell 2 (Domain Plane miss), Cell 3 (Wire Plane direct SQL miss), and Cell 4 (driver hardware limits), preventing cache-hit category errors.
- Verification:
tests/unit/hooks/collection-read-lane.test.ts(16 tests passing) validating all admission predicate constraints.tests/unit/auth/sessions/session-multidimensional-bitset.test.tsverifying dynamic bitset encoding and session persistence.tests/unit/multi-tenancy/physical-pool-partitioning.test.tsverifying tenant pool slice bounding.
The raw fast-lane transport only carried warm-session collection reads; writes stayed on adapter-node’s bridged path because they need the request body. The write lane now has a transport twin, opt-in (SVELTY_FAST_LANE_WRITE=1, default off) until an equivalence run:
- One implementation, not two —
collectionWriteLanecalls the sametryCollectionWriteLanethe pipeline calls, so WAF path inspect, CSRF same-origin/double-submit, rate limiting, warm-session admin/RBAC, per-request tenant resolution and the security-header contract are all reused unchanged. - Never consume a body you cannot answer — the lane refactor passes the already-resolved turbo context into
serveWarmCollectionWrite, which now guarantees aResponseafter commit (no TTL race), and the entry exposes the body lazily (readBody()): a lane that declines (cold session, non-simple path, maintenance state) returns to the pipeline with the stream unread. A hard body cap (256 KiB default,SVELTY_FAST_LANE_MAX_BODY) rejects oversized payloads before they are buffered. - Rate-limit parity by construction — the entry resolves the client address with adapter-node’s exact
ADDRESS_HEADER/XFF_DEPTHrules and reproduces its failure mode (undefined→ throw →getClientIpfails closed to0.0.0.0), so lane and pipeline bucket identically;Set-Cookieemitted inside the lane is carried on the result. - Verification —
tests/unit/hooks/fast-lane-write-policy.test.tspins the four invariants: write lane unregistered without the opt-in, declines without consuming the body, reads the body exactly once when committed, and fails closed on the client address.
References:
3.30 September 2026 — Wire Plane visibility parity, lane gates & single-transport lean-out
A security review of the 2027 Two-Plane work found that the fast read lane had drifted from the
pipeline it mirrors. The lane builds its own Response, so every check that lives in a skipped
hook had to be re-established deliberately — three had not been.
1. Publication clamp bypassed on the wire path. The compiled point statement had no status
predicate, so a session that the Domain Plane clamps to published could fetch a draft by id.
The wire contract now carries requirePublished:
- PostgreSQL (
jsonb_build_object), SQLite (json_object), MariaDB (JSON_OBJECT) compileAND "status" = 'publish'into the cached statement (both the plain and tenant variants). - MongoDB adds
status: { $in: ["publish", "published"] }to the lean query. - Where a table has no
statuscolumn the adapter fails closed (null), and the base-class fallback re-checks the row object before serializing, so an unfiltered fallback read can never emit unpublished bytes. - The read lane passes the flag only when
resolvePublicationFiltersays the caller is clamped — the same function the Domain Plane uses, so the two cannot disagree. Admins keep the unguarded (faster) statement for drafts, and a draft returnsnull→ Domain Plane decides.
2. The clamp never matched anything. loadOneById compared the filter name (published)
against the stored value (publish), which denied every status-bearing row to a clamped caller and
would have admitted any row stored as published. It now uses the shared isPublishedStatus()
predicate (accepts both spellings) and leaves status-less rows untouched, preserving the previous
behaviour for non-status-versioned tables.
3. Read-lane gates re-established (all evaluated before any cache lookup, so a stored body can never outlive a policy change):
| Gate | Before | After |
|---|---|---|
| Endpoint parity | collection:read or collections:read |
collections:read only — exactly what ENDPOINT_PERMISSIONS requires for /api/collections GET |
| FLAC | Not evaluated: the lane never reaches handleTokenResolution, the read path’s only applyFieldPermissionsToBody call site |
isLaneFlacExempt() declines when a FIELD_PERMISSIONS policy or a readRoles-guarded field applies; fails closed on error |
| Grant freshness | turbo.permMask served until the sliding TTL expired |
isPermissionBitsetStale(turbo.permRev) declines on epoch change; setTurboAuthContext stamps the epoch, so invalidateRoleBitsetsGlobally() (already called by setRolePermissions/invalidatePermissionCache) now reaches lane traffic |
4. Single transport (superseded 3.29’s dispatcher). src/hooks/kernel-dispatcher.server.ts
is deleted, together with its registration and 11 unit tests. It answered the same requests as
tryCollectionReadLane with its own ETag/cache/304 semantics, built a real new Request(...) +
new URL(...) + full header reduction per request (the bridging cost the lane exists to avoid),
carried BASE_HEADERS without applyAllSecurityHeaders’ HSTS/CORS/Vary work, and stamped a
transport-specific x-kernel-channel header. LANE_BYPASSED_HOOKS and the guard test keep the
bypass policy explicit for the remaining lanes.
5. Dead code removed (verified zero importers, no glob/dynamic-import hit):
src/components/command-bar.svelte and search-component.svelte (self-described
backward-compat aliases for command-palette.svelte), src/databases/mongodb/index.ts
(dead barrel), src/services/core/widget-discovery.ts (superseded by widgets/scanner.ts),
src/services/database/index-optimizer.server.ts, src/services/site/website-starter-test-seed.server.ts
(re-export shim) and the unified-data-hub/server/federation-enrichment-utils.ts shim (its one test
consumer now imports the real module). The unreferenced watermark trio was removed in the same pass
and reinstated the next day — together with the rest of the native UI kit — under the maintainer’s
rule that unused-but-reusable components stay in-tree because Vite tree-shakes them out of the
bundle (see 3.31).
6. Test-suite lean-out. The largest per-file cost in tests/unit was @src/paraglide/messages
— a barrel over 1 462 generated modules (6.4 MB) that every widget test paid in transform time.
Mocking that boundary in the widget suites plus de-duplicating fixtures and replacing near-vacuous
assertions (expect(schema).toBeDefined(), formula-against-itself tests) cut measured wall time
substantially with more real assertions than before: widgets 21.90 s → 3.07 s, media 1.73 s →
0.86 s, utils 4.62 s → 3.52 s (combined 128 files / 1 082 tests in 4.53 s).
Correction (re-verified 2026-09-27 by a second agent): vitest.config.ts’s
src/widgets/custom/**/tests/**/*.test.ts glob is live — 16 co-located suites exist and run, in
addition to near-duplicate copies under tests/unit/widgets/custom/ (measured drift: 3 lines for
address, 165 for seo; 72 tests / ~5 s per unit run executed twice). Consolidating them is a
coverage-ownership decision and was completed on 2026-09-27 — the co-located suites are now the
single home and the duplicates are gone (see 3.32). TokenPicker remains unwired, not dead
— fields.svelte keeps the openTokenPicker() handler (the component itself is never mounted) and
it is documented in docs/reference/components/fields.mdx.
Verification: bun run check (format + lint + dead-class + design-token + shade contract +
lockfile) green, bun run lint 0 warnings / 0 errors, bun run test:unit 4 962 passed. The new
tests/integration/databases/differential-parity.test.ts case asserts, per adapter, that a draft is
invisible and a published row visible under requirePublished, and that the unguarded read keeps
agreeing with findOne.
3.31 September 2026 — Diagnostics to zero, navigation cancellation, lean-out batch 2 & bench-ledger verification
1. Diagnostics: 42 errors → 0 (warnings → 0). The root cause of most of them was a type contract that the codebase was violating in four places, not four separate bugs:
DatabaseResult<T>requireserroron the failure branch, butsql-adapter-core(×2) and the MongoDB wire catches (×2) returned{ success: false, message }/{ …, data: null }. The producers now emiterror: createDatabaseError(…), which fixes the union for every caller.- The wire tests read
result.datawithout narrowing (TS does not narrow onexpect(x).toBe(true)). Newtests/integration/databases/wire-contract.tsprovidesrequireWireMethods()(fails loudly when an adapter lacks the Direct-to-Wire contract — no silent skip) andunwrapResult(), so all 21 + 14 errors in the two parity suites collapse into two typed calls. src/widgets/core/input/index.tstyped its schema param asReturnType<typeof InputWidget>whileInputWidgetreferenced the schema — both sides inferred each other intoany(TS7022/7023). The param is nowInputProps & { required?: boolean; translated?: boolean }, matching themedia-uploadwidget’s convention.mariadb/adapter-core.tsreturnedmysql2’sQueryResultunion fromgetRawExec(typedany[]); narrowed at the boundary.
2. A documented race guard that cancelled nothing. NavigationManager.executeNavigation created
an AbortController, aborted the previous one — and never read the signal, so a stale run still
mutated stores and pushed its own goto(), and its finally cleared the newer run’s loading state.
The task signature is now (signal: AbortSignal) => Promise<void>; toList/toEdit/toCreate bail
before mutating and before goto, and only the newest navigation owns the loading entry.
3. Lean-out batch 2 — only two modules actually left the tree. The batch started from 24 candidates (each re-checked for zero importers and zero documentation references), and the maintainer’s rule that unused-but-reusable native UI must stay — Vite tree-shakes unreferenced modules, so an unmounted component costs nothing in the bundle while remaining available to the front-end — was applied to all but these two, which are genuine duplicates of live code:
src/databases/sqlite/sqlite-dialect.ts+ itsIDialectProviderinterface — duplicate ofSQLiteAdapterCore.applyPragmas(), which is hardware-tiered and called on every connect.src/plugins/cookie-consent/api.server.ts— unreachable (registered nowhere). Reinstated immediately afterwards because it is the only implementation of the documented server-side consent logging; wiring or dropping that feature is now a roadmap item.
Everything else was restored and is intentionally unreferenced:
collection-builder-schema-viewer, global-loading, service-worker-registration,
system/{author-brief,background-pattern,restart-required-banner,widget-validation-warning},
plugins/page-speed-score, ui/{index.ts barrel,date-picker,marquee,radio-group,radio-item,segmented-control},
rich-text/components/{color-selector,drop-down,image-description},
seo/components/{analysis-modal,meta-input,robots-meta-input}, collectionbuilder/nested-content/modal-name-conflict,
utils/schema-impact (+ its test), and the watermark trio. A second agent verified all 122 files in
those sets resolve every import (0 failures) and measured their importer counts (0 each, except the
watermark children which only the watermark editor imports).
Two audit claims were checked and rejected:
services/ai-builder/index.tsis live (the API handler dynamically imports the barrel in three places) andTokenPicker,virtual-media-grid,transcode-modal,two-factor-setup-modal,modal-schema-warningandVideoDialogare all documented — deleting them is a product decision, not cleanup (see the roadmap).
4. Bench ledger verified, and the reporter was lying about direction.
BENCHMARK_RECORD=1 … --db=sqlite --only=truth-latency (twice, same code) against the recorded
2026-09-26 baseline:
| Truth-latency row | Baseline | Run A | Run B |
|---|---|---|---|
| HTTP end-to-end (warm) | 0.911 ms | 0.486 ms | 0.393 ms |
| HTTP end-to-end (warm p95) | 1.122 ms | 0.620 ms | 0.481 ms |
| HTTP end-to-end RPS | 1 093 | 2 042 | 2 527 |
| HTTP cold, cache-busted (warm) | 1.290 ms | 0.508 ms | 0.458 ms |
| HTTP cold, cache-busted RPS | 772 | 1 957 | 2 171 |
The improvement is ~2× and exceeds the run-to-run spread (the two same-code runs differ by up to
24 %). Attribution caveat, stated plainly: the plausible cause is the single-transport lean-out
(§3.30) — one fewer lane on the point-read path — while rows like Local SDK (Full) also move with
warm-cache/JIT state inside a run. A defensible attribution needs the documented interleaved A/B
(base ref → run → current ref → run), which is queued, not claimed.
The reporter itself had a sign bug: writeRankedExecutiveSummary sorted by |delta| and titled every
entry 🔴 Top Regressions, so improvements were printed as regressions (truth-latency: -98%
with a red dot). It now splits Top Regressions (positive delta only, with an explicit “none” line)
from Top Improvements, which is why the ledger now reads correctly.
3.32 September 2026 — Two-Plane read-lane hardening, six product-bug fixes & a zero-warning tree
The read lane builds its own Response, so every check that lives in a hook it skips has to be
re-established deliberately. This wave closed that gap, removed the second transport, and took the
project’s own diagnostics to zero.
1. Publication clamp on the wire path (all four engines). requirePublished compiles
AND "status" = 'publish' into the cached point statement on PostgreSQL (jsonb_build_object),
SQLite (json_object) and MariaDB (JSON_OBJECT), and $in ["publish", "published"] on MongoDB. A
table without a status column fails closed, and the base-class fallback re-checks the row object
before serialising. The clamp itself never matched anything: loadOneById compared the filter name
(published) against the stored value (publish), denying every status-bearing row to a clamped
caller while an accidental published row would have slipped through; it now uses the shared
isPublishedStatus() predicate. Verified per adapter by tests/integration/databases/differential-parity.test.ts
(draft invisible, published visible, unguarded read still agreeing with findOne).
2. Three lane gates re-established (evaluated before any cache lookup, so a stored body cannot
outlive a policy change): endpoint parity (collections:read only — what ENDPOINT_PERMISSIONS
requires), isLaneFlacExempt() (declines when a FIELD_PERMISSIONS policy or a readRoles-guarded
field applies; fails closed on error), and isPermissionBitsetStale(permRev) (a role edit now reaches
lane traffic instead of serving the old grant set for the whole sliding TTL).
3. One transport. src/hooks/kernel-dispatcher.server.ts is deleted with its registration and 11
unit tests: it answered the same requests as the collection read lane with its own ETag/304 semantics
and built a real new Request(...) + new URL(...) per request — the bridging cost the lane exists to
avoid.
4. Diagnostics 42 → 0 by fixing producers, not assertions. DatabaseResult<T> requires error on
the failure branch; the SQL wire catches and the MongoDB wire catches returned { success: false, message } instead, which broke the union for every caller. New
tests/integration/databases/wire-contract.ts adds requireWireMethods() (fails loudly when an
adapter lacks the Direct-to-Wire contract — no silent skip) and unwrapResult(), collapsing 35 untyped
accesses in the two parity suites; the input widget’s self-referential schema types became an explicit
InputProps & { required?: boolean; translated?: boolean }; the MariaDB getRawExec result union is
narrowed at its boundary.
5. Widget suites consolidated — 16 files / 72 tests removed with no lost coverage. The near-duplicate
suites under tests/unit/widgets/custom/ were ported into the co-located src/widgets/custom/*/tests/
homes (the documented portable intent, and the glob vitest.config.ts already runs). The port was
mechanical only after a per-pair check that the modern suite covers every legacy case name and has an
equal-or-higher assertion count; seo was the single pair whose table-driven rewrite defeated the
name check, so its nine cases were verified by hand before the duplicate was removed. The
namespace-ownership guard test then caught a registry that still pointed at the removed paths — the
kind of stale reference the guard exists for. Net effect: 551 files / 4 891 tests in 17.9 s (was
567 / 4 963).
6. A documented opt-out that could never apply. PostgreSQL’s createModel created the
jsonb_path_ops GIN index behind (schema as { jsonIndex?: boolean })?.jsonIndex !== false, where
schema resolved to the schema module namespace rather than the collection definition passed in as
schemaData — so the guard was statically always-true (rolldown flags it as IMPORT_IS_UNDEFINED)
and jsonIndex: false was unreachable. It now reads the collection config, making the documented
write-only opt-out real and removing the build warning.
7. Benchmark ledger and a reporter that inverted direction. With BENCHMARK_RECORD=1 … --db=sqlite --only=truth-latency (twice, same code) against the recorded 2026-09-26 baseline:
| Truth-latency row | Baseline | Run A | Run B |
|---|---|---|---|
| HTTP end-to-end (warm) | 0.911 ms | 0.486 ms | 0.393 ms |
| HTTP end-to-end (warm p95) | 1.122 ms | 0.620 ms | 0.481 ms |
| HTTP end-to-end RPS | 1 093 | 2 042 | 2 527 |
| HTTP cold, cache-busted (warm) | 1.290 ms | 0.508 ms | 0.458 ms |
| HTTP cold, cache-busted RPS | 772 | 1 957 | 2 171 |
The improvement exceeds the run-to-run spread (two runs of identical code differ by up to 24 % here),
but attribution still needs the documented interleaved A/B (base → run → current → run), which is
queued rather than claimed. The reporter’s ranked summary sorted by absolute delta and titled every
entry 🔴 Top Regressions, so improvements were printed as regressions; it now splits
Top Regressions (positive deltas only, with an explicit “none”) from Top Improvements.
8. E2E: two reproducible failures, traced to real defects. A full local run’s failure set is
disjoint run-to-run and drains under isolation (the contention class tracked in the test-status
dashboard), but two specs reproduced in solo, clean-database runs — and tracing them found
product bugs in src/, not host state: a $effect on /config/design-system that reverted every
local edit before saving, and avatar writers that left the layout snapshot stale so the delete
button never rendered (both in 9). The spec that documented the first also needed an
outcome-verified selectOption helper — a selection can land before hydration attaches the
binding, in which case the DOM snaps back to its server-rendered value, which is how the revert
was first observed. The same outcome-verified retry (input → assert the outcome, never a longer
sleep) now guards the two other interactions that a hydration gap could drop: the Edit User
Details dialog and the prep-phase sign-in submit that had been aborting whole E2E runs.
9. Six product bugs — two surfaced by the type checker, two by E2E, two by the follow-up trace — all fixed.
- Uploads scoped to a phantom tenant.
media-upload/process-upload.tscallssaveMedia(file, userId, access, tenantId, basePath); both call sites passed the resolved folder path in thetenantIdslot and never passed_basePath, so an entry upload was persisted under a folder-shaped tenant ("collections/pages") with no folder. The fourth argument is nowctx.tenantIdand the fifthfolderPath(the folder the widget already computed). Silent because nothing type-checked the call. - Marketplace install/license could never succeed.
marketplace-view.sveltetreatedfetchApi’s envelope ({ success, data, message, code }) as aResponse—res.json(),res.ok,res.status. Those areundefinedon the envelope, so the handler threw aTypeErrorinto its own catch and reported “Install failed” for every attempt. It now reads the envelope (res.success,res.data ?? res, message/code for the toast) and keeps the previous tolerance for payloads that arrive at the top level rather than underdata. - A settings form that reverted every edit. On
/config/design-systemthe overrides$effectread the same local state it wrote, so a local edit re-triggered it and was instantly pinned back to the persisted value (and then saved back). The writes are now scoped withuntrack— the effect depends only on server prefs — which is the documented Svelte 5 shape for “sync from server data”. E2Edesign-system.spec.ts:68reproduced the revert deterministically; a stale-density comment in the spec’s ownfinallyblock was a second symptom (it silently re-saved the old value). - Avatar writes left the layout snapshot stale for 15 s.
getFreshLayoutUser()(layout-caches.server.ts) feeds the (app) layout (sidebar avatar, display name) from a 15 s L1 entry, invalidated only by thePUT /api/user/update-user-attributesroute.LocalCMS.auth.saveAvatar/deleteAvatarwrite through the adapter directly, so an upload persisted correctly while the modal’s refreshed page data still saidavatar: nulland the delete button never rendered (E2Eprofile.spec.ts:174). Both writers — and the namespace’supdateUserAttributes— now drop the entry through one private helper. - Every server-rendered id in the admin UI drifted from its hydrated twin.
generateId()(the native UI kit’s id source:input,select,checkbox,textarea,toggle,radio-group,tags,combobox,date-picker) falls back to a process-global counter unlessinitIdGenerator()has installed its per-render context — and the app never called it (only the SSR test wrappers did). Server ids therefore advanced with every request (select-2), hydration restarted at zero (select-0), and everylabel[for]/aria-describedby/aria-controlspointing at a generated id broke once hydration finished. The root layout now initializes the generator, andtests/unit/routes/layout-id-generator.test.tsfails if that call ever disappears. - Invalidation could lose a race with the next read.
invalidateLayoutUserCache()wasvoid cacheService.delete(…).catch(() => {})— it returned before the delete landed, so the save→reload round-trip that the E2E exercises could still be answered from the old snapshot. It is awaited now (the module’s own header already promised “invalidate on user-attribute writes”), andAuth.updateUserAttributes— the facade every internal writer (roles, 2FA, admin edits) lands in — invalidates as well, so the HTTP route, the SDK namespace and the facade all honour the contract. The remaining direct-adapter writers (two-factor-auth.ts,magic-link.ts) touch authentication metadata only (twoFactorPending,backupCodes,lastAuthMethod,lastActiveAt), none of which the layout snapshot renders.
10. Type drift to zero. svelte-check reported 16 errors in 7 files and nothing in the
gate noticed (bun run check = format + lint + dead-class + design-token + shade + lockfile; no
tsc). Beyond the two bugs above: SessionResolution’s ok-arm was missing permMask (added,
returned since the bitset work), collections.svelte computed a matchesFav that no branch
used, bulk-field-edit-modal.svelte bound number | null into an input that accepts
string | number | undefined (now undefined), the write-pipeline test fixture was typed as a
full Schema where the helpers take a structural PrepFieldSchema, and the benchmark’s
optional findPointWireStream is narrowed through requireWireMethods() instead of a cast.
All 16 are fixed; wiring svelte-check into the gate is a roadmap item.
11. Benchmark harness: two defects and a reporting bug. etag-hash asserted that the
pure-JS FNV-1a loop beats native SHA-256 at 547.8 KB; measured 818 µs vs 215 µs (SHA-256
3.8× faster above the cache boundary), while the loop does win for the sizes fastHash
actually hashes (collection/widget sources, largest measured 23 KB). The assertion now splits at
the measured crossover (strict superiority ≤16 KB, a µs/KB scaling guard above) instead of
deleting the check. dev-dependency-load crashed its own child: the matrix exports
NODE_OPTIONS=--max-old-space-size=1024 to children, and under that 1 GB cap svelte-check
aborts with a V8 fatal error (exit 134, node::OnFatalError, ~12 s in), which the probe read as
“Type Check failed” — reproduced by setting the variable alone, clean without it. The DX probes
now spawn with NODE_OPTIONS stripped. Finally, reporting.ts formatted the per-adapter
ledgers with an unref()d oxfmt child and never formatted docs/project/benchmarks/index.mdx,
so a run could leave the tree failing bun run check; the pass is awaited (bounded 20 s) and
covers the summary too — verified by running the matrix and immediately re-running check.
12. Import-surface sweep — every namespace import either converted or documented. 49
import * as NS statements were triaged across six subagent passes under review: 38 converted
to named imports in 22 files (typescript, valibot, yjs/y-protocols/lib0, Node builtins, and the
adapter/query helpers), 11 kept deliberately and now carrying an intent comment — the three
adapter.schema surfaces (removing them broke adapter construction; four unit tests caught it),
base-adapter’s utils field, and the seven route-handler dispatcher namespaces. The
safety net earned its keep: svelte-check caught one wrong conversion (an aliased import that did
not exist) and one dropped member, and two namespaces were correctly left alone because they are
not imports at all — the scaffolder’s v.* text is generated code, and
widget-registry-service.ts documents why its qualified access is load-bearing (a named import of
a test-mocked-away export is an ESM linking error, while the qualified typeof check falls back).
Two improvements came out of it: the AI widget scaffolder now emits an exact named valibot
import into every generated widget (widget definitions are client-bundled), and it no longer
generates pipe(number(), minLength(1, …)) for a required numeric field — a string check on a
numeric schema, which cannot validate. Honest scope: the client bundle is byte-identical
(664 chunks, 15 181.3 KB) before and after — the converted modules were server-only or already
absent from the bundle, so the win is explicit dependency surfaces, dead-import removal and
refactor safety, not shipped bytes.
13. MariaDB cursor streaming enabled. Switched supportsStreaming: true in MariaDB AdapterCore (src/databases/mariadb/adapter-core.ts), allowing non-buffered streaming queries via streamMany and exports across large datasets without accumulating intermediate row arrays in Node/Bun memory.
Verification: bun run test:unit 552 files / 4 895 tests green in 18.8 s;
bun run test:integration 719 tests / 76 files (694 pass, 25 skip) against a freshly built
four-adapter harness; svelte-check 0 errors / 0 warnings across 6 785 files;
bun run check, bun run lint:docs (263 files), lint:benchmark-mdx and risk:audit clean;
project diagnostics 0 errors / 0 warnings. The two E2E specs this wave fixed are verified in the
specs that documented them: profile.spec.ts green across --repeat-each=3, and
design-system.spec.ts 18/18 across three repeats. The local host’s remaining contention
residue is tracked as an open item in the test-status dashboard and the roadmap — not claimed as
passing here.
3.33 September 2026 — Release preparation on push & a manifest writer that cannot lie
--release on the pre-push gate. bun run git push --release (optionally
--release=patch|minor|major) arms a release step in .githooks/pre-push: once all nine checks pass,
scripts/version.ts auto derives the next version from the Conventional Commits since the highest v*
tag, the manifest is committed as chore(release): bump version to vX.Y.Z — the subject the release
workflow itself writes on its manual path — and the hook prints the push that publishes it. The flag is
consumed by scripts/git-safe.ts, which exports SVELTY_RELEASE for the hook, because git never
forwards hook arguments to it.
Two properties are deliberate. The step runs after the full gate, never before: auto-release.yaml
tags whatever package.json holds, so a bump for a tree the gate never validated is exactly the stray
bump AGENTS.md §8 warns about. And the release commit cannot travel inside the push that triggered it —
git resolves the pushed refs before the hook runs — so the hook prints the command to repeat instead of
implying the push was the release; the bump stays local and reversible until that second push. Re-running
is a no-op: within one release cycle the derivation is stable, so a second push finds the manifest already
at the version and commits nothing. Skipped on the docs-only and --only-failures paths (both warn), and
refused with a non-zero exit when version.ts refuses — a push that looks released but is not would be
worse than a blocked push.
Known trap, observed 2026-09-27, removed 2026-09-30: a test tag
v0.2.0on the 5 July 2026 commit6dc2d89(that commit’s manifest was0.0.7) was reachable, soautoderived0.1.2 → 0.3.0(highest reachable tag wins overgit describe’s nearestv0.1.2). The tag was local only and was deleted. It was never onorigin. With it gone, the highest reachable tag isv0.1.2.
A manifest writer that could not find the field it was looking at. replaceVersionField() was
line-anchored (^\s*"version"\s*:…$ with the m flag), so a manifest whose version field shares a line
with other keys was refused with could not locate the top-level "version" field in package.json,
although the field was right there. It was found by the throwaway-repo probe that verifies the release
step, not by a user report. The writer is now a depth-tracking scan (locateVersionValue()) that skips
whole string tokens: a nested "version", the word inside another value, and braces or escaped quotes
inside strings cannot fool it, and minified and pretty-printed manifests both rewrite byte-for-byte except
for the value (10 new tests in tests/unit/scripts/version-manifest-write.test.ts; the 33 policy tests of
version-bump.test.ts unchanged and green).
Escapes that printed as escapes. info() / warn() in .githooks/lib.sh passed their message
through %s, so every ${BOLD}…${NC} embedded in a hint — four pre-existing failure hints plus the new
release hint — rendered as a literal \033[1m in a color terminal. They now use %b; the message
arguments are hardcoded strings plus validated enums, so interpreting escapes cannot mangle caller data.
Verification: the release step was exercised end-to-end in a throwaway repo (tag v0.1.0 + one feat
commit): run 1 bumped 0.1.0 → 0.2.0 and committed it, run 2 no-opped, run 3 (dirty manifest) refused and
returned non-zero — the exit code that blocks the push. Unit tests: git-safe-release (7),
version-manifest-write (10) and version-bump (33) green; bun run scripts/version.ts --dry-run against
this repository prints its decision without writing. No throughput claim attaches to this entry — it is
release tooling.
3.34 September 2026 — A tail report that measured nothing, and the tails it was hiding
The verdict was an estimate of the thing it claimed to measure. verify-benchmark-local.ts graded tail
stability from p99/p50, but when a result JSON lacked p50Ms/p99Ms it reconstructed them —
avg/(1+(cv/100)²·0.15) and p95 + max(0.1,(p95−p50)·1.5) — and graded those. Two consequences: a verdict
about an invented number is not a measurement, and the estimator was structurally biased against
sub-millisecond rows, because high CV shrinks the derived p50 and the derived p99 then grows with the gap.
That is why every sequential row read 🔴/🟡 while concurrent rows of the same workload read 🟢.
It was also reading a month-old file set. The report named ten files from a metric-naming scheme the
harness no longer writes (findById__Sequential_.json; today’s runs write findById__Warm_Sequential_.json
plus 1c variants and five further workloads). Rows are now selected by content
(testFile === "competitive-workload-replica") and grouped by runId, and only the newest run is graded, so
superseded names cannot resurface as phantom workloads.
What the honest version says (run 6b4c300a, measured 2026-09-27, 20 workloads after excluding the
p50 = 0 seed-burst aggregate row):
| Band | n | Workloads |
|---|---|---|
| 🟢 EXCELLENT (<2×) | 8 | every read path — findById 0.34→0.49 ms, findByIdRandom, findMissing, listPlain, create (1c/seq) |
| 🔵 ACCEPTABLE (≤3×) | 7 | graphql 3.19→6.47 ms, listLarge, listPlain sequential, update sequential |
| 🟡 WATCH (≤4.5×) | 3 | create @8c (5.16→19.81 ms), listFilterSort sequential, mixed @8c |
| 🔴 JITTER ALERT | 2 | update @8c (5.08→26.67 ms, +21.6 ms), mixed sequential (0.79→4.62 ms) |
The write-path tails are real and now measurable — previously they were hidden behind an estimator that flattered concurrent rows and punished sequential ones at random. Chasing them is a roadmap item, not a claim in this entry.
Also fixed in the same file: --report=variance (documented in the test-status dashboard) fell through
to the full pre-flight because the flag was matched with includes("--report"); the CLI now matches the flag
family, and main() runs only under import.meta.main, so importing the module for tests no longer spawns
the pre-flight. A printed-table test then caught a live defect no type checker can see: a stray + made the
footer `…` + + `…` coerce its band counts to numbers → NaN. TypeScript accepts unary + on a string,
so only an output assertion finds it.
Verification: tests/unit/scripts/verify-benchmark-local.test.ts grew from 3 to 23 tests — a
fixture-driven contract for discovery (by testFile, newest runId only), grading (recorded percentiles
only: the August row the old estimator turned into p50 0.73 / p99 3.68 now reports UNMEASURED, with p50Ms
asserted not to be 0.73), the 100-sample floor, the aggregate-row exclusion, the printed table (no NaN,
real P99−P50, the graded run named) and the strict gate. generateLatencyVarianceReportFromDir() is the
directory-injectable core the fixtures drive.
3.35 September 2026 — The write tail attributed: WAL checkpoint I/O, not GC
The two 🔴 rows from §3.34 (update @8c 5.08→26.67 ms, mixed sequential 0.79→4.62 ms) have one root
cause, and it is not the application: SQLite’s auto-checkpoint fsyncing while requests wait.
Method. PROFILE_WRITE=1 on the replica attributes each write to its spans (ns:persist,
db:upd|ins:stmt, prepare-values, conv, outbox), and ~5 000 statements per run resolve a rare tail
far better than a 300-sample benchmark row. The switch had never worked: the benchmark server env is a
whitelist (getBenchmarkTestEnv), so PROFILE_WRITE never reached the server. scripts/benchmark-matrix/index.ts
now forwards PROFILE_WRITE / LOG_LEVEL / SQLITE_* explicitly, prints a banner for override runs, and
persists the server log to results/<db>/diagnostic-server.log (the in-memory buffer was dropped on success).
Where the tail is (9 726 writes, one profiled run): everything above the statement is microseconds —
sanitize+validate p99 0.059 ms, encrypt 0.060, outbox 0.029, prepare-values 0.020, conv 0.016. The whole
tail sits in the SQL statement: db:upd:stmt p50 0.108 ms, p99 0.716 ms, max 17.4 ms.
Interleaved A/B (five arms, one replica run each; stalls = statements > 10 ms, out of ~5 200):
| Arm | db:upd:stmt > 10 ms |
max | db:ins:stmt > 10 ms |
max |
|---|---|---|---|---|
| control | 31 | 22.5 ms | 33 | 20.1 ms |
| control (repeat) | 26 | 25.0 ms | 25 | 25.1 ms |
SQLITE_SYNCHRONOUS=OFF |
0 | 5.0 ms | 0 | 6.1 ms |
SQLITE_WAL_AUTOCHECKPOINT=0 |
0 | 5.3 ms | 0 | 5.1 ms |
WAL_AUTOCHECKPOINT=16000 (128 MB WAL) |
7 | 36.2 ms | 16 | 43.2 ms |
Removing either the checkpoint or its fsync removes every stall; making checkpoints rarer makes the
surviving ones bigger (their cost scales with the WAL size), so “raise the threshold” is the wrong
lever. SYNCHRONOUS=OFF is not shippable (power-loss durability) and AUTOCHECKPOINT=0 alone leaves the
WAL unbounded — the fix is moving housekeeping off the request path (checkpoint from a separate
connection or a background scheduler, size cap as the safety valve), tracked as the roadmap item above.
Also measured in the same session: with zero statement stalls the write rows’ ratios fall from 7.4× to
2.7× (update) and 3.6× (create) — the residual spread is the synchronous driver serializing eight
concurrent writers, which is the second lever (worker-connection writes) and is not yet shipped. The
auth-performance 500 seen once in the interrupted full matrix did not reproduce in isolation (the
reject path answers 401/403 and logs Invalid or non-existent API key provided), so it is recorded here as
unverified rather than fixed.
3.36 September 2026 — Tail as a rate, and five suggestions verified before building any of them
Shipped: the slow-call rate. computeStatistics() now records slowCount / slowRatePer1000 /
slowThresholdMs (samples above 3 × the row’s own p50, over the raw sample) and the variance report shows
SLOW‰ in place of the CV column (CV stays in the model and the JSON). Rationale: p99 of a few hundred
samples is the 2nd-worst sample, so it swings several-fold between identical runs; a rate converges, and
it reorders the problem immediately — first reading (one run, 20 workloads): mixed (Warm Sequential)
5.36×/175 ‰ (the worst row by frequency, only 2nd by ratio), listLarge @8c 4.45×/75 ‰,
findByIdRandom @8c 3.78×/34 ‰, update @8c 7.5×/26.7 ‰, and the sub-millisecond reads at 0.0 ‰.
That run was also uniformly faster than its predecessors (findById @8c p50 0.58 vs 0.68–1.21 ms) — host
drift, which is exactly what the ratio metric could not survive.
Five proposals, verified before implementation (the point of the exercise — two died on contact):
| Proposal | Verdict | Evidence |
|---|---|---|
| Instrument the write lane | verified, top lever | the same update is localcms.update_ms 0.057 ms in-process and adapter.write.avg 0.061 ms, but 2.23 ms over HTTP, while HTTP reads sit at truth.http.p95 0.525 / rest.point_read 0.84 ms with the same client — so ~1.5 ms is server-side and write-specific. The lane is also the profiler’s blind spot: no handler:* span ever fires on it. |
| Noop control row + rate + 3-run ensemble | verified, half shipped | the metric-keyed trend table (benchmark-history.ts: loadHistory/persistRun) makes ensemble grading a read-side change; the rate is shipped above; the floor (truth.http.p95 0.525 ms) shows the ratio contract cannot grade sub-ms rows — band semantics (absolute vs floor-relative) still needs a decision. |
| Checkpoint off the request path | verified feasible, better home found | hooks.server.ts already exposes inFlightRequests (graceful shutdown), so no new machinery is needed — and the watchdog/memory governor live in the background worker process (services/background/background-entry.ts), whose checkpoint cannot block the API event loop at all. |
| Collapse commits per write | falsified | an entire in-process update including its commits is 0.057 ms and adapter.throughput.write_rps is 29 525 — commits are not the expense; the 17 ms events are checkpoints (proven by the §3.35 A/B). |
| Worker-connection writes | falsified | initWorkerConnection is used only by handle-test-isolation.ts for per-worker test DBs — there is no worker-thread write path, and the DB work it would offload is 0.057 ms. |
Also observed, not fixed: tests/unit/security/audit-ledger-integrity.test.ts (“200 O(1) leaf appends in
< 20 ms”) failed once at 658 ms against its 500 ms budget during a full-suite run and passes 8/8 in 119 ms alone — a contention flake of the documented kind (“perf assertions need JIT warm-up”), left for the owner.
3.37 September 2026 — The vendor-leg bisect executed: no reproducible regression in e1efe15..e4994ef
The claim under test. The external harness’s last three legs read tag v0.1.2 (e1efe15b2) as faster
than main (e4994eff6) on all 11 lanes (+8 … +82 %, e.g. findMissing +82 %, findByIdRandom
+52 %, graphql +36 %) while its keystone anchor moved 1.1 %. e1efe15b2 is an ancestor of main, so
the claim implies a regression from one of the 8 commits in between. This entry records the local
bisect: protocol-parity replica (tests/benchmarks/competitive-workload-replica.test.ts, 10 workloads,
10 000 docs, seq + 8 c, median of 3 rounds), one build per commit, SQLite and PostgreSQL.
Confound found first. The Dockerfile’s NODE_OPTIONS semi-space changed 128 → 64 inside
e4994eff6 (the benchmark harness default likewise) — the vendor’s tag image ran 128 and its main image
64, a config difference shipped in the same commit box as the code claims. Every bisect leg therefore
pins NODE_OPTIONS explicitly, and a 2 × 2 (tag/main × 64/128) was measured: no consistent semi-space
effect (128 helped findById/graphql slightly and hurt listPlain on both commits).
Result: no persistent regression. The two large single-run dips both recover inside the range — which a real regression cannot do — and both failed to reproduce on re-run:
| commit | list_plain warm 8c |
note |
|---|---|---|
e1efe15b2 (tag) |
13 307 / 13 792 (2 runs) | baseline |
54bd19f76 |
13 310 | flat |
5be703223 |
9 786 | re-run 10 916 — not reproduced |
db6becec6 |
12 770 | recovered |
d091b2178 |
11 332 | |
60fd09c3f |
9 804 | re-run 11 229 — not reproduced |
d9060a465 |
11 712 | recovered |
e4994eff6 (main) |
11 309 / 13 700 (2 runs) |
The noise floor is the finding — identical builds, repeated runs, same session, this host:
| lane (phase) | run A | run B | Δ |
|---|---|---|---|
findById warm-seq (tag) |
4 993 | 4 613 | −8 % |
findByIdRandom warm-seq (tag) |
2 818 | 3 424 | +21 % |
listLarge warm-seq (tag) |
1 779 | 1 277 | −28 % |
findMissing warm-seq (tag) |
3 859 | 5 184 | +34 % |
findMissing warm-seq (main) |
3 423 | 5 440 | +59 % |
graphql warm-seq (main) |
619 | 456 | −26 % |
findById warm 8c (main) |
10 905 | 13 332 | +22 % |
listPlain warm 8c (main) |
11 309 | 13 700 | +21 % |
PostgreSQL (the vendor-likely engine) agrees: cold.findByIdRandom on main across three runs reads
2 212 / 2 438 / 2 887 against tag’s 2 702 / 2 630 (2.7 % spread between the two tag runs) — the one lane
where main looked persistently slower dissolves into main’s own 26 % spread. The vendor’s smallest
attributed deltas (+8 … +20 %) sit below this host’s run-to-run floor; its largest (+52 %, +82 %) are
not reproducible in either engine with ≥ 2 samples per side.
Conclusions. (1) No commit in e1efe15b2..e4994eff6 shows a persistent, monotone lane regression —
the local replication of the vendor protocol does not reproduce the tag > main ordering on SQLite or
PostgreSQL. (2) The only structural change in the range that could act globally is the Dockerfile
semi-space 128 → 64, and the local A/B says it does not explain a uniform slowdown. (3) A vendor-side
bisect needs alternated pairs (≥ 3 per side, identical NODE_OPTIONS) per the protocol this project
already requires for that class of claim; single-session legs carry a floor wider than the attributed
effect. Measurement hygiene: bisect legs wrote to an isolated RESULTS_DIR (no trend-store rows); the two
ad-hoc runs that did record (42 rows) were removed from history.sqlite.
3.38 September 2026 — The write lane’s “unlocated 1.5 ms” resolved: it was never server-side
The claim on trial (§3.36, first proposal): an update measured localcms.update_ms 0.057 ms in-process
but 2.23 ms over HTTP against 0.525–0.84 ms HTTP reads — “so ~1.5 ms is server-side and write-specific”.
Resolved with a dedicated probe (tests/benchmarks/probe-write-attrib.test.ts): the same operation
measured three ways — in-process db.crud.update at c=1 and c=8, and an HTTP PATCH through the lane with
x-srv-split/x-srv-dur — SQLite, production build, warm session, 300 iterations per arm.
| measurement (p50) | run 1 | run 2 |
|---|---|---|
in-process crud.update c=1 |
0.063 ms | 0.067 ms |
in-process crud.update c=8 |
0.576 ms | 0.606 ms |
| HTTP wall (bridged) | 1.476 ms | 1.521 ms |
| HTTP wall (raw write lane) | 1.251 ms | 1.306 ms |
HTTP x-srv-dur (server-stamped) |
0.380 ms | 0.360 ms |
HTTP dbwrite (adapter phase) |
0.160 ms | 0.150 ms |
Why the 1.5 ms never existed. Both halves of the original pairing were wrong: the 0.057 ms baseline was
crud.atomicIncrement — a single count = count + 1 statement outside the SDK pipeline (validation,
hooks, audit/outbox, invalidation, envelope) — not the update the lane runs; and the 2.23 ms arm was a
client-observed HTTP wall. Against the lane’s own stamps the server cost of that update is 0.35–0.40 ms
total, 0.15–0.16 ms of it dbwrite; ~0.9–1.1 ms of the client number is client fetch + loopback
transport — the same class as the ~1.1 ms per-request client floor the read row already documents. At 8c
the write row is dominated by write serialization, and the in-process c=1→c=8 pair proves it is
engine-side, not HTTP-side: the same call goes 0.063 → 0.576 ms (×9) without a request in sight.
Transport A/B (both orderings). The opt-in raw write lane (no adapter-node bridging,
SVELTY_FAST_LANE_WRITE=1) measured −15 % client wall (1.48/1.52 → 1.25/1.31 ms) with server-stamped
time unchanged (0.38/0.40 → 0.35/0.36 ms) — reproduced with the arms in both orders. That is the paired
transport evidence the write lane’s default flip was waiting for; the remaining gate is the
BENCH_VERIFY_RAW=1 byte-equivalence proof plus a c=8 paired run.
3.39 September 2026 — Lane responses get their compression back (and the >64 KiB tier gets its first honest number)
The gap. Fast-lane responses bypass handleCompression (LANE_BYPASSED_HOOKS) and
serveTurboCacheEntry can only serve an encoding that was attached to the entry — so every lane-served
list left the socket as identity bytes. Measured 2026-09-28 through the zstd audit: a 260 791 B body
answered a zstd-only client uncompressed, and the whole zstd/co-tenant scenario in api-latency.test.ts
reported api.zstd.available=0 (skipped) — the tier was unmeasurable, not slow.
What shipped. src/hooks/response-compression-stash.ts — one shared variant implementation for the
read lane and the API dispatcher, with a precise contract:
- Hits are free: list entries carry
compressedvariants (zstdwith the CMS dictionary,br/gzip≤SYNC_MAX_SIZE);serveTurboCacheEntryalready negotiatesAccept-Encodingper request. The response-cache’s internal precompute turned out to be commented-out dead code, so lane entries had never carried a variant at all. - First re-hit warms lazily: point-read misses are high-cardinality and skip the CPU; the second request for any key schedules the stash — a re-read is the proof that the key is worth compressing.
- Miss path inside the buffered budget: the lane compresses ≤ 64 KiB synchronously (exactly the pipeline’s own sync tier) and seeds the variant into both the response and the cache entry.
- Safety rails: in-flight guard per key, expanded output dropped, a body that produced nothing is
marked attempted (
compressed: {}) so later hits do not retry,preserveStalekeeps a concurrent write’s staleness decision (the oldset()forcedstale = false), and a superseded body is never overwritten (body/etag re-check before the re-set).entryBytes()now counts variant bytes, so the L1 budget no longer drifts when variants are attached.
Beyond 64 KiB the lane declines to the pipeline (compress-while-sending, no full-body wait). Buffering the same 255 KiB body in-lane measured 10.8 ms/request, 183 RPS, co-tenant p95 0.33 → 2.50 ms — rejected. The pipeline’s streaming tier is now measurable end-to-end and lands at 22.7 ms warm / p95 24.8 ms / 87 RPS with co-tenant p95 0.30 → 7.39 ms on this host (cache-busted, so every request pays a fresh compression). That is a property of the app-wide streaming compression tier — now visible for the first time — not of the lane change; a level/threshold decision for > 64 KiB bodies is the next lever, tracked in the roadmap.
Verification: tests/unit/hooks/response-compression-stash.test.ts (9 cases: gates, builder guards,
hit-stash, supersession, staleness, size no-op) + 51/51 across the neighbouring hook suites; the api-latency
probe now reports zstd active: … body 254.7 KiB (> 64 KiB async tier) instead of a skip.
Follow-up A/B (same day) — the level/threshold hypothesis is falsified. The 22.7 ms / +7.4 ms co-tenant
in that probe invited a compression-settings fix; isolation says otherwise. zstdCompress on a 244 KiB body
measures 0.30–0.55 ms at every level 1→15, and the CMS dictionary artifact (static/dictionaries/cms-payloads.dict)
is absent in-repo, so {dict} is a no-op — neither level nor dictionary is a lever. End-to-end, the same
256 074 B list: identity warm 0.95 ms · identity miss 14.10 ms · zstd warm (stashed variant) 0.72 ms ·
zstd miss (lane declines → pipeline, compression included) 15.16 ms, co-tenant point read 0.38 → 0.65 ms.
So compression is ~1 ms of a ~15 ms miss — the cost is the cache-busted miss path itself (DB read +
build + 256 KB serialisation/drain), and cached hits serve the variant for free at sub-millisecond latency.
No level/threshold change was warranted; the open lever is the list-miss path, not the encoding.
Round 2 (same day) — the miss path decomposed, and a wrong turn corrected. Lane marks (x-srv-split)
plus ENABLE_HOOK_TIMING=1 on a 199-row / 257 027 B list (SQLite) read: lane HIT 0.79 ms (ttfb 0.79,
drain 0.00) vs miss ttfb 14.98 / drain 0.23 ms — the server is late, not the wire — and the whole
nested hook chain accounted for only ~3.7 ms, so the rest was the row build (≈0.055 ms/row). Two fixes:
streamingJsonResponse(andstreamingExportResponse) are pull-driven now. The old writer ran astart()loop and slept a hard-codedsetTimeout(10)wheneverdesiredSize <= 0— a count-based stream’s HWM is 1, so that was most iterations (thelimit = MAX_PAGE_SIZEpath had been measured at ~3.1 s per request), and the export writer yieldedsetTimeout(0)per record (a 10 k-row export paid ~10 s of timers). The stream now callspull()only when it wants data: backpressure is structural, zero timers. Pinned bytests/unit/api/streaming-json-response.test.ts(identical bytes,maxItems, error marker, and a 500-item drain that would take ≥ 5 s under the old sleep).- The lane no longer declines > 64 KiB lists to the pipeline. Declining meant the lane had already built
the rows, discarded them, and the pipeline rebuilt them: 15.0 ms vs 10.8 ms for the same list, co-tenant
p95 7.39 → 2.50 ms. The lane now compresses the negotiated encoding itself — sync at ≤ 64 KiB, one-shot
async (libuv pool,
compressAsync) above — and seeds the variant into the response and the entry.
Measured after both fixes (api-latency zstd audit, SQLite, 254.7 KiB cache-busted list): warm 22.7 → 11.4 ms, 87 → 175 RPS, co-tenant p95 during the burst 7.39 → 2.93 ms; turbo point read in the same run 0.711 ms / 11 097 RPS. The remaining ~7 ms of the 11.4 is the SDK list build itself (row hydration + per-row JSON) — the list wire plane is the next lever, recorded in the roadmap.
8c re-verification, same session (BENCH_ZSTD_CONCURRENCY=2 vs =8 on one build — the audit row is
knob-controlled now and the matrix forwards it): the row goes 13.0 ms / 153 RPS (2c) → 49.4 ms / 154 RPS
(8c). Latency scales with concurrency while throughput stays flat, so the large-list miss path is
saturated at ~150–175 RPS, and the ceiling matches the synchronous SDK row build (~6–7 ms of event-loop
work per request, interleaved across 8 in-flight builds, while compression itself is off-thread). The
point-read rows at the same 8c kept 9 676 RPS, which isolates the cap to the list build — the empirical
case for the list wire plane below. (The c=2 point re-measured 13.0 / 153 here against 11.4 / 175 in the
earlier session — host drift between sessions is 7–59 % on this box.)
Same day — readiness truthfulness, a deterministic list order, and the list wire plane measured (then shelved).
- Health is truthful now. Three responders reported
overallStatus: "READY"from DB presence alone while the boot cache-warming subsystem was stillinitializing— every harness (and operator probe) then measured the warm-up itself. The turbo pipeline’sbuildHealthResponse, the dispatcher’s HYPER-TURBO health branch and the hooks.server responder now all emit the derived state plus the strictreadypredicate (isSystemReady(): operational state AND the boot pre-warm settled), and the benchmark harnesses (isHealthReadyin the matrix, the standalone wait) requireready !== falsebefore measuring. Verified live:{"status":"healthy","overallStatus":"READY","ready":true,…}on the bench server; the waits still pass. - Unsorted list reads have a deterministic order.
defaultListSortOption()(updatedAtdesc,_iddesc) is now the SDKfinddefault for every non-keyset read. Without an ORDER BY the engine picks the access path, so the same logical query could return different LIMIT/OFFSET windows under two plans (proven:SELECT rowid,_idserved from the_idauto-index vsSELECT <all cols>served from a table scan, same table, different first rows) — a real pagination bug, and the reason a compiled list statement could never be byte-stable. The order matches the keyset default and rides the(tenantId, updatedAt, _id)index (no temp B-tree, verified viaEXPLAIN QUERY PLAN). Note for a rolling deploy: the default-list response cache key stays…:find:default_50, so entries cached under the old (unspecified) order live out their TTL — bodies are otherwise identical. - The list wire plane (engine-built
dataJSON) was implemented to byte parity and measured — it loses. On SQLite it was built to completion (row =json_objectover the physical selection +json_removeblob splice; fail-closedbad_rowsguard for NULL/non-object blobs; the lane frames the exact envelope incl.meta.executionTime= BaseAdapter’s constant wrap meta and the_collection-only-when-rows rule) and reached provable byte parity: the integration differential suite compares wire output against the domain envelope byte-for-byte, and an HTTP A/B on the bench server (x-wire: listmarker) returned 37 916 B == 37 916 B. Then the timing: on a fresh bench DB (201 rows), a 50-row × ~750 B list measures wire p50 5.05 ms (SQL alone 1.7–2.9 ms) vs domain p50 2.43 ms — SQLite’s JSON functions are text-based (render +json_removere-render + splice copies ≈ 58 µs/row) andnode:sqliteis synchronous anyway, so nothing moves off the event loop. On PostgreSQL/MariaDB the same idea cannot be byte-identical at all:json_build_object/JSON_OBJECTrender spaced JSON ({"a" : 1}) andjsonbadditionally reorders keys, so V8JSON.stringifybytes are unattainable without value-corrupting string surgery (the shipped PG point wire already serves engine-formatted bytes, so parity there means stability, not identity); MongoDB has no engine-side JSON without$function. Conclusion: the remaining ~7 ms of a large list miss is not removable by engine JSON — the saturation evidence (153 RPS at 2c / 154 at 8c) stands, the lever does not. The SQLite implementation, the lane branch and the interfaces were removed again (no dead code); the reusable pieces kept are the deterministic order and the point-wire delegation fix below. - Point-wire delegation in the tenant guard.
createTenantGuardedCrudbuilds an explicit method list, so under MULTI_TENANT the wrapper hidfindPointWireStreamand every lane read silently fell back to the Domain Plane. The wire method is now delegated through the same tenant-options guard as every other op. - Row-shape lever profiled and falsified (same day). A faithful stage replay of the list build
(199 rows × ~1.3 KB blob, 16 keys/row, real
convertDatesToISO+trimListEnvelope+ stringify) costs 0.66 ms total (3.3 µs/row): blobJSON.parse0.27 ms, parse+flatten+date/bool conversion 0.38 ms, trim copy 0.03 ms,JSON.stringifyof the 364 KB envelope 0.31 ms. A blob-only projection shape saves ~0.2 ms, dropping the blob entirely ~0.5 ms — the row shape is not where the list cost lives. The 310 KB (199 × ~1.5 KB) list’s realx-srv-splitbuild mark is 6.4 ms, ~10× the stage replay: the remaining mass is string materialisation (body + ETag hash + variant handling) and the GC that rides on it, not per-row parsing. Concurrency check on that shape (cache-bypassed, identity): p50 8.64 / 9.28 / 12.99 ms and RPS 116 → 194 → 567 at c=1/2/8 — the earlier “flat ~153/154 RPS” saturation reading did not reproduce outside the zstd audit’s own setup, so that number should not be quoted as a general list ceiling until it is re-measured with the compression path separated out. Harness note for whoever picks this up: freshDB_NAMEs currently 401 at benchmark admin login (authUsersabsent after pre-boot seeding, boot logs aThemeFileSyncfailure) while existing DBs work — flagged, not attributed to this change.
3.40 September 2026 — The 380 KB list miss’s build mark, found and cut: an 8 ms FNV fold → native digest (−84 %)
The open measurement, closed. §3.39 left one explicit next step: re-measure the list saturation with the
compression path separated from the build path. The new diagnostic probe
tests/benchmarks/probe-list-compression-ab.test.ts does exactly that — Accept-Encoding: identity vs zstd
at c=1/2/4/8 on a 199-row / 381 681 B list (220 × ~1.5 KB rows seeded through the API into BenchmarkStable,
one unique probe= cache key per request so every request is a true miss and single-flight coalescing cannot
fold the arms together), reporting client p50/RPS plus the x-srv-split deltas (db−lookup, build−db,
cachewrite−build, serve−cachewrite).
Result, before any change (SQLite, production build): buildΔ (trim + stringify + etag) is 6.2–6.4 ms on
both encodings — compression is not in the build; cacheΔ isolates it: identity 0.14–0.16 ms vs zstd
0.96 / 7.16 / 15.6 / 29.1 ms at c=1/2/4/8 (the awaited compressAsync plus its worker-pool queueing;
server x-srv-dur p50 grows 7.5 → 35.5 ms on the zstd arm). The zstd response is 5 132 B where identity is
381 681 B, so at c=8 the client is faster on zstd (p50 53.2 vs 63.8 ms, 136 vs 119 RPS) despite the server
paying more — the compression wait buys a 74× smaller transfer and pays for itself on the wire.
The build mark profiled: an in-process profile of the exact lane steps on the captured 381 681-char body:
Buffer.byteLength 0.004 ms, Buffer.from 0.083 ms — and generateContentEtag (FNV-1a 64 over UTF-16 code
units) 8.2 ms (Bun) / 3.9–7.9 ms (Node). It is the entire build segment, and the single most expensive
per-request op on the large-list path. Two same-output rewrites were measured and rejected: int-limb fold
1.03×, reciprocal-multiply carry 1.00× — FNV-1a is a serial multiply-accumulate chain, so arithmetic
micro-optimisation cannot move it; byte-wise FNV (different output) only 2×.
The fix — size-gated native digest. generateContentEtag keeps the FNV fold byte-identical below 1 KiB
(small settings/GraphQL/point payloads: zero validator churn), and above it returns "s256|<sha256-hex>"
computed with crypto.hash("sha256", body, "hex") (fallback createHash). The T15 contract is unchanged —
the validator still covers the entire body, so any content change flips it — and the digest is 20–50× faster
at every measured size (0.0013 ms @1 KiB, 0.10–0.21 ms @380 KB). One function backs lane, dispatcher, GraphQL
and turbo-stash etags, so the win lands on every large-body miss path at once.
Result, after (same probe, same shape): buildΔ 6.4 → 0.94–1.01 ms (−84 %); identity p50 c=1
8.84 → 3.26 ms, RPS 99/119 → 252/343 at c=1/c=8; zstd cacheΔ @c=8 29.1 → 7.75 ms (less event-loop
contention in front of the worker pool); in-process etag cost 9.8 → 0.102 ms. Regression test:
tests/unit/core/request-classifier-and-response-cache.test.ts pins "…16-hex" below the threshold,
"s256|…64-hex" above, plus determinism and sensitivity.
Harness findings (the flagged 401, diagnosed). The “fresh DB_NAMEs 401” note from §3.39: in local profile
the pre-boot seeder creates the sandbox DB through the env-only adapter path, which materialises only the
content/collection schema — no auth_users/system_preferences — so seedBenchmarkState’s createUser
silently no-ops (its result is unchecked) and the server can never authenticate. Snapshotting a completed DB
(VACUUM INTO config/test-database/<name>.sqlite) restores it. Also: seeding POSTs must carry
content-type: application/json — Bun’s fetch sends text/plain for string bodies and the write lane then
500s with Unexpected end of JSON input (probe-side bug, fixed in the probe).
3.41 September 2026 — Harness DBs stop landing in the user’s folder, composite indexes get a policy, and the update lane’s residual is proven to be the durable commit
Three threads, one honest measurement discipline: no claim without a falsification run.
1. The E2E/benchmark databases that appeared in config/database/ (root cause: a require). The
user’s live-data folder had accumulated e2e_auth_test.sqlite, bench_parent.sqlite, benchmark_shared.sqlite,
sveltycms_test.sqlite and a set of probe_* scratch DBs — none of which belong there. Fingerprints showed
36-table, 0-row shells (auth/settings empty, only worker-written svelty_jobs rows), i.e. harness debris, not
user data. Root cause proven from the worker’s own log
([DB] Registry resolved connection string: config/database/probe_test_whoF.sqlite): resolveSqlitePath()
(src/databases/config-state.ts) created the test folder via a bare require("node:fs") inside the try that
also returned the test path. In the esbuild-built background-worker bundle that require throws
(Dynamic require of "node:fs" is not supported), the catch swallowed it, control fell through the
if (isTest) block, and the network-address branch answered config/database/<name>.sqlite. Fix: static ESM
imports (they cannot throw), best-effort mkdirSync, and the return outside the try so a mkdir failure can
never re-route the path again. The name heuristic now calls the shared isIsolatedTestDbName() classifier that
enforceTestSafety() already uses (test/bench/e2e/_functional, minus production-like names) instead of a
divergent includes("test") || includes("benchmark") copy. Verified after rebuild by running the worker directly:
harness flag + probe_test_whoG → config/test-database/…, harness flag + plain probe_plainA →
config/test-database/…, no flag + probe_plainB → config/database/… (true-dev behaviour preserved).
Regression guard: tests/unit/databases/resolve-sqlite-path.test.ts — 10 tests covering flag routing, classifier
names, dev names, explicit POSIX/Windows/relative hosts, .sqlite canonicalization, and — because this is the
actual incident — a real mkdir failure (a process.cwd spy pointing at an existing file, so no fs mocking
can be dodged). The guard was falsified before shipping: moving the return back inside the try makes it fail
with expected 'config/test-database/probe_test_worker.sqlite' / received 'config/database/probe_test_worker.sqlite'
— exactly the incident’s signature.
2. The covering composite index now has a policy (all three SQL engines). createModel used to provision
(tenantId, status, <col>, _id) for every materialized column; each one is maintained on every write,
because updatedAt is indexed and rewritten by each update, so PostgreSQL can never take the HOT path and must
insert into all indexes on the table. A psql A/B on a narrow table (3 vs 4 indexes, 2 000-row batches, alternating
orders) measured that cost directly: +11–17 % per extra index (≈ 0.7 µs/row/index). The policy — shared by
PostgreSQL, MariaDB and SQLite via buildCompositeIndexColumns() in src/databases/core/drizzle-sql-helpers.ts —
provisions the composite only for declared query targets: indexed: true fields, numeric fields (the default
listFilterSort sort target), and the publishedAt base column. Equality-lookup base columns (collection,
slug, locale) and storage-only columns (unique, materialize: true) keep their single-column index (still
filterable) but no longer pay for a covering twin; legacy twins are dropped explicitly, following the existing
…_tenant_status_updated twin-drop precedent. The default list order is untouched — it is served by
…_tenant_status_updated_id, which the policy always creates. On the 113 k-row benchmark table the policy takes
the index set 15 → 12 (pg_indexes, verified after a shipped-code boot). Coverage:
tests/unit/databases/composite-index-policy.test.ts (eligibility + column-set contract) and the cross-engine
integration block Composite-index policy — {engine} in tests/integration/databases/dynamic-field-filter-index.test.ts
(a numeric field keeps its composite; unique/materialize/lookup columns do not; their single-column index
exists) — green on SQLite and PostgreSQL.
3. Where the update lane’s residual actually goes — the durable commit, measured. The write-split probe
(tests/benchmarks/probe-write-split.test.ts, PostgreSQL, production-mode server, 2 400 iterations at 8c) puts the
update lane at x-srv-dur p50 3.67 ms / avg 3.78 ms, decomposed as security 0.014 · parse 0.081 · schema
0.000 · prep 0.028 · encrypt 0.011 · dbwrite 3.548 · postwrite 0.026 · serve 0.058 ms. Every app phase sums to
~0.16 ms (4 %); dbwrite owns 94 %. Then the same statement was measured with and without the durability
wait (psql, single-row autocommit = the app’s pattern): 1.566 / 1.658 / 4.496 ms with synchronous_commit=on
vs 0.310 / 0.347 / 0.430 ms with off — i.e. ~75–85 % of dbwrite is the WAL fsync/commit wait on this host’s
Docker volume, and 200 rows in one transaction cost 42–44 µs/row for all the actual tuple+index work. That is
why neither the composite policy nor the earlier numeric-materialization change can move the lane here: the
effect sizes are single-digit µs against a ~3.7 ms commit-dominated op, and the replica’s own pair
(update 1 280 → 1 406 RPS) also moved the read lanes +21–32 %, i.e. host drift, not policy. Both facts are now
recorded as such — the policy ships as a write-amplification/WAL reduction, not as a latency claim.
Harness hygiene found on the way: a leftover vite dev server for the external benchmark dashboard had been
burning CPU since 2026-09-26 (57 631 s of CPU time), one more reason the recorded cross-day spreads are wide;
and a 20-minute build stall reproduced once and then vanished on re-run (no product change involved).
3.42 September 2026 — Session risk, lane threat scan, SSO metadata, WebAuthn ceremony
- Read lane: a warm collection
GETorHEADrunswafGuard.inspectEventand the honeypot / non-local scanner response before any cache or database read.handleSecurity(heap shedding, the Redis WAF limiter, the GraphQL shield) stays off that lane. No new benchmark file; the scan is the same linear walk the write lane already calls. - Session risk: a changed user-agent steps mutations up (
SESSION_RISK_STEP_UP, or/login?reason=step-upfor pages) untilPOST /api/user/sessions/reauthverifies the password and adopts the current browser. An IP-only change is logged. Reads stay available. - SSO: OIDC session metadata is written to the session cache (
sso:session:{id}, 7 days) as well as process memory, so RP-initiated logout can finish on another node. Back-channel logout still walks the sessions this process has in memory. - WebAuthn: register and sign-in reject a foreign origin and a missing user-presence flag. The assertion signature is checked over the raw client data bytes.
- Self-assessment, re-run 2026-09-29: the August weights still total 99.93/100 (~99.9%).
bun run test:securitypassed 343 tests.bun run risk:auditpassed 5/5 after the read redaction path was switched to canonicalisAdmin()(the first audit pass had oneadmin-role-fragmentationfinding). The two 99.8 rows were not raised. The figure is the internal checklist. A dated penetration test by someone outside the project is still open, and it is not one of the six weights.
3.43 September 2026 — Missing columns on an existing database, and the warm write lane’s remaining URLs
- Schema boot:
CREATE TABLE IF NOT EXISTSdoes not add a column to a table that already exists. A boot that only ran it stored the spec fingerprint anyway, so the next start skipped the pass and left the live table behind the spec. MariaDBbench_parent.auth_sessionswas missingamrandmfaVerifiedAt; session insert failed and admin login returned 401Failed to create session. Boot now adds any spec column an existing table does not have. A bare spec fingerprint triggers that pass once. After it succeeds the stored marker iscomputeColumnSyncedFingerprint, and a later boot skips both the DDL pass and the column scan. A declaredrenamedFromis replayed. The dropped v0.0.8 repairs are still not. Documented in Upgrading. Test:tests/unit/databases/schema-spec-parity.test.ts. - Warm writes: batch, bulk, increment, status, and
POST /api/graphqljoin the SvelteKit collection write lane when the session is warm and the caller is an admin. The socket write lane stays opt-in.Prefer: return=minimalon increment and status skips the row read-back on all four adapters, the same contract create and update already had. The default response is still the full document. The PostgreSQL full-document update remains the durable commit described in §3.41.
3.44 September 2026 — Schema model
The lean collection schema model shipped in src/content/schema-model.ts, src/content/schema-hooks.ts, and src/databases/core/column-renames.ts. Documented in Schema model. Relation values stay ids. There is no new foreign-key index on the write path, because each extra index is maintained on every update (§3.41). A full-document update is still the durable commit. The remaining model surfaces (compiled relation filters, nested writes, onDelete, virtual, omit) are tracked in the roadmap.
- GraphQL lists take
where: JSON,orderBy: String, andtake: Intin one operator language (one JSON argument, not a generated input type per field).parseOrderByacceptsfield,+field,-field,field:asc|desc, andfield asc|descand rejects unknown or non-orderable fields (ORDERBY_UNKNOWN_FIELD,ORDERBY_FIELD_NOT_ORDERABLE). Inverse relations takewhereand filter loaded rows in memory;isFilterable: falserejects a filter on that field. Where shape cap: 8000 characters, depth 6, 64 nodes. - Hooks, on the collection and on
field.hooks:resolveInput,validate(many errors, one abort before the write),beforeOperation,afterOperation, in addition tobeforeValidate/afterValidate. Order is field and listresolveInput,beforeValidate, field and listvalidateplus the numeric/exact gate,beforeOperation,afterValidate;scheduleAfterOperationruns the post-commitafterOperationwithout awaiting, and a failure is logged at debug.prepareWritePayloadpassesschema.fieldswhenever a field declares hooks, andensureSchemaHotFlagsderives_hasHookFields/_hasAfterOperationHooksso hook-free schemas pay nothing. decimal,bigint,calendarDay, andbytesare exact string columns (materializedSqlType: SQLiteTEXT; PostgreSQL and MariaDBVARCHAR(64),VARCHAR(10), andTEXT) and do not earn a covering composite index unlessindexed: true.shouldMaterializeFieldmaterializes them by default, so they are real columns rather than blob fields.- Schema actions
{ permission, increment?: { field, amount }, patch?, run? }are checked by the action permission inCollectionsNamespace.runAction. REST isPOST /api/collections/:id/actions/:namewith{ entryId, input }; GraphQL isrunCollectionAction. REST does not also requirecollections:write. An undefined action is 404ACTION_NOT_FOUND; an action without a permission is admin-only (ACTION_FORBIDDEN). renamedFromon a systemColumnSpecor a collection field renames when the old column exists and the new one does not, beforeADD.column-renames.tsmatches the live catalog case-insensitively and issues the rename in the stored case; a failed rename is retried on the next provisioning pass. Mongo uses$renameonce per process when the model is created. The dropped v0.0.8 repairs are still not replayed unless a column declaresrenamedFrom. Tests:tests/unit/databases/column-renames.test.ts,tests/unit/content/schema-model.test.ts, andtests/unit/databases/schema-spec-parity.test.ts.isFilterable/isOrderableare booleans or functions of{ user }and are enforced on GraphQL filters and sorts.cacheHint: { maxAge, scope }sets GraphQLCache-Control(tightestmaxAge, private wins); a private hint is not stored in the shared GraphQL response cache.- Row ownership
ownership: { field }:find,count, andfindStreamingscope tofield === user._idbefore the cache key (admins and system scope bypass; a caller without a user id matches nothing), andpresentReaddrops a non-matching point-read row on a copied envelope so the shared cache entry is never mutated or poisoned. Tests:tests/unit/sdk/collections-ownership.test.ts. singleton: trueforces_idsingleton:<collectionId>increate; a second create answers 409SINGLETON_EXISTS. The_idprimary key makes a concurrent double create fail on the insert instead of persisting two rows.
3.45 October 2026 — Durable-commit parity for every SQL engine, UNNEST/JSON_TABLE bulk writes & the system index overhaul
The 2026-09-23 engine-knob probe ended with “deliberately NOT shipped” because the knob was cluster-global. It is now shipped the safe way: per-connection GUC parameters, so only the CMS pool relaxes durability and production keeps the server default unless the operator opts in.
- PostgreSQL durability knob wired —
pgConnectionParameters()(src/databases/postgresql/adapter-core.ts) passesPG_SYNCHRONOUS_COMMIT/DATABASE_SYNCHRONOUS_COMMIT(plusPG_WORK_MEM,PG_JITdefaultoff) into every pool (primary, tenant pools, replicas). Self-measured on the local WSL2/Docker host (2026-10-04): updatedbwritep50 9.76 ms → 2.3–2.9 ms (96 % → ~70 % of the request), competitive update lane (warm 8c) ~1.5 k → 2.0–2.6 k RPS. - PostgreSQL UNNEST bulk writes —
insertMany/rawInsertManyReturning/rawBulkUpdatebind onetext[]array per column with element-wise casts; constant SQL per column set (plan-cache friendly), presence arrays keep explicit-NULL vs omitted semantics,data_modepatch/set/keep preserves per-row JSON blob safety. Param count drops from 2×N scalars to 1 array/column. - MariaDB JSON_TABLE bulk update — the MariaDB twin of the PG UNNEST path (
UPDATE … JOIN JSON_TABLE, version-gated 10.6+, CASE-ladder fallback below). Fixed live (2026-10-04): the JOIN was pinned toutf8mb4_unicode_ci, which raises error 1267 on tables created under the MariaDB 12 default collation (utf8mb4_uca1400_ai_ci) — the error was silently swallowed and every heterogeneous batch fell into the 100× per-row loop (68.4 ms). The JOIN now comparesCOLLATE utf8mb4_bin(works for both collations) and the catch logs instead of vanishing. Self-measured after the fix: BULK UPDATE (100 heterogeneous) 68.4 → 3.6 ms (~19×, 14 → 267 RPS), contract suite 18/18. Measured and deliberately NOT shipped (2026-10-04): the INSERT twin (INSERT … SELECT FROM JSON_TABLE) lost the A/B — BULK INSERT (100) 2.70 → 3.24 ms (+19 %) and seed burst 7,676 → 4,899 docs/s (−36 %) — because the multi-VALUES path binds values natively while JSON_TABLE pays a full JSON document parse per batch; the UPDATE twin won because it replaced N per-row statements, not direct binding. The finding is documented inrawInsertManyReturning. - Durability parity baked into the harness DBs (
tests/docker-compose.yml): PostgreSQLsynchronous_commit=off+ buffer tuning; MariaDBinnodb_flush_log_at_trx_commit=2(the MariaDB twin — there is no per-session GUC, so the server flag is the only knob). Self-measured competitive replica (warm 8c, 2026-10-04): MariaDB update 1 269 → 1 918 RPS (+51 %), create 1 466 → 2 083 RPS (+42 %); seed burst 5 686 → 7 676 docs/s. - System index overhaul (single source of truth
system-schema-spec.ts+ drizzle schemas + renderer):audit_logstimestamp btree → BRIN +(tenantId, timestamp DESC);content_nodeschildren index(tenantId, parentId, position, _id);content_draftslatest index(contentId, version DESC);content_revisionsUNIQUE(contentId, version);auth_usersemail uniqueNULLS NOT DISTINCT; redirects UNIQUE partial(tenantId, source) WHERE active = TRUEwith legacy dedupe + drop extensions. MariaDB/SQLite equivalents kept in parity. Provisioned and verified via psql/schema-spec-parity(48 files / 347 tests). - Benchmark scale guard recalibrated (2026-10-04): the denominator was
findByIdRandom, which now runs at the HTTP+DB floor (~8.8–9.5 k RPS), structurally capping any DB-bound write ratio at ~0.20. The guard now compares writes againstlistLarge(the heaviest DB-backed read lane, 5.1–6.5 k RPS) at a 0.25 threshold: PostgreSQL passes at 0.40/0.40, MariaDB at 0.31/0.27 (both self-measured 2026-10-04, warm 8c). - Fast-lane oversized-body fix (
index.server.mjs):content-length> 256 KB now rejects with a proper 413 before the lane reads the body (no moreFAST_LANE_PAYLOAD_TOO_LARGE500s). - UPDATE
?fields=projection (2026-10-05):PATCH /api/collections/:id/:entryId?fields=_id,count,updatedAtprunes the SQLRETURNINGread-back to the requested physical columns on both write paths (fast lane + full pipeline) — the JSONdatablob is never re-read/de-TOASTed for callers that only need the changed fields. Blob-only names are resolved away (resolveUpdateProjection, unit-tested); an empty projection falls back to the full representation. Verified end-to-end by a response-shape guard in the replica benchmark (BENCH_UPDATE_FIELDS=_id,count,updatedAt): projected responses contain exactly the requested keys. Self-measured on the small-blob replica the lane sits within host noise of the full-return row (2,840 vs 2,964 RPS, warm 8c) — the gain scales with blob size/TOAST, which is the 100k-doc external-harness regime. The other two Keystone-gap levers were verified as already implemented: pure physical-column updates emit noCOALESCE(data,…) || $nmerge clause, and the audit hook already fire-and-forgets (voidflush + 15 ms coalescing timer, never awaited by the response). - Find/write path lean-out, probe-first (2026-10-05):
probe-point-read-splitran per engine — cold misses showbuild = 0on all four (PG db p50 0.54 ms, MariaDB 0.56, SQLite 0.06, MongoDB 0.75), i.e. the wire statement serves every admissible point read and the JS copy chain (date-conversion clone, blob flatten,_collectionstamp, HTTP trim, second stringify) never executes on that path — per the probe’s own decision rule the find path stays alone. Two safe items shipped instead: (1) the domain fallbackfindByIdnow converts the fresh Drizzle row in place (nothing else holds a reference — the lane never caches the converted object); (2) the warm write lane admits an author carrying a freshcollections:writebit (isSimpleWriteLaneAuthorized, mirrors the read lane’s rule) — previously a signed-in author with a warm session got a hard 403 on simple POST/PATCH and never reached the permission map; unauthorized/stale callers still fall through to the full pipeline while the body is unread, and WAF/CSRF/session-risk/field-guard checks all stay. Declined with measured reasons: the_collection-stamp skip (the wire path never runs it — one assignment on non-admissible reads only) and theupdatedAt-on-counter-only experiment (commit-bound lane; changes newest-first sort semantics). - Lane-lean rate limit: local enforcement, async Redis ledger (2026-10-05): the warm collection write lane opts into
rateLimit({ asyncRemote: true })— the in-memory token bucket enforces synchronously (pure math, no I/O) and the identical spend propagates to Redis without awaiting it, so a mutation never pays the Redis round trip on its critical path. The limit itself is unchanged and a second process still sees the spend a tick later; GraphQL writes and the full pipeline keep the awaited Redis enforcement. Self-measured paired medians (probe-write-split, SQLite, Redis up, same session, 3 runs/side): warm create srv-dur p50 1.860 → 0.360 ms (−81 %), warm update 1.485 → 0.310 ms (−79 %); PostgreSQL create/update sit at 2.93/2.96 ms p50 with the fix — the ~2.4–2.8 ms commit remains the limiter there, exactly as the review predicted. Unit-tested (async-remote.test.ts, 4 cases: returns before Redis resolves, local exhaustion while Redis pending, awaited default path unchanged, memory fallback when Redis is down). - Plugin server-module bundling fixed + zero-warning build (2026-10-06): the template-literal
import(\./${pluginId}/index.server`)in the plugin registry/init was not statically analyzable — rolldown globbed./*//index.server, matched nothing (empty-globwarning), and the modules silently 404'd in production builds (dev-only behavior): redirect-manager's migrations +beforeSavehooks and pagespeed'sssrHooknever loaded in prod. Fixed with a literalimport.meta.glob(”./*/index.server.ts”)in the server-onlyinit.server.ts, loaders registered intopluginServerRegistry, and all three registry lookup sites routed through it (migrations, ssrHook, activation);PluginServerModulenow typeshooks/migrations/ssrHookexplicitly. Also addedsrc/utils/v8-shim.tstopackage.jsonsideEffects— the Bun v8-module shim was being tree-shaken out of bundles (ignored-bare-importwarning), which would break bson/mongoose under Bun. Build is now warning-free and the plugin chunks are verifiably inbuild/server/chunks/`. - Stripe 22.6.2 → 23.0.0: no removed APIs touched; API version pin moved
2025-03-31.basil→2026-09-30.endive(src/plugins/stripe/server/stripe.ts). - Index write-amplification A/B (2026-10-04, MariaDB + PostgreSQL, paired alternated arms + PK-read control, statistics equalized via ANALYZE): the legacy prefix-duplicate collection index (
(tenantId, status, updatedAt)— pre-§3.41 policy shape, no longer created) is write-lane-neutral on both engines: update 1c −2.3 %/−1.9 %, update 8c +2.3 %/−5.3 %, create 1c −2.8 %/+2.1 % — every lane inside the control drift (28 % MariaDB / 11 % PostgreSQL). Two measurement artifacts were caught and corrected en route: stale planner statistics (CREATE INDEX recomputes them post-seed — made the legacy arm look +10–20 % faster) and an underpowered 8c sample (~19 ops/worker at 150 shared iterations). Verdict: the policy was right to stop creating the duplicate; keeping it costs nothing measurable on the lane, so no cleanup migration is justified by write throughput (WAL/disk hygiene remains the only reason to drop it). Harness:tests/benchmarks/index-write-amplification-ab.test.ts. - Pre-existing formatter crash repaired:
{theme.customCss}/{loginBranding.customCss}inside<style>crashed oxfmt’s CSS parser (breakingbun run check); both now render via<svelte:element this={'style'}>.
Quality gates (2026-10-04): bun run check 0 errors / 0 warnings · bun run test:unit 572 files / 5 105 tests · bun run lint:tenant -- --full 934 files · bun run risk:audit 5/5. Benchmarks: competitive-workload-replica green with BENCH_SCALE_GUARD=1 on both PostgreSQL and MariaDB.
3.46 October 2026 — 4-Adapter Transaction Parity, Audit Micro-Batching & Projection Type-Preservation
- Cross-adapter transaction parity across all 4 database engines (
src/databases/*/transaction-module.ts): SQLite, PostgreSQL, MariaDB, and MongoDB share unified transaction semantics. Includes statement timeout propagation, transaction isolation level configuration (read-committed,repeatable-read,serializable), re-entrant transaction detection (re-using existing transactions or falling back gracefully to SAVEPOINTs/client sessions), and explicit rollback signal handling (ROLLBACK_TRANSACTION). Comprehensive parity test suite (tests/unit/databases/multi-adapter-transaction-parity.test.ts, 400 lines) validates identical contract compliance across all adapters. - Audit log micro-batching (
src/services/security/audit-service.ts): Mutating operations no longer suffer latency penalties or lag by 5 seconds. Micro-batch window (25 ms flush timer or 50 items) flushes pending audit records synchronously when configured or in background batches, ensuring mutations remain sub-millisecond while maintaining tamper-evident audit ledger integrity. - MongoDB unordered bulk operations (
src/databases/mongodb/): High-throughput write batching leverages{ ordered: false }to prevent head-of-line blocking on batch inserts and updates (tests/unit/databases/mongo-unordered-bulk.test.ts). - Virtual field projection type-preservation & fuzz resilience (
src/databases/core/sql-adapter-core.ts): When SELECT projections request virtual fields located in JSON data blobs, the adapter preserves thedatacolumn to maintain native typed JSON values (numbers, booleans, objects, arrays) rather than coercing them into stringified SQL text. Skips the data blob only when all requested fields are known physical schema columns. Pinned via property-based fuzz test suite (tests/unit/databases/virtual-field-projections.test.ts) covering randomized column subsets, deeply nested dot-paths (a.b.c.d), Unicode identifiers, and malformed projection payloads. - Micro-batch adversarial concurrency stress test (
tests/unit/services/audit-log-microbatch.test.ts): Verified that 500 simultaneousservice.log()calls across 10 concurrent worker promises execute with zero dropped entries, buffer memory staying strictly bounded underMAX_TOTAL_BUFFER = 200, and an uninterrupted cryptographic SHA-256 hash sequence across all 500 records. - Smart local 4-database matrix runner (
scripts/run-local-db-matrix.ts,bun run test:matrix:local): Probes local TCP listener ports (SQLite, PostgreSQL 5432, MariaDB 3306, MongoDB 27017) to auto-detect active engines and orchestrates the integration contract suite (tests/integration/databases/contract.test.ts) with automated server boot/shutdown across all online adapters. - System monitor E2E dashboard validation (
tests/e2e/routes/config/operations.spec.ts): Automated Playwright test verifying that/config/monitorrenders service health tables, system CPU/memory cards, and cryptographic audit trail with zero browser console errors. - Live truth-latency re-verification (
tests/benchmarks/truth-latency.test.tson 2026-10-05): Warm HTTP End-to-End 0.262 ms (p95: 0.322 ms, 3,785 RPS); Cold HTTP 0.432 ms (p95: 0.587 ms, 2,304 RPS); Local SDK 0.005 ms (p95: 0.009 ms, 177,378 RPS). ACID transaction commit: 0.005 ms, 218,055 RPS.
Quality gates (2026-10-05): bun run check 0 errors / 0 warnings · bun run test:unit 577+ files / 5,150+ tests · bun run test:matrix:local 4/4 adapters passed · bun run lint:docs 263 files clean · bun run risk:audit 5/5 passed.
Storefront — European checkout (2026-10-05)
Catalog prices on /shop are gross. Checkout shows the trader, the VAT breakdown, and an order button that states the payment obligation (zahlungspflichtig bestellen when the store language is German). Guests withdraw at /widerruf. The order stores an invoice number, net, and tax. A price reduction shows the lowest price of the previous 30 days when that history exists. OSS returns, packaging registration, and WEEE stay with the merchant. Guide: E-Commerce Overview.
3.47 October 2026 — Pre-compiled Query Builder Templates & Sub-Millisecond SQL CRUD Acceleration
- Shared SQL query builder unification (
src/databases/core/sql-query-builder.ts): Unified the duplicated builder logic across SQLite, PostgreSQL, and MariaDB into a singleSqlQueryBuilder<T>parameterized bySqlDialect. All engine-specific quoting, placeholder binding ($1vs?), JSON extraction, and ILIKE/LIKE operators are driven by dialect constants. - Compiled plan expansion: Extended the compiled prepared-statement cache beyond scalar equality to support
whereIn,whereNotIn,whereBetween,whereNull,whereNotNull,orWhere, and parameterized pattern searches (search_or). Any dynamic JSON field filters, unmapped expressions, or non-bindable objects safely degrade to Drizzle dynamic AST with zero runtime errors. - Dialect-safe LIKE ESCAPE binding: MariaDB treats backslashes inside string literals as escape characters; binding the escape backslash as a query parameter (
ESCAPE ?/ESCAPE $N) ensures cross-dialect compatibility without double-escaping bugs or syntax failures. - Standard CRUD read path acceleration (
src/databases/core/sql-adapter-core.ts): Added pre-compiled template fast paths tofindMany,findPage, andcount. Pre-compiled query shapes execute directly viaexecuteCompiled(), bypass Drizzle runtime AST building and SQL string assembly, and hoist schema date-conversion metadata so read lists stop re-resolving table column schemas per row. - Self-measured benchmark gains (2026-10-06):
- PostgreSQL:
QUERY BUILDER LIST (50)4.88 ms → 0.75 ms (~6.5× faster, 202 → 1,204 RPS);FIND MANY (limit 50)1.27 ms → 0.79 ms (−37 % latency, +40 % RPS);FIND PAGE (50 hasMore)1.26 ms → 0.77 ms (−39 % latency, +61 % RPS);FIND MANY offset 501.09 ms → 0.55 ms (−49 % latency, 1,728 RPS). - MariaDB:
FIND MANY (limit 50)dropped to 0.558 ms (1,694 RPS);QUERY BUILDER LIST (50)dropped to 0.545 ms (1,753 RPS);FIND PAGE (50 hasMore)dropped to 0.587 ms (1,684 RPS);COUNTdropped to 0.487 ms (2,020 RPS). - SQLite: Reads stay sub-100µs:
FIND MANY (limit 50)0.059–0.065 ms (14,000–15,600 RPS);COUNT0.058 ms (16,231 RPS).
- PostgreSQL:
- Documentation & test coverage: Reference documentation published at SQL Query Builder. Verified via 19 operator unit tests (
tests/unit/databases/sql-query-builder-operators.test.ts), compile tests (sql-query-builder-compile.test.ts), and read-path tests (sql-query-builder-read.test.ts). - Keyset pagination onto the compiled plan (2026-10-06):
mergeKeysetFilter(core/page-utils.ts) wraps a base query plus the(sortField, _id)cursor in$and/$or; the builder previously bailed on every top-level$key except$or, so deep pages fell back to the dynamic AST.applyFilterAndOptionsnow flattens$andrecursively andorWherecompiles OR-of-AND groups of physical leaves, andorWhereself-heals to Drizzle for nested$or, JSON fields and non-bindable operands — it never partially compiles an OR. The compiled path also coerces ISO strings / epoch numbers toDatefor timestamp columns (sharedcoerceDateColumnValue, parity withmapQuery), so a compiled comparison can never bind TEXT against anINTEGERtimestamp_mscolumn. Verified end-to-end by both keyset integrations (tests/integration/api/collections-keyset-cursor.test.ts,tests/integration/databases/find-page-count-contract.test.ts) and the operator/compile unit suites.
3.48 October 2026 — SQLite write tail: checkpointing moved off the request path by default, and group commit (opt-in) takes the 8c queue tail from 🔴 to 🟡
Two levers, measured separately on the same build (competitive-workload-replica, sqlite, warm 8c,
production server, 300 samples; each verdict is the repo’s own 3-run ensemble median via
bun run verify:benchmark-local).
Baseline (recorded pre-change run, both 🔴): create p50 4.41 / p99 24.14 ms (+19.7 ms excess), update p50 4.23 / p99 30.8 ms (+26.6 ms); the tail was entirely SQLite’s synchronous auto-checkpoint (§3.35).
Shipped default — off-request-path checkpointing. applyPragmas now defaults to
PRAGMA wal_autocheckpoint=0 unless SQLITE_WAL_AUTOCHECKPOINT is set explicitly, and
startWalHousekeeping runs startWalCheckpointScheduler by default (opt out SVELTY_WAL_CHECKPOINT=0)
with a new WAL-size cap (SVELTY_WAL_MAX_BYTES, default 64 MB) beside the existing idle and
60 s-force rules. synchronous=NORMAL is unchanged — durability is not weakened. Result (3-run ensemble):
create avg 4.17 / p50 4.0 / p95 5.41 / p99 10.48 / +6.477 ms 🔴, update avg 3.63 / p50 3.47 / p95 5.67 /
p99 7.68 / +4.214 ms 🔴 — slowCount 0 on every write row (no sample above 10 ms; the discrete ~25 ms
checkpoint spikes are gone), matching the earlier single-run A/B (AUTOCHECKPOINT=0 → slowCount 0, residual
excess ≈ 6.5 ms). The absolute tail excess is still above the 4 ms WATCH bound: the checkpoint spikes were
removed, the write-mutex queue is not.
Shipped default ON (2026-10-09) — group commit is the default write path. The batcher is now constructed
by default (SVELTY_SQLITE_GROUP_COMMIT defaults to 1; =0 restores the per-write commit control lane).
Paired A/B on the 10k-doc matrix replica (competitive-workload-replica, --db=sqlite):
create @8c 1,833 → 2,892 RPS (+57.8 %) and update @8c 1,896 → 3,347 RPS (+76.6 %) with improved p95
(6.74 → 5.29 ms and 5.38 → 3.48 ms), mixed @8c +12.1 %, Seed Burst +8.0 %; sequential lanes neutral
(update +1.7 %) except create −9.9 % (single-pair, within the lane’s CoV). The remaining acceptance gate
(no 🔴 row, --strict-variance green on a ≥3-run ensemble) is tracked in the roadmap.
Opt-in (SVELTY_SQLITE_GROUP_COMMIT=1) — write coalescing (original measurement). New src/databases/sqlite/write-batcher.ts
queues batchable single-statement writes and drains them on a bounded event-loop tick
(setImmediate/queueMicrotask, never a delay timer) into one BEGIN IMMEDIATE … COMMIT; each op keeps a
SAVEPOINT so one failure rolls back only itself, results are buffered and settled after COMMIT, and a lone
write takes the runSingle (no group transaction) path. The adapter only constructs the batcher behind the
flag; transaction-control/PRAGMA statements and active write spans (re-entrant/worker contexts) bypass it.
Result (3-run ensemble): create avg 2.86 / p50 2.6 / p95 4.38 / p99 5.54 / +2.943 ms 🟡, update avg 2.81 /
p50 2.72 / p95 4.07 / p99 4.95 / +2.225 ms 🟡, slowCount 0 — both rows out of 🔴, and p50 fell ~1.3 ms
(4.0 → 2.6 create, 3.5 → 2.7 update), so the drain is genuinely coalescing writes rather than just adding a hop.
Residual floor, stated plainly: ≈ 2.2–2.9 ms p99 − p50, not ≤ 1 ms. Group commit only folds
writes that reach the queue within the same tick; at 8c the requests are staggered by the HTTP/auth pipeline,
so many drains still carry a single op, and the synchronous driver blocks the event loop for each batch’s
execute phase — the queue is shortened, not flattened. §3.36’s commit-collapse falsification still holds for the
per-statement commit itself; the win here is queue coalescing, not a cheaper commit. Kept opt-in as specified.
Verification: tests/unit/databases/write-batcher.test.ts (10 cases: batching, lone-write path,
maxBatchSize split, per-op isolation, throw/unsettled safety net, default-scheduler deferral) and
tests/unit/databases/wal-scheduling.test.ts (9 cases: size-cap band, interval gate, idle precedence,
scheduler wiring). node ./node_modules/vitest/vitest.mjs run tests/unit/databases → 53 files / 461 passed.
-
Phase 4: True Statement Coalescing & Adaptive Window (2026-10): Replaced per-write TCP statement round trips with PostgreSQL array-based multi-row statement coalescing (
statement-coalescer.ts,postgresql/adapter-core.ts):- INSERT Coalescing (default ON via
SVELTY_WRITE_COALESCING): Batches concurrent single-row inserts for the same collection intoINSERT INTO ... SELECT ... FROM UNNEST($1::text[], ...) RETURNING *withString(val)text-array coercion. Measured across alternated replica runs on PostgreSQL (100k docs, 8c): +12–18% create throughput (2,216 → 2,624 RPS) with improved p95 latency (4.05–4.21 ms vs 4.76–4.94 ms). - UPDATE Coalescing with Lever 2 Adaptive Window (
SVELTY_WRITE_COALESCE_UPDATE=1,SVELTY_COALESCE_UPDATE_WINDOW_MS=1): Executes multi-rowUPDATE ... FROM UNNEST(...) WHERE t._id = u._idwith an adaptive 1 ms burst-hold window. While wave-sized batches (2–5 rows) previously regressed due to UNNEST join/cast overhead, the 1 ms window grows batches to 8–16 rows where the join amortizes, delivering +11–15% update throughput gain (update/create ratio 1.17–1.18 vs control 1.02–1.07) on the 100k-doc harness. Lone writes still flush on the tick boundary with 0 ms added hold delay. 2026-10-09: two paired A/Bs on the 10k-doc matrix replica measured −41–54 % on the update lanes with the lever defaulted, so it stays opt-in — the gain is harness-profile-dependent (batch-size dynamics at 10k docs never reach the amortizing 8–16-row waves). - Fault Isolation: Batch rejection or duplicate-key aborts automatically replay each row individually through its single-statement path — no sibling write is corrupted by an error. Verified:
tests/unit/databases/statement-coalescer.test.ts(8/8 green), full unit suite 5,666/5,666 green.
- INSERT Coalescing (default ON via
-
Phase 5: Cold Read Lane Direct-to-Wire List Streaming (
findListWireStream) (2026-10-09): Extended the Wire Plane architecture from point reads (findPointWireStream) to cold list reads (findListWireStreaminhandle-collection-read-lane.ts):- In-Engine Direct-to-Wire Aggregation: Eliminates V8 entity hydration, DTO transformations, and
JSON.stringifyallocations by producing the wire JSON array inside the database engine. - Fail-Closed Admission Predicate: Enforces strict admissibility via
isWirePlaneAdmissible(event, wireMeta): requires published-only status, default pagination limit (50), page 1, no custom filter/sort, absence of mutating lifecycle hooks, and projection equivalence. - Strict Multi-Engine Paired A/B Measurement: Evaluated across paired alternating arms (Domain Plane vs Direct-to-Wire, 3 alternated pairs, 50-row pages):
- SQLite (In-Process C Engine): +22.7% to +50.2% RPS (0.124 ms → 0.083 ms, −33.1% latency). Shipped and enabled.
- PostgreSQL (Client-Server Network Engine): −29.9% RPS (0.852 ms → 1.215 ms, +42.6% latency). Server-side
jsonb_aggserialization in the Postgres backend is slower than Node.js V8 binary-row parsing + C++JSON.stringify. Declines list wire contract ({ kind: "declined" }), seamlessly preserving the faster Domain Plane. - MariaDB (Client-Server Network Engine): −16.0% RPS (0.873 ms → 1.046 ms, +19.8% latency).
JSON_ARRAYAGGsimilarly incurs server-side serialization overhead. Declines list wire contract.
- Verification:
tests/unit/hooks/collection-read-lane.test.ts(22/22 green), full core unit suite (139 files, 1,416 tests green),tsc --noEmitclean,bun run checkclean,bun run risk:audit5/5 clean.
- In-Engine Direct-to-Wire Aggregation: Eliminates V8 entity hydration, DTO transformations, and
-
Phase 6: Release-Pipeline Hardening (
version:verify, SBOM lockstep, artifact verification) (2026-10-10): Closed the failure modes that leftv0.2.0sitting untagged for days — the release job is gated on a fully greenmainCI, so a red run silently skips every release (14 consecutive skips traced to the plugin-glob/telemetry/RTL failures now fixed).- Manifest ↔ SBOM lockstep:
scripts/version.tsnow writes the CycloneDX root component (sbom.jsonmetadata.componentname/version/purl) in the same write aspackage.json, so a bump no longer depends on the pre-commit hook to avoid drift.bun run version:verifyasserts that lockstep read-only and runs insidebun run check(pre-push Quality Gate + CI03-check). - Release-workflow hardening (
auto-release.yaml): a pre-flightversion:verify, manual-versioninput validation, post-flight artifact verification (tag pushed + GitHub Release created — fails loudly otherwise), honest npm reporting (missingNPM_TOKEN→ visible warning, rejected token → visible error; neither swallows the GitHub Release), and a$GITHUB_STEP_SUMMARYreport. Aworkflow_dispatchrun is the documented escape hatch when a persistent CI failure blocks the gate. - Verification:
tests/unit/scripts/version-sbom.test.ts(SBOM rewrite preserves every unrelated byte, drift detection on name/version/purl,--verifyCLI contract) plus the updatedversion-bump.test.tsCLI assertions; livebun run version:verifygreen oncreate-sveltycms@0.2.0, andv0.2.0released end-to-end oncemainCI went green.
- Manifest ↔ SBOM lockstep: