Skip to content

Commit 7e6e9e0

Browse files
committed
feat(rest): declared OS_REST_LOG fault-log level seam, gate, docs and audit discharge
Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TSf4DV7ziu4V5j73e46b7c
1 parent 8f31aa3 commit 7e6e9e0

6 files changed

Lines changed: 696 additions & 6 deletions

File tree

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
---
2+
"@objectstack/rest": patch
3+
---
4+
5+
`packages/rest`'s fault logging gains a **declared level seam**, `OS_REST_LOG`, with the **shipped default unchanged**. At the default — and an unset or unrecognised value *is* the default — a reported fault still prints the whole `Error`: message, `cause` chain and stack frames, exactly as before. ⛔ No wire byte moves, no published payload gains a key, and no existing log line changes shape.
6+
7+
What is new is that the loud/quiet choice is now **declared and machine-read** instead of implicit in whether an author happened to pass `error` or `error.message`:
8+
9+
- **`OS_REST_LOG`** accepts `debug` / `info` / `warn` / `error` / `silent` — deliberately the same vocabulary and the same `'info'` default as `@objectstack/objectql`'s `OS_REGISTRY_LOG`, so the two are one logging contract with two populations rather than a second ad-hoc environment variable. Documented for operators in this package's README.
10+
- **`scripts/check-rest-log-declared.mjs`** enforces it: the seam is located by its environment read (never a hardcoded path), the vocabulary is read from `REST_LOG_LEVELS` rather than copied, the two seams' vocabularies are held equal, a harness declaration must name a level the seam actually recognises — an unrecognised one resolves to the default *silently* — and every inline vitest project must carry its own declaration, because a root-level `env` is inert for project runs.
11+
- **The shipped default is gated, not just documented.** Lowering `REST_LOG_DEFAULT_LEVEL` to `error` or `silent` is a finding, because at those levels this package stops reporting faults it is the only reporter of.
12+
13+
**Why the default does not move.** Measured on one green `packages/rest` run: 2,095 indented `at ` frame lines, 36.7% of captured output, 100% of them arriving through this one shim. They are not dead weight. When a 5xx is withheld from the client, the log is the only copy of the driver text, and that text lives on `error.cause` — printed only because a whole `Error` object, not a summary, reaches `console.error`. Four assertions across `rest-5xx-message-sanitization.test.ts` and `rest-expected-error-logging.test.ts` pin that by asserting the **identity** of the error that arrives, one of them carrying an explicit do-not-delete warning aimed at exactly this repair.
14+
15+
Operators: nothing to do. A deployment that wants the REST layer quieter can now say so — `OS_REST_LOG=error` drops the warning half, `silent` drops both — but doing so discards diagnostics that have no second copy anywhere, and the README says so at the seam.

‎.github/workflows/lint.yml‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3762,6 +3762,19 @@ jobs:
37623762
- name: Declared registry log level
37633763
run: node scripts/check-registry-log-declared.mjs --self-test && node scripts/check-registry-log-declared.mjs
37643764

3765+
# The same defect one package down (#15484). `packages/rest`'s own fault
3766+
# logging hands whole `Error` objects to `console.error`, and Node prints
3767+
# the stack and the `[cause]` chain with them — 2,095 indented `at ` frame
3768+
# lines on one green run, 36.7% of the captured output, all of it through
3769+
# one shim. ⛔ Those frames are READ (four assertions pin the identity of
3770+
# the error that arrives, #5437 / #4886 / #5489), so the ruled repair was a
3771+
# DECLARED level, not a quieter product. This holds the two halves that rot
3772+
# silently: a harness declaration that is deleted or typo'd to a level the
3773+
# seam does not recognise (it resolves to the DEFAULT, silently), and the
3774+
# shipped default itself being lowered to quieten a log. Self-test first.
3775+
- name: Declared REST fault-log level
3776+
run: node scripts/check-rest-log-declared.mjs --self-test && node scripts/check-rest-log-declared.mjs
3777+
37653778
# Live-server database isolation (#10382). CI provisions ONE Postgres and
37663779
# ONE MySQL for the whole temporal-conformance job and points every live
37673780
# leg at them, and every live suite in the repo issues a `drop` when it

‎docs/audits/2026-09-test-log-volume-census.md‎

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,12 @@ total volume than the five heaviest suites already were. See
2727

