@ultimat3/core 9.0.0 → 11.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CLAUDE.md CHANGED
@@ -12,6 +12,7 @@ is a change to every package.
12
12
  | A value a CALLER supplied | `describeValue()` — shape, never content. `renderCauseValue` is safe against throwing, not against leaking |
13
13
  | Reading a caught value | `renderThrowable()` / `isThrownError()` / `stringField()`; never `error.message`, `error instanceof Error` or `typeof error.code === 'string'` directly — the probe throws before the renderer runs |
14
14
  | New code | add to `CORE_CODE_TITLES` in `error-codes.ts`, else the title is auto-humanised |
15
+ | Where an error points | `ERROR_DOCS_URL` — one constant, never a per-code URL. `docs:` is omitted at every construction site and resolved from the registry |
15
16
  | Time | take a `Clock`; `Date.now()` / `new Date()` only inside `clock.ts` |
16
17
  | Context | never thread `ctx` as a parameter — `useContext()` |
17
18
  | A value ambient across an `await` | `asyncContext<T>(subject)` from `async-context.ts`, in **every** package — never `new AsyncLocalStorage` |
@@ -276,10 +277,17 @@ in a different trace, which is worse than no span because it looks authoritative
276
277
  including the three that open no other socket — `queue_depth` belongs to one of them.
277
278
 
278
279
  ```bash
279
- bun test # from packages/core
280
+ bun test packages/core/src # from the REPO ROOT, never from packages/core
280
281
  bun run typecheck
281
282
  ```
282
283
 
284
+ **The root is not a preference.** `bunfig.toml`'s `preload = ["./scripts/test-setup.ts"]` is what
285
+ installs `@ultimat3/testing`'s matchers, and Bun reads `bunfig.toml` from the cwd — so `bun test`
286
+ run inside `packages/core` loads no preload and 17 tests in `secrets.test.ts` die on
287
+ `expect(...).rejects.toBeUltimateError is not a function`, which reads as this package's failure
288
+ and is the shell's. `.github/workflows/ci.yml`'s `package` job spawns `bun test packages/<pkg>`
289
+ with `cwd` at the root for the same reason (`scripts/coverage-gate.ts`).
290
+
283
291
  `markReady()` means **bound**, and readiness means **usable** — two different facts since
284
292
  `registerReadinessCheck(name, check)`. `/readyz` is ready only when the state is `ready` AND every
285
293
  named check passes, and `HealthReport.checks` carries them by name because "alert on check
@@ -348,6 +356,18 @@ the customer's. The non-blank-reason assert is `@ultimat3/entity`'s `crossTenant
348
356
  verbatim — two escapes from the framework's default posture should not look like two things. Do
349
357
  not add a second impersonation path.
350
358
 
359
+ **`ERROR_DOCS_URL` replaced `ERROR_DOCS_BASE` + `errorDocsUrl(code)` `As of 2026-08-23`, and it is
360
+ a breaking change** — it lands in the next major, not in the released line. `https://ultimate.dev/errors/<code>` answered **404**, host included, on every error the
361
+ framework has ever thrown — including the first line a new agent reads (`x --json` →
362
+ `"docs":"https://ultimate.dev/errors/X_CLI_UNKNOWN_COMMAND"`). A dead link in every error is a
363
+ defect under axiom 4, and it is not "not built yet": `wiki/` is the only public documentation
364
+ surface there is. There is no per-code URL because there is no per-code ANCHOR — codes live in
365
+ `wiki/Error-Codes.md` as TABLE ROWS, and a `#X_DB_DRIFT` fragment would be a second dead
366
+ declaration rather than a fix for the first. So the function is gone rather than kept with an
367
+ ignored parameter, and `descriptor()` lost its `code` parameter with it. A package constructing an
368
+ `UltimateError` now OMITS `docs:` entirely and lets the constructor resolve the registered
369
+ descriptor — one URL, one place, instead of the fifteen packages that each spelled the base out.
370
+
351
371
  Every `UltimateError` carries `retry` (`terminal | retryable | retry-after`), **defaulting to
352
372
  `terminal`** — fail closed, because a client retrying on `status >= 500` hammers `X_DB_DRIFT` and
353
373
  `X_TENANCY_UNSCOPED`, which are permanent config faults. `registerErrorRetry()` is the one
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ultimat3/core",
3
- "version": "9.0.0",
3
+ "version": "11.0.0",
4
4
  "description": "Ultimate's foundation: errors, context, env, config, clock, ids, logging, telemetry, lifecycle",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/src/config.ts CHANGED
