11 KiB
Active framework — ecosystem audit (arts / libs / svrs)
Scope: src/arts/, src/libs/, src/svrs/. The src/web/ folder is
explicitly excluded. Findings are grouped by audit axis. Severity tags:
- bug — incorrect runtime behaviour
- violation — breaks an established framework rule
- drift — code or docs out of sync with the rest
- opportunity — refactor / clean-up that is not a bug today
Each item includes file paths and line ranges so the fix can be applied without re-searching. Items are ordered roughly by priority within each section.
1. Layer-boundary violations
Rule: arts/<X> may import from $libs/* and from its own internal
files; cross-artifact imports ($<other-artifact>) are reserved for the
composition root arts/aapp. libs/* may not import from any
$<artifact> alias. svrs/* is similarly restricted.
Real coupling (runtime, not test) — violation
src/arts/fend/active-frontend.svelte.ts:2—import { createActiveDom, type ActiveDom } from '$adom';arts/fendreaches intoarts/adom. The DOM port should live inlibs/domandfendshould accept an interface, leaving the DOM engine wiring toaapp.src/arts/sium/engine-resolver.ts:1-2—import { ID_FALLBACK_SEPARATOR, parseLangRef } from '$lang';arts/siumimports runtime symbols fromarts/lang. These two values are pure helpers and belong inlibs/langsosiumcan consume them without crossing artifacts.src/arts/conn/engine-connections.ts:1—import { createEngineTimers } from '$timr';arts/connconstructs its own timer engine instead of accepting aTimerSchedulerthrough options. The pattern elsewhere (sess, cach, perm) is "App injects Timers"; conn should follow it.
Type-only cross-artifact imports — opportunity
These compile away but still couple the two artifacts at the type
level. They should be promoted to libs/<X>/types.ts so consumers
import the contract without naming the engine.
src/arts/auth/client.ts:2—import type { EngineHttp, HttpBodyInit, HttpResult } from '$http';src/arts/sess/types.ts:33—import type { SyncStorageAdapter } from '$stor';src/arts/sium/engine-resolver.ts:1—import type { EngineLang, LangParams, SupportedLocale } from '$lang';src/arts/conn/types.tsand related —import type { TimerScheduler, ... } from '$timr';
svrs/* and aapp — clean
src/svrs/* does not import any $<artifact> alias. arts/aapp is
the composition root and is allowed to import every engine; the
imports there are intentional.
2. Naming convention drift
Rule: every module-event / method / diagnostic value must be a
module-scoped lowercase string ('sess.lifecycle.adopted',
'cach.delete', 'conn.connected', 'buss.event.published'). Bare
names ('delete', 'hit', 'auth_failed') collide across artifacts
once aggregated in logs or wired through the bus.
Unscoped diagnostic event values — violation
src/arts/conn/consts.ts:4-20—CONNECTION_DIAGNOSTIC_EVENTShas 15 bare values:'auth_failed','browser_reconnect','connect_failed','frame_decode_failed','frame_encode_failed','heartbeat_timeout','listener_threw','reauth_failed','reconnect_exhausted','send_failed','session_changed','session_expired','session_refreshed','session_revoked','transport_error'. All should carry the'conn.'prefix.src/arts/sess/consts.ts:4-20—SESSION_DIAGNOSTIC_EVENTShas 15 bare values:'adopt_server_invalid','disposed_access','listener_threw','refresh_*'(5×),'revoke_global_*'(3×),'storage_*'(3×). All need'sess.'.src/arts/perm/consts.ts:41-45—PERMISSION_CLIENT_DIAGNOSTIC_EVENTShas'remote_batch_failed','remote_check_failed','remote_what_failed'. All need'perm.'.src/svrs/perm/consts.ts:3-7—PERMISSION_DIAGNOSTIC_EVENTShas'decision','denied','indeterminate'. All need a'perm.'prefix (or a server-specific scope, e.g.'perm.server.decision').
Unscoped method constants — violation
src/svrs/perm/consts.ts:19-25—PERMISSION_METHOD_*constants hold bare names ('check','can','assert','explain','what','who','filter'). The client-side counterpart inarts/permalready uses'perm.check','perm.can', … — server must align.
Why this matters
Logger.warn(category, message) aggregates across artifacts. With
unscoped diagnostic strings, a value like 'listener_threw' appears
under both category: 'conn' and category: 'sess' and is
indistinguishable in any flat log search.
3. Error-class and dispose-pattern consistency
Hard-coded error name strings — violation
The convention is this.name = <MOD>_ERROR_NAME_* (read from
consts.ts).
src/arts/sium/core/types.ts:302—SiumValidationErrorsetsthis.name = 'SiumValidationError'literally.src/arts/sium/core/types.ts:322—SiumAsyncSchemaErrorsame pattern.
Error classes without matching is*Error guards — violation
The pattern is "one class, one guard". Missing guards make instanceof
checks leak into call sites.
src/arts/conn/errors.ts:49—ConnChannelAlreadyExistsErrornoisConnChannelAlreadyExistsError.src/arts/conn/errors.ts:58—ConnChannelNotFoundErrornoisConnChannelNotFoundError.src/libs/auth/errors.ts:24-66— 10 auth error classes have no guards:AuthAccountNotLinkedError,AuthSessionRevokedError,AuthAssuranceRequiredError,AuthRateLimitedError,AuthTenantBoundaryError,AuthOAuthStateInvalidError,AuthOAuthProviderError,AuthOtpInvalidError,AuthMfaRequiredError,AuthWebAuthnError.
dispose() without idempotency guard — bug
The convention is if (disposed) return; disposed = true; …. Calling
dispose() twice should be a no-op.
src/arts/conn/active-connections.svelte.ts:118— second call would re-traversedetachers(empty by then) and callengine.dispose()again.src/arts/fend/active-frontend.svelte.ts:185— second call would re-invokeownedDom?.dispose()if the inner DOM was created here.
Logger option not defaulting to SILENT_LOGGER — violation
Pattern in the codebase: const logger = options.logger ?? SILENT_LOGGER;
(see arts/buss/engine-bus.ts:54).
src/arts/stor/engine-storage.ts—createStorageDiagnostics(options.logger)passes theundefinedstraight through.src/arts/timr/engine-timers.ts— same issue withcreateTimerDiagnostics.src/arts/http/engine-options.ts— same issue withcreateHttpDiagnostics.
If diagnostics ever calls a method on the logger without guarding for
undefined, these three engines crash when used without a logger
(e.g. in unit tests or smoke harnesses). Verify each diagnostics
constructor; if it already null-checks internally, demote to
opportunity for consistency only.
Generic throw new Error(...) in runtime code — opportunity
Typed error classes carry the discriminating name plus optional
fields (cause, code). Generic throws lose that.
src/arts/lang/engine-lang.ts:124, 133— circular-reference message thrown as plainError.src/libs/perm/runtime.ts:154, 181— throwsError(PERMISSION_ERROR_MSG_FILTER_REQUIRES_ACTOR)where aPermissionInvalidFilterErrorwould fit.
4. Dead code, casts, and any
Codebase is largely clean. Specific findings worth acting on:
src/svrs/auth/adapters/db.ts:40, 64, 72, 79, 111, 134— six repetitions ofas unknown as Readonly<Record<string, unknown>>. Consolidate into aDbRowtype alias or acoerceRow()helper. opportunity.src/libs/buss/silent-bus.ts:137—} as unknown as EngineBus;. Acceptable because the no-op surface is intentionally generic, but worth replacing with proper generic constraints when the object literal grows. opportunity.src/arts/lang/types.ts:83—HasParams<T>usesanydeliberately (contravariant variance). Comment is in place. no action.src/libs/days/_vendor/**— multiple@ts-ignoreandTODOcomments. Vendor code, leave as-is. no action.
No dead exports were found in the spot checks of libs/buss,
libs/auth, arts/conn, libs/aapp/events.ts. No duplicate type
definitions detected across the audited modules.
5. Documentation drift
Only the high-traffic READMEs were sampled.
arts/buss/README.md
- The session-translator example (around line 563) hardcodes
cause: APP_USER_IDENTITY_CAUSE_SESSION_ADOPTEDfor every event. The actual translator (arts/aapp/integrations/session-translator.ts:37) callsresolveAppIdentityCause(payload.event)to map eachEVENT_*to the correct cause. The example should mirror that mapping or it teaches the wrong pattern. drift. - The README was already partially refreshed earlier in this session;
re-check the "Implementation gaps → Done" list now that
subscribe(),publishCausedBy(),invokeListener, the depth guard, the payload-cloneable check, the Svelte adapter, thecreateBusRecentwrapper, thebus-context.svelte.ts, and theAPP_EVENT_RUNTIMEStable all landed.
arts/sess/README.md
- The README still references a
publishSessIdentityChangedhelper. Actual export ispublishSessLifecycleEventinarts/sess/bus-helpers.ts. Either rename in code or update the README. drift.
arts/cach/README.md
- README written in Spanish (a deliberate choice; not flagged as a
rule break). At lines 11–12 it suggests importing
createEngineCachefrom$svrs/cach, but the artifact's barrel (arts/cach/index.ts) only exportscreateActiveCache. Add a one- line note clarifying the engine vs active split. drift.
Other artifacts (aapp, adom, auth, conn, fend, fmts, http, lang, logr, perm, sium, stor, timr)
Spot checks did not surface drift — claims match exports.
Suggested fix order
- Coupling fixes (Section 1, runtime violations). They constrain
every other refactor: until
fend → adom,sium → lang, andconn → timrare decoupled, those artifacts cannot be tested in isolation. - Naming convention pass (Section 2). Mechanical, scoped to four
consts.tsfiles, and unblocks coherent log aggregation. Update any test that hard-codes the literal values. - Error guards + idempotent
dispose(Section 3). Low-risk and localised; prevents subtle bugs if any consumer ever callsdispose()twice or tries to discriminate auth errors. - Logger defaults (Section 3). Verify the three diagnostics
constructors and add
?? SILENT_LOGGERwhere missing. - Documentation refresh (Section 5). Easiest after the code changes above so the README reflects the final shape.
- Cast consolidation (Section 4). Pure refactor, ship it whenever convenient.
Items in Section 4 (vendor code, justified any, intentional cast)
require no action.