Changelog
All notable changes to Ndigo are documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
Maintenance convention
Section titled “Maintenance convention”- Every merged change that alters user-visible behavior adds an entry under
[Unreleased]in the matching category (Added / Changed / Deprecated / Removed / Fixed / Security). - Feature work links its decision record (
AD-XX) where one exists — the ADR index is the source of truth for why; this file is the user-facing what changed. - Entries are imperative, user-facing, and unnumbered; keep them short enough for release notes. Formatting is oxfmt-checked.
- Pre-1.0: entries accumulate under
[Unreleased]; the first release cuts it as## [1.0.0].
[Unreleased]
Section titled “[Unreleased]”-
OAuth scope enforcement (issue #377):
Principal.scopeswithhasScope/hasAnyScope, the@authorizedScopes(...)decorator, and scope checks inAuthRouteGuard,AuthMatrixGuard, and the socket dispatch path (403 on HTTP,1008 insufficient scopeon sockets). The JWT and OIDC adapters read thescope(space-delimited),scp(Azure AD), andscopes(custom) claims;TokenSubject.scopesemits thescopeclaim for store-less issuance. A scope requirement implies authentication. -
Benchmarks:
pnpm benchis now a serial lane with JSON artifacts and an environment fingerprint, a committed reference baseline,pnpm bench:compare, and a user-facingBENCHMARKS.md. Comparator rows verify semantic equivalence before timing;pnpm bench:httpcompares four routes against Fastify, Express, and Hono; jsdom DOM targets that cannot execute module-script bundles fail fast with an explicit quarantine reason. -
@ndigo/cli:ndigo routes --toon— one TOON document on stdout for the route list, an experimental sibling beside the--jsoncontract (issue #349). Measured first (scripts/toon-measure.mjs): on a real 20-route manifest TOON is −59% vs pretty / −36% vs minified JSON;ui list’s heterogeneous registry items measure −21%/<0% and stay JSON-only. The two flags error together,--jsonstays canonical, the flag brief is marked[experimental]while TOON is a Working Draft, and the SDK arrives as a regular lazily imported dependency (CLI startup untouched). -
@ndigo/view-toon: the TOON view technology (AD-102) —ToonView(text/toon;charset=utf-8),toonViewResolver(viewName, options?)for opt-in per-route negotiation (register the JSON resolver first: registration order is what keeps JSON the default), andencodeToon()for synchronous in-process prompt composition.@toon-format/toonis a regular pinned dependency (spec v4.4), so the wire surface fails at install time, never as a request-time missing-module error. Supersedes AD-66 D-3 in part: TOON may appear in HTTP responses as an explicitly composed representation with an app-registered.toonextension; the framework wires nothing for it, and storage, SSE, WS/STOMP, external protocols, MCP results/schemas, and every negotiation default stay out of reach. JSON remains the fallback and the fixed default everywhere. -
@ndigo/ai: provider-neutral agent contracts — messages, streaming events, structured output, and a bounded tool-calling loop over@ndigo/mcptool sources, with GenAI spans, rate limits, and rollout flags. -
@ndigo/ai-sdk: the Vercel AI SDK integration layer (ai), the only package that imports the SDK; tools are declared but executed by the agent loop. -
@ndigo/mcp:McpToolRegistry.from(dispatcher)now accepts the real dispatcher type, andargumentResolversbinds parameterized tools through the same chain HTTP uses. Tools without it fail with a clear error instead of a missing-resolver crash. -
apps/21-ndigo-agent-lab: a local-model tool loop over the same@toolMappinghandlers served by HTTP and MCP, streamed over SSE with one trace. -
@ndigo/boot:ApplicationBuilder.fromConfig(url, options?)accepts TOON documents (AD-66 Phase 3) — a.toonaddress suffix decodes as TOON (query strings and fragments stripped first), an explicitformat: 'json' | 'toon'option overrides the address, andformat: 'json'over a.toonaddress errors instead of silently re-parsing. Extensionless and.jsonaddresses stay JSON, the optional@toon-format/toonpeer is only resolved on the TOON path, and the decode contract is identical to.toonconfig files (strict mode, JSON-shaped rename hint, object shape guard, bounded diagnostics). -
@ndigo/ui: the owned design system — canonical--nd-*tokens, the opt-in base layer, the typed token catalog, and a shadcn-compatible copy-in registry (41 items: theme, element foundation, native-first and Zag.js primitives, and five blocks). -
@ndigo/cli: theuifamily —ui init,list,view,validate,add, andmcp(a stdio MCP server for registry discovery and installation). -
@ndigo/cli:mcp— a stdio MCP server over application introspection (get_manifest,get_routes,get_config_keys,get_best_practices,run_doctor,get_logs,run_target,record_rule), with--readOnlygating and--livedelegation to the app’sscripts.mcp. -
@ndigo/cli: one--jsoncontract acrossdoctor,info,logs,config list,env,openapi,db status,lint,typecheck, androutes— one document on stdout, diagnostics on stderr, exit codes unchanged, human output untouched without the flag.ndigo commands(and the MCPget_commandstool) publishes the command index with flags, positionals, and exit codes. -
@ndigo/cli:ai:init/ai:check/ai:update— a version-matched managedAGENTS.mdblock, nine skills under.agents/skills/, and optional.mcp.jsonwiring, generated fromtools/agent-content/and shipped with the CLI.create-ndigo-appgains--agents/--no-agentsfor the bootstrap pointer. -
create-ndigo-app:--ui/--no-ui— browser scaffolds can opt into the Ndigo design system. The flag writes the version-free pointers only (ndigo.ui.jsonwith thendigo ui initaliases, asrc/styles.cssthat imports@ndigo/ui/tokens.css, and the@ndigo/uidependency); components and the lockfile stay withndigo ui. Off by default and never implied by--yes;--uiwithout--kindmeans browser, and--kind node --uiis a usage error. -
@ndigo/cli:ui catalogand theget_catalogMCP tool — an A2UI 1.0 catalog derived from the registry and the application’sui.lock.json, so an agent can only generate components the application installed. -
@ndigo/mcp: MCP tools projected from dispatch metadata —@toolMappinghandlers servetools/list,tools/call, resources, and prompts over stdio and Streamable HTTP, with read-only and@whenFeaturepolicy, OpenAPI schema parity, and one GenAI span per call.ndigo add @ndigo/mcpruns themcp-setupgenerator.@ndigo/platform-nodeexposes the raw request on the route for transport-owning handlers. -
@ndigo/view-webcomponent: WebMCP tools —provideWebMcpTools/declareWebMcpTool(AbortSignal lifecycle),AsWebMcpElement(connect/disconnect registration), andwebMcpToolForHandler(schema from@parameters+validatedBy). -
@ndigo/mcp: MCP Apps —_meta.ui.resourceUrifrom@toolMapping({ ui }),ui://resources served astext/html;profile=mcp-app, acreateUiResourcesingle-file document helper, and theio.modelcontextprotocol/uicapability. -
@ndigo/core+@ndigo/view:application/a2ui+jsonandA2UIView— an A2UI 1.0 message-array representation selected by content negotiation, with ana2uiViewResolverhelper and no A2UI runtime dependency. -
@ndigo/ui: thea2ui-surfaceregistry item —<nd-a2ui-surface>renders A2UI 1.0 surfaces with@a2ui/web_coreand the installednd-*elements. The component map is generated fromregistry.jsonand filtered to elements the page defines; unknown components emitnd-a2ui-error. -
apps/20-ndigo-ui-lab: a browser lab for the full ndigo-ui registry — tokens, native-first primitives, Zag.js behaviour elements, and all five blocks — with keyboard and axe coverage. -
@ndigo/cli: the command surface expanded from the AD-75 core (serve, dev, generate, db, inspect, doctor) to the full developer loop —build,test,lint,format,typecheck,clean,info,config,routes,logs,completion,env,openapi(--out/--format),add(installs an@ndigo/*capability and runs its setup generator),update(manifest-driven@ndigo/*bumps with--check), andnew --preset(minimal,browser,api). -
@ndigo/cli: the engine-agnosticdbfamily —list,push,pull,studio,seed,rollback, plus--db <name>selection on the existingmigrate/generate/status. Commands front the app’sdb:*scripts or its registered adapter, so the CLI carries no ORM knowledge. -
@ndigo/orm: theNdigoDatabaseAdaptercontract andrunDbCommanddispatcher — the seam@ndigo/clitalks to, with engine packages providing adapters (@ndigo/orm-drizzleshipscreateDrizzleAdapter). -
@ndigo/generators:entity(real@entity/@columntemplate),controller,service,middleware, andconfiggenerators, plusredis/jobs/socket/cachesetup generators invoked byndigo add. Theappgenerator gains--presetand anapipreset that scaffolds a sample entity and a database adapter registry. -
@ndigo/openapi:writeOpenAPISpec— generate anOpenAPIDocumentand write it to a file (json;yamlreserved in the signature). -
@ndigo/ai-sdk:provider.chatModelmodel naming plusprovider/rolloutsetup; the same integration is reachable through@ndigo/ai(agent) and@ndigo/mcp(tool registry). -
@ndigo/boot:application.jsondecode-time file context, rejected absolute URLs, and safer JSON path segmentation. -
@ndigo/cli:doctorandinfosurface their failing checks as issue-report starter text. -
@ndigo/ui: hardennd-a2ui-surfaceDOM projections (tabindexandaria-live) and support multiple surfaces in one element. -
@ndigo/mcp:mcpvalidation and generated tool naming checks for the@toolMappingsurface. -
@ndigo/mcp:createUiResource’s body renderer,scriptTyperuntime check, and CSP-domain validation. -
@ndigo/cluster:waitForExitnow resolves for a child that has already exited, and maps signal termination to a defined exit code — removes an intermittent supervisor shutdown hang. -
@ndigo/auth-client(issue #61): the OIDC refresh-token grant —OIDCAdapter.refresh(refreshToken)performs thegrant_type=refresh_tokenexchange (discovery-resolved, client-secret auth for confidential clients) and verifies the response’s ID token through the full path with no nonce and subject stability against the token’s binding. Refresh tokens are captured at code exchange and bound to the verified subject in arefreshStore(default in-memory,pendingStore-style instance affinity); only captured tokens are redeemable — access/ID tokens are rejected before any network call — and rotation consumes the presented token single-use. Provider rejections (400 invalid_grant) surface asinvalid_token.exchangeCodeForTokensnow returnsOIDCResult(theAuthResultplusrefreshToken), the adapter gains an optionalpromptauthorization parameter (several providers requireprompt: 'consent'before grantingoffline_access), andAuthClientcaptures rotated refresh tokens from any refresh-capable adapter and presents them onclient.refresh(adapterId). The conformance leg runs against a live provider:18-ndigo-auth-playgroundmode 6 captures the embeddedoidc-provider’s refresh token server-side and redeems it viaGET /oidc/refresh, rotation included. -
@ndigo/auth-core:AuthResultgains an optionalrefreshTokenseat for rotation-capable adapters. -
@ndigo/flags-openfeature(AD-87 Phase 2): the browser entry (@ndigo/flags-openfeature/web) —WebOpenFeatureFlagEvaluatorover@openfeature/web-sdk(static-context snapshot semantics, the same reason/error mapping,nullnarrowing, and never-throw boundary as the server evaluator) — andFeatureFlagTelemetryHook: onefeature_flag.evaluationspan per evaluation with the OTel feature-flag semconv attributes (feature_flag.key,feature_flag.provider.name,feature_flag.result.reason,feature_flag.result.variant,error.typeon abnormal outcomes), usable on both SDKs and inert until telemetry providers are wired. Guide 28 gains the browser recipe and provider notes (flagd/OFREP). -
@ndigo/platform-node(AD-87 companion): the flag targeting-context seam — theFLAG_CONTEXT_RESOLVERbean (FlagEvaluationContextResolver; defaultSessionPrincipalFlagContextResolver: the session-bag principal’s id as the targeting key with its roles as attributes, falling back to the AD-37 client address;ClientAddressFlagContextResolverships for address-only targeting), and late-bound gate dependencies: theFeatureGateInterceptornow resolvesFLAG_EVALUATOR/FLAG_CONTEXT_RESOLVERfrom the application context per check, so app-level bean overrides (the vendor-bridge recipe included) reach@whenFeatureroutes instead of pinning the auto-configuration defaults. -
@ndigo/flags(AD-87): the evaluation vocabulary widens to the full OpenFeature surface — reasonsCACHED/UNKNOWN/STALE, error codesTARGETING_KEY_MISSING/INVALID_CONTEXT, and an optionalflagMetadataonFlagEvaluation(DISABLEDis documented as a successful evaluation). -
@ndigo/flags-openfeature(AD-87): the OpenFeature bridge — aFlagEvaluatorover@openfeature/server-sdkclients (global, domain-scoped, or explicit), never-throw at the boundary, providerflagMetadatapassed through; vendor flagging (flagd, OFREP, LaunchDarkly, …) plugs in behind the existing SPI, so@whenFeaturegates and app code stay unchanged. -
@ndigo/generators(AD-85): theappgenerator — scaffolds a demo application underapps/NN-ndigo-<slug>from the01-ndigo-hello(browser) or02-ndigo-hello-node(node) shape, allocating the reading-order number and dev port automatically (the port scan covers the ladder’s documented conventions; explicit--number/--portfail loud on collisions). The generator factories now load through Node’s native TypeScript stripping — Nx-loaded generator files import their own modules with.tsspecifiers, rewritten on emit byrewriteRelativeImportExtensions. -
@ndigo/cli(AD-85):ndigo new <name>(--kind,--number,--port,--description,--dry-run) fronts the app generator through the workspace’s Nx bridge;ndigo generate appfails with a pointer at it (one canonical path per artifact). -
@ndigo/generators(drive-by, 2026-11-01): thepackagegenerator’s manifest now shipsfiles: ["api", "lib"]— the docs standard the manifest gate enforces. -
@ndigo/flags(AD-82): feature flags — theFlagEvaluatorSPI (typed, async, never-throw: abnormal evaluation returns the supplied default with areason+errorCode), the zero-configPropertyFlagProvider(flagname→feature.<name>properties, read-through soenvironment.refresh()flips flags live; percentage rollouts viafeature.rollout.<flag>∈ [0, 1] bucketed per targeting key —SPLITreason), the@whenFeatureclass/method gate metadata (flag-key validated at decoration time), and theFLAG_EVALUATORbean token. -
@ndigo/platform-node(AD-82): theFeatureGateInterceptor— default-on in the node mapping, evaluates@whenFeaturegates per request and denies an off flag with a 404 problem document (urn:ndigo:not-found; the@FeatureGate/DisabledFeaturesHandleranalog); the defaultFLAG_EVALUATORbean reads the application environment. -
apps/04-ndigo-api(AD-82): the gated/gated-reportdemo route (@whenFeature('gated.report'),feature.gated.reportinconfig/application.json). -
@ndigo/orm-drizzle(AD-78): migration files —loadMigrations(dir)(one versioned module per file,db/migrations/<NNNN>-<slug>.tsby convention),nextMigrationVersion(dir), andmigrateOnBoot({dataSource, dir})— apply pending journaled migrations at boot, failing closed. The AD-30 runner contract is unchanged. -
@ndigo/orm-drizzle(AD-78): schema generation —projectEntities,diffProjections,renderMigrationFile, and the committedschema.jsonsnapshot (renderSnapshot/readSnapshot,predicate: 1). Generation is a diff against the committed snapshot; constraints the metadata cannot express are reported, never emitted silently. -
ndigo db new|generate|status(AD-78): scaffold the next versioned migration, generate one from entity metadata (runs the app’sscripts.db:generate), and report the journal (runs the app’sscripts.db:status). -
apps/04-ndigo-api: the migration reference consumer —db:migrate/db:generate/db:statusscripts, committed baseline migration +db/schema.json,migrations.{runOnBoot,dir}configuration withmigrateOnBootin the start path, and config-file loading (config/application.json) as the dotted-key carrier. -
Guide 27 — ORM & Migrations (the journaled lifecycle, the snapshot freshness gate, and the fidelity ceiling).
-
@ndigo/rate-limit(AD-79): theRateLimiterSPI as a leaf package — asyncconsume(AD-39),RateLimitPolicy/RateLimitResult,validateRateLimitPolicy,likeRateLimiter,FixedWindowRateLimiter(moved from@ndigo/platform-node), and theRATE_LIMITERcontext token. The platform interceptor awaits, the bean registers under the token, and apps importRateLimitPolicyfrom the new package (clean cutover — no re-exports). -
@ndigo/redis/rate-limit(AD-79):RedisRateLimiter— the shared-store fixed-window driver, one Lua script per consume (INCR, PEXPIRE on the first request of a window, PTTL as theRetry-Afterbasis), keysndigo:rl:<namespace>:<windowMs>:<key>, windows anchored at first request; budgets survive restarts and N processes share one budget (fail-closed by default;onError: 'fail-open'logs through@ndigo/logand allows). -
@ndigo/platform-node(AD-80/AD-81, 2026-10-09): opt-in TLS origin with native HTTP/2 —tls?: TLSServerOptionsonHTTPServerOptionsand the adapter (key/cert/cainline-or-path,minVersion?,ciphers?,clientAuth?for mutual TLS,maxConcurrentStreams?to bound h2 streams per connection), plus the all-or-nothingNDIGO_TLS_KEY/NDIGO_TLS_CERT+ optionalNDIGO_TLS_CAenv pair (default adapter only; blank values count as unset). TLS material switches the listener to anhttp2.createSecureServerinallowHTTP1mode — ALPN negotiates h2 with an HTTP/1.1 fallback, and WebSockets keep riding h1.1 (no RFC 8441 inws/browsers). Plaintext, edge-terminated stays the default posture (AD-80); a half-configured env pair throws rather than silently serving plaintext. -
@ndigo/platform-node(AD-90): therunNodeApplication(sources, options)lifecycle preset — composesNodeAutoConfigurationandNodeEnvironmentwith config files (new default readernodeConfigFileReader), theconfigureBuilder/beforeStarthooks, extra teardownresources, and theSIGINT/SIGTERMwiring, then starts the managedHTTP_SERVERand returns{ context, server, port, close }. Every implicit action has afalseswitch;ApplicationBuilderandgracefulShutdownstay public and unchanged. -
@ndigo/platform-node(AD-90):HTTPServer.portreports the bound port once started — the kernel-assigned port whenport: 0was requested — and reverts to the configured port onclose(). -
@ndigo/platform-web(AD-90): thecreateWebApplication(sources, options)lifecycle preset — composesWebAutoConfigurationandBrowserEnvironmentwith theDOMElementViewTarget/BrowserDispatcher/HashRouterwiring and the first navigation, registersVIEW_TARGET/DISPATCHER/ROUTER, and exposesroot/eventMap/interceptors/initialPath/autoNavigate/beforeNavigate/platform/profiles/propertiesoptions;destroy()is idempotent. The manualMetadataConfigApplicationContextpath stays public and unchanged. -
create-ndigo-app(AD-92): an unscopednpm create ndigo-appbin that scaffolds a standalone pnpm project (node or browser kind) using the AD-90 presets (runNodeApplication/createWebApplication), with--localdev mode against the workspace, a copied decorator-lowering plugin, and a smoke + bundle guard in the generated app. Ships at 1.0.
Changed
Section titled “Changed”-
@ndigo/auth-core: thePrincipalcontract gainsscopes,hasScope, andhasAnyScope. Custom principal implementations must add them (return[]/falsewhen the identity has no OAuth scopes); the framework’sDefaultPrincipaltakes scopes as its fourth constructor argument. -
@ndigo/core: the benchmark estate’s LRU row exposed a structural flaw in the Map-insertion-order cache (O(n) eviction once delete tombstones pile up at the table front); main’s #382 adopted a doubly-linked-list LRU in response. Measured against the estate’s pre-382 baseline:get55.78 → 33.85 ns (−39%) andset25.29 → 7.70 ns (−70%), both RME < 1% (BENCHMARKS.md). -
@ndigo/dispatcher:RadixMetadataHandlerMapping._getHandlercandidate selection is a single allocation-free pass (wasfilter()+reduce()per request, identical winner semantics). Same-host bench: −8% static, −8.5% single-param, −2.6% deep, RME < 0.4% (BENCHMARKS.md). -
@ndigo/router/@ndigo/dispatcher: the resolved pattern is a normal optionalRoute.patternproperty assigned directly, instead of a non-enumerableReflect.definePropertythat pushed route objects into V8 dictionary mode. Same-host bench: static 306 → 197 ns (−36%), fresh route 373 → 202 ns (−46%), deep −26%, RME < 0.4% (BENCHMARKS.md). -
@ndigo/dispatcher:RouteHandlerMethod.invokecaches the handler’s route mapping per instance and fast-paths zero-parameter handlers before the metadata read. Same-host bench: zero-param 99 → 3 ns, one path 173 → 57 ns, three mixed 329 → 220 ns, RME < 0.3% (BENCHMARKS.md). -
@ndigo/dispatcher:validatedBymemoizes the normalized failure for the three empty-body shapes per schema. Nullish bodies fail from the template before parsing; keyless plain objects are classified after a failed parse, so valid bodies pay nothing. Same-host bench:{}−7 to −10%,undefined−12 to −14%, valid unchanged (BENCHMARKS.md). -
@ndigo/dispatcher: validation failures carry one stack instead of two (AD-103): theZodValidationErrorcause is built withError.stackTraceLimitscoped to zero and restored infinally, leaving the thrownParameterConversionError’s stack as the only one. Same-host bench: invalid 10.95 → ~7.36 µs (−33%), empty{}−33 to −36%,undefined−38 to −39%, valid unchanged; rejection/valid ratio ~166× → ~110× (BENCHMARKS.md). -
@ndigo/factory: instance values are tracked in a name→valueMapas well as the own property, sogetValuereads a shape-independent Map entry instead of a megamorphic dictionary property. Same-host bench: bean lookups 19.6 → 8.6–10.1 ns (−49 to −56%), dynamic 1/1,000 values −32 to −36%,hasValuehit 2.4 → 3.5–3.6 ns (BENCHMARKS.md). -
@ndigo/factory: a sentinel marks values registered asundefined, sogetValuemisses take oneMap.getinstead of two probes, andhasInstancereads a companionSetagain. Same-host bench:getValuemiss 6.30–6.36 → 4.55–4.57 ns (−28%),hasValuepresent 2.59 → 2.43 ns (BENCHMARKS.md). -
@ndigo/orm:schemaForEntity/insertSchemaForEntitymemoize the derived Zod schema per entity class (module-levelWeakMaps) instead of rebuilding it on everypersist; the returned schema is shared and documented as immutable. Same-host bench: insert 15.16 µs → 735 ns (20.6×), update 14.64 µs → 441 ns (33.2×) (BENCHMARKS.md). -
@ndigo/orm: entity metadata is derived once per class (module-levelWeakMap) and lifecycle hooks are iterated instead of filtered per entity. TheSymbol.metadatapolyfill read behind eachgetEntityMetadatacall dominated scans. Same-host bench:findAll10k rows 1.80 ms → 0.45 ms (−75%),find367 → 119 ns,persist(new)728 → 309 ns,remove747 → 304 ns (BENCHMARKS.md). -
apps/07-ndigo-island: migrated from Web Awesome to the ndigo-ui registry items — the form now uses native<input>/<button>with copy-in CSS and no component-library dependency (AD-95). -
@ndigo/cli(AD-75): the CLI underpinnings moved from@oclif/corev5 to@stricli/core— commands arebuildCommanddeclarations, nesteddbroutes usebuildRouteMap, and the bin reads the same Stricli application therunCliembedding entry exposes. Exit-code contract unchanged (0ok /2usage /1runtime). -
create-ndigo-appis unchanged: it uses a hand-rolled argument parser (no OCLIF), so the migration did not apply to it. -
@ndigo/generators(AD-93): the repo-only Nx generator collection moved out ofpackages/totools/generators/(private: true) — it hard-codes this repository’s layout and is never published.packages/now contains consumer packages only;ndigo new/ndigo generateare unchanged. -
The project is renamed: BlueJS → ndigo (issue #78, AD-88): packages publish under
@ndigo/*, the CLI isndigo, problem-type URIs areurn:ndigo:*, env vars areNDIGO_*, Redis keys arendigo:*, session cookies arendigo-session/__Host-ndigo-session, and the durable tables arendigo_sessions/ndigo_jobs/ndigo_queue_messages/_ndigo_migrations. The repository becomesgithub.com/ndigo/ndigo. Dated historical records keep the old name pending the tracked scrub pass. -
@ndigo/oxlint-plugin+ lint rules (AD-86): repository-only rules moved out of the package into the internal plugin at rootsrc/oxlint/, loading directly from TypeScript (rule IDsinternal/*, e.g.internal/no-internal-file-exports). The package now ships the consumer-facing rules only;pnpm test:internalruns the internal specs. -
@ndigo/oxlint-plugin(AD-86, #117):no-raw-consoleandno-browser-globals-in-isomorphicreturned to the internal plugin (internal/*) — both key off this repository’spackages/ndigo-*layout, so they are inert or misleading in consumer projects. The package ships its six layout-independent rules until the two are de-hardcoded for app repos. -
@ndigo/host(AD-18): dropped theprivate: truestaging flag — the package is publishable and the AD-86packages/surface guard now carries no exceptions. -
ndigo db generate(2026-10-08 review, REV-002/REV-006): fails loud instead of emitting what the operator has not reviewed — a delta with destructive drops (dropped columns/tables) or changes the projection cannot express (type/nullability/PK) exits non-zero with the offending statements on stderr until acknowledged viaNDIGO_DB_ACCEPT_DROPS=true/NDIGO_DB_ACCEPT_UNSUPPORTED=true. Previously the unsupported subset printed a warning, exited 0 and advanced the snapshot anyway, so the CI freshness gate could not see the DB/entity divergence. -
/metrics(2026-10-08 review, REV-003): the endpoint no longer dead-ends when no telemetry pull reader is configured —ManagementAutoConfigurationregisters its feature aggregator as the fallback snapshot source, so feature counters (counted/FeatureMetricAggregator) are served in the Prometheus text + JSON views out of the box; a configured telemetry reader still wins. -
@ndigo/telemetry(2026-10-08 review, REV-004): the Prometheus text renderer keeps the first snapshot when two instrument names sanitize to the same exposition name (hyphen vs underscore class) and sanitizes attribute keys, so a scrape document can no longer carry malformed or duplicate metric names. -
@ndigo/scheduler(croner 10 → 11): DST handling is realigned with Vixie cron. A fixed-time job whose wall time falls in the spring-forward gap now runs at the first valid time after the gap (0 30 2 * * *inEurope/Parisfires 03:00, not 03:30), a job scoped to the skipped hour does not fire that day, and an interval job runs through the repeated fall-back hour on both of its occurrences instead of jumping over it by the gap’s length. Patterns using?in the day-of-month or day-of-week field now behave as the documented wildcard alias (0 0 ? * MONran daily on 10.x, now runs on Mondays). No API change:@scheduledspecs, expressions and time zones are untouched. -
@ndigo/content-i18next(i18next 24 → 26): the adapter initializes withinitAsync: false(the deprecatedinitImmediatealias it used was removed in i18next 26) and language resolution follows the 25.x matcher — a region-qualified code such asfr-CAstill resolves to the base language, andchangeLanguagerequests no longer race. Behavior is unchanged for the adapter’s surface; consumers that keep their owni18nextcopy alongside this package now need ≥26 for a single instance type. -
@ndigo/orm-drizzle(@libsql/client0.17.4 → 0.18.0): the libSQL local driver now pools connections, so the transaction boundary is a real interactive transaction (client.transaction('write'),BEGIN IMMEDIATE) bound to the caller’s async context instead of rawBEGIN/COMMITon a single connection. Statements insiderun(fn)— the Drizzle database, raw client calls, durable stores handeddataSource.client— join it as before; a statement from another async context now runs on its own connection instead of being silently enrolled in (and rolled back with) someone else’s transaction, and the two cases that cannot be served this way fail loud:TRANSACTION_ACTIVEwhile an open transaction holds an in-memory database’s only connection,SQLITE_BUSYon a concurrent write to a file database.TransactionManager.run(fn),@transactionaland the nested/concurrent rejection are unchanged. -
API reference regrouped by source file (
pnpm doc:api, guides 12 and STYLE_GUIDE §5): each package’s generated reference is now one module per source file instead of a flat list of kinds.api/README.mdlists the files, each file page lists its own symbols, and symbol pages are namespaced by file (api/<file>/classes/<Symbol>.md)._*.tsinternal files stay out; the@moduleheader tag is retired (TypeDoc derives module names from paths — a package-wide tag collapsed multi-entry packages into duplicate modules) and the header generator, lint rule and 1,489 file headers were updated with it. Cross-file{@link}targets must now name their module ({@link <module>.<Symbol> | Symbol}); the doc-coverage gate was repaired (it had been checking the wrong reflection kinds and could never fire) and the 62 public symbols it flagged are documented. -
apps/02-ndigo-hello-node,apps/05-ndigo-api,@ndigo/generators(AD-90): the Node entrypoints adopt therunNodeApplicationpreset — no moreprocess.env['PORT']mutation or hand-written start/shutdown assembly, and05-ndigo-apidrops its hand-written config-file reader. Generated node apps infer the dev port from{ port: … }and no longer declare@ndigo/bootdirectly (the platform carries it). -
apps/08-ndigo-views,apps/19-ndigo-hn,@ndigo/generators(AD-90): the browser entrypoints adopt thecreateWebApplicationpreset — no more hand-wired dispatcher/router/view-target assembly or duplicated hash-path helper. Generated browser apps ship<name>-configuration.ts(replacing<name>-context.ts) and no longer declare@ndigo/routerdirectly.
ndigo doctor(2026-11-02 review follow-up): the Node floor was pinned at 22 while rootenginesrequires>=24underengine-strict— the diagnostic users attach to issue reports pronounced an unusable runtime healthy. The floor is 24 now, a parity spec fails the lane if it drifts fromengines.nodeagain, andCONTRIBUTING.mdstates the same floor.apps/04-ndigo-api(2026-10-08 review, REV-001):migrations.runOnBootbinds with strict boolean parsing — the string"false"/"0"(env/CLI sources) now stays false;z.coerce.boolean()appliedBoolean("false")→trueand silently enabled migration-on-boot. A misspelled flag now fails boot instead of flipping the default.apps/04-ndigo-api(AD-78 remainder): the Drizzle controller opens the file-backed SQL store lazily insideensureInit()— afterensureDbDir()— instead of at construction, so the lane boots on a fresh checkout (no pre-existingdata/directory; libSQL CANTOPEN class) and tests can pinNDIGO_DB_URLbefore any request touches the store, as documented.ndigo(AD-75 review, discovered by AD-78): space-form nested commands (ndigo db migrate,ndigo db new …) printed topic help instead of running — the ocliftopicSeparatorwas:so onlyndigo db:migrateworked.topicSeparatoris now a space; colon form keeps working.ndigo(AD-75, discovered by AD-78): one-shot commands (db:*,inspect, run-through scripts) received onlyPATHin their environment — config knobs likeNDIGO_DB_URL, OTel endpoints and secret keys never reached the app script. The invoking environment now flows through (shell-free signal relays unchanged).@ndigo/boot(AD-90 D-5): the profile resolver no longer readsprocess.envdirectly —NDIGO_PROFILES_ACTIVE,NDIGO_PROFILES_DEFAULT, andNDIGO_PROFILES_GROUPSresolve throughgetEnv, so booting throughApplicationBuilder/Applicationworks in browsers and workers whereprocessis absent instead of failing startup withprocess is not defined; profile precedence is unchanged.
@ndigo/cluster(AD-74 review): the crash-loop guard now tears down every sibling slot — a tripped cluster settles withreason: 'crash-loop'instead of hanging with live children; a spawn failure aborts fail-closed (no orphaned children); the worst sibling exit code survives a trip (floor 1); and astop()after anabort()is a no-op.ndigo serve(AD-74/75 review):--portis now forwarded as the cluster’sbasePort, so--cluster N --port Pgives every slot a distinctPORT = P + slotas documented — slots no longer collide on one port.ndigo generate(AD-75 review): forwards each generator’s declared flags (--package,--file,--force,--className,--summary,--author,--description,--module,--path,--jobs) —componentandheaderare runnable from the CLI.ndigo doctor(AD-75 review): reports the workspace root by walking up topnpm-workspace.yaml(not thenode_modulesdirpnpm rootanswers), performs real environment checks, and exits1when one fails.ndigo(AD-75 review): one-shot commands install their signal relays before spawning (a signal in the spawn window is never dropped), and the shell-operator guard also rejects$()/backtick scripts.@ndigo/jobs/@ndigo/redis/jobs(AD-72 review): the Redis driver’sheartbeatandretryare single-Lua-script atomic units — a stale heartbeat can no longer re-lease a job a successor reclaimed, and a retry can no longer resurrect a completed (terminal) job; the claim script sweeps companion hashes of phantom records (no unbounded growth); the runner awaits its last in-flight heartbeat before the terminal transition; enqueue now fails loud onundefinedpayload members; the LibSQL job store gained a(state, lease_expires_at)reclaim index.@ndigo/redis/locks(AD-73 review):close()now releases the driver’s own leases (token-checked, in-process parity), and only a successful acquire advances the fencing-token counter (consecutive tokens on both drivers).
-
@ndigo/telemetry-node+@ndigo/management(AD-77): Prometheus metrics —metricExporter: 'prometheus'(metric signal only) registers a pullPrometheusMetricReaderover the SDKMeterProvider; the management/metricsendpoint serves the same snapshot two ways — the Prometheus text exposition document by default (the scraper contract), and a JSON projection onAccept: application/json.HTTPServerInstrumentationrecords the semconvhttp.server.request.durationhistogram (seconds, method + status attributes) for every request in every export mode. The/metrics{ features }aggregator document is replaced by the meter-derived view (breaking change, documented in guide 26 §2). New snapshot model + renderers in@ndigo/telemetry; the telemetry lab gainsNDIGO_TELEMETRY_METRIC_EXPORTER=prometheus. -
@ndigo/management(AD-76): actuator parity —/loggers(level index, per-logger reads, and livePOSTlevel changes backed by a per-name registry in@ndigo/log),/env(property sources with origins, redacted values by default,showValuesopt-in that never serves decrypted plaintext,?toMatch=filtering),/configprops(declared@configurationPropertieskeys and types only — never values), and health groups + single-component reads under/health/<name>. The new endpoints are opt-in viamanagement.endpoints.exposure.include(a real config-file read path — the HI-5 annotation flips for themanagement.*read keys), with the AD-36 access-guard model extended to every management path. New guide 26. -
ndigo generate package --jobs— wire the@ndigo/jobsdependency into a new package at generation time. -
@ndigo/jobs(AD-72): durable job queues and the transactional outbox — theJobDriverSPI (lease-based at-least-once claims, attempts incremented at claim, expired leases reclaimed by the nextclaim, terminalfailwith outcome telemetry/logs), theJobQueueproducer facade, theJobRunnerengine (concurrency slots, abort-on-timeout, lease heartbeat, exponential backoff, onejobs.job.<type>span and@ndigo/logoutcome per execution),@jobHandlerdiscovery mirroring@scheduled(registry scans at refresh, phase 0), and the outbox (OutboxStore,OutboxRelay,JobOutboxPublisher). Backends:InProcessJobDriver(the non-durable default),LibSQLJobStore+LibSQLOutboxStorein@ndigo/orm-drizzle(the outbox store borrows the data-source client, soappend()inside@transactionalcommits or rolls back with the business data), andRedisJobDriverin the new@ndigo/redis/jobssubpath (atomic claim via one Lua script).NodeAutoConfigurationprovidesjobDriver,jobQueue,jobHandlerRegistry, andjobRunner; the runner starts explicitly — an unstarted runner arms no timer (worker processes callstart(); see guide 22). -
@ndigo/inspect— programmatic inspection manifest (inspectApplication): a versioned, deterministic JSON view of routes, dispatcher mappings (pattern, HTTP methods/socket, handler class + method, parameters) and declared config keys (type, origin, presence — values are never read) for tooling, CI and docs. Seams:MappingRegistry.entries()/getMappingEntries()in@ndigo/dispatcher, anddeclaredConfigurationProperties()in@ndigo/boot;apps/ndigo-apigains aninspectscript (AD-70). -
Config JSON Schema (
@ndigo/boot/schema/application.schema.json) forconfig/application.jsonand profile variants, including the{cipher:…}at-rest secret marker, wired into.vscode/settings.json; the environment guide documents the$schemareference (AD-70). -
@ndigo/oxlint-plugin:no-unsafe-view-name-literal(the view-name traversal guard) andno-duplicate-mapping-path(method-level duplicate registrations), both enabled as errors; the duplicate rule is off in specs, where duplicate-registration fixtures are deliberate (AD-70). -
Component integration contract (AD-69): islands can paint server content before any script runs —
IslandView({ shadowHtml })emits<template shadowrootmode="open">,hydrateIslandsadopts the browser-parsed shadow root and upgrades the element in place, andAbstractWebComponent.ensureShadowRoot()accepts a pre-existing root — plusAsFormAssociated(Base)in@ndigo/view-webcomponentfor form-associated custom elements whose values submit as standard form data through the existing binding/Zod path. No UI library is bundled; the decision record defines the adoption checklist and the no-wrappers discipline. -
Component library selection (AD-71): Web Awesome is the adopted library for the single reference integration — a dependency of
apps/ndigo-islandonly (framework packages stay library-free). The island’s add-item form uses its form-associated controls end-to-end: nativeFormData→application/x-www-form-urlencoded→POST /items→dataBinderValue+ Zod → SSE push. Theming is the library’s token stylesheet plus--wa-*overrides; first paint stays the DSD fallback pattern. Guide 21 documents the workflow, and the AD-69 vetting checklist carries recorded results (Spectrum and Material Web ship no form-associated controls; FAST has no maintained component set). -
@ndigo/log— dependency-light logging core (Logger contract,getLogger/LoggerFactory, console + OpenTelemetry appenders, severity mappings) extracted so framework internals can log without the telemetry stack (AD-67). -
delay(ms)— shared async delay helper in@ndigo/util, re-exported from@ndigo/test(replaces copy-pasted test helpers across packages) (HG-6). -
Environment accessors
getEnv(name, fallback?)/isDevMode()in@ndigo/util, re-exported from@ndigo/context— centralized env reads for framework and app code (replaces scatteredprocess.envlookups). -
@ndigo/dispatcher:registerArgumentResolvers()andpassthroughArgumentValue()exports (consolidated platform resolver utilities). -
TOON support (AD-66):
MediaTypes.TEXT_TOONmedia token and opt-inapplication.toonconfig files —StandardConfigFileLoaderprobes JSON first, then TOON (format: 'auto' | 'json' | 'toon'); the@toon-format/toondecoder is an optional peer, loaded lazily only when a.toonfile is actually found. A restrictedformatalso applies toNDIGO_CONFIG_IMPORTpaths (extension mismatch errors), and JSON-looking.toondocuments error with a rename hint instead of decoding to garbage. JSON stays the default everywhere. -
Framework core: application contexts, DI/factory with scopes and entry processors, profiles and typed configuration binding (
@configurationProperties), placeholder resolution, CLI args, live refresh and at-rest secret encryption (AD-11..17, AD-29). -
HTTP client: interceptor pipeline (retry, cache, error normalization, status, app-reset, trace headers, timeouts) and the composable body transform pipeline with subpath exports (AD-01, AD-47, AD-49).
-
Dispatcher/routing: radix metadata handler mapping, per-source argument resolvers (query/path/header/body/cookie/session), whole-source data binding (
dataBinderValue),@requestPartmultipart JSON, matrix parameter parity (AD-26, AD-27). -
Validation: Zod as the single runtime-schema primitive —
validatedBy/validatedBody/validatedQuery, entity-derived schemas (insertSchemaForEntity), structured validation issues (AD-07). -
Node platform:
HTTPServer+NodeDispatcher, view targets, static files (ETag/conditional GET, range, precompressed variants, SPA fallback), multipart streaming uploads, security headers, rate limiting, trusted-proxy client IP (AD-05, AD-25, AD-26, AD-28, AD-33, AD-37, AD-60). -
Auth & sessions: credentials auth with password encoders, OIDC authorization-code + PKCE, JWTs, role guards, HttpOnly cookie sessions, durable LibSQL sessions (AD-33, AD-40, AD-56).
-
Persistence: decorator entity ORM with Drizzle adapter, relation fetch, journaled migrations, durable queue message store; opt-in Redis shared cache and live cross-node WebSocket/STOMP fan-out (AD-30, AD-31, AD-40).
-
Caching: awaitable
Cache/CacheManager(@ndigo/cache),@cacheddecorator,memoize, Redis backend (AD-24, AD-39, AD-40). -
Scheduling:
@scheduleddecorator with single-flight engine (AD-20); distributed scheduling explicitly deferred (AD-41). -
Content/i18n:
MessageSourcecontract, i18next adapter, Intl formatters, locale negotiation (AD-19). -
WebSockets & STOMP: session registry with rooms/heartbeat, isomorphic client with reconnect, STOMP 1.2 broker/client (AD-21..23).
-
Server push: SSE-first island reactivity (AD-09).
-
Signals:
@ndigo/signalsreactive state on@preact/signals-core(AD-59). -
Micro-frontends: host runtime, feature contexts with event-gateway isolation, route partitioning, shared-dependency contract (AD-18, AD-54, AD-62).
-
Resilience:
@ndigo/proxymethod interception with retry + circuit breaker (@ndigo/resilience) (AD-42, AD-43). -
Observability: OpenTelemetry tracing/metrics/logging with Node and web instrumentations (AD-32).
-
API docs: OpenAPI 3.0.3 generation from dispatch metadata + Swagger UI, DTO/schema-target conversion (AD-35, AD-38).
-
Tooling:
@ndigo/testintegration harness (bootApp/MockHTTP), oxlint plugin architectural rules (AD-45, AD-46, AD-63). -
Example applications
08–19:08-ndigo-httpbin(HTTP semantics playground),09-ndigo-todobackend(Todo-Backend REST spec + CORS/HEAD/ OPTIONS),10-ndigo-conduit-api(RealWorld API, JWT + spec-exact error envelope),11-ndigo-conduit-web(RealWorld frontend),12-ndigo-petclinic(server-rendered forms, i18n, JSON negotiation),13-ndigo-chat(rooms, presence, history; STOMP interop with@stomp/stompjs),14-ndigo-kanban(versioned cards, 409 conflicts, optimistic UI),15-ndigo-resilience-lab(@retry/@circuitBreakerbreaker cycle),16-ndigo-redis-fanout(cross-node cache + socket fan-out),17-ndigo-telemetry-lab(browser→server trace continuation, sampling, OTLP),18-ndigo-auth-playground(cookie/JWT/ role/session-durability matrix + embedded OIDC PKCE issuer),19-ndigo-hn(cached/offline HN reader) — each with tests and a boot-smoke probe (docs/plans/2026-10-07_EXAMPLE_APPLICATIONS.md). -
Root
pnpm apps:smoke— boots every Node example app on a dedicated probe port, asserts its documented route (plus the hello-node WebSocket echo), requires a clean SIGTERM exit and a released port; wired into the CI quality lane. -
@ndigo/locks(AD-73): distributed locks with fencing tokens — theLockDriverSPI (non-blockingacquirewith lease TTLs, monotonic per-key tokens where only the current token canrenew/release/isHeld), the process-wideInProcessLockDriver(single-node truth — every driver in the process coordinates, zero config), and theLOCK_DRIVERcontext token. -
@ndigo/redis/locks(AD-73):RedisLockDriver— LuaSET NX PXacquire + compare-and-delete release, one namespace per environment, borrowed connection. Cross-process single-executor for multi-node deployments. -
@ndigo/cli(AD-75): thendigobinary —serve(with the AD-74--cluster <n>surface and--portderivation),dev,generate(fronting@ndigo/generators),db:migrate,inspect(the AD-70 entry), anddoctor(environment diagnostics). Built on@oclif/core^5 as an embedded library; scripts run shell-free so SIGTERM/SIGINT reach the actual application process. -
@ndigo/cluster(AD-74): the process supervisor —ClusterRunnerspawns N replicated worker slots (NDIGO_CLUSTER_SLOT/SIZE,PORT = base + slotderivation), relaysSIGINT/SIGTERMinto each child’s own graceful shutdown (shared deadline,SIGKILLstragglers), respawns crashed slots with exponential backoff, trips fail-closed pastmaxRestartsPerWindowper slot, and aborts on a second signal;runCluster()runs one cluster to completion. Thenode:child_processspawner andprocesssignal host are injectable seams for embeddings and tests. -
@scheduled({ lock })(AD-73): one node executes each fire of a locked schedule — the runner acquires before the run and releases in thefinally(contended fires skip with a logged warning;lockWait/lockWaitStep/lockTtltune the waiting, polling, and lease duration).lockDriverbean wired throughNodeAutoConfiguration(in-process default, Redis override). -
Spec-typecheck lane (TST-A):
pnpm typechecknow runstsc -p tsconfig.spec.jsonafter the per-project build checks, typechecking every package/appsrcandtestfile (strict, vitest globals, livesrc/resolution mirroringvitest.aliases.ts). Specs that never typechecked before are now green and gated red on an intentional error. -
pnpm test:tooling: durablenode --testlane (SEC-B/WC-I) pinning the decorator-lowering plugin’s extension scope (.ctsincluded), vendored-vs- workspace resolution, and the@-pre-check — the /tmp probes now live in CI. -
@ndigo/telemetry-web(TEL-08):countSocketClientBytes()wires exact outbound-wire-byte telemetry through the socket client’sonSendseam;@ndigo/socketexposesSocketClient.onSendWiredso the instrumentation’ssendwrap skips its own payload estimation for seamed clients — bytes count exactly once, per write (buffered flushes and retried writes included).
Changed
Section titled “Changed”-
@ndigo/inspectconfig introspection (REU-9):declaredConfigurationPropertiesandrelaxedKeyCandidatesmoved to@ndigo/context(re-exported from@ndigo/bootunchanged) — the tooling package no longer pulls the boot layer into its runtime closure. -
OXC tooling updated to the 2026-10-05 releases:
oxlintand@oxlint/plugins^1.87.0,oxfmt^0.72.0(oxlint-tsgolintwas already current). oxfmt 0.72’s native Markdown formatter (upstream breaking change) reformatted 31 docs — wrapped-prose indentation only. The tools AD-58 left unadopted were re-assessed at oxc 0.153: standard-decorator lowering is still absent (andlegacymode mis-invokes TC39 decorators), sotsc -band the esbuild decorator pre-transform stand; oxc-parser is mature but unnecessary, oxc-minify/oxc-resolver have no consumer (docs/audits/2026-10-07_OXC_UPDATE_AND_MATURITY_ASSESSMENT.md). -
Example-app directories are numbered in reading order (
apps/01-ndigo-hello…apps/07-ndigo-views, new apps08–19); package names stay unnumbered (@ndigo/hello…) except the two unscoped demos, now@ndigo/islandand@ndigo/views.ndigo-todo’s Vite dev port moves 3000 → 3003 andndigo-hello-node’s default 3000 → 3010; the port registry lives inapps/README.md. Lockfile importers, CI artifact paths, lint ignores and all doc links were swept with the rename. -
Example-app practice sweep (guides + AD-67): every app imports
getLoggerfrom the@ndigo/logleaf instead of@ndigo/telemetry, and the twelve apps that only log dropped their@ndigo/telemetrydependency;03-ndigo-todobootsMetadataConfigApplicationContextinstead of the Node-orientedApplicationBuilder(−98 kB in the client bundle; guide 01’s minimal-app snippet now matches02-ndigo-hello-nodeagain);01-ndigo-hello’s smoke spec uses the@ndigo/testbrowser harness (docs/plans/2026-10-07_EXAMPLE_APPLICATIONS.md§12). -
The
internal/requires-doc-headeroxlint rule now rejects stray* * @…header lines; a repo-wide sweep repaired 115 files (the artifact rendered as a literal*in generated module docs). -
Framework diagnostics (
@ndigo/factory,@ndigo/platform-node,@ndigo/test) now import the logger from@ndigo/log;@ndigo/telemetryre-exports the logging surface, and the@ndigo/telemetry/loggersubpath is gone — importgetLoggerfrom@ndigo/log(or the@ndigo/telemetryroot). -
OpenTelemetry
ContextInstrumentation/DispatcherInstrumentationare single implementations in@ndigo/telemetry(the node/web packages re-export them; the core dispatcher instrumentation gains the_resolveViewNamespan). The forked copies and the unused nodeCountedInstrumentationduplicate are removed. -
Decorators migrated from legacy experimental to TC39 Stage 3 standard (AD-44).
-
Conditional auto-configuration decorators renamed
@conditionalOn*→@when*:@whenBrowser,@whenNode,@whenProperty,@whenProfile,@whenMissingEntry; options interface nowWhenPropertyOptions. The runtimeSymbol.metadatakeys follow the rename (boot:whenPlatform,boot:whenProperty,boot:whenProfile,boot:whenMissingEntry) — code reading the oldboot:conditionalOn*keys must be updated. -
Cache API became fully awaitable, including
size()(AD-39). -
@ndigo/reactivemodernized into@ndigo/signals(AD-59). -
Toolchain migrated from ESLint/Prettier to oxlint/oxfmt;
tsc -bbuild retained (AD-58). -
Single-
Applicationboot model; JSON-config loading and conditional browser/node boots (AD-03, AD-05).
Removed
Section titled “Removed”@ndigo/telemetry’s./loggersubpath — use@ndigo/log(AD-67).- Legacy validation:
@ndigo/validateconstraint system and@ndigo/model/objectmodel, replaced by Zod (AD-07). @ndigo/domainMVVM layer, archived (AD-02 supersession).@ndigo/parser(dissolved into@ndigo/core) and the Ramda/Arcsecond dependencies (AD-57).SiteBuilderstub and the legacy Site/Page model remnants (AD-03).
-
@ndigo/generators:packageemits the current package tsconfigs (strict +@ndigo/*build paths; the stalebaseUrlmade every generated package failtsc -bunder TypeScript 7) and registers the new alias in the sharedvitest.aliases.tstable instead of the obsoletevitest.config.tsinsertion (a generated package previously missed manifest-gate rule 11). -
@ndigo/auth-coreno longer exports the Node-onlyScryptPasswordEncoderfrom its package root: the module importsnode:cryptoat evaluation time, and@ndigo/auth-clientre-exported the whole root — so any browser app importing the auth client dragged a Node built-in into its module graph and crashed undervite dev(the production build masked it by compilingprocess.envreads away). The encoder now lives at@ndigo/auth-core/scrypt; the root stays isomorphic. Consumers updated (apps/04,apps/10,apps/18, the auth-server and orm-drizzle specs, guides 11 and 16, the package README) — verified by loadingndigo-todo’s dev server in a real browser (zero console errors, app renders). -
ndigo-hello-node’s/echoWebSocket handler now declares its session argument (@parameters({ session: socketSessionValue('session') })) — the previous form ran the handler withundefinedand every connection closed with 1011 before echoing; locked withapps/02-ndigo-hello-node/test/hello-socket.spec.ts. -
App default ports are seeded into
process.env.PORTbefore boot in02-ndigo-hello-node,08-ndigo-httpbinand13-ndigo-chat: the managedHTTPServerreadsprocess.env.PORT(falling back to 3000), so the documented defaults were logged but not bound. -
App client bundles are valid JavaScript again: Vite 8’s oxc transform passes TC39 decorators through unchanged (oxc 0.151 offers no lowering option), so decorator-using bundles —
@service/@getCallinndigo-island,@signal/@effectinndigo-todo— shipped as syntax errors while Vitest suites stayed green (Vitest transforms through esbuild). A sharedscripts/vite-plugin-lower-decorators.mjsesbuild pre-transform lowers them for app builds; the built bundles are syntax-checked in CI-adjacent tests. -
ndigo-todorenders its starter items on every todo route (/,/active,/completed,/index): the root/route models previously omitteditems, so a fresh load or deep link showed an empty list. The Playwright e2e lane passes end-to-end again (build order, bundle syntax, and seeding were all broken). -
JWT verification now requires the
expandsubclaims (exp-less tokens are rejected instead of authenticating indefinitely; a missing/emptysubno longer maps to the shared'unknown'id), with an optionalclockToleranceSeconds; HMAC configs now reject secrets shorter than 32 bytes at construction (SE-1, SE-2). -
Refresh-scoped beans with
destroy()/close()now tear down when the refresh scope clears on environment refresh (config:refresh): the container registers the destruction callback at creation time, exactly once per instance (AD-29). -
Conditional sources are now gated consistently: an inactive
@whenBrowser/@whenNode/@whenProperty/…@configurationPropertiesclass no longer binds, no longer rebinds on refresh, and no longer contributes@environmentschemas (CF-1). -
Boot bind errors no longer interpolate the property value, so a decrypted secret can no longer leak into the failure message (CF-2).
-
@ndigo/testMockHTTPfinalizes interceptor denials exactly like the live Node adapter (401/403problem documents,302redirects), so guards are testable without writing the response themselves (IN-1). -
@ndigo/utiltoRegExptreats plain patterns that merely contain flag letters (my-pattern,greedy) as literals instead of throwing aSyntaxError(IN-2). -
CacheInterceptorno longer stores or serves responses whose credentials were attached by a request-time interceptor — the cache gates on the request that was actually sent (HT-1). -
HashRouterpublic navigation now mirrors the route intolocation.hash(suppressing the self-causedhashchangeecho) and restores it on rejected navigations (PL-1). -
Multipart
fileThreshold: 0now really forces every part to disk, and negative/non-finiteMultipartLimitsthrow a descriptiveRangeErrorinstead of silently falling back to defaults (PL-2). -
FetchCredentialsInterceptorclones the outgoing request, preserving signal/mode/redirect/cache/integrity and no longer throwing on bodied requests in Node (PL-3). -
Cookies.getJSON/Cookies.getreturnundefinedfor a missing key instead of throwing (PL-4). -
Drizzle DDL derives SQLite storage types from the declared column metadata (varchar/boolean/json → TEXT, real/float → REAL, blob → BLOB) and string primary keys round-trip (
NUMERICaffinity for caller-assigned keys; only generated INTEGER keys auto-increment) — matchingdrizzle-zod’s mapping (DA-1). -
@countedcounters resolve lazily on first call, so instrumentations constructed before the global meter provider no longer bind to the NoopMeter and drop datapoints (OP-1). -
The scheduler’s
maxRunTimecap timer is cleared when a run settles, so a completed run no longer pins the event loop for the rest of the cap (OP-2). -
WebSocketClient.sendcopies binary payloads (Buffer/subarray views) to the exact byte window instead of shipping the whole ArrayBuffer pool slab (PR-1). -
OpenAPI documents convert Zod schemas with the
openapi-3.0target (nullable/enum/itemsinstead of draft-2020-12typearrays,const,prefixItems) (PR-2). -
/healthand/metricsresponses now carry the default security-header set (SecurityHeadersInterceptorprepended to the management mapping, matching the API-docs mapping) (SE-4). -
Boot: nested
@defaultValueconverts like top-level defaults (numbers and booleans bind typed), and a failure after context creation closes the context instead of leaking listeners/beans (CF-4, CF-6). -
@ndigo/factory:init()failures now propagate asFrameworkErrorinstead of warning and serving the half-initialized instance (CF-5). -
HTTP client:
TimeoutInterceptorreleases its timer when a request settles and composes a caller-suppliedAbortSignalwith its own (caller cancels propagate as cancels; only real timeouts yield 408) (HT-2, HT-5);TransformInterceptorleaves bodyless/empty bodies untouched (HEAD/204, empty POSTs) and routespreRequestfailures through the documented error/completion hooks (HT-3). -
Dispatcher: base-class-declared
@routeMapping/@errorMappingmembers are discovered on subclasses (no more 404s), and an unmapped handler error is rethrown instead of being replaced byTypeError('view name is undefined')(HT-4, HT-6). -
BrowserRouterruns the full preHandle → postHandle → afterCompletion chain with mapped-interceptor param binding; the offline retry replays the original request (method/headers/body) instead of a bare GET (PL-5, PL-7); the node adapter’s socket-probe memo no longer retains an entry per distinct parameterized path (PL-8). -
Web components: delegated events keep working after disconnect → reconnect (PL-6).
-
ORM: Drizzle reads decode declared
boolean/jsoncolumns (load → modify → save round-trips), and in-memory transactions snapshot/restore deep-cloned stores so rollback reverts in-place mutations (DA-2, DA-3). -
Telemetry: router spans cover the async navigation and flag failures; inbound HTTP spans carry
http.response.status_codeand ERROR on 5xx; a configuredlogExporterswitchesLoggerFactoryto the OpenTelemetry appender;fatal/criticalmap to FATAL severities; instrumentations get a base-class enable latch (no double-wrap on re-enable);sdk.shutdown()is documented and wired into the ndigo-api graceful shutdown (OP-3, OP-5, OP-6, OP-8, OP-9, OP-10)./metricsreturns duration summaries again (OP-4). -
Resilience: invalid circuit-breaker specs fail loud at weave/invoke time instead of silently inverting the policy (OP-7).
-
Signals: subclass reactivation limitation documented; the activation error names both possible causes (IN-3).
-
Demos: ndigo-todo’s signals counter renders; ndigo-host nav uses real hash links; ndigo-views
/reportloads from a production bundle; ndigo-island’s documenteddevflow works (AP-1..AP-4). -
Tooling:
@ndigo/generatorsgainsbuild:lib; nx legacy project blocks removed andbuild:todomvcrewired; scaffold manifests pass the docs gate; generated headers use@module ndigo-<name>; the dead JSDoc migration script is gone (TO-2..TO-7, HG-5). -
STOMP/SSE/service:
maxUnacked: 0accepts the spec-mandated id-only ACK as a receipted no-op; duplicate caller receipt ids reject instead of stranding a send; in-memory queue capacity no longer shrinks to the byte budget; SSE parsers stop emitting phantommessageevents forretry:/id-only blocks; the service proxy delegatesObject.prototypemembers; stream spans flag request-level failures (PR-3..PR-8, PR-10). -
OpenAPI: recursive schemas hoist into
components.schemaswith resolvable pointers, and path template variables without a declared extractor get synthesized parameters (PR-9, PR-11). -
Auth client: OIDC pending state is a pluggable store — a callback can be completed by another adapter instance (or process) sharing it (SE-5).
-
Reuse:
getErrorMessageis promoted to@ndigo/utiland used at all inline sites (HG-3). -
Platform/dispatcher: streamed renders honor response backpressure; formatting services fail loud without a locale;
HTTPResponseViewTargetdelegates entity serialization (problem+json parity);OfflineInterceptorgainsdispose(); content negotiation threads the request per call instead of a process global; empty-value and repeated-key route conditions behave as documented; mapping-name lookups are live; duplicate feature prefixes and sparse parameter maps fail loud (PL-9..PL-12, HT-7..HT-11, A-4). -
Test harness: MockHTTP mirrors query fidelity (repeated params, null-prototype data), socket-only 404s, OPTIONS dispatch, and 405 + Allow (IN-4, IN-5).
-
Context/factory:
@propertybinds without@propertySourceand follows environment changes; exported contracts documented; dead code removed;getInstancereturns registered non-constructor values as-is (CF-9..CF-12). -
Data: set-based
count/existsById/deleteById; Redis fanout reports the most recent error (decode errors never masked); one sharedresolveCacheName;@cachedtolerates a detached receiver (DA-7..DA-9, DA-11). -
Tooling/hygiene: root
lintbuilds the oxlint plugin first; benchmarks resolve hashed bundles and reap their preview server; dead root devDeps andvite.config.lib.tsremoved; a roottypecheckscript covers every package and app (CI widened); the docs-link gate now checks package/app READMEs;.DS_Storeuntracked; audit moderates cleared (TO-8..TO-13, HG-12, SE-9). Every package README now meets the enforced section standard (docs gate widened with it). -
Docs: plans/migrations indexes completed; CSP docs aligned with SEC-4; root README inventories refreshed; new Guide 17 (static files) and Guide 18 (signals & islands); removed-tooling references swept (DO-3..DO-9).
-
Demos: app READMEs corrected; unused app deps pruned; dead auth helpers removed; ndigo-api route list + matrix tests (AP-5..AP-9).
Security
Section titled “Security”@ndigo/uiA2UI surfaces sanitize rendered attributes/props.nd-a2ui-surfacewrites to the DOM only through an allow-list of keys; dangerous attributes, event handlers, and any prop that escapes the registry surface are dropped. Focus is restored to the focused element when the surface re-renders, and the surface container carriesrole="region"+aria-live="polite"by default.@ndigo/mcpMCP Apps hardening: the default body is escaped (display: trustedMarkup: truerenders author-owned markup), the renderer validates the barrier URI,scriptTypematches a narrow set, and CSP domains are validated before the document is written.@ndigo/bootfromConfig hardens against SSRF and unsafe dynamic imports: relative helpers avoidimport()on remote specifiers, absolute URLs must behttps, andsourceCatalogis a required allowlist of configuration factories.@ndigo/cliremote UI documentation: local path sources have a local registry root; remote content must include registries and_remoteis only for fetched chunks with a 10-s timeout and a cap on response size.@ndigo/cli:ndigo aimirrors the A2UI component map generation for both the registry and the lab, so the lab’s copy-innd-a2ui-surfacecannot drift from the registry.@ndigo/aiagent correctness: intermediate text accumulates in a single-run result, unknown finish reasons map to a known value, and the lab agent refresh config when the application context refreshes.@ndigo/dispatcherprevents tool names with odd casing from entering the MCP tool namespace; generated names must follow the same snake-case rule as declared names.- JWT
validateaccepts only access tokens: framework refresh tokens (token_use: 'refresh', 7-day TTL) can no longer authenticate an API request; a present claim that is not'access'fails closed (SE-7). - ndigo-todo’s
todo-item.checkedgetter now reflects the value assigned to the property (it previously read a host attribute nothing ever set) (AP-11). - Dependency override
source-map-js >= 1.2.2(GHSA-68fv-2mgg-jv7q):pnpm audit --prodreports zero known vulnerabilities again. - The CSRF double-submit cookie is hardened to the
__Host-prefix with a spoofed-cookie rejection spec (SE-8). - Remote config sources gain a fetch timeout (10 s) and a response-size cap (1 MiB) with the existing keep-last-good policy (SE-10).
- Cookie attribute writing truncates values at the first
;and rejects;/CR/LF in attribute names (SE-11). - Security headers default-on in
NodeAutoConfiguration(CSP, HSTS, X-Frame-Options, nosniff, …) (AD-25). - Rate limiting with fixed-window limiter and real client IP behind trusted proxies (AD-33, AD-37).
- At-rest secret encryption via
{cipher:...}markers, AES-256-GCM (AD-29).