33 KiB
arts/session — Design Consultation Document
Historical note 2026-05-14. This consultation predates the directory rename sweep. Current canonical artifact names are
langs,logger,format,storage,session,connection,cache,timerandactive-app. Mentions oflang,logr,fmts,fend,stor,sess,conn,cach,timrandaappbelow are historical context, not current naming doctrine.Purpose. Lay out the design space for
arts/sessionso external reviewers (humans or AI) can evaluate proposed designs independently. The author has already iterated several times and proposed multiple incompatible designs; this document is intentionally neutral about which answer is correct. It should give you everything needed to form an opinion from first principles.Audience. Software architects familiar with TypeScript, SvelteKit and auth/session patterns. Read sections 1–6 for context; sections 7–9 are the actual decisions to evaluate.
1. Project context
What is being built
A SvelteKit application at G:\dev\svelte\active whose runtime
infrastructure lives under src/arts/. Each subdirectory is an independent
"artifact" (zero-deps, runtime-focused, headless-of-UI). Eight artifacts
already exist:
| Artifact | Layer(s) | Purpose |
|---|---|---|
lang |
EngineLangs, ActiveLangs, MonoLangs |
i18n: type-safe translations, BCP 47 resolution |
logr |
EngineLogger |
Structured logger: levels, transports, filters |
fmts |
EngineFormat, ActiveFormat |
Localized formatting: numbers, currency, units, dates |
sium |
EngineSium |
Validation contracts (Standard Schema interop) |
adom |
ActiveDom |
Reactive DOM service |
stor |
EngineStorage, ActiveStorage |
Reactive sync key/value with pluggable adapters |
http |
EngineHttp |
HTTP client with retry, schema validation, hooks |
aapp |
ActiveApp |
Composition root that wires the rest |
A ninth artifact, arts/sess, is being designed: session lifecycle
infrastructure on the client.
Quality bar
The author has stated repeatedly: "professional/impeccable" library quality. Zero deps. Strict types. Comprehensive tests. Production-grade docs. The artifacts will be consumed by many developers.
2. Established patterns from existing artifacts
2.1 Engine* vs Active* convention
Engine*— public methods over private state (or no state at all). Pure factory; locale, logger or any volatile input is passed as argument on every call. Lives in.tsfiles, runes-free, safe to import from+page.server.ts/hooks.server.ts.Active*— anEngine*that exposes public reactive state. Lives in a.svelte.tsfile because it owns$state. Imports must target the file directly, not the barrel, to keep the rest of the artifact runes-free.
2.2 Generic-on-construction patterns
Each artifact handles consumer-defined types differently:
// langs: generic on the schema parameter
createEngineLangs<S extends LangNode>(schema: S): EngineLangs<S>
// Consumer's S extends LangNode flows through.
// stor: generic per entry, NOT per engine
const Storage = createActiveStorage(opts); // engine-level, not generic
const theme = Storage.entry<string>('theme', 'base'); // generic at entry call
// sium: generic at primitive call sites
sium.object({ x: sium.string() }) // schema construction is typed; engine is monomorphic
// http: generic per request via opt-in schema parameter
http.get<S>(url, { schema?: S })
// Returns Out<S> = StandardSchemaV1.InferOutput<S> | unknown
2.3 Composition via createActiveApp
createActiveApp(options) builds the runtime members. Every member is
always present — when not configured, App provides a structurally
identical adapter (mono lang, in-memory storage, etc.):
const App = createActiveApp({
langs: { schema, defaultLocale: 'es' },
logger: { level: LogLevel.INFO, transports: [...] },
storage: { adapter: localAdapter },
http: { baseUrl: 'https://api.example.com' }
});
App.langs.t(...)
App.storage.entry(...)
App.http.get(...)
2.4 Sium-on-App exception
Sium is intentionally not a member of App. Validation is page-scoped — pages with forms construct their own engine via a one-line helper:
// In service schema:
const App = createActiveApp({
services: { sium: defineEngineSium() }
});
// At call site:
const sium = App.sium;
const result = await sium.validate(LoginSchema, input);
The helper takes zero arguments because Lang, Logger and the active
locale all flow from App. From feedback_minimal_apis.md:
When designing helpers on
ActiveAppthat build dependent objects, do NOT accept anoptionsparameter for fields that App already owns.
2.5 Auto-wiring between artifacts
createActiveApp already auto-wires obvious cross-artifact integrations:
- Storage diagnostics → shared
Logger - Lang
setLogger(Logger)(so DEV warnings flow through Logger) - Format
localeSourcederived from Lang - Http
logger→ injected automatically
The pattern: pairs of artifacts that always go together get auto-wired.
2.6 arts/stor.entry<T> precedent (relevant for sess)
arts/stor is the closest existing precedent for "reactive value container
with lifecycle":
interface ActiveStorageEntry<T> {
readonly key: string;
readonly fullKey: string;
/** Reactive — the only public reactive property in the artifact. */
current: T;
set(value: T): void;
update(fn: (prev: T) => T): void;
remove(): void;
onChange(fn): () => void;
dispose(): void;
}
Pattern: opaque T, single reactive property current, plus methods.
Engine* returns entry() instances; Active* adds the reactive current
getter. The engine doesn't know what T is.
3. Memory-encoded constraints
These are explicit rules from .claude/projects/.../memory/*.md. The author
has reaffirmed each multiple times in conversation:
3.1 No domain in arts/
From project_layer_boundaries.md:
arts/*are runtime infrastructure — logging, i18n, formatting, validation contracts, DOM service, preference projection, storage, app composition. Each is independent, runtime-focused, and headless of UI choices.
Things that do not belong in
arts/:
- Forms (state binding to inputs, generated UI)
- Generated components (buttons, inputs, dialogs)
- Stores for domain state (cart, user, session) — Svelte 5 runes in
.svelte.tsmodules where they belong, not underApp.Stores.
Rule: when evaluating a new artifact proposal, ask: "could this run server-side or in a Web Worker?" If no, it belongs above
arts/.
3.2 Minimal API surface
From feedback_minimal_apis.md:
When designing helpers on
ActiveAppthat build dependent objects, do not accept anoptionsparameter for fields that App already owns.
Reasoning: "si están todos en la App, no tiene porque pasar ningún parámetro". Adding overrides duplicates what the App already provides as the single source of truth, invites inconsistency between layers, and bloats the API surface for cases that don't exist in practice.
3.3 Professional design preferences
From feedback_professional_design.md:
Patterns explicitly rejected during prior artifact builds:
- i18n coupled into the logger
minLevelsingle cutoff on transports (replaced bylevelsper-severity)- Runtime arg-shape discrimination (explicit > magical)
anyin public types (useunknown+ type guards or proper unions)- Directory-name prefixes in external identifiers
- 4-level log hierarchy (upgraded to 6 to match industry standard)
3.4 Standard Schema as the validation contract
All artifacts that accept user-defined schemas use the Standard Schema v1
interface ($libs/standard-schema) so consumers can pass Sium / Zod /
Valibot / ArkType interchangeably. No vendor lock-in.
3.5 arts/sess was the topic of prior parking-lot entries
From project_deferred_decisions.md (current draft):
arts/sock(real-time WebSocket / SSE) — parked. Real-time push is a real gap but deliberately deferred until a concrete consumer surfaces.
arts/cache (TanStack-Query-style reactive cache) is also parked — a
reactive cache with dedup/staleTime/gcTime sits above stor and sess.
4. Prior art summary
What other libraries do, briefly. Conclusions are intentionally not drawn — they are inputs for the reviewer.
| Library | Storage | Refresh | User | Perms | Cross-tab | Notable |
|---|---|---|---|---|---|---|
| Lucia v3 | Server DB + cookie | Rolling on validate | userId (FK) |
n/a | implicit | Server-only. Register augmentation for session/user attrs. |
| iron-session | Encrypted cookie | Manual save() |
Opaque <T> |
n/a | implicit | 4 KB hard limit. Password rotation supported. |
| Supabase | Pluggable, default localStorage | Pre-emptive ticker (30s tick, 90s margin) + visibility + 401 | Strongly typed Session.user |
n/a (server RLS) | BroadcastChannel |
_refreshingDeferred dedup. ~3000 LOC monolith. |
| better-auth | DB + signed cookie cache | Rolling on getSession | Inferred from server | First-class plugins | None at lib | Cookie chunking (3896 B). Plugin-driven. |
| Auth.js | DB + JWT | Provider-driven | Module augmentation | None | Cookie-implicit | Provider-coupled (OAuth in scope). |
| Clerk | Opaque session ID | Pre-emptive | First-class | session.has(...) first-class |
BroadcastChannel |
~75 KB. addListener event bus. |
Common patterns observable across all:
- Refresh dedup via shared promise
- Pre-emptive timer + reactive 401
INITIAL_SESSIONsynchronous on subscribe (Supabase pattern)- Some form of cross-tab broadcast
- Visibility-change handling
Common divergences:
- Whether user identity is in the engine or external
- Whether credential shape is named or opaque
- Whether permissions are first-class or external
- Whether the engine assumes JWT or is auth-scheme-agnostic
5. Use cases the final design MUST support
Any proposed design must handle ALL of the following without mental gymnastics. If a use case requires unusual ceremony, the design has a problem.
UC-1: JWT/OAuth SPA
- Login form returns
{user, accessToken, refreshToken, expiresAt}. - Auto-refresh fires before expiry.
- All HTTP requests carry
Authorization: Bearer <accessToken>. - 401 from server → refresh → retry the request.
UC-2: Cookie-based SSR
- Server sets
HttpOnlycookie on login. - Client never sees the credential.
- Browser sends cookie automatically with
credentials: 'include'. - "Logout" hits a server endpoint that clears the cookie.
UC-3: API-key SaaS dashboard
- Long-lived API key stored in localStorage (or generated per-tenant).
- No refresh — keys are revoked, not rotated.
- All requests carry
X-Api-Key: <key>.
UC-4: Anonymous session + later login (e-commerce)
- Visitor lands, server starts an anonymous session with a
cartId. - Visitor adds items to cart without identifying.
- Visitor logs in: same session, now with
userattached, cart preserved.
UC-5: Multi-tab logout
- User has 3 tabs open.
- Tab A clicks "Logout".
- Tabs B and C reflect the logout (UI shows login prompt) without polling.
UC-6: SSR hydration without re-validation
+layout.server.tsreads cookie, validates session against DB, returns{user, expiresAt, ...}to the client viadata.- Client adopts the snapshot. Schema validation runs ON THE SERVER ONLY — client trusts the SSR boundary.
UC-7: Concurrent 401s during refresh
- 5 in-flight requests all return 401 simultaneously.
- ONE refresh fires, the other 4 share its promise.
- After refresh, all 5 retry with the new credential.
UC-8: Refresh failure semantics
- Network glitch (transient) → preserve session, retry later.
- Server says "refresh token invalid" (fatal) → revoke locally, emit
EXPIREDevent, navigate to/login. - Caller must distinguish these without imposing logout-on-flaky-network.
UC-9: Visibility-change refresh
- User closes laptop lid for 90 minutes.
- Opens it. Tab becomes visible.
- Engine checks expiry, refreshes if needed, before any new request goes out.
UC-10: Cross-tab WITHOUT exposing tokens via broadcast
- Tab A refreshes → broadcast fires → tab B re-reads storage and updates local state.
- Broadcast payload contains event + version, NEVER tokens.
- An XSS in tab A cannot exfiltrate tokens through the broadcast channel.
UC-11: Three-state user model (newly raised)
The author has stated the design must distinguish:
- No-identificado (no session OR anonymous session)
- Identificado (session has a user)
- Autorizado (session+user+permission for a specific action)
These are three conceptually distinct states the design must be able to express clearly.
6. Decisions LOCKED IN (not up for debate)
These came out of audit + iteration cycles and are settled. A proposed design is invalid if it violates any of them.
6.1 Refresh contract
type RefreshFn = (current) => Promise<NewSession | null>;
// Resolves to NewSession → engine adopts, fires REFRESHED
// Resolves to null → fatal: engine auto-revokes, fires EXPIRED
// Throws → transient: engine preserves session, fires REFRESH_FAILED
Rationale: distinguishes "credential is dead" from "network is flaky". Mismatch = user logged out by a flaky wifi.
6.2 Generation guard
The engine maintains a generation counter incremented on every
adopt/revoke. refresh() captures the counter at start and discards the
result if the counter changed during the await.
Rationale: prevents stale refresh from resurrecting a revoked session when ticker + visibility + 401 hook + manual revoke + cross-tab all coexist.
6.3 Refresh dedup
Concurrent refresh() calls share a single in-flight promise
(refreshingDeferred slot, Supabase pattern).
Rationale: prevents stampede when N requests fail with 401 simultaneously.
6.4 RevokeResult is tagged
type RevokeResult =
| { localRevoked: true; globalRevoked: true; scope: 'global' }
| { localRevoked: true; globalRevoked: false; scope: 'local';
reason?: 'missing_revoke_url' | 'network_error' | 'server_rejected' };
revoke() is never Promise<void>. Caller must be able to distinguish
"signed out everywhere" from "signed out locally only" — UI copy and
security decisions depend on it.
6.5 INITIAL_SESSION synchronous on subscribe
onChange(listener) invokes the listener synchronously once with
('INITIAL_SESSION', current) so subscribers are the single source of
truth — no race between "read snapshot" and "subscribe".
6.6 Broadcast carries event + generation only
The BroadcastChannel payload is {type, event, generation}. NEVER
includes tokens, user data, or any sensitive fields. Receiving tabs
re-read storage as the source of truth.
6.7 expiresAt as epoch ms internally
Internal storage and broadcast use number (epoch ms). Public APIs
accept Date | number | string and normalize at the boundary.
6.8 adoptServer is a separate method, not a flag
adopt(input): Promise<...> // validates against schema
adoptServer(input): SessionSnapshot // skips validation, normalizes only
Rationale: adopt(snapshot, { trusted: true }) is a footgun (caller might
forget the flag); separate method names enforce the distinction.
6.9 Auto-refresh wrapper opt-in, not default
withAutoRefresh(engine, { tickMs, marginMs, jitterMs, refreshOnVisible });
- Includes
visibilitychangehandling (battery saver + recovery) - Includes jitter to avoid N-tab stampede
- Cleanup function returned
6.10 Standard Schema validation (no vendor lock)
Any user-supplied schema is StandardSchemaV1<unknown, T>. Works with
Sium, Zod, Valibot, ArkType identically. The artifact only depends on the
type interface (~30 LOC in $libs/standard-schema).
6.11 Out of scope
The following are NOT arts/sess responsibilities:
- OAuth flows (Google, GitHub, etc.)
- Password handling / hashing
- MFA / TOTP / WebAuthn
- JWT signature verification
- CSRF protection (delegate to SvelteKit
csrf) - Server-side session storage (DB, Redis) — separate
arts/sess-serverif needed - Acquisition flow (login form, credential exchange) — consumer territory
7. OPEN design questions
Each question lists 2–4 options with concrete pros/cons. The reviewer is expected to form an opinion based on the philosophy in §2–3 and the use cases in §5, not on the author's framing.
Q1: Session payload generic shape
What shape does SessionSnapshot take?
Option A — Single opaque payload generic.
SessionSnapshot<TPayload> = {
payload: TPayload;
expiresAt: number;
issuedAt: number;
}
- (+) Maximum agnosticism; engine knows nothing about user/credential structure
- (+) Mirrors
arts/stor.entry<T>precedent exactly - (–) Loses semantic structure (can't distinguish identity from credential)
- (–)
Sess.current.payload.user.nameis verbose
Option B — User generic + opaque credential.
SessionSnapshot<TUser, TCredential = undefined> = {
user: TUser | null; // null = anonymous
credential?: TCredential; // opaque
expiresAt: number;
issuedAt: number;
}
- (+)
user: TUser | nullcodifies the unidentified/anonymous/identified state machine - (+) Credential stays opaque (auth-scheme agnostic)
- (–) Two generics; potentially over-modeled
- (–) Forces consumer to think about user shape upfront
Option C — Three generics: user + credential + claims.
SessionSnapshot<TUser, TCredential, TClaims> = {
user: TUser | null;
credential?: TCredential;
claims: TClaims;
expiresAt: number;
issuedAt: number;
}
- (+) Mirrors Clerk's session.has({permission}) pattern
- (–)
TClaimsis authorization concern — does it belong in sess? - (–) Three generics is a lot
Option D — Lucia-style fixed shape with module augmentation.
SessionSnapshot = {
id: string;
user: User; // declared via module augmentation
expiresAt: number;
fresh: boolean;
} & SessionAttributes // also from augmentation
- (+) Single shape, consumer extends globally
- (–) Module augmentation is project-global (one shape per app — fine?)
- (–) Doesn't match generic-on-construction patterns elsewhere in
arts/*
Q2: Where does "user" live?
Strongly correlated with Q1.
Option A — In sess as typed field.
Sess.current.useris the consumer's user object.- Lifecycle of "user" tied to lifecycle of session.
Option B — NOT in sess.
- Sess only knows credential + lifecycle.
- Consumer's separate
.svelte.tsmodule loads/caches user from/api/meonADOPTEDevent. - Cleanest separation but no engine-level support for "anonymous session with cart" (UC-4) — consumer fully owns the distinction.
Option C — Both (minimal subject + full profile separately).
- Sess has
subject: string | null(login/email/userId). - Full
Userprofile lives in consumer's user store. - "Identified" means
subject !== null.
Q3: Where does "credential" live?
Option A — Named accessToken/refreshToken fields.
Sess.current.accessToken,Sess.current.refreshToken.- (+) Trivial bearer header construction; first-class refresh contract
- (–) Couples to JWT/OAuth; cookie-only and API-key apps don't fit
Option B — Opaque credential: TCredential generic.
- Consumer types it as
{accessToken, refreshToken}for JWT or{apiKey}for API-key auth orundefinedfor cookie auth. - (+) Auth-scheme agnostic
- (–) Engine can't auto-construct headers; consumer composes
Option C — Not in sess at all.
- Cookie-only model: server handles credential, client never sees it.
- For JWT/API-key, consumer stores credential in their own module.
- (–) Loses centralized lifecycle (refresh rotates the credential — needs a writable target)
Q4: Perms/roles in sess?
Option A — Sess.has({permission, role}) first-class.
- Typed via
claimsSchemaif provided. - (+) Convenient call site
- (–) Authorization is per-action runtime decision, not session state
- (–) Forces a permission model on consumers (RBAC vs ABAC vs claims-based)
Option B — Not in sess.
- Consumer module exposes
has(permission). - (+) Consumer picks RBAC, ABAC, capability-based, anything
- (–) Every consumer reimplements the wheel
Option C — Future arts/auth or $libs/auth artifact.
- Sess provides primitives; auth artifact provides permission models.
- (+) Composable
- (–) Doesn't exist yet; speculative
Q5: How is the session engine constructed?
Option A — Standalone factory, consumer composes with App.
const Sess = createActiveSession<MyUser>({...});
const App = createActiveApp({
http: { headers: () => bearerHeader(Sess) ?? {} }
});
- (+) Sess fully independent of App; testable in isolation
- (+) Consumer controls construction order
- (–) App can't auto-wire Sess→Http integrations
- (–) Awkward closure pattern when
onRefreshneedsApp.http
Option B — App accepts a pre-built Sess instance.
const Sess = createActiveSession<MyUser>({...});
const App = createActiveApp({ sess: Sess, http: {...} });
// App.session === Sess; App auto-wires 401 hook
- (+) Type flows from Sess to App.session
- (+) App can auto-wire what's auto-wireable
- (–) Two-step setup
- (–) Sess construction can't reference App (circular)
Option C — App constructs Sess from config.
const App = createActiveApp({
sess: { userSchema: MyUserSchema, ... }
});
// App.session = ActiveSession<unknown> (untyped) OR
// ActiveApp<S, TUser> (App is now generic on TUser)
- (+) Single setup step; matches Lang/Storage/Http pattern
- (–) App gains generics for TUser/TCredential — surface bloat
- (–) Or App.session is untyped — caller casts everywhere
Option D — App provides factory; consumer calls it.
const App = createActiveApp({
auth: { // infrastructure config
storage: ..., onRefresh: ..., revokeUrl: ...
}
});
const Sess = App.createActiveSession<MyUser>({ // typed factory
userSchema: MyUserSchema
});
// App.session === Sess (cached); typed at call site
- (+) Mirrors
App.siumprecedent - (+) Infrastructure at App, types at call-site
- (+) Type flows correctly via factory generics
- (–) Single-call required (second call must throw or replace)
- (–) Asymmetry: App constructs Sess but not via App config; uses factory instead
Q6: Where does configuration live?
Option A — All on call-site of createActiveSession.
- userSchema, storage, onRefresh, revokeUrl, autoRefresh — all together
- (+) One place to look
- (–) Mixes infrastructure (storage) with types (userSchema)
Option B — Split: infra on App, types on call-site.
- App config: storage, onRefresh, revokeUrl, autoRefresh
- Call site: userSchema, credentialSchema
- (+) Clean separation
- (–) Two sources of truth to reconcile
Option C — All on App config; factory takes only generics.
- App config: everything including userSchema
- Call site: just
App.createActiveSession<TUser>() - (–) Tight coupling between App and consumer types
Q7: Section name in App config
Option A — auth: { ... }
- Consumer-facing semantic name ("I'm configuring authentication").
Option B — sess: { ... }
- Matches the artifact name.
Option C — Both/different name (e.g., session, identity).
Q8: HTTP coupling
How tightly does the design couple arts/sess to arts/http?
Option A — App auto-wires both headers and beforeError hook.
- Requires sess to expose
bearerHeader()(assumes JWT/Bearer). - (+) Zero-config for the common case
- (–) Wrong for cookie auth, API-key auth, custom schemes
Option B — App auto-wires only beforeError hook (refresh is agnostic).
- Consumer composes headers themselves (3 lines).
- (+) Auth-scheme agnostic
- (–) Consumer must remember to wire bearer if they use it
Option C — No auto-wiring, consumer composes everything.
- (+) Maximum control
- (–) Boilerplate at every consumer
Option D — App auto-wires via consumer-provided header builder.
auth: {
bearerHeader: () => Sess.current?.token // consumer provides
}
// App applies it to App.http.headers
Q9: User profile loading — where?
Option A — In sess (user is part of session snapshot).
- See Q2 Option A.
Option B — Consumer .svelte.ts store (no library helper).
- Consumer subscribes to Sess events, loads user from
/api/me, caches in$state. - ~30 LOC per app.
Option C — $libs/auth helper module.
- Provides
createUserStore({ fetcher, cache }). - (+) Reusable
- (–) Duplicates what
arts/cache(deferred) will provide.
Option D — Defer until arts/cache lands.
- User profile is just a query; let consumers wire it manually until cache exists; then promote the pattern.
Q10: Multi-instance support
Option A — Single Sess per app.
- Second
createActiveSessionthrowsSessionAlreadyCreatedError. - (+) Simple
- (–) No multi-account UX
Option B — Multiple Sess instances allowed.
- Each has its own storage key, refresh, etc.
- App.session is the "active" one; can be switched.
- (–) Complicates auto-wiring (which Sess does http use?)
Option C — Single Sess, but with internal "scopes".
- (–) Speculative; defer.
8. Decision dependencies
How the questions interact:
- Q1 ↔ Q2 ↔ Q3 — payload shape decides where user/credential live.
- Q5 ↔ Q6 — factory location determines where config lives.
- Q4 ↔ Q9 — permissions and user are both auth-layer concerns; if one is in sess, the other probably should be too (or strictly neither).
- Q3 ↔ Q8 — auto-wiring headers is only possible if credential shape is known to the engine.
- Q5 (Option D) ↔ Q6 (Option B) — the factory-on-App pattern naturally splits config: App config for infra, factory call for types.
- Q7 ↔ Q5 — section name should match where the config lives.
9. Cross-AI evaluation framework
When evaluating a proposed design, ask:
- Does it work for ALL 11 use cases (UC-1 to UC-11)? If any case requires unusual ceremony, the design has a problem.
- Does it respect §3 constraints? Specifically: no domain in arts/,
minimal surface, professional preferences (no
any, no magical discrimination, opaque generics). - Does it match existing patterns in §2? Engine/Active split, Standard Schema, opaque generics, auto-wiring conventions, Sium-on-App helper.
- Does it survive
arts/cachelanding later? A design that builds a custom user-store mechanism becomes redundant when cache exists. - Does it work for cookie-only auth (UC-2) where the client has NO credential? Many auth-tutorial designs assume JWT and break here.
- Is the auth-scheme agnosticism real or aspirational? Test by mentally instantiating UC-1, UC-2, UC-3 and seeing whether the API forces different code paths or scales naturally.
- Is the three-state machine (UC-11) expressible without consumer code tracking previous values? The engine should emit the events that distinguish the transitions.
- Can each piece be tested in isolation? Engine without runes, wrappers without engine, etc.
- Is the API surface small? Count public exports. The author has stated a strong preference for minimal API.
10. Prior implementation that was rejected
A first implementation of arts/sess was built and rejected by the author.
What it did:
SessionSnapshot<TUser, TClaims>withuser,claims,accessToken,refreshTokenall as named fields.userSchemaandclaimsSchemarequired at engine construction.bearerHeader()method on the engine.has({permission, role})method on the engine.- Generics flowed through
App<S, TUser, TClaims>. - App auto-wired bearer header into
App.http.
Reasons for rejection (in author's own words):
"no entiendo porque session y autentication estan tan acoplados, eso es una mierda y donde caen todos los sistemas, el diseño es una puta mierda. deberian de estar en dos modulos separados session a nivel de arts a con un la informacion de session que no la veo, el usuario deberia de definirse con un interface minimo, login, name, date start, pero no acoplado a session y el modulo auth a nivel de libreria"
"se debe de exponer un usuario como generico en session pero la auth no le corresponde a session y los permisos tampoco, eso corresponde a auth y el modelo de permisos que se van a propoercionar, al igual que no tiene sentido el acoplamiento al token porque ya has definido que todo va a ser por jwt y si quiero otro sistema de autenticacion y autorizacion ?"
"tampoco has tenido en cuenta el estado de un usuario que puede ser no-identificado | identificado | autorizado"
"la app tiene que propoercionar de crear la session app.createActiveSession(...), la configuracion de auth debera de estar definida explicitamente a nivel de la app"
Distilled feedback (interpreted, may contain bias):
- Session and authentication should be separated.
- User SHOULD be a generic on session, but permissions and authentication should NOT be in session.
- Don't couple to JWT/Bearer (e.g., named
accessTokenfield assumes one auth scheme). - The state machine (unidentified | identified | authorized) must be first-class.
- App should provide the session factory:
App.createActiveSession<TUser>(...). - Auth configuration should be explicit at App level.
These are interpretive — the reviewer should not assume any one implementation matches the author's intent; instead, evaluate which design options in §7 best satisfy these statements simultaneously.
11. What the author is asking for
A design that:
- Resolves the 10 open questions in §7 with explicit reasoning.
- Provides TypeScript signatures for the public API (
SessionSnapshot,EngineSession,ActiveSession, factories, hooks, events). - Shows the App composition shape (config section + how Sess is built).
- Demonstrates how each of the 11 use cases is implemented in 5–10 lines of consumer code.
- Identifies any locked-in decisions in §6 that the design forces a change to (and justifies it).
- Calls out trade-offs honestly — "I picked X over Y because Z, but acknowledge that Y is better for use case Q".
The reviewer should NOT assume any design proposed earlier in the conversation is correct. The author has explicitly stated distrust of the prior design iterations.
Appendix A — Existing artifact code references
Key files for context:
src/arts/active-app/active-app.svelte.ts— composition patternsrc/arts/active-app/types.ts—ActiveAppOptionsshapesrc/arts/storage/active-storage.svelte.ts—entry().currentreactive patternsrc/arts/storage/types.ts—SyncStorageAdapter,withBroadcastinterfacesrc/arts/http/types.ts—HttpResulttagged union, hook lifecyclesrc/arts/http/engine-http.ts—headers: () => ...lazy hook patternsrc/arts/sium/engine-sium.ts—App.siumprecedentsrc/libs/standard-schema.ts— Standard Schema v1 type port
Appendix B — Locked-in pieces of the engine implementation
These are implementation details from the prior iteration that the author considered correct and would carry over regardless of the high-level design:
- Generation guard (counter + capture-then-validate-on-resolve)
refreshingDeferredslot for in-flight dedupapplyExternalChangehelper for cross-tab broadcast receivers (re-reads storage rather than trusting payload)validateAndNormalizehelper that runs schemas + normalizes time fieldscommitAdoption/commitRevocationhelpers that bump generation and dispatch events atomicallywithAutoRefresh(engine, opts)as a separate composable wrapper (not baked into the engine)extractJwtExp(token)as opt-in helper in a separate filereadSessionFromCookies(cookies, opts)as SSR helper
These can be reused regardless of the public API decisions in §7.