@@ -15,7 +15,6 @@ import type { OfflineStrategy } from './route-vocabulary';
15
15
  import { isIanaZoneName } from './time-zone-name';
16
16
 
17
17
  export type ThemeMode = 'light' | 'dark' | 'system';
18
- export type RealtimeTier = 'channels' | 'live-queries' | 'local-first';
19
18
  export type RealtimeTransport = 'memory' | 'nats' | 'redis';
20
19
 
21
20
  export interface ThemeConfig {
@@ -119,10 +118,19 @@ export interface JobsConfig {
119
118
  * server config, and the presence beat is DERIVED (`PresenceRegistry.heartbeatMs` is
120
119
  * `max(1000, floor(ttlMs / 3))`). A second knob is a second number that can disagree with the one
121
120
  * it is a fraction of, and a knob nothing reads is a knob nothing enforces — axioms 1 and 3.
121
+ *
122
+ * No `tier` either, and no `RealtimeTier` — deleted 2026-08-23, the thirteenth instance of the
123
+ * same defect and the dangerous shape of it. It accepted
124
+ * `'channels' | 'live-queries' | 'local-first'`, defaulted to `'channels'`, was documented with
125
+ * per-value semantics, was set by both tracked apps — and no file anywhere compared it, branched
126
+ * on it or dereferenced it. `transport` and `urlEnv` are the only two fields of this section any
127
+ * code reads. So `tier: 'local-first'` bought the durable client store that does not exist
128
+ * (`createOpfsLocalStore` still throws `X_NOT_IMPLEMENTED`), exactly as `jobs: { driver: 'redis' }`
129
+ * bought Postgres. Which realtime tier an app is on is decided by what it DECLARES — a `channel()`
130
+ * topic, a `live: true` query, a local store — never by a config key.
122
131
  */
123
132
  export interface RealtimeConfig {
124
133
  readonly enabled: boolean;
125
- readonly tier: RealtimeTier;
126
134
  readonly transport: RealtimeTransport;
127
135
  readonly urlEnv: string | undefined;
128
136
  }
@@ -244,7 +252,7 @@ function defaults(name: string): Omit<AppConfig, 'name'> {
244
252
  backoff: 'exponential',
245
253
  visibilityTimeoutMs: 30_000,
246
254
  },
247
- realtime: { enabled: false, tier: 'channels', transport: 'memory', urlEnv: undefined },
255
+ realtime: { enabled: false, transport: 'memory', urlEnv: undefined },
248
256
  ai: { mcp: { expose: true, path: '/mcp' } },
249
257
  };
250
258
  }
