Repository navigation
Commit 11e6fc5
feat(cli): Boot diagnostics noise budget, printer half: the line that needs the author first with its fix, stack traces only at debug (#22236)
Part of #22160 (the printer half; owners' levels routed)
Clause-②: no
The maintainer's ruling, quoted on the card: 「建议给 dev
模式定一个"噪音预算":预期中的降级记 info,堆栈只在 `--log-level debug`
出现,真正需要动手的那条单独高亮并给一行修法。」 This PR is the printer's half, per the seat's
scope cut (claim `6053785493`). The level each other lane's line is
logged at stays with its owner. The table below names each owner.
## What changes (`packages/cli/src/utils/format.ts`,
`printBootDiagnostics` only)
- **The record that needs the author prints first, once, highlighted,
with its one-line fix.** A closed table, `ACTIONABLE_BOOT_CLASSES`,
holds one row today: `@objectstack/objectql`'s `[action-governance]
declared script actions with NO handler` (a dead button). It is matched
the way #22097's `restatedAbove` matches. The highlighted line is the
record's statement plus its subjects (the `actions` list from the
record's structured tail). The fix is derived from the producer's own
message: the text after its `; `, as `leadSentence` derives a flow
reason. Nothing is hand-copied. A reworded producer that drops the `;`
still prints highlighted and whole. It is never dropped.
- **The header counts the two apart:** `⚠ Boot diagnostics — 1 needs
your attention · 2 informational:`. With nothing actionable the header
reads `ℹ Boot diagnostics — 4 informational (2 more already listed
above):`, with no alarm glyph. The exception is a capture that dropped
records: the printer has not read those, so the header keeps the `⚠`.
- **Stack traces are withheld at the default level.** The record's
structured tail is located (pretty, text and JSON renderings) and every
`stack` property whose value is a V8 trace is dropped. A `stack` key
that names something stays. The message stays. A record without a trace
prints byte-identical. The last line counts what was withheld and points
at `--log-level debug`.
- **The debug half needs no code.** At `--log-level debug` (and `info`),
`serve` never opens the boot-quiet window (`isVerboseBootLevel`). The
same record streams live as `ObjectLogger` rendered it, stack included,
and no replay block prints. Measured both before and after (below).
`serve.ts` is untouched.
- #22097's print-once and `restatedAbove` rules are unchanged.
## Measured (`origin/main` `7d7943dd`, built `packages/cli/dist`, `os
dev --fresh`)
Blank starter (from `packages/create-objectstack/src/templates/blank`,
namespace `support_desk`) plus the tutorial's four files, extracted
verbatim from `build-with-claude-code.mdx` as it stood at `7d7943dd`,
`--ui`. At that commit Resolve was a script action with an unregistered
`target`. #22204 has since made it a declarative update, which needs no
handler (see the re-measure after the merge, below):
```text
before:
⚠ Boot diagnostics — 3 warnings logged during startup:
… WARN [Analytics] No admitObjectRead configured … (one line)
… WARN [action-governance] declared script actions with NO handler — a button wired to nothing (ADR-0078); add a `body`, or register a handler under the declared `target` {"count":1,"actions":["support_desk_ticket:resolve_ticket"]}
… WARN Insert operation failed {"object":"sys_migration","error":{"message":"UNIQUE constraint failed: sys_migration.id [statement and bound values redacted]","stack":"SqliteError: … (10 knex/driver/engine frames)"}}
after:
⚠ Boot diagnostics — 1 needs your attention · 2 informational:
⚠ [action-governance] declared script actions with NO handler — a button wired to nothing (ADR-0078): support_desk_ticket:resolve_ticket
fix: add a `body`, or register a handler under the declared `target`
… WARN [Analytics] No admitObjectRead configured … (one line)
… WARN Insert operation failed {"object":"sys_migration","error":{"message":"UNIQUE constraint failed: sys_migration.id [statement and bound values redacted]"}}
1 stack trace withheld — run with --log-level debug to watch the boot stream live, stacks included
```
`examples/app-showcase` (`dev --seed-admin --fresh`): before, `⚠ Boot
diagnostics — 4 warnings logged during startup (2 more already listed
above):` with the `sys_migration` stack. After, `ℹ Boot diagnostics — 4
informational (2 more already listed above):` with the stack withheld.
The four records are `[Analytics]`, `[SettingsService]`, `sys_migration`
and `[sharing-rule]`, none actionable. The banner's two `Flows:` class
lines are unchanged.
`--log-level debug` on the blank project, before and after: 0 replay
blocks; the `sys_migration` record streams live 1 time, carrying
`"stack":"SqliteError…` 1 time.
On `main` the blank project shows 3 records, not the card's 4. The
loopback OAuth line moved to `info` in #22097 and is gone at the default
level.
**Re-measured after merging `origin/main` (`7b926f76`, which carries
#22204), at head `f8a80640`.** The blank starter plus the tutorial's
current four files (Resolve is now `operation: 'update'` + `patch`)
prints `ℹ Boot diagnostics — 2 informational:`, which is `[Analytics]`
and the `sys_migration` insert with its stack withheld, plus the hint
line. Nothing there needs the author any more. The two remaining lines
are #22154's and #22099's to lower.
## Each non-actionable line and its owner (H5)
| line (default level) | level | emitter | owner | card tracking its
level |
|---|---|---|---|---|
| `[Analytics] No admitObjectRead configured and no "security" service
registered at init …` | warn |
`packages/services/service-analytics/src/plugin.ts:1450` |
`domain:services` | #22154 (open) |
| `Insert operation failed {"object":"sys_migration", …}` | warn |
logged at `packages/objectql/src/engine.ts:14006`. The duplicate insert
is plugin-auth's default-org owner bind recording id
`adr-0093-default-org-owner-bind` twice (debug stream, blank project) |
`domain:services` (plugin-auth) | #22099 (open) |
| `[SettingsService] Pre-bind READ of namespace 'auth' …` | warn |
`packages/services/service-settings/src/settings-service.ts:774` |
`domain:services` | none known to this PR |
| `[sharing-rule] active business-unit rule expands to NO recipients …`
| warn |
`packages/plugins/plugin-sharing/src/sharing-rule-service.ts:1524` |
plugin-sharing | none known to this PR |
| `N flow(s) declare a 'schedule' / 'time_relative' trigger but are NOT
bound — disabled by deployment policy …` | banner class line (yellow
`⚠`), restating `service-automation`'s `warn` | `printAutomationSummary`
(this file) restating
`packages/services/service-automation/src/plugin.ts:1533` | claim routes
it to `service-automation`; see the open question in the report | none
known to this PR |
The dev did not search the tracker for the three "none known" rows. The
role file reserves search for the filing seat. Dedupe words are in the
report.
## Pins
`packages/cli/src/utils/format.boot-warning-classes.test.ts` (unit tier;
11 existing + 7 new legs):
- **Real producers, all three log formats** (`it.each` over pretty /
text / json): a real `ObjectLogger` writes through the real
`BootLogCapture`. The real `runActionGovernanceInventory` runs over a
ticket whose Resolve is a script action with an unregistered `target`.
The dead button prints once, first, highlighted, ending `:
support_desk_ticket:resolve_ticket`. The next line is `fix: ` plus the
inventory's own message after its `; `, read off the producer at call
time. The header carries `1 needs your attention · 2 informational`.
- **Stack withheld at the default level:** a failed-insert record built
from a real thrown `Error` is captured with its escaped stack (premise
asserted). It prints with `object` and `error.message` and no `"stack"`.
No escaped frame is anywhere on screen. One hint line names the count
and `--log-level debug`.
- **Stack at `--log-level debug`:** `isVerboseBootLevel('debug')` holds.
A debug-level `ObjectLogger` writes the same record to the terminal with
its stack.
- **Formatter:** a `stack` key that is not a trace prints unchanged. A
capture with dropped records keeps the `⚠`.
- **#22097's legs stay green.** Three of them asserted the header string
verbatim, which is the text this ruling replaces. Those three
expectations now read the new header. Their withheld counts are
unchanged (`(3 more already listed above)`, `(1 more …)`), and every
class and print-once assertion is untouched.
`packages/cli/test/serve-boot-diagnostics-noise-budget.e2e.test.ts`
(new; integration tier by behaviour, nightly by name). The blank starter
is scaffolded through the real on-ramp `bin/`. Added to it: the
tutorial's ticket object, its current declarative Resolve, and one
`Escalate` script action whose `target` nothing registers. Real `os
serve` then boots. Boot diagnostics highlights exactly Escalate's
record, with one `fix:` line. Its header says `1 needs your attention`.
Resolve, the control in the same boot, is named on no line, and no stack
trace reaches the block. No informational count is pinned, because the
owners above are moving those.
### Ablations
Each ran on the committed fix through `scripts/ablation-replace.mjs`
(WRAP mode). The unit legs ran at `77f6a48c`. The boot-pin leg ran at
`77f6a48c` and again at `f8a80640`, red both times; `format.ts` is the
same blob at both. Each anchor hit 1 time, the write was verified on
disk by blob change, and each restore was proven `blob == HEAD` with an
empty `git diff HEAD`. The test subjects import `format.ts` /
`boot-log-capture.ts` from source, so no `dist` is involved. Expected
direction: red. Observed: red every time.
| leg | mutation | result |
|---|---|---|
| A | class row's record text no longer matches | 3 failed / 18 (the
three format legs) |
| B | `stack` traces kept | 1 failed / 18 (the withholding leg) |
| C | fix never split off (`at = -1`) | 3 failed / 18 |
| D | any string `stack` withheld, trace or not | 1 failed / 18 (the
narrowness leg) |
| E | dropped records no longer keep the alarm | 1 failed / 18 |
| G | `isVerboseBootLevel` without `debug` | 1 failed / 18 (the debug
leg) |
| A, boot pin | class row's record text no longer matches | 1 failed / 3
(the highlight assertion) |
Final restore proof: `format.ts` blob `9dc1b9d9` and
`boot-log-capture.ts` blob `d2545d2e` equal `HEAD`'s, and `git diff
HEAD` is empty.
## Tests and gates
All readings at head `f8a80640`, after merging `origin/main` `7b926f76`;
the merge had no conflicts and the CLI closure was rebuilt, 59/59.
- `pnpm --filter @objectstack/cli typecheck`: exit 0. `tsc --noEmit`
passed, then `check:test-typecheck: OK … 3 file(s) / 28 error(s) / 6
pinned signature(s)`, unchanged. Both test files are in a tsc program
(`--listFilesOnly`): the unit pin under `tsconfig.json`, the e2e under
`tsconfig.test.json`.
- `pnpm --filter @objectstack/cli exec vitest run --project unit
--maxWorkers=2`: `Test Files 263 passed (263)`, `Tests 3881 passed
(3881)`.
- The new e2e was run locally because the diff adds an integration-tier
file: `OS_TEST_TIERS=nightly pnpm --filter @objectstack/cli exec vitest
run --project integration --maxWorkers=2
test/serve-boot-diagnostics-noise-budget.e2e.test.ts` gave `Tests 3
passed (3)`. The rest of the integration tier is declared to CI.
- Gates: `node scripts/pm/dispatch-gates.mjs --commands` derives 65 for
this change set (5 paths vs merge base `7b926f76`). Against the order's
list, that adds `check:cli-test-child-env` and drops
`check:dispatcher-error-vocabulary` and `check:route-envelope`, which no
path here reaches. I ran the union of 68, full `pnpm lint` included. 67
exit 0. `node scripts/check-empty-changeset.mjs --base origin/main`
exits 1: that is the release-note correction named under Changeset.
`dispatch-gates --ran`: `65 derived, 65 run, 0 NOT-MEASURED, 0 UNRUN`.
- Full `pnpm lint` (`eslint . --no-inline-config`): exit 0.
- At `e1a46a46`, before the merge, `check:dual-build-cjs-loads` printed
PREREQUISITE NOT MET because eight packages had no `dist`, so it
measured nothing. After building them it was green (106 require entries
across 66 packages load), and it is green at `f8a80640`.
## Changeset
`.changeset/22160-boot-diagnostics-noise-budget.md`, `@objectstack/cli`
`patch`.
**A pending release note is corrected here, and that needs a person's
confirmation.** One sentence in
`.changeset/22073-boot-warning-one-line-per-class.md` (pending, not yet
released) quoted the old header verbatim: `⚠ Boot diagnostics — 5
warnings logged during startup (8 more already listed above):`. This PR
changes that header, so the sentence would publish false. It now quotes
only the withheld count, `(8 more already listed above)`, which stays
true; nothing else in that note moved. `node
scripts/check-empty-changeset.mjs --base origin/main` is red on this PR
by its own design. It refuses any edit to a changeset the PR did not
add, and for a deliberate correction it asks the PR to name the note and
get it confirmed. The alternative is to restore the note from base and
let the superseded sentence publish.
## Acceptance notes
- **The card is not closed by this PR.** Its pin 4 (the blank template
boots with `0 warnings`; the showcase boots with no non-actionable line
above `info`) depends on the owners in the table above.
- **Boundary:** at `--log-level info` the quiet window does not open
either, so a record's stack streams there too. Changing what
`ObjectLogger` renders at `info` is `packages/core`'s, not this
printer's.
- **Candidate actionable classes, not added (the table is closed and
grows by a row with a real-producer pin):** `[action-governance]
registered handlers with NO declaration …` (its fix is hedged, not
`;`-shaped), and the runtime-assets `Branding asset not served … To fix,
…` (its fix is introduced by `To fix,`, not by `; `). Neither appeared
on the measured boots.
- `docs/qa/platform-checklist/areas/platform-core.json`'s showcase
boot-health reading classifies the `Flows:` banner's `⚠` lines and needs
no change. On a stock showcase the *Boot diagnostics* header is now `ℹ`.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01RWZbGvPFcRKvUqASZtunCU)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 036bf1d commit 11e6fc5
5 files changed
Lines changed: 609 additions & 16 deletions
File tree
- .changeset
- packages/cli
- src/utils
- test
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
8 | 8 | | |
9 | 9 | | |
10 | 10 | | |
11 | | - | |
| 11 | + | |
12 | 12 | | |
13 | 13 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | 2 | | |
3 | 3 | | |
4 | | - | |
| 4 | + | |
| 5 | + | |
5 | 6 | | |
6 | 7 | | |
7 | 8 | | |
| |||
10 | 11 | | |
11 | 12 | | |
12 | 13 | | |
13 | | - | |
| 14 | + | |
14 | 15 | | |
15 | 16 | | |
16 | 17 | | |
| |||
45 | 46 | | |
46 | 47 | | |
47 | 48 | | |
48 | | - | |
49 | | - | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
50 | 71 | | |
51 | 72 | | |
52 | 73 | | |
| |||
303 | 324 | | |
304 | 325 | | |
305 | 326 | | |
| 327 | + | |
| 328 | + | |
306 | 329 | | |
307 | | - | |
| 330 | + | |
308 | 331 | | |
309 | 332 | | |
310 | 333 | | |
| |||
364 | 387 | | |
365 | 388 | | |
366 | 389 | | |
367 | | - | |
| 390 | + | |
368 | 391 | | |
369 | 392 | | |
370 | 393 | | |
371 | 394 | | |
372 | 395 | | |
373 | 396 | | |
374 | | - | |
| 397 | + | |
| 398 | + | |
| 399 | + | |
| 400 | + | |
| 401 | + | |
| 402 | + | |
| 403 | + | |
| 404 | + | |
| 405 | + | |
| 406 | + | |
| 407 | + | |
| 408 | + | |
| 409 | + | |
| 410 | + | |
| 411 | + | |
| 412 | + | |
| 413 | + | |
| 414 | + | |
| 415 | + | |
| 416 | + | |
| 417 | + | |
| 418 | + | |
| 419 | + | |
| 420 | + | |
| 421 | + | |
| 422 | + | |
| 423 | + | |
| 424 | + | |
| 425 | + | |
| 426 | + | |
| 427 | + | |
| 428 | + | |
| 429 | + | |
| 430 | + | |
| 431 | + | |
| 432 | + | |
| 433 | + | |
| 434 | + | |
| 435 | + | |
| 436 | + | |
| 437 | + | |
| 438 | + | |
| 439 | + | |
| 440 | + | |
| 441 | + | |
| 442 | + | |
| 443 | + | |
| 444 | + | |
| 445 | + | |
| 446 | + | |
| 447 | + | |
| 448 | + | |
| 449 | + | |
| 450 | + | |
| 451 | + | |
| 452 | + | |
| 453 | + | |
| 454 | + | |
| 455 | + | |
| 456 | + | |
| 457 | + | |
| 458 | + | |
| 459 | + | |
| 460 | + | |
| 461 | + | |
| 462 | + | |
| 463 | + | |
| 464 | + | |
| 465 | + | |
| 466 | + | |
| 467 | + | |
| 468 | + | |
| 469 | + | |
| 470 | + | |
| 471 | + | |
| 472 | + | |
| 473 | + | |
| 474 | + | |
| 475 | + | |
| 476 | + | |
| 477 | + | |
| 478 | + | |
| 479 | + | |
| 480 | + | |
| 481 | + | |
| 482 | + | |
| 483 | + | |
| 484 | + | |
| 485 | + | |
| 486 | + | |
| 487 | + | |
| 488 | + | |
| 489 | + | |
| 490 | + | |
| 491 | + | |
| 492 | + | |
| 493 | + | |
| 494 | + | |
| 495 | + | |
| 496 | + | |
| 497 | + | |
| 498 | + | |
| 499 | + | |
| 500 | + | |
| 501 | + | |
| 502 | + | |
| 503 | + | |
| 504 | + | |
| 505 | + | |
| 506 | + | |
| 507 | + | |
| 508 | + | |
| 509 | + | |
| 510 | + | |
| 511 | + | |
| 512 | + | |
| 513 | + | |
| 514 | + | |
| 515 | + | |
| 516 | + | |
| 517 | + | |
| 518 | + | |
| 519 | + | |
| 520 | + | |
| 521 | + | |
| 522 | + | |
| 523 | + | |
| 524 | + | |
| 525 | + | |
| 526 | + | |
| 527 | + | |
| 528 | + | |
| 529 | + | |
| 530 | + | |
| 531 | + | |
| 532 | + | |
| 533 | + | |
| 534 | + | |
| 535 | + | |
| 536 | + | |
| 537 | + | |
| 538 | + | |
| 539 | + | |
| 540 | + | |
| 541 | + | |
| 542 | + | |
| 543 | + | |
| 544 | + | |
| 545 | + | |
| 546 | + | |
| 547 | + | |
| 548 | + | |
| 549 | + | |
| 550 | + | |
| 551 | + | |
| 552 | + | |
| 553 | + | |
| 554 | + | |
| 555 | + | |
| 556 | + | |
| 557 | + | |
375 | 558 | | |
376 | 559 | | |
0 commit comments