@ultimat3/core 2.0.0 → 4.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 +46 -0
- package/README.md +39 -0
- package/package.json +1 -1
- package/src/actor.ts +27 -1
- package/src/canonical-json.ts +102 -0
- package/src/config.ts +33 -10
- package/src/decimal-order.ts +76 -0
- package/src/env-example.ts +9 -1
- package/src/error-codes.ts +2 -1
- package/src/error-retry.ts +40 -14
- package/src/exports/error-contract.ts +1 -0
- package/src/image/errors.ts +1 -1
- package/src/index.ts +10 -12
- package/src/intl-cache.ts +43 -0
- package/src/lifecycle.ts +65 -24
- package/src/logger.ts +21 -2
- package/src/metrics-types.ts +102 -0
- package/src/metrics.ts +107 -92
- package/src/otlp-metric-exporter.ts +26 -3
- package/src/otlp-span-exporter.ts +28 -3
- package/src/type-pins.ts +10 -3
package/CLAUDE.md
CHANGED
|
@@ -62,6 +62,7 @@ shape against a locally declared sample interface for exactly that reason.
|
|
|
62
62
|
| `.env.example` | `env-example.ts` | a projection of the schema, never hand-maintained |
|
|
63
63
|
| loading `.env` | **Bun**, not us | `envFileCandidates()` documents the measured order; there is no `.env.staging` |
|
|
64
64
|
| a value that must not be printed | `secret.ts` | redacted by VALUE; `revealSecret()` is the one way out, on purpose greppable |
|
|
65
|
+
| an `Intl` formatter cache | `intl-cache.ts` (`cachedFormatter`, `canonicalLocale`, `MAX_CACHED_FORMATTERS`) | a locale and a zone arrive from a request header, so the key must be canonical AND the cache bounded — never a second copy of either half |
|
|
65
66
|
| the committed encrypted values | `secrets.ts` (envelope) + `secrets-store.ts` (files, `installSecrets`) | plaintext is a flat map of ENV NAMES; there is no `secrets.get()` |
|
|
66
67
|
|
|
67
68
|
`installSecrets()` is the ONLY path from `secrets.enc.json` to an app value, and it lands in
|
|
@@ -70,6 +71,16 @@ shape against a locally declared sample interface for exactly that reason.
|
|
|
70
71
|
implementations. The real environment always wins, which is what lets one image run in Compose and
|
|
71
72
|
on K8s off one committed file.
|
|
72
73
|
|
|
74
|
+
`intl-cache.ts` is tier 0 because two tier-1 packages need it and tier 1 may not import sideways.
|
|
75
|
+
It was `@ultimat3/time`'s, internal, until 2.0.0, when `@ultimat3/money`'s `formatMoney` was found
|
|
76
|
+
keyed raw on the caller's locale into an unbounded `Map` — 20,000 valid `en-US-x-*` tags from one
|
|
77
|
+
`Accept-Language` header retained +55.1 MB of RSS (measured `As of 2026-08`). Copying the FIFO into
|
|
78
|
+
`money` would have been a second answer to one question (axiom 1); `money → time` is a sideways
|
|
79
|
+
import `bun run boundaries` refuses. The bound and the canonical key are **two halves of one rule**
|
|
80
|
+
and live in one file for that reason: a canonical key bounds nothing (an unknown `-u-` extension
|
|
81
|
+
value survives canonicalization as a distinct string) and the cap alone lets one locale evict
|
|
82
|
+
itself under three spellings. Never build an `Intl` formatter on a caller string without both.
|
|
83
|
+
|
|
73
84
|
`secrets-errors.ts` registers its seven codes through `registerErrorCodes()` rather than joining
|
|
74
85
|
`CORE_CODE_TITLES` — the codes and the module that throws them ship together, and `registerErrorCodes`
|
|
75
86
|
is the one mechanism that raises `X_ERROR_CODE_DUPLICATE` if anything else claims one. Consequence
|
|
@@ -90,6 +101,32 @@ the pin (`schema-error-codes-pin.test.ts`) lives in `@ultimat3/cli`, which may l
|
|
|
90
101
|
`@ultimat3/storage` both need — core is the lowest tier both can reach, so the shared code lives
|
|
91
102
|
here rather than in either package copying the other's file.
|
|
92
103
|
|
|
104
|
+
`canonical-json.ts` is the same shape for the hash every SHARING key in the framework is taken
|
|
105
|
+
over, `As of 2026-08`. `canonicalJson` is an INJECTIVE canonical form and `fingerprint` is
|
|
106
|
+
SHA-256/16 of it, and three tier-3 packages needed exactly this while none may import another:
|
|
107
|
+
`@ultimat3/action`'s `requestHash` and job dedupe key, `@ultimat3/query`'s `queryHash` (a
|
|
108
|
+
read-cache entry, a cursor scope, a live query id) and `@ultimat3/realtime`'s `qid`. Each kept its
|
|
109
|
+
own copy and the copies had **diverged in a way that leaked**: query's had no `Date` branch, so
|
|
110
|
+
`Object.keys(date)` was `[]`, every date rendered `{}`, and one cache key, one cursor scope and one
|
|
111
|
+
live window answered for every date window of a read — reachable straight off a query string, since
|
|
112
|
+
`coerceQuery` turns a `t.date` member into a real `Date`. Injective is the whole requirement, not a
|
|
113
|
+
formatting preference: every one of those keys decides which of two callers is served the other's
|
|
114
|
+
answer. So `NaN`, `±Infinity` and `-0` are bare tokens the quoting `string` branch cannot spell,
|
|
115
|
+
and a `Date`, a `Map` and a `Set` — the three values with no own enumerable key — are TAGGED. Never
|
|
116
|
+
add a fourth copy, and never make it parseable: `@ultimat3/action`'s `stableStringify` is the
|
|
117
|
+
DOCUMENT form for that (it publishes `openapi.json`), and it is a different function on purpose.
|
|
118
|
+
|
|
119
|
+
`decimal-order.ts` is the third instance of the same rule, over a value rather than a shape.
|
|
120
|
+
`compareDecimalText` is the exact ordering of two decimals however long the digits run — the order
|
|
121
|
+
Postgres gives a `numeric` or an `int8` over the TEXT `@ultimat3/entity`'s `bigint()` and
|
|
122
|
+
`decimal()` hand back, where `String(left) < String(right)` answers `["10","100","2","9"]` for
|
|
123
|
+
`["2","9","10","100"]` and cuts a keyset page where the database does not. It answers **`undefined`**
|
|
124
|
+
when either side is not a plain decimal, and that is the contract, not a convenience: a caller that
|
|
125
|
+
knows the column's declared kind asks (`@ultimat3/entity`'s `compareByKind`), and a caller that does
|
|
126
|
+
NOT — `@ultimat3/query`, whose `OrderKey` is a name and a direction — must never, because Postgres
|
|
127
|
+
orders a `text` column of digits lexically and a comparator guessing would trade one disagreement
|
|
128
|
+
with the SQL it printed for another.
|
|
129
|
+
|
|
93
130
|
`mcp-exposure.ts` is the same shape for a declaration rather than an algorithm: `isMcpExposed` is
|
|
94
131
|
the ONE answer to "did this primitive opt into being an MCP tool?", asked by `action`, `query`
|
|
95
132
|
(t3), `mcp`, `ai`, `manifest` (t4) — five packages that cannot import each other, so core is the
|
|
@@ -214,6 +251,15 @@ Every `UltimateError` carries `retry` (`terminal | retryable | retry-after`), **
|
|
|
214
251
|
registration path and it refuses to reclassify a core code, the same way `registerErrorStatus`
|
|
215
252
|
refuses to remap one. A new code in any package should be classified beside its declaration.
|
|
216
253
|
|
|
254
|
+
Two readers of that table, and picking the wrong one is a live defect. `retryFor(code)` answers
|
|
255
|
+
*what to do* and fails closed; `declaredErrorRetry(code)` answers *what somebody declared* and is
|
|
256
|
+
`undefined` when nobody did. `retry` on the instance is `init.retry ?? retryFor(code)`, so every
|
|
257
|
+
unclassified error already reads `terminal` — a caller deciding whether to STOP work in flight
|
|
258
|
+
(`@ultimat3/jobs`' executor) must read `declaredErrorRetry`, or it dead-letters attempt 1 of every
|
|
259
|
+
job in every app whose codes nobody classified. An instance `retry: 'terminal'` on an **unregistered**
|
|
260
|
+
code is indistinguishable from the default and is therefore read as unclassified; registering the
|
|
261
|
+
code is the one way to have it honoured.
|
|
262
|
+
|
|
217
263
|
Gotchas:
|
|
218
264
|
- `exactOptionalPropertyTypes` is on — declare optional fields as `x?: T | undefined`.
|
|
219
265
|
- `noPropertyAccessFromIndexSignature` is on — `ctx.services['mail']`, not `.mail`.
|
package/README.md
CHANGED
|
@@ -73,6 +73,16 @@ registerErrorRetry({ X_OAUTH_EXCHANGE_FAILED: 'retryable', X_RATE_LIMITED: 'retr
|
|
|
73
73
|
Core's own classifications are closed, exactly as `registerErrorStatus`'s framework table is: a
|
|
74
74
|
second, different registration for one code throws `X_ERROR_RETRY_INVALID`.
|
|
75
75
|
|
|
76
|
+
**Two readers, and the difference is load-bearing.** `retryFor(code)` answers *what to do* and
|
|
77
|
+
fails closed — `terminal` for a code nobody classified. `declaredErrorRetry(code)` answers *what
|
|
78
|
+
somebody actually declared*, and is `undefined` when nobody did. `UltimateError.retry` is
|
|
79
|
+
`init.retry ?? retryFor(code)`, so every unclassified error already carries `terminal`: a caller
|
|
80
|
+
deciding whether to stop work already in flight reads `declaredErrorRetry`, and the job executor
|
|
81
|
+
that read `retryFor` instead would dead-letter attempt 1 of every job in every app whose codes
|
|
82
|
+
nobody has classified. An instance-level `retry: 'terminal'` on an **unregistered** code is
|
|
83
|
+
indistinguishable from that default and is read as unclassified — register the code
|
|
84
|
+
(`registerErrorRetry({ X_YOUR_CODE: 'terminal' })`), which is the one way.
|
|
85
|
+
|
|
76
86
|
| Code | Subclass |
|
|
77
87
|
|---|---|
|
|
78
88
|
| `X_CONFIG_INVALID` | `ConfigInvalidError` |
|
|
@@ -333,6 +343,7 @@ collectMetrics(); // the same numbers as data, for a MetricExporter
|
|
|
333
343
|
| Names | lowercase `snake_case`, the intersection every exposition format accepts. Dotted OTel names survive OTLP and die at a Prometheus scrape |
|
|
334
344
|
| Attributes | `string \| number \| boolean` only — each distinct set is a stored series, so a user id here is an outage |
|
|
335
345
|
| Cardinality | enforced, not advised: `maxSeries` per instrument (default `DEFAULT_MAX_SERIES`), and past it every new label set folds into one `otel_metric_overflow="true"` series with `X_METRIC_CARDINALITY` logged once, naming the instrument |
|
|
346
|
+
| Async gauges | an `observe()` that throws, or answers a non-finite number, costs that instrument its point and **nothing beside it** — `X_METRIC_VALUE_INVALID` logged once, naming the instrument. Unguarded it took the whole `/metrics` body down with it, and `startMetricExport`'s timer callback raised where nothing can catch it |
|
|
336
347
|
| Driver seam | `MetricExporter`, defaulting to a no-op. `memoryMetricExporter()` for tests, `startMetricExport(ms)` for a periodic push, `otlpMetricExporter()` for a collector |
|
|
337
348
|
|
|
338
349
|
`runtime-metrics.ts` holds the series every process emits, and `SCALING_METRICS` maps each
|
|
@@ -423,6 +434,34 @@ never a silently wrong page.
|
|
|
423
434
|
| `usesDevCursorSecret()` | true while the shipped dev key is in use |
|
|
424
435
|
| `resetCursorSigning()` | test seam: forget `configureCursorSigning` and fall back to the environment |
|
|
425
436
|
|
|
437
|
+
## One bounded cache for every `Intl` formatter
|
|
438
|
+
|
|
439
|
+
```ts
|
|
440
|
+
import { cachedFormatter, canonicalLocale } from '@ultimat3/core';
|
|
441
|
+
|
|
442
|
+
const cache = new Map<string, Intl.NumberFormat>();
|
|
443
|
+
|
|
444
|
+
export function euroFormatter(locale: string): Intl.NumberFormat {
|
|
445
|
+
// `EN-us` and `en-latn-us` collapse to one key, so one locale cannot evict itself.
|
|
446
|
+
const tag = canonicalLocale(locale) ?? locale;
|
|
447
|
+
return cachedFormatter(
|
|
448
|
+
cache,
|
|
449
|
+
`${tag}|EUR`,
|
|
450
|
+
() => new Intl.NumberFormat(tag, { style: 'currency', currency: 'EUR' }),
|
|
451
|
+
);
|
|
452
|
+
}
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
A locale arrives from `Accept-Language` and a zone from `x-timezone`, so an unbounded `Map` keyed
|
|
456
|
+
on that string is **memory the client chooses**. Measured `As of 2026-08`: 4,096 casings of one
|
|
457
|
+
zone name retained 31 MB, and 20,000 valid `en-US-x-*` tags through `formatMoney` retained 55.1 MB.
|
|
458
|
+
The bound
|
|
459
|
+
(`MAX_CACHED_FORMATTERS`, 512, FIFO) and the canonical key are two halves of one rule and neither
|
|
460
|
+
is sufficient alone — an unknown `-u-` extension value survives canonicalization as a distinct
|
|
461
|
+
string, and the cap alone lets one locale evict itself under three spellings. A miss costs one
|
|
462
|
+
`Intl` construction, never a wrong answer, which is what makes the bound safe. It lives here rather
|
|
463
|
+
than in `@ultimat3/time` because `@ultimat3/money` needs it too and tier 1 may not import sideways.
|
|
464
|
+
|
|
426
465
|
## One image pipeline, everywhere
|
|
427
466
|
|
|
428
467
|
```ts
|
package/package.json
CHANGED
package/src/actor.ts
CHANGED
|
@@ -76,8 +76,27 @@ export interface Actor {
|
|
|
76
76
|
readonly orgId?: string | undefined;
|
|
77
77
|
/** Application roles (`admin`, `editor`). Unrelated to the runtime `Role`. */
|
|
78
78
|
readonly roles: readonly string[];
|
|
79
|
-
/**
|
|
79
|
+
/**
|
|
80
|
+
* FRAMEWORK capabilities, checked with `hasScope()`. One reader in the whole framework —
|
|
81
|
+
* `@ultimat3/entity`'s `crossTenant()`, gating `tenancy:cross` — and that narrowness is the
|
|
82
|
+
* point: a scope is an escape hatch the framework itself honours, never the app's authz
|
|
83
|
+
* vocabulary. That is `roles` and `permissions`, which `@ultimat3/policy` reads.
|
|
84
|
+
*/
|
|
80
85
|
readonly scopes: readonly string[];
|
|
86
|
+
/**
|
|
87
|
+
* DIRECT grants, bypassing roles: what a service token or a break-glass account holds
|
|
88
|
+
* (`post:publish`). `@ultimat3/policy` flattens these together with every grant `roles` expands
|
|
89
|
+
* to, so the two are one set by the time a predicate runs, and neither is `scopes`.
|
|
90
|
+
*
|
|
91
|
+
* Declared here rather than on policy's own `Actor` (`As of 2026-08-19`). It was policy's, which
|
|
92
|
+
* made it unreachable from the one place actors are built: core is tier 0 and cannot import
|
|
93
|
+
* policy, so `build()` below had no field to carry and `userActor({ permissions })` compiled and
|
|
94
|
+
* silently discarded the argument. Every caller worked around it with
|
|
95
|
+
* `{ ...userActor({ id }), permissions: [...] }` — a spread over a frozen actor, producing an
|
|
96
|
+
* UNFROZEN one — so the fixtures proving authz had a shape no request ever mints. `@ultimat3/auth`
|
|
97
|
+
* carried a second, hand-synced copy of the declaration for the same tier reason.
|
|
98
|
+
*/
|
|
99
|
+
readonly permissions: readonly string[];
|
|
81
100
|
/** App-declared facts. Read it through `actorFact()`; never logged — `actorLabel` is id-only. */
|
|
82
101
|
readonly facts?: ActorFactMap | undefined;
|
|
83
102
|
/**
|
|
@@ -93,6 +112,8 @@ export interface ActorInit {
|
|
|
93
112
|
readonly orgId?: string | undefined;
|
|
94
113
|
readonly roles?: readonly string[] | undefined;
|
|
95
114
|
readonly scopes?: readonly string[] | undefined;
|
|
115
|
+
/** Direct grants. Absent means none — never "inherit some"; there is nothing to inherit from. */
|
|
116
|
+
readonly permissions?: readonly string[] | undefined;
|
|
96
117
|
readonly facts?: ActorFactMap | undefined;
|
|
97
118
|
/** For a session that already recorded an impersonation; `impersonate()` sets it otherwise. */
|
|
98
119
|
readonly onBehalfOf?: ActorOrigin | undefined;
|
|
@@ -105,6 +126,7 @@ const ANONYMOUS: Actor = Object.freeze({
|
|
|
105
126
|
id: 'anonymous',
|
|
106
127
|
roles: Object.freeze([]),
|
|
107
128
|
scopes: Object.freeze([]),
|
|
129
|
+
permissions: Object.freeze([]),
|
|
108
130
|
facts: NO_FACTS,
|
|
109
131
|
});
|
|
110
132
|
|
|
@@ -115,6 +137,10 @@ function build(kind: ActorKind, init: ActorInit): Actor {
|
|
|
115
137
|
orgId: init.orgId,
|
|
116
138
|
roles: Object.freeze([...(init.roles ?? [])]),
|
|
117
139
|
scopes: Object.freeze([...(init.scopes ?? [])]),
|
|
140
|
+
// Copied and frozen like the two above, and for the sharper reason: this list IS the actor's
|
|
141
|
+
// authz. Handing back the caller's array would let whoever still holds it `push` a grant into
|
|
142
|
+
// a decision already made about a frozen actor.
|
|
143
|
+
permissions: Object.freeze([...(init.permissions ?? [])]),
|
|
118
144
|
facts: Object.freeze({ ...init.facts }),
|
|
119
145
|
onBehalfOf: init.onBehalfOf === undefined ? undefined : Object.freeze({ ...init.onBehalfOf }),
|
|
120
146
|
});
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The INJECTIVE canonical form of a value, and the sharing key taken over it.
|
|
3
|
+
*
|
|
4
|
+
* Tier 0 because three tier-3 packages need exactly this and none of them may import another:
|
|
5
|
+
* `@ultimat3/action`'s `requestHash` and job dedupe key, `@ultimat3/query`'s `queryHash` (the
|
|
6
|
+
* read-cache entry, the cursor scope, the live query id) and `@ultimat3/realtime`'s `qid`. Each
|
|
7
|
+
* kept its own copy, and the copies had already diverged — query's rendered every `Date` as `{}`,
|
|
8
|
+
* so one key answered for every date window a read ever served.
|
|
9
|
+
*
|
|
10
|
+
* Injective is the whole requirement: every one of those keys decides which of two callers is
|
|
11
|
+
* served the other's answer, so two distinct inputs sharing one string is a leak and not a
|
|
12
|
+
* collision. Nothing here is ever parsed back — `@ultimat3/action`'s `stableStringify` is the
|
|
13
|
+
* DOCUMENT form for that, and it is a different function on purpose.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Bare tokens, never quoted and never `'null'`. This output is only ever hashed, so an unquoted
|
|
18
|
+
* word cannot collide with the `string` branch (which always quotes), while `'null'` collided with
|
|
19
|
+
* JSON `null` itself — `{ n: NaN }`, `{ n: Infinity }`, `{ n: -Infinity }` and `{ n: null }` were
|
|
20
|
+
* one key and therefore one idempotency record, one cache entry and one cursor scope. `-0` is
|
|
21
|
+
* spelled out for the same reason: `String(-0)` is `"0"`.
|
|
22
|
+
*/
|
|
23
|
+
function hashNumber(value: number): string {
|
|
24
|
+
if (Number.isNaN(value)) return 'NaN';
|
|
25
|
+
if (!Number.isFinite(value)) return value > 0 ? 'Infinity' : '-Infinity';
|
|
26
|
+
return Object.is(value, -0) ? '-0' : String(value);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* The epoch, TAGGED — the same per-type tagging `hashNumber` uses for `NaN` and `-0`, for the same
|
|
31
|
+
* reason. Untagged, a `t.date` field and a `t.number` field holding that field's epoch would be one
|
|
32
|
+
* key, which is the collision this form exists to refuse; an Invalid Date would be the bare `NaN`
|
|
33
|
+
* token a `t.number` field already owns.
|
|
34
|
+
*/
|
|
35
|
+
function hashDate(value: Date): string {
|
|
36
|
+
return `Date(${hashNumber(value.getTime())})`;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Tagged for the reason a `Date` is: a `Map`, a `Set` and `{}` all have no own enumerable key, so
|
|
41
|
+
* the object branch rendered all three `{}` and one hash answered for three payloads sharing
|
|
42
|
+
* nothing. Entries are SORTED, as an object's keys are: insertion order is not part of what a Map
|
|
43
|
+
* or a Set holds, so two spellings of one payload stay one key.
|
|
44
|
+
*/
|
|
45
|
+
function hashCollection(value: Map<unknown, unknown> | Set<unknown>): string {
|
|
46
|
+
const entries =
|
|
47
|
+
value instanceof Map
|
|
48
|
+
? [...value].map(([key, item]) => `${canonicalJson(key)}:${canonicalJson(item)}`)
|
|
49
|
+
: [...value].map((item) => canonicalJson(item));
|
|
50
|
+
return `${value instanceof Map ? 'Map' : 'Set'}(${entries.sort().join(',')})`;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* JSON with object keys sorted at every depth, and every value a JSON document would fold onto
|
|
55
|
+
* `null` or `{}` given a token of its own. No timestamps, no insertion-order leaks.
|
|
56
|
+
*/
|
|
57
|
+
export function canonicalJson(value: unknown): string {
|
|
58
|
+
if (value === null) return 'null';
|
|
59
|
+
switch (typeof value) {
|
|
60
|
+
case 'string':
|
|
61
|
+
return JSON.stringify(value);
|
|
62
|
+
case 'number':
|
|
63
|
+
return hashNumber(value);
|
|
64
|
+
case 'boolean':
|
|
65
|
+
return String(value);
|
|
66
|
+
case 'bigint':
|
|
67
|
+
return JSON.stringify(`${value}n`);
|
|
68
|
+
case 'undefined':
|
|
69
|
+
case 'function':
|
|
70
|
+
case 'symbol':
|
|
71
|
+
return 'null';
|
|
72
|
+
default:
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
75
|
+
// Ahead of the object branch, because none of the three has an own enumerable key: `Object.keys`
|
|
76
|
+
// is empty for all of them and the branch below would answer `{}` for every date, every map and
|
|
77
|
+
// every set alike.
|
|
78
|
+
if (value instanceof Date) return hashDate(value);
|
|
79
|
+
if (value instanceof Map || value instanceof Set) return hashCollection(value);
|
|
80
|
+
if (Array.isArray(value)) return `[${value.map(canonicalJson).join(',')}]`;
|
|
81
|
+
const record = value as Record<string, unknown>;
|
|
82
|
+
const keys = Object.keys(record)
|
|
83
|
+
.filter((key) => record[key] !== undefined)
|
|
84
|
+
.sort();
|
|
85
|
+
const entries = keys.map((key) => `${JSON.stringify(key)}:${canonicalJson(record[key])}`);
|
|
86
|
+
return `{${entries.join(',')}}`;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* SHA-256, first 16 hex characters — the same primitive and width `@ultimat3/entity`'s `planScope`
|
|
91
|
+
* already chose, and for the same reason.
|
|
92
|
+
*
|
|
93
|
+
* This is a SHARING key over input a client chooses, not a checksum. It decides "same request,
|
|
94
|
+
* replay the stored response", which read-cache entry two callers are served from, which scope a
|
|
95
|
+
* cursor is bound to and which subscribers are served out of one live window — so a collision
|
|
96
|
+
* hands one caller another's rows. FNV-1a/32, which two of the three copies started as, is
|
|
97
|
+
* 4x10^9 values and brute-forceable offline in seconds: an input landing on another read's key was
|
|
98
|
+
* something an attacker could mint rather than something they had to wait for.
|
|
99
|
+
*/
|
|
100
|
+
export function fingerprint(value: unknown): string {
|
|
101
|
+
return new Bun.CryptoHasher('sha256').update(canonicalJson(value)).digest('hex').slice(0, 16);
|
|
102
|
+
}
|
package/src/config.ts
CHANGED
|
@@ -28,13 +28,28 @@ export interface ThemeConfig {
|
|
|
28
28
|
*/
|
|
29
29
|
export interface AuthConfig {
|
|
30
30
|
readonly signInPath: string | null;
|
|
31
|
-
/**
|
|
31
|
+
/**
|
|
32
|
+
* Where sign-in lands when there is nowhere to return to, or `?next=` is not same-origin.
|
|
33
|
+
*
|
|
34
|
+
* **Consulted by nothing, `As of 2026-08.`** Accepted, defaulted and merged here and read by no
|
|
35
|
+
* file in the repo — `dummy/social-media-clone/app.config.ts` sets `/dashboard` and gets
|
|
36
|
+
* whatever the sign-in route does on its own. Same shape `urlEnv`, `poolSize` and `schema` were
|
|
37
|
+
* deleted for below; this one is not deleted yet only because its writer is a tracked app's
|
|
38
|
+
* config, so removing the key and the line that sets it is one commit across two file sets.
|
|
39
|
+
*/
|
|
32
40
|
readonly afterSignInPath: string;
|
|
33
41
|
}
|
|
34
42
|
|
|
35
43
|
export interface PwaConfig {
|
|
36
44
|
readonly enabled: boolean;
|
|
37
45
|
readonly offline: OfflineStrategy;
|
|
46
|
+
/**
|
|
47
|
+
* **Consulted by nothing, `As of 2026-08.`** `wiki/Configuration.md` describes it as "render
|
|
48
|
+
* your own install affordance from the deferred event", both tracked apps set it, and
|
|
49
|
+
* `x new`'s scaffold writes it into every generated app — and no file reads it.
|
|
50
|
+
* `@ultimat3/pwa`'s `install.ts` is real and complete; nothing threads this flag into it.
|
|
51
|
+
* Delete the key or thread it; leaving it is a switch with no wire.
|
|
52
|
+
*/
|
|
38
53
|
readonly installPrompt: boolean;
|
|
39
54
|
readonly backgroundSync: boolean;
|
|
40
55
|
readonly push: boolean;
|
|
@@ -76,12 +91,18 @@ export interface JobsConfig {
|
|
|
76
91
|
readonly visibilityTimeoutMs: number;
|
|
77
92
|
}
|
|
78
93
|
|
|
94
|
+
/**
|
|
95
|
+
* No `heartbeatMs`. It was declared here, defaulted to 15_000, and read by NOTHING — deleted
|
|
96
|
+
* 2026-08-19. The socket beat is `new LiveClient({ heartbeatMs })`, browser code that cannot read
|
|
97
|
+
* server config, and the presence beat is DERIVED (`PresenceRegistry.heartbeatMs` is
|
|
98
|
+
* `max(1000, floor(ttlMs / 3))`). A second knob is a second number that can disagree with the one
|
|
99
|
+
* it is a fraction of, and a knob nothing reads is a knob nothing enforces — axioms 1 and 3.
|
|
100
|
+
*/
|
|
79
101
|
export interface RealtimeConfig {
|
|
80
102
|
readonly enabled: boolean;
|
|
81
103
|
readonly tier: RealtimeTier;
|
|
82
104
|
readonly transport: RealtimeTransport;
|
|
83
105
|
readonly urlEnv: string | undefined;
|
|
84
|
-
readonly heartbeatMs: number;
|
|
85
106
|
}
|
|
86
107
|
|
|
87
108
|
export interface McpConfig {
|
|
@@ -91,7 +112,15 @@ export interface McpConfig {
|
|
|
91
112
|
|
|
92
113
|
export interface AiConfig {
|
|
93
114
|
readonly mcp: McpConfig;
|
|
94
|
-
/**
|
|
115
|
+
/**
|
|
116
|
+
* Env key for the model id, so no model string is baked into the image — **an intention, not a
|
|
117
|
+
* behaviour, `As of 2026-08`.** The only read of it in the repo is the merge two hundred lines
|
|
118
|
+
* below, which copies it from input to output; nothing consumes the merged value, so
|
|
119
|
+
* `examples/dummy`'s `modelEnv: 'ANTHROPIC_MODEL'` selects no model. `@ultimat3/ai` reads env
|
|
120
|
+
* for API KEYS only (`ANTHROPIC_API_KEY`, `OPENAI_API_KEY`); the model is
|
|
121
|
+
* `request.model ?? DEFAULT_MODEL`, a compile-time constant in `models.ts`. So the exact thing
|
|
122
|
+
* this key exists to prevent — a model string baked into the image — is what actually happens.
|
|
123
|
+
*/
|
|
95
124
|
readonly modelEnv: string | undefined;
|
|
96
125
|
}
|
|
97
126
|
|
|
@@ -210,13 +239,7 @@ function defaults(name: string): Omit<AppConfig, 'name'> {
|
|
|
210
239
|
backoff: 'exponential',
|
|
211
240
|
visibilityTimeoutMs: 30_000,
|
|
212
241
|
},
|
|
213
|
-
realtime: {
|
|
214
|
-
enabled: false,
|
|
215
|
-
tier: 'channels',
|
|
216
|
-
transport: 'memory',
|
|
217
|
-
urlEnv: undefined,
|
|
218
|
-
heartbeatMs: 15_000,
|
|
219
|
-
},
|
|
242
|
+
realtime: { enabled: false, tier: 'channels', transport: 'memory', urlEnv: undefined },
|
|
220
243
|
ai: { mcp: { expose: true, path: '/mcp' }, modelEnv: undefined },
|
|
221
244
|
};
|
|
222
245
|
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Two decimals compared EXACTLY, however long the digits run — the ordering Postgres gives a
|
|
3
|
+
* `numeric` or an `int8`, over the TEXT those columns' row values are.
|
|
4
|
+
*
|
|
5
|
+
* Tier 0 because the values are text and the fix has to be available wherever they arrive.
|
|
6
|
+
* `@ultimat3/entity`'s `bigint()` and `decimal()` both hand digits back as a string on purpose — a
|
|
7
|
+
* JS `bigint` is what `JSON.stringify` throws on and a `number` loses digits past 2^53, exactly
|
|
8
|
+
* where a legacy `int8` key lives — so no `typeof` branch catches them: `String(left) <
|
|
9
|
+
* String(right)` answered `["10","100","2","9"]` where the database answers `["2","9","10","100"]`,
|
|
10
|
+
* and a keyset page boundary was cut where the database never cuts one.
|
|
11
|
+
*
|
|
12
|
+
* It answers `undefined` rather than guessing, and that is the whole of its contract: a caller
|
|
13
|
+
* that knows the column's declared kind (`@ultimat3/entity`'s `compareByKind`) asks; a caller that
|
|
14
|
+
* does NOT know it — `@ultimat3/query`, whose `OrderKey` is a name and a direction — must not,
|
|
15
|
+
* because Postgres orders a `text` column holding `"10"` and `"9"` lexically and a comparator
|
|
16
|
+
* guessing "both sides look like decimals" would disagree with the SQL it printed.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/** A decimal, split so two of them can be compared exactly however long the digits run. */
|
|
20
|
+
interface Decimal {
|
|
21
|
+
readonly negative: boolean;
|
|
22
|
+
readonly whole: string;
|
|
23
|
+
readonly fraction: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const DECIMAL_SHAPE = /^([+-]?)(\d+)(?:\.(\d*))?$/;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* The digits, or `undefined` for anything that is not a plain decimal — an exponent
|
|
30
|
+
* (`String(1e21)` is `"1e+21"`), a `NaN`, an empty string. Those are not values a `numeric` column
|
|
31
|
+
* can hold, so they are not values Postgres would be ordering either.
|
|
32
|
+
*/
|
|
33
|
+
function decimalOf(value: unknown): Decimal | undefined {
|
|
34
|
+
const text =
|
|
35
|
+
typeof value === 'bigint' || typeof value === 'number'
|
|
36
|
+
? String(value)
|
|
37
|
+
: typeof value === 'string'
|
|
38
|
+
? value.trim()
|
|
39
|
+
: undefined;
|
|
40
|
+
const parts = text === undefined ? null : DECIMAL_SHAPE.exec(text);
|
|
41
|
+
const whole = parts?.[2];
|
|
42
|
+
if (parts === null || whole === undefined) return undefined;
|
|
43
|
+
return { negative: parts[1] === '-', whole, fraction: parts[3] ?? '' };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Exact at any width: the fractions are padded to one length and both sides become one integer, so
|
|
48
|
+
* a 38-digit `numeric` orders by its digits rather than by whatever a `Number` rounded it to.
|
|
49
|
+
*/
|
|
50
|
+
function compare(left: Decimal, right: Decimal): number {
|
|
51
|
+
if (left.negative !== right.negative) return left.negative ? -1 : 1;
|
|
52
|
+
const width = Math.max(left.fraction.length, right.fraction.length);
|
|
53
|
+
const scaled = (value: Decimal): bigint =>
|
|
54
|
+
BigInt(`${value.whole}${value.fraction.padEnd(width, '0')}`);
|
|
55
|
+
const first = scaled(left);
|
|
56
|
+
const second = scaled(right);
|
|
57
|
+
// Never a subtraction: the difference between two `bigint`s is exact and the return type is a
|
|
58
|
+
// `number`, which cannot hold it.
|
|
59
|
+
const order = first < second ? -1 : first > second ? 1 : 0;
|
|
60
|
+
// Guarded rather than negated: `-0` is a different value from `0` to `Object.is` and to a caller
|
|
61
|
+
// writing `=== 0`, and two equal negatives are a tie.
|
|
62
|
+
return left.negative && order !== 0 ? -order : order;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* `-1`, `0` or `1` for two plain decimals; `undefined` when EITHER side is not one.
|
|
67
|
+
*
|
|
68
|
+
* Both, or neither: one decimal against a value that is not one is not a numeric comparison, and
|
|
69
|
+
* answering for that pair would order a mixed column by a rule the database does not use.
|
|
70
|
+
*/
|
|
71
|
+
export function compareDecimalText(left: unknown, right: unknown): number | undefined {
|
|
72
|
+
const first = decimalOf(left);
|
|
73
|
+
if (first === undefined) return undefined;
|
|
74
|
+
const second = decimalOf(right);
|
|
75
|
+
return second === undefined ? undefined : compare(first, second);
|
|
76
|
+
}
|
package/src/env-example.ts
CHANGED
|
@@ -96,7 +96,15 @@ export interface EnvExampleReport {
|
|
|
96
96
|
readonly ok: boolean;
|
|
97
97
|
/** Declared in the schema, absent from the file. Always a defect. */
|
|
98
98
|
readonly missing: readonly string[];
|
|
99
|
-
/**
|
|
99
|
+
/**
|
|
100
|
+
* In the file, not in the schema — never fatal, because apps set keys nothing declares.
|
|
101
|
+
*
|
|
102
|
+
* NOT reported on its own, and the comment here said it was. `ok` is `missing.length === 0`, so
|
|
103
|
+
* an example carrying only extra keys returns `ok: true` and `assertEnvExample` never builds an
|
|
104
|
+
* error: the list reaches a surface only as `meta` on a drift some MISSING key already raised.
|
|
105
|
+
* A caller that wants it reads `checkEnvExample(...).extra` itself, which is why this stays
|
|
106
|
+
* public. `env-example.test.ts` pins both halves.
|
|
107
|
+
*/
|
|
100
108
|
readonly extra: readonly string[];
|
|
101
109
|
}
|
|
102
110
|
|
package/src/error-codes.ts
CHANGED
|
@@ -46,7 +46,8 @@ const CORE_CODE_TITLES = {
|
|
|
46
46
|
X_INVARIANT: 'invariant violated',
|
|
47
47
|
X_METRIC_CARDINALITY:
|
|
48
48
|
'a metric exceeded its series ceiling and is folding into one overflow series',
|
|
49
|
-
X_METRIC_NAME_INVALID:
|
|
49
|
+
X_METRIC_NAME_INVALID:
|
|
50
|
+
'metric name is malformed, or redeclared with a different kind, bounds or observer',
|
|
50
51
|
X_METRIC_VALUE_INVALID: 'metric value is not recordable',
|
|
51
52
|
X_NO_CONTEXT: 'no request context is active',
|
|
52
53
|
X_NOT_IMPLEMENTED: 'this driver does not implement the requested feature',
|
package/src/error-retry.ts
CHANGED
|
@@ -27,15 +27,29 @@ export const DEFAULT_ERROR_RETRY: ErrorRetry = 'terminal';
|
|
|
27
27
|
* app whose clients stop retrying a rolling restart, which is the one case retrying always wins.
|
|
28
28
|
* Only the exceptions are listed — everything else is `terminal` by the default above.
|
|
29
29
|
*/
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
30
|
+
// A `Map`, not a frozen object: `code` is a caller's string on every read below, and
|
|
31
|
+
// `CORE_ERROR_RETRY['constructor']` on an object literal answers the `Object` FUNCTION — which
|
|
32
|
+
// `retryFor` then returned as an `ErrorRetry`, into every `UltimateError.retry` and `toJSON()`.
|
|
33
|
+
const CORE_ERROR_RETRY: ReadonlyMap<string, ErrorRetry> = new Map(
|
|
34
|
+
Object.entries({
|
|
35
|
+
X_DRAINING: 'retryable',
|
|
36
|
+
// A deadline that expired is the canonical back-off-and-try-again case: nothing about the
|
|
37
|
+
// request was wrong, the budget ran out. Deliberately NOT `retry-after` — that spelling means
|
|
38
|
+
// the responder named a time, and a timeout by definition produced no such answer. Its twin
|
|
39
|
+
// `X_ABORTED` (the caller went away) is left to the `terminal` DEFAULT rather than listed here:
|
|
40
|
+
// the answer is the same, and listing it would close a door nobody has asked to open.
|
|
41
|
+
X_TIMEOUT: 'retryable',
|
|
42
|
+
// Listed even though `terminal` is the default, and that is the whole point: `classifyThrown`
|
|
43
|
+
// reads an UNREGISTERED code carrying `terminal` as unclassified, because a per-instance
|
|
44
|
+
// `terminal` is indistinguishable from the default and honouring it would dead-letter the
|
|
45
|
+
// first attempt of every job in every app whose codes nobody has classified. So a stub that
|
|
46
|
+
// says "this build does not have the feature" fell through to the attempt count and burned a
|
|
47
|
+
// job's whole retry policy on a fact that cannot change between attempt 1 and attempt 5.
|
|
48
|
+
// It is core's code — every `notImplemented()` stub in the framework raises it — so it is
|
|
49
|
+
// classified once here rather than by each package that happens to throw it.
|
|
50
|
+
X_NOT_IMPLEMENTED: 'terminal',
|
|
51
|
+
} as const),
|
|
52
|
+
);
|
|
39
53
|
|
|
40
54
|
const REGISTERED = new Map<string, ErrorRetry>();
|
|
41
55
|
|
|
@@ -70,7 +84,7 @@ export function registerErrorRetry(retries: Readonly<Record<string, ErrorRetry>>
|
|
|
70
84
|
if (!isErrorRetry(retry)) {
|
|
71
85
|
throw retryInvalid(code, `"${String(retry)}" is not ${ERROR_RETRY_KINDS.join(' | ')}`);
|
|
72
86
|
}
|
|
73
|
-
const core = CORE_ERROR_RETRY
|
|
87
|
+
const core = CORE_ERROR_RETRY.get(code);
|
|
74
88
|
if (core !== undefined) {
|
|
75
89
|
throw retryInvalid(code, `the framework already classifies it as ${core}`);
|
|
76
90
|
}
|
|
@@ -87,11 +101,23 @@ export function resetErrorRetry(): void {
|
|
|
87
101
|
REGISTERED.clear();
|
|
88
102
|
}
|
|
89
103
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
104
|
+
/**
|
|
105
|
+
* The classification somebody actually DECLARED for this code, `undefined` when nobody did.
|
|
106
|
+
*
|
|
107
|
+
* `retryFor` answers a client's question — "may I send this again?" — and fails closed, so it
|
|
108
|
+
* cannot tell a code declared `terminal` from a code nobody classified. A caller deciding whether
|
|
109
|
+
* to STOP work already in flight has to tell them apart: the job executor reads this, because
|
|
110
|
+
* treating every unclassified code as `terminal` would end the retry policy of every job in every
|
|
111
|
+
* shipped app, which is a far larger fault than the one that reading brings.
|
|
112
|
+
*
|
|
113
|
+
* Core table first, for the same belt-and-braces reason `retryFor` had it first.
|
|
114
|
+
*/
|
|
115
|
+
export function declaredErrorRetry(code: string): ErrorRetry | undefined {
|
|
116
|
+
return CORE_ERROR_RETRY.get(code) ?? REGISTERED.get(code);
|
|
117
|
+
}
|
|
118
|
+
|
|
93
119
|
export function retryFor(code: string): ErrorRetry {
|
|
94
|
-
return
|
|
120
|
+
return declaredErrorRetry(code) ?? DEFAULT_ERROR_RETRY;
|
|
95
121
|
}
|
|
96
122
|
|
|
97
123
|
/** Every classification a package or app declared, for `x errors list` and the manifest. */
|
package/src/image/errors.ts
CHANGED
|
@@ -42,7 +42,7 @@ export const imageDecodeFailed = (
|
|
|
42
42
|
): ImageDecodeFailedError =>
|
|
43
43
|
new ImageDecodeFailedError(
|
|
44
44
|
cause,
|
|
45
|
-
'
|
|
45
|
+
're-export the image from its source: `file <path>` reports what these bytes actually are',
|
|
46
46
|
meta,
|
|
47
47
|
);
|
|
48
48
|
|
package/src/index.ts
CHANGED
|
@@ -30,10 +30,9 @@ export {
|
|
|
30
30
|
withFacts,
|
|
31
31
|
} from './actor';
|
|
32
32
|
export { APP_VERSION_KEY, appVersion, DEFAULT_APP_VERSION } from './app-version';
|
|
33
|
-
export type
|
|
34
|
-
export {
|
|
35
|
-
export type
|
|
36
|
-
export { frozenClock, systemClock } from './clock';
|
|
33
|
+
export { assert, assertNever, type InvariantOptions, invariant } from './assert';
|
|
34
|
+
export { canonicalJson, fingerprint } from './canonical-json';
|
|
35
|
+
export { type Clock, type FrozenClock, frozenClock, systemClock } from './clock';
|
|
37
36
|
export type {
|
|
38
37
|
AiConfig,
|
|
39
38
|
AiConfigInput,
|
|
@@ -78,6 +77,7 @@ export {
|
|
|
78
77
|
resetCursorSigning,
|
|
79
78
|
usesDevCursorSecret,
|
|
80
79
|
} from './cursor';
|
|
80
|
+
export { compareDecimalText } from './decimal-order';
|
|
81
81
|
export type {
|
|
82
82
|
Env,
|
|
83
83
|
EnvBooleanVar,
|
|
@@ -130,6 +130,7 @@ export {
|
|
|
130
130
|
CORE_ERROR_CODES,
|
|
131
131
|
ConfigInvalidError,
|
|
132
132
|
DEFAULT_ERROR_RETRY,
|
|
133
|
+
declaredErrorRetry,
|
|
133
134
|
describeErrorCode,
|
|
134
135
|
describeValue,
|
|
135
136
|
EnvMissingError,
|
|
@@ -424,9 +425,9 @@ export {
|
|
|
424
425
|
MAX_IMAGE_PIXELS,
|
|
425
426
|
rasterFrom,
|
|
426
427
|
} from './image/raster';
|
|
427
|
-
export type
|
|
428
|
-
export { fitBox, resizeRaster, scaledToFit } from './image/resize';
|
|
428
|
+
export { fitBox, type ImageFit, type ResizeSpec, resizeRaster, scaledToFit } from './image/resize';
|
|
429
429
|
export { impersonate, impersonationReason, isImpersonating } from './impersonate';
|
|
430
|
+
export { cachedFormatter, canonicalLocale, MAX_CACHED_FORMATTERS } from './intl-cache';
|
|
430
431
|
export type {
|
|
431
432
|
HealthPayload,
|
|
432
433
|
HealthReport,
|
|
@@ -469,10 +470,8 @@ export {
|
|
|
469
470
|
markListening,
|
|
470
471
|
resetListeners,
|
|
471
472
|
} from './listeners';
|
|
472
|
-
export type
|
|
473
|
-
export {
|
|
474
|
-
export type { CappedBody } from './read-capped';
|
|
475
|
-
export { readWithinLimit } from './read-capped';
|
|
473
|
+
export { isMcpExposed, type McpExposureDeclaration } from './mcp-exposure';
|
|
474
|
+
export { type CappedBody, readWithinLimit } from './read-capped';
|
|
476
475
|
export type { ModuleRegistrar, PrimitiveKind, RegisteredPrimitive } from './registrar';
|
|
477
476
|
export {
|
|
478
477
|
hasPrimitiveRegistrar,
|
|
@@ -486,8 +485,7 @@ export { err, isErr, isOk, map, mapErr, ok, tryCatch, unwrap, unwrapOr } from '.
|
|
|
486
485
|
export type { ResolveRoleOptions, Role, RoleInfo, ScalingSignal } from './roles';
|
|
487
486
|
export { DEFAULT_ROLE, isRole, ROLE_INFO, ROLES, resolveRole } from './roles';
|
|
488
487
|
export { safeUrl, URL_ATTRIBUTES } from './safe-url';
|
|
489
|
-
export type
|
|
490
|
-
export { defineService, resetServices } from './service';
|
|
488
|
+
export { defineService, resetServices, type ServiceFactory } from './service';
|
|
491
489
|
export { timingSafeEqual } from './timing-safe-equal';
|
|
492
490
|
export {
|
|
493
491
|
frameworkVersion,
|