package/src/context.ts CHANGED
@@ -200,7 +200,10 @@ export function withChildContext<T>(patch: CtxPatch, fn: () => T): T {
200
200
  /** Resolve a late-bound service. Throws `X_SERVICE_MISSING` rather than returning undefined. */
201
201
  export function useService<T>(name: string): T {
202
202
  const ctx = useContext();
203
- const service = ctx.services[name];
203
+ // Own keys only, and the SAME read the cause below lists. A raw index walks the prototype, so
204
+ // `useService('constructor')` answered with the `Object` function and the caller's first method
205
+ // call was a bare `TypeError` frames away — which is the failure this function exists to name.
206
+ const service = Object.hasOwn(ctx.services, name) ? ctx.services[name] : undefined;
204
207
  if (service === undefined) {
205
208
  throw new UltimateError({
206
209
  code: 'X_SERVICE_MISSING',
@@ -19,11 +19,14 @@ export interface ErrorCodeEntry extends ErrorCodeDescriptor {
19
19
  readonly code: string;
20
20
  }
21
21
 
22
- export const ERROR_DOCS_BASE = 'https://ultimate.dev/errors/';
23
-
24
- export function errorDocsUrl(code: string): string {
25
- return `${ERROR_DOCS_BASE}${code}`;
26
- }
22
+ /**
23
+ * Where an error sends its reader. One URL for every code, and deliberately not one per code:
24
+ * `wiki/` is the framework's only public documentation surface, codes live there in TABLE ROWS,
25
+ * and a table row has no anchor — so a `#X_DB_DRIFT` fragment would land on the page top while
26
+ * declaring a target that does not exist. The `https://ultimate.dev/errors/<code>` links this
27
+ * shipped until 9.x answered 404, host included, on every error the framework has ever thrown.
28
+ */
29
+ export const ERROR_DOCS_URL = 'https://github.com/developerz-ai/ultimate/wiki/Error-Codes';
27
30
 
28
31
  /** Codes owned by `@ultimat3/core`. Every other package calls `registerErrorCodes()`. */
29
32
  const CORE_CODE_TITLES = {
@@ -75,13 +78,13 @@ const CORE_CODE_TITLES = {
75
78
 
76
79
  export type CoreErrorCode = keyof typeof CORE_CODE_TITLES;
77
80
 
78
- function descriptor(code: string, declaration: ErrorCodeDeclaration): ErrorCodeDescriptor {
79
- return Object.freeze({ title: declaration.title, docs: declaration.docs ?? errorDocsUrl(code) });
81
+ function descriptor(declaration: ErrorCodeDeclaration): ErrorCodeDescriptor {
82
+ return Object.freeze({ title: declaration.title, docs: declaration.docs ?? ERROR_DOCS_URL });
80
83
  }
81
84
 
82
85
  export const CORE_ERROR_CODES: Readonly<Record<CoreErrorCode, ErrorCodeDescriptor>> = Object.freeze(
83
86
  Object.fromEntries(
84
- Object.entries(CORE_CODE_TITLES).map(([code, title]) => [code, descriptor(code, { title })]),
87
+ Object.entries(CORE_CODE_TITLES).map(([code, title]) => [code, descriptor({ title })]),
85
88
  ) as Record<CoreErrorCode, ErrorCodeDescriptor>,
86
89
  );
87
90
 
@@ -105,7 +108,7 @@ export function registerErrorCodes(codes: Readonly<Record<string, ErrorCodeDecla
105
108
  });
106
109
  }
107
110
  for (const [code, declaration] of Object.entries(codes)) {
108
- registry.set(code, descriptor(code, declaration));
111
+ registry.set(code, descriptor(declaration));
109
112
  }
110
113
  }
111
114
 
@@ -117,7 +120,7 @@ function humanize(code: string): string {
117
120
  export function describeErrorCode(code: string): ErrorCodeDescriptor {
118
121
  const known = registry.get(code);
119
122
  if (known !== undefined) return known;
120
- return descriptor(code, { title: humanize(code) });
123
+ return descriptor({ title: humanize(code) });
121
124
  }
122
125
 
123
126
  export function hasErrorCode(code: string): boolean {
@@ -12,9 +12,8 @@ export type {
12
12
  export {
13
13
  CORE_ERROR_CODES,
14
14
  describeErrorCode,
15
- ERROR_DOCS_BASE,
15
+ ERROR_DOCS_URL,
16
16
  errorCodeSnapshot,
17
- errorDocsUrl,
18
17
  hasErrorCode,
19
18
  listErrorCodes,
20
19
  registerErrorCodes,
package/src/index.ts CHANGED
@@ -48,7 +48,6 @@ export type {
48
48
  McpConfig,
49
49
  PwaConfig,
50
50
  RealtimeConfig,
51
- RealtimeTier,
52
51
  RealtimeTransport,
53
52
  ThemeConfig,
54
53
  ThemeMode,
@@ -133,10 +132,9 @@ export {
133
132
  describeErrorCode,
134
133
  describeValue,
135
134
  EnvMissingError,
136
- ERROR_DOCS_BASE,
135
+ ERROR_DOCS_URL,
137
136
  ERROR_RETRY_KINDS,
138
137
  errorCodeSnapshot,
139
- errorDocsUrl,
140
138
  errorRetry,
141
139
  formatError,
142
140
  hasErrorCode,
package/src/logger.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  // Single responsibility: structured JSON logging. One line per event, machine-readable by
2
2
  // default because the primary reader is an agent tailing `x logs --json`.
3
3
 
4
+ import { assert } from './assert';
4
5
  import { type Clock, systemClock } from './clock';
5
6
  import { renderCauseValue } from './error-render';
6
7
  import { isUltimateError } from './errors';
@@ -244,8 +245,29 @@ function envLevel(): LogLevel {
244
245
  : 'info';
245
246
  }
246
247
 
248
+ /**
249
+ * The level this logger enforces, refused when it is not one.
250
+ *
251
+ * `LEVEL_WEIGHT[level]` on anything else is `undefined`, and every `weight < undefined` is false —
252
+ * so the threshold failed OPEN and the logger emitted every line at every level. That is a `trace`
253
+ * stream out of a production process from one typo, which is the direction this read must never
254
+ * fail in. `LOG_LEVELS`, the same list `envLevel()` filters `LOG_LEVEL` through, because a level
255
+ * is typed here and arrives untyped: an `app.config.ts` value, a JSON file, a CLI flag.
256
+ */
257
+ function resolveLevel(declared: LogLevel): LogLevel {
258
+ assert(
259
+ (LOG_LEVELS as readonly unknown[]).includes(declared),
260
+ // `renderCauseValue`, never `JSON.stringify`: it raises on a bigint and on a cycle, and a
261
+ // level that arrived from a config file can be either — the refusal must not be replaced by
262
+ // a `TypeError` from building its own message.
263
+ `${renderCauseValue(declared)} is not a log level`,
264
+ `pass one of ${LOG_LEVELS.join(', ')} to createLogger({ level })`,
265
+ );
266
+ return declared;
267
+ }
268
+
247
269
  export function createLogger(options?: LoggerOptions): Logger {
248
- const level = options?.level ?? envLevel();
270
+ const level = options?.level === undefined ? envLevel() : resolveLevel(options.level);
249
271
  const bound = options?.fields ?? {};
250
272
  const clock = options?.clock ?? systemClock;
251
273
  const writer = options?.writer ?? defaultWriter;
package/src/metrics.ts CHANGED
@@ -174,14 +174,24 @@ export function resetMetrics(): void {
174
174
  }
175
175
  }
176
176
 
177
- /** Stable series key: attribute order must not create a second series for one label set. */
177
+ /**
178
+ * Stable series key: attribute order must not create a second series for one label set, and no
179
+ * label set may spell another one's key.
180
+ *
181
+ * `JSON.stringify` over the sorted pairs, because a DELIMITER cannot carry the second property:
182
+ * the key was the pairs joined by control characters (U+0000 inside a pair, U+0001 between them),
183
+ * and a value holding those bytes IS another set's key — `{ a: 'b\u0001c\u0000d' }` was
184
+ * `{ a: 'b', c: 'd' }`, so the point landed on whichever series arrived first and was exported
185
+ * under labels the caller never passed. Attribute values are app data. Quoting is the only total
186
+ * answer and is not slower: 644 ns/op against the join's 709, on a 3-label set. `String(value)`
187
+ * stays, so `1` and `'1'` are still one series rather than two rows an exporter renders alike.
188
+ */
178
189
  function seriesKey(attributes: MetricAttributes): string {
179
190
  const entries = Object.entries(attributes);
180
191
  if (entries.length === 0) return '';
181
- return entries
182
- .sort(([a], [b]) => (a < b ? -1 : 1))
183
- .map(([key, value]) => `${key}\u0000${String(value)}`)
184
- .join('');
192
+ return JSON.stringify(
193
+ entries.sort(([a], [b]) => (a < b ? -1 : 1)).map(([key, value]) => [key, String(value)]),
194
+ );
185
195
  }
186
196
 
187
197
  function finite(name: string, value: number): number {
@@ -195,6 +205,31 @@ function finite(name: string, value: number): number {
195
205
  return value;
196
206
  }
197
207
 
208
+ /**
209
+ * Bounds are strictly ascending finite numbers, refused at DECLARATION like `maxSeries` beside it.
210
+ * `record` takes the first bound an observation fits, and the exposition format emits one
211
+ * cumulative `le` series per bound in array order — so `[1, 0.5, 5]` both counted observations
212
+ * into a bucket that was not theirs and rendered a non-monotonic `le` series that Prometheus and
213
+ * OpenMetrics each reject. Two wrong numbers, neither visible from the other, and nothing at the
214
+ * call site to notice: the observations themselves were all valid.
215
+ */
216
+ function assertBounds(name: string, bounds: readonly number[] | undefined): void {
217
+ if (bounds === undefined) return;
218
+ const bad = bounds.findIndex((bound, index) => {
219
+ const previous = index === 0 ? Number.NEGATIVE_INFINITY : (bounds[index - 1] as number);
220
+ return !Number.isFinite(bound) || bound <= previous;
221
+ });
222
+ if (bad === -1) return;
223
+ const repaired = [...new Set(bounds.filter((bound) => Number.isFinite(bound)))].sort(
224
+ (left, right) => left - right,
225
+ );
226
+ throw new MetricNameInvalidError({
227
+ cause: `${name} declared bounds [${bounds.map((bound) => String(bound)).join(', ')}], which are not strictly ascending finite numbers — [${String(bad)}] is ${String(bounds[bad])}`,
228
+ fix: `sort the bounds and drop the duplicates: histogram('${name}', { bounds: [${repaired.join(', ')}] })`,
229
+ meta: { metric: name, bounds: bounds.map((bound) => String(bound)), at: bad },
230
+ });
231
+ }
232
+
198
233
  function declare(name: string, kind: MetricKind, options: GaugeOptions & HistogramOptions) {
199
234
  if (!METRIC_NAME_RE.test(name)) {
200
235
  throw new MetricNameInvalidError({
@@ -203,6 +238,7 @@ function declare(name: string, kind: MetricKind, options: GaugeOptions & Histogr
203
238
  meta: { name },
204
239
  });
205
240
  }
241
+ assertBounds(name, options.bounds);
206
242
  const existing = instruments.get(name);
207
243
  if (existing !== undefined) {
208
244
  if (existing.descriptor.kind !== kind) {
package/src/telemetry.ts CHANGED
@@ -326,7 +326,13 @@ const TRACEPARENT_RE = /^00-([0-9a-f]{32})-([0-9a-f]{16})-([0-9a-f]{2})$/;
326
326
  */
327
327
  export function traceparent(context: SpanContext): string {
328
328
  const flags = (context.traceFlags & 0xff).toString(16).padStart(2, '0');
329
- return `00-${context.traceId}-${context.spanId}-${flags}`;
329
+ // The empty `spanId` `currentSpanContext()` synthesises is the one value this function cannot
330
+ // interpolate bare: `00-<trace>--01` is 39 characters and `TRACEPARENT_RE` — like every
331
+ // collector — rejects it, so the trace the header exists to continue is lost either way. A
332
+ // freshly minted id is what a propagator with no reported parent sends, and it keeps the trace
333
+ // id joinable. Deliberately not all-zero: `parseTraceparent` refuses that, as the spec requires.
334
+ const parentId = context.spanId === '' ? newSpanId() : context.spanId;
335
+ return `00-${context.traceId}-${parentId}-${flags}`;
330
336
  }
331
337
 
332
338
  export function parseTraceparent(header: string | null | undefined): SpanContext | undefined {
package/src/type-pins.ts CHANGED
@@ -6,7 +6,7 @@
6
6
 
7
7
  import type { Actor, ActorFactMap, FactKeysOf, FactMapOf } from './actor';
8
8
  import type { CacheTierName } from './cache-vocabulary';
9
- import type { AppConfigInput, CacheConfig, DatabaseConfig } from './config';
9
+ import type { AppConfigInput, CacheConfig, DatabaseConfig, RealtimeConfig } from './config';
10
10
  import type { CtxPatch } from './context';
11
11
  import type { HydrateStrategy, OfflineStrategy, RenderMode } from './route-vocabulary';
12
12
 
@@ -119,6 +119,26 @@ type _CacheInputCarriesNoDeadField = Assert<
119
119
  Extract<keyof NonNullable<AppConfigInput['cache']>, DeadCacheField> extends never ? true : false
120
120
  >;
121
121
 
122
+ /**
123
+ * The two `config.realtime` fields deleted for the same rule — `heartbeatMs` (2026-08-19) and
124
+ * `tier` (2026-08-23). `tier` is the worse of the two and the reason this pin exists: it accepted
125
+ * three values with three documented meanings, and `transport`/`urlEnv` are the only fields of
126
+ * this section any code reads, so all three meanings were one behaviour. Re-adding it restores a
127
+ * knob whose `'local-first'` setting promises a durable local store the framework does not build.
128
+ */
129
+ type DeadRealtimeField = 'tier' | 'heartbeatMs';
130
+
131
+ type _RealtimeConfigCarriesNoDeadField = Assert<
132
+ Extract<keyof RealtimeConfig, DeadRealtimeField> extends never ? true : false
133
+ >;
134
+
135
+ /** And the input side with it — `Input<RealtimeConfig>` is what an `app.config.ts` writes. */
136
+ type _RealtimeInputCarriesNoDeadField = Assert<
137
+ Extract<keyof NonNullable<AppConfigInput['realtime']>, DeadRealtimeField> extends never
138
+ ? true
139
+ : false
140
+ >;
141
+
122
142
  /**
123
143
  * Neither id a child context may patch. `withChildContext` forwards the parent's `buildId`
124
144
  * verbatim, so `{ buildId }` on the patch was an option that read as honoured and was dropped