Skip to content

test(publish-smoke): judge the boot before the probes run - #16976

Merged
baozhoutao merged 2 commits into
mainfrom
claude/issue-16793-publish-smoke-boot-failure
Sep 8, 2026
Merged

test(publish-smoke): judge the boot before the probes run#16976
baozhoutao merged 2 commits into
mainfrom
claude/issue-16793-publish-smoke-boot-failure

Conversation

@claude

@claude claude Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Part of #16793

The registry canary could not name a WARN-level boot failure. Two properties of the
server-log scan combined, and fixing either alone leaves the other:

  1. Severity — a plugin that fails to load logs at WARN; the scan matched error|fatal only.
  2. Order — the scan is the last thing in the script, after the probes, so even at a
    matching severity a boot defect was reported as a probe failure first.

The cost is not a false green (CI went red either way) but ownership attribution: a
boot-load failure and a genuine auth regression are different owners and produced the
same job output.

Following the pattern this script already endorses

This is not a new idea in this file. ~140 lines below the boot, the audience-posture
assertion already refuses early, and its comment states this card's principle outright:

⛔ The RC's own default is not negotiable … the release candidate must go red for it —
BEFORE the behaviour probes, which would otherwise report the widening as a cheerful 200.

The new gate is the same refusal applied to the case that assertion missed, sited between
sections 2 and 3. ⛔ Section 4 was not hoisted — it stays after the probes and keeps
catching what they provoke; hoisting it would drop exactly that. check:...-style pin:
the ORDER test asserts gate before probes before section 4 by byte offset.

The predicate, and the healthy baseline it was chosen against

⛔ Severity is deliberately not the predicate. warn is where this repo puts functional
degradation on purpose (AGENTS.md → Degradation log levels), so a healthy boot has
warn lines and a canary that reds on them stops being read.

Measured, not assumed — the warn-shaped lines a healthy boot emits:

mode run warn-shaped line in a healthy boot
pack 34276056630 (all auth+CRUD probes green, Plugins: 34 loaded) ⚠ Console dist not found — install @object-ui/console …
registry normal for a published artifact TIMESTAMP WARN [MetadataPlugin] artifact … predates this runtime's spec … ADR-0087 conversion

The first carries the same glyph as the specimen. So , warn, and "a plugin word
appears" are all disqualified. So is a bare failed to load grep — it matches the benign
content-load degradations of plugins that did load (SettingsServicePlugin: failed to load translations for 'fr', [platform-objects] failed to load … translations,
Loader … failed to load TYPE:NAME).

The predicate is a unit of the composition did not arrive, in two legs:

  • A — the load site names the unit, which is what gives the run an owner. One
    alternative per real emit site in packages/cli/src/commands/serve.ts: :3917
    (AuthPlugin failed to load:, warn), :3958 (Failed to load plugin:), :4338
    ([Capability:CAP] failed to load). A1 carries a tail guard — "load" must not be
    followed by another WORD — which is the whole difference from the benign family above.
  • B — the kernel's own degraded-boot verdict (packages/core/src/kernel.ts :354/:370).
    This is the answer to "the next boot defect will be worded differently": it never reads
    the cause, so it fires whatever the load site said, or said nothing. By construction never
    benign — a core service that did get a fallback takes the sibling warn
    Service '…' not provided — using in-memory fallback, which the pattern does not match.

What it would not have caught: a boot defect that costs no core service and whose load
site says nothing at all — leg B is keyed to the kernel's core criticality, so an optional
plugin failing silently still slips through. That is stated rather than papered over.

Before / after, with every control

Driven through the real gate block, extracted from the script by text marker:

=== real gate block vs the SPECIMEN boot log ===
block_exit=1
    == Checking the boot for a composition that did not load
    5:  ⚠ AuthPlugin failed to load: The requested module '@better-auth/core/db' does not provide an export named 'createLocalAccountIssuer'
    16:    …Z WARN CORE: Core service missing, functionality may be degraded: auth
    17:    …Z WARN System started with degraded capabilities. Missing core services: auth
    ::error::the server booted WITHOUT part of the composition …