2828
- **No urgency, no correctness impact.** This is CI log volume. The earlier
2929
reading said so and nothing here changes it.
30-
- **No seam was added, and none is recommended.** The two candidates — an env
31-
read in the kernel logger, a level field on `BootOptions` — both touch
32-
published surface, and choosing between them is not a measurement.
30+
- **No seam was added by this document, and neither candidate it listed is
31+
recommended.** The two candidates — an env read in the kernel logger, a level
32+
field on `BootOptions` — both touch published surface, and choosing between
33+
them is not a measurement. ⚠️ A different seam was later ruled and built for
34+
a different population: `OS_REST_LOG`, on `packages/rest`'s own fault logging
35+
(#15484). See the closing section — that is not one of these two.
3336
- **"A reader of a production boot log may well want every one of these
3437
lines."** Test-environment noise and production observability are two ends of
3538
one switch. Nothing here is an argument for lowering the engine's boot INFO
@@ -416,6 +419,18 @@ point does the combined population cross back toward parity, let alone toward
416419
`console` being the majority — it stays firmly structured-dominated (77.1%)
417420
throughout.
418421

419-
**No seam was added.** Per triage's ruling, this document is the measurement
420-
only; which of the two candidate seams (if either) to build is triage's call,
421-
made with this table in hand.
422+
**No seam was added by this document, and the reservation it held is now
423+
DISCHARGED.** This document was the measurement only, and it reserved to triage
424+
「which of the two candidate seams (if either) to build」. The maintainer decided
425+
it in decision batch #49, item 2 (2026-09-05, recorded on #15484): option **A**,
426+
a declared level seam on `packages/rest`'s `logError`, in the `OS_REGISTRY_LOG`
427+
shape, **with the shipped default unchanged**.
428+
429+
⇒ The seam that was built against that ruling is `OS_REST_LOG`
430+
(`packages/rest/src/log.ts`, `REST_LOG_LEVELS`), enforced by
431+
`scripts/check-rest-log-declared.mjs`. ⛔ It is a DECLARATION, not a quieter
432+
product: at the shipped default a reported fault still prints the whole `Error`
433+
— message, `cause` chain and frames — for every real caller, and suppression is
434+
only ever something a harness declares. The two candidates this document listed
435+
(an env read in the kernel logger, a level field on `BootOptions`) remain
436+
unbuilt; neither was chosen.

‎package.json‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@
158158
"examples:live-imports": "node scripts/check-examples-live-imports.mjs --list",
159159
"check:test-source-alias": "node scripts/check-test-source-alias.mjs --self-test && node scripts/check-test-source-alias.mjs",
160160
"check:registry-log-declared": "node scripts/check-registry-log-declared.mjs --self-test && node scripts/check-registry-log-declared.mjs",
161+
"check:rest-log-declared": "node scripts/check-rest-log-declared.mjs --self-test && node scripts/check-rest-log-declared.mjs",
161162
"check:refd-timer-probe": "node scripts/check-refd-timer-probe.mjs --self-test && node scripts/check-refd-timer-probe.mjs",
162163
"check:type-source-resolution": "node scripts/check-type-source-resolution.mjs --self-test && node scripts/check-type-source-resolution.mjs",
163164
"check:undeclared-dep-imports": "node scripts/check-undeclared-dep-imports.mjs --self-test && node scripts/check-undeclared-dep-imports.mjs",

‎packages/rest/README.md‎

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,29 @@ Plus metadata and discovery routes:
9797
| `caching.etag` | `boolean` | `true` | Emits `ETag` header. |
9898
| `caching.lastModified` | `boolean` | `true` | Emits `Last-Modified`. |
9999

100+
### Environment
101+
102+
| Variable | Values | Default | Notes |
103+
|:---|:---|:---|:---|
104+
| `OS_REST_LOG` | `debug` \| `info` \| `warn` \| `error` \| `silent` | `info` | Level for this package's own fault logging. |
105+
106+
`OS_REST_LOG` declares how loud the REST layer is about faults **it reports
107+
itself** — the `[REST] …` lines written when a request fails. It is the same
108+
vocabulary, and the same shipped default, as `@objectstack/objectql`'s
109+
`OS_REGISTRY_LOG`; an unrecognised value falls back to the default rather than
110+
silencing anything.
111+
112+
At the default a reported fault prints the **whole** `Error`: its message, its
113+
`cause` chain and its stack frames. That is deliberate and it is the reason to
114+
leave it alone. When a 5xx is withheld from the client, the log is the only
115+
place the underlying driver text exists, and that text travels on `error.cause`
116+
— it is printed only because a whole `Error`, not a summary, reaches the
117+
console. Lowering the level to `error` or `silent` discards diagnostics that
118+
have no second copy anywhere.
119+
120+
⇒ Prefer declaring a quieter level in a **test harness** (`vitest.config.ts`'s
121+
`env` block) over exporting it for a running server.
122+
100123
## HTTP semantics
101124

102125
- JSON envelope: `{ success, data, error?, meta? }`.

0 commit comments

Comments
 (0)