=== real gate block vs the HEALTHY boot log ===
block_exit=0
      ok — the boot log names no failed plugin, capability or core service
    BLOCK_REACHED_END
  • Before: the pre-fix pattern matches 0 lines in the specimen's boot window — pinned as OLD_PATTERN_HITS.
  • Firing control: the verbatim healthy pack boot passes, with a vacuity guard proving it really carries lines.
  • Firing control 2: a healthy registry boot that spells WARN in full also passes. ⭐ This one was
    added because a measurement contradicted my expectation: under a deliberately wrong WARN|warn
    predicate the pack-mode firing control stayed green, since its only warn-shaped line uses the glyph
    and never the word. Without this second fixture the file could not redden on the mistake it exists to prevent.
  • Decoration control (per the NO_COLOR note at :719-721): ANSI placed mid-span
    (⚠ {bold}AuthPlugin{/bold} failed to load:), not at the edges — edge decoration leaves the
    span contiguous and would prove nothing. Measured: unscrubbed the run still detects a failed
    boot (leg B survives) but loses the plugin's name; scrubbed it keeps all three hits. That is
    the measured reason the gate scrubs first rather than trusting NO_COLOR.
  • Benign control: the whole content-load failed to load family plus the in-memory-fallback warn → no match.
  • Nonsense control → no match.

Ablations (each mutation proven on disk by grepping the changed text, each restore proven by
blob hash and git diff HEAD empty):

ablation result
predicate := pre-fix error-only set 4 of 11 red
predicate := WARN|warn wholesale 5 of 11 red, incl. FIRING CONTROL 2
leg B removed (leg A only) 3 of 11 red

Also corrected

Section 4's comment asserted a premise that no longer holds alone — "the #3091 breakage …
would have been caught by ANY error-level line"
. True of #3091, which logged at error level;
the reason it did not generalise is this card's whole subject. Updated in place.

⛔ The Failed to register OIDC discovery routes special case in the error pattern is
kept — nothing here generalises it.

Not in this PR

Server is ready on a degraded boot (#16630), the declared-range half (#16634, on main),
the guidance half (#16500).

Verification

pnpm lint (whole repo, eslint . --no-inline-config) exit 0 · 68/68 derived gate
families run, 64 exit 0 and 4 exit 3 (PREREQUISITE NOT MET — all four need a full closure
build; read as NOT MEASURED, not green, not red) · dispatch-gates --ran reconciles
68 derived / 68 run / 0 unrun · @objectstack/spec test:repo 403 passed, test
13098 passed, typecheck exit 0 (coverage of the new file confirmed with --listFiles
under tsconfig.scripts.json, since the root tsc --noEmit does not reach
scripts/*.test.ts). All at f2af98a45a.

⚠ The end-to-end publish-smoke.sh run is not executed locally — it packs every
publishable package, installs from a registry and boots a server; publish-smoke.yml owns it.
What is executed locally is the real gate block against real boot logs, above.

No changeset: measured with npm pack --dry-run --json in packages/spec276 published
entries, none of this PR's paths among them (positive control: 207 README.md/*.zod.ts
entries do ship); scripts/publish-smoke.sh sits under a private: true root. skip-changeset
label applied.

🤖 Generated with Claude Code


Generated by Claude Code

The registry canary could not name a WARN-level boot failure. Two properties
of the server-log scan combined:

  * SEVERITY - a plugin that fails to load logs at WARN, and the scan matched
    error|fatal only.
  * ORDER - the scan is the last thing in the script, after the probes, so
    even at a matching severity a boot defect was reported as a probe failure
    first.

Measured on the specimen (run 34084559243): the pre-fix pattern matches ZERO
lines in that boot window, after which the job probed every auth and CRUD
route against a server with no auth and failed on a probe, naming nothing.
A boot-load failure and an auth regression have different owners and produced
the same job output.

Adds a boot gate between sections 2 and 3 - the same shape the audience-posture
assertion already uses, which fails before the behaviour probes for the same
reason. Section 4 is NOT hoisted: it stays after the probes and keeps catching
what they provoke.

The predicate is severity-blind and keys on the sentence: a unit of the
composition did not arrive. Widening to WARN was rejected against a measured
healthy baseline - pack run 34276056630 boots healthily while emitting
`Console dist not found` behind the same warn glyph the specimen uses.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012GKcPZbMoGq7WPzKLfRBTU
…ontrol

The pack-mode healthy fixture could not catch the mistake the predicate exists
to avoid. Measured, not foreseen: under a deliberately wrong `WARN|warn`
predicate the firing control stayed GREEN, because that fixture's only
warn-shaped line carries the glyph and never the word.

Registry mode installs the last published release, so a protocol-floor
conversion warning is normal there and arrives as a full `<ISO>Z WARN ...`
line. Asserting a healthy boot that spells WARN in full is what turns "do not
widen the severity set wholesale" into something this file can redden on.

The fixture is reconstructed rather than verbatim - the specimen's own boot
window with the four lines attributable to the defect removed - and its
docblock says so.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012GKcPZbMoGq7WPzKLfRBTU
@claude claude Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 8, 2026
@github-actions github-actions Bot added the size/l label Sep 8, 2026
@github-actions

github-actions Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

⚠️ 1 changed file(s) yielded no anchor (packages/spec/vitest.repo-tests.json), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files. Nothing else in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 1 changed package(s)).

What this run could not see
  • 1 changed file(s) yielded no anchor (packages/spec/vitest.repo-tests.json) — pages documenting those are invisible to this run
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 131 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 53d55dee20e541255696f379be278c4bb3610de7packageMentionDocs.

@claude

claude Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor Author

ACCEPT in substance — PR #16976, reviewed against the tree, ⛔ not against the report

Flip + arm withheld until the sixteen running legs report.

The ordering, verified from the CALL SITE

line landmark
:793 ── 2. boot the dev server
:858 ── boot gate — BETWEEN 2 and 3, and that position is the point
:879 if smoke_boot_failure_lines "$BOOT_LOG"; then ← the gate runs
:887 ── 3. probes
:1096 ── 4. log scan — still last, ⛔ not hoisted

793 < 858 < 879 < 887 < 1096.

⚠️ My first anchor was wrong and I caught it before reporting: SMOKE_BOOT_FAILURE_PATTERN first appears at :488 in a docblock, and an ordering claim resting on that would have been an argument about a comment. Re-anchored on the executable if at :879. Same class as the seven false zeros this seat has logged today: a landmark you did not verify is executable is not a landmark.

probe want got
SMOKE_ERROR_LOG_PATTERN extracted and reused >0 4
SMOKE_BOOT_FAILURE_PATTERN defined >0 4
⛔ the stale premise «would have been caught by ANY error-level line» 0 0
staleness control — that sentence was on origin/main 1 1
the OIDC special case kept >0 3
firing control — section 4 still after section 3 yes yes
nonsense control 0 0

⭐ The predicate is not a severity rule, and that is the whole answer

'[[:alnum:]_]+Plugin failed to (load|start)[[:space:]]*($|[^[:alnum:][:space:]])|[Ff]ailed to load plugin|\[Capability:…'

Severity-blind, and stated as "a unit of the composition did not arrive" in two legs: the load site naming the unit (one alternative per real emit site in serve.ts), and the kernel's own degraded-boot verdict, which never reads the cause and therefore fires whatever the load site said — or said nothing.

Leg B is the part I would not have specified and it is the better half. It is by construction never benign: a core service that did get a fallback takes the sibling warn Service '…' not provided — using in-memory fallback, which the pattern does not match. ⇒ the gate catches boots whose load site is silent, which is precisely the class a wording-based grep leaves open.

⭐ And A1's tail guardload must not be followed by another word — is what separates it from the benign content-load family (SettingsServicePlugin translations, platform-objects translations, Loader TYPE:NAME, webhook-auto-enqueuer, SmtpTransport nodemailer). One character of grammar doing the work a severity threshold could not.

⭐ The healthy baseline was measured, in TWO modes, and it disqualified both naive options

I asked for the healthy-boot WARN baseline before the predicate was chosen. It came from real CI runs, not fixtures:

  • pack mode (run 34276056630): exactly one warn-shaped line — Console dist not found … — and zero structured warns.
  • registry mode (the specimen, run 34084559243): four structured boot warns, one baseline-normal for that mode (the ADR-0087 protocol-skew notice — the canary installs the last published release, so skew is expected) and three consequences of the defect.

⇒ Wholesale-WARN reds a healthy boot in both modes. ⭐ And the sharper disqualification: the healthy pack-mode line carries the same warn glyph as the specimen, so the glyph is out too. A bare failed to load grep is out separately, on the content-load family.

⇒ Every cheap answer was excluded by measurement, which is the only way that exclusion is worth anything.

⭐ Two unexpected measurements, reported rather than smoothed over — the best thing in this report

1. The WARN-wholesale ablation FAILED to red the firing control, the opposite of the prediction. Cause: the healthy pack fixture's only warn-shaped line uses the glyph and never the literal word, so it could not discriminate against a WARN|warn predicate. ⇒ the firing control was weaker than claimed. Fixed by adding a second healthy fixture that spells WARN in full; the ablation now reds it.

⭐ That is the discipline turned on itself: a control that cannot fail on the mutation it exists to catch is decoration, and finding that out by running the ablation you expected to pass is exactly why ablations are run.

2. Mid-span and edge ANSI decoration are NOT equivalent. With decoration at the edges the unanchored pattern matches even unscrubbed ⇒ a decorated-line test built on that shape proves nothing about the scrubber. Only mid-span shows what scrubbing buys — and the measured answer is precise: unscrubbed, leg B still detects the failed boot but the plugin's NAME is lost; scrubbed, all three hits survive. ⇒ the scrubber buys attribution, which is this card's entire subject.

⚠️ I asked for a decorated control on the strength of :719-721. The report came back with a better control than the one I specified, plus the reason mine would have been vacuous.

⭐ The process incident — read this one

The first ablation's restore leg was git checkout HEAD -- scripts/publish-smoke.sh, and at that moment HEAD was still origin/main, which does not carry the fix — so "restore" silently undid the work instead of undoing the mutation, and its blob-hash check passed because a blob did exist, just the wrong one.

Detected immediately (an anchor assertion found no SMOKE_BOOT_FAILURE_PATTERN), work reapplied, committed, both ablations re-run against a HEAD that carries the fix, and the harness now asserts the precondition explicitly with a distinct exit code.

A restore-from-HEAD is only a restore if HEAD carries the thing being restored — and a blob-hash equality check does not notice, because it compares against the wrong reference by construction. That is a platform reading worth more than the card, and it was volunteered.

File surface — one declared extension, accepted

packages/spec/vitest.repo-tests.json (+1 line) is outside the literal surface. ⇒ Accepted: it is the registration list for the vitest repo project, and without the entry the new test is never executed — registering it is the mechanism by which the test is a test. ⭐ And the coverage question was checked, not assumed: the root tsc --noEmit does not reach scripts/*.test.ts (--listFiles count 0, same as the sibling port-collision test); tsconfig.scripts.json does (count 1).

skip-changeset was measured, not categorised: npm pack --dry-run --json shows 276 published entries and none of this PR's paths among them, with a positive control that 207 README.md/*.zod.ts entries do ship.

Residuals, correctly named

  • serve.ts:3958 and :4338 print bare error-intent text (no [error] prefix, no ISO-Z ERROR), so section 4's pattern never matched them either. Both are boot-time-only sites ⇒ now covered by the boot gate, and the residual miss is theoretical. Recorded inline in the script next to the alternatives that cover them, so the next reader finds it without this report.
  • A healthy pack-mode RC emits Console dist not found …. ⛔ Correctly not filed — whether a packed RC should ship a console dist is a packaging question the dev had no evidence about, and filing on a guess is worse than flagging it. ⇒ Noted here for whoever owns pack-mode fidelity.

Four gates NOT MEASURED, declared

check:dts-closure, check:dual-build-cjs-loads, check:sourcemap-no-sources-content, check:type-check-debt — all exit 3 PREREQUISITE NOT MET (they need a full closure build and say "This is NOT a pass"). 68 of 68 derived families run, 0 UNRUN; pnpm lint whole-repo, so no narrowing claim is owed.


Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/l skip-changeset PR has no user-facing published change; bypasses the changeset gate tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants