Repository navigation
fix(cli,plugin-auth): one boot line per warning class, each printed once, and the loopback OAuth notice at info - #22097
Conversation
…withholds what the banner restated Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RWZbGvPFcRKvUqASZtunCU
…ack issuer, warn on private and link-local Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RWZbGvPFcRKvUqASZtunCU
…st the real automation plugin Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RWZbGvPFcRKvUqASZtunCU
Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RWZbGvPFcRKvUqASZtunCU
…e with the banner Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RWZbGvPFcRKvUqASZtunCU
…lass Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RWZbGvPFcRKvUqASZtunCU
…ssets branding warning's shape Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RWZbGvPFcRKvUqASZtunCU
📓 Docs Drift Check9 anchor(s) derived from 2 changed package(s); no hand-written page names any of them, so this run has nothing to list — not a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run. What this run could not see
Coarse fallback — 36 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin a1d155ac9d999bb9a3af743f4820e839a20183d7 && git checkout a1d155ac9d999bb9a3af743f4820e839a20183d7
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin bafb58bb08aa3989b1d72a633496b361fcf0842e d722b4746e2a7f3bf55b53efb7f9d51a276e0c9c && git checkout -B drift-repro bafb58bb08aa3989b1d72a633496b361fcf0842e && git merge --no-ff d722b4746e2a7f3bf55b53efb7f9d51a276e0c9c
node scripts/docs-audit/affected-docs.mjs --json bafb58bb08aa3989b1d72a633496b361fcf0842e |
⛔ merge queue 构建失败 — 先分诊,再决定要不要重排队列构建 37655130539 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集), 分类: 失败的 job(日志抽取,best effort):
跨 PR 相同签名(24h,按失败测试文件聚合):
历史信号:
分诊清单:
Generated by Claude Code · merge-queue-triage workflow (#4859) |
Fixes #22073
Clause-②: no
What changes
Three boot-output changes, as the triage ruling on the card (comment
6038562452) directs. Log text and log levels only: no accept set, answer or public signature moves.printAutomationSummary(packages/cli/src/utils/format.ts) groups the engine's binding audit by (trigger type, reason) and prints one line per class with its flows listed. The class's short text is the reason's first sentence, derived byleadSentence(). It is never a second, hand-written wording: the engine owns every reason sentence (describeUnboundReason, andscheduledWorkDisabledReasonfor the deployment policy). A reason that is already one sentence (a missing trigger, a binding failure, a declined subflow) prints whole, remedy included. Distinct reasons stay distinct lines.printAutomationSummarynow returns the identifying text of every logger record it restated, at the moment it prints its own line.printBootDiagnosticswithholds exactly those and counts them in its header:⚠ Boot diagnostics — 3 warnings logged during startup (2 more already listed above):. The shadowed-flow bootstrap restatement is covered by the same rule. The pull-timeFlow name collisionrecord still replays, because it says which rule armed the body.info. Inpackages/plugins/plugin-auth/src/auth-plugin.ts,OAuth is served UNENCRYPTED: …logs atinfowhen the issuer's host is loopback and stayswarnfor private and link-local hosts. The sentence is the same and is still emitted on every accepted plain-HTTP boot, so D1 of ruling batch Fix fumadocs-mdx validation: flatten nested pages in concepts meta.json #210 item 5 ("eligibility decides WHICH sentence, never WHETHER") holds across both levels.Where the long text now prints: at
--log-level debug(orinfo) the boot-quiet window never opens. The live stream then carries@objectstack/service-automation's per-flow[Automation] flow 'NAME' declares a 'TYPE' trigger but is NOT bound — it will never auto-launch. REASONwarning, with the whole reason. When any class line was shortened, one dim line under the classes says so.Measured, before → after (H1)
examples/app-todohas 2 package-authoredscheduleflows. Booted withos dev --fresh --no-watch, scheduled work off (the default), default log level:3d918850)NOT boundsentenceOAuth is served UNENCRYPTEDlines (localhost)WARN, in Boot diagnostics)The after banner line:
The second producer was the one the PM named: the banner's
a.unboundloop, plus Boot diagnostics replayingservice-automation'skernel:bootstrappedaudit warning (plugin.ts:1533). There was no third copy. By construction, the hotcrm shape (8 flows) goes from 16 lines to 1 class line plus 1 hint line. hotcrm itself was not booted here.How a new boot warning stays single (H7)
Boot diagnostics withholds only a record that a banner section handed back as restated when it printed its own line. Today the only such section is the automation summary, and only for the service-automation audit records it summarises.
logger.warn, such as the branding-asset warning cli: app branding assets are served only from<cwd>/assets(orOS_RUNTIME_ASSETS_DIR) and silently skipped when that directory is absent — an artifact boot from any other directory draws a broken logo and favicon, with no warning #22071 adds, is restated by no banner section. It is never matched, so it replays once.BootDiagnosticsReplayOptions.restatedAbove.PM readings, measured
service-automationis untouched. Hosts that boot the kernel without the CLI banner read the service's warning as their only notice:packages/runtime/src/domains/automation.ts(the runtime composition every adapter host uses),packages/verify/src/harness.ts, and the plugin's own comment atplugin.ts:1490–:1492("The warn matters for embedded hosts and tests"). Lowering that warning, or grouping it, would change what those hosts see. No@objectstack/service-automationchangeset.getAuthIssuer(),getMcpResourceUrl()andisMcpOAuthEnabled()all derive from the onegetCanonicalOrigin(), so "every published origin is loopback" reads as "the issuer's host is loopback". Partly falsified: the transport rule has no separable loopback predicate to reuse.isOAuthEligibleBaseUrlcheckslocalhost/*.localhostinline (auth-manager.ts:558), andisPrivateOrLoopbackHostLiteralchecks127.0.0.0/8and::1in the same function as the private ranges. Extracting one means editingauth-manager.ts, which is outside this card's declared file surface. So the notice's module-localisLoopbackIssuer()composes the rule's own pieces: the samelocalhost/*.localhostnames, the127.0.0.0/8block judged by the same ADR-0069 D5 matcher (ipMatchesRange, already exported), and[::1]. It adds no new host regex, and it reads the WHATWG-canonical hostname as the rule does. It is asked only of an issuer the rule already accepted. Agreement is pinned by the loopback / private table below. Extracting one shared predicate is left as an open question in the report.mcp-oauth-plaintext-notice.test.tsflip, both for the ruling's reason.fires on a loopback deployment too — plain HTTP is plain HTTP(localhost: 1warn) is replaced bylogs the sentence at info, never warn, on a LOOPBACK deployment, overlocalhost,app.localhost,127.0.0.1,127.0.0.2,127.1and[::1], each 0warnand 1infonaming the issuer.every plain-HTTP boot gets exactly one of the two sentencesnow counts across both levels; its localhost row would otherwise read 0. The control stayswarn:is emitted at warn on a private address(172.16.0.1), plus a newkeeps the warning on a PRIVATE or LINK-LOCAL deploymentover10.0.0.5,172.16.0.1,192.168.1.10,169.254.10.20,[fd00::1]and[fe80::1].packages/cli/src/commands/doctor.ts:289is unchanged. Text that quoted the old lines moved with them, outsidecontent/docs/**:docs/qa/platform-checklist/areas/platform-core.json: theplatform-core.boot-healthacceptanceverifyquoted the per-flow banner line. Revision 6 → 7, with a history entry.docs/qa/platform-checklist/areas/ai.json:ai.mcp-oauth-private-host-transportexpected the accepted-transport line "exactly 1 on (c)" (loopback) at the default level. It now names the level per boot, and boot (c) runs at--log-level info. Revision 2 → 3, with a history entry.pnpm check:platform-checklistis green.content/docs/**page needed an edit.automation/flows.mdx:2380anddeployment/environment-variables.mdx:91say such flows are "listed by … the CLI startup summary as disabled by deployment policy", which is still true.releases/v17/17-5.mdxis release-owned and untouched.packages/spec/scripts/publish-smoke-boot-failure.test.tsholds verbatim historical specimens of the old header with no restated records, which are still valid.Tests (at
de6b4a08)New
packages/cli/src/utils/format.boot-warning-classes.test.ts, unit tier, 11 tests. The first block boots the realAutomationServicePluginon aLiteKernelwith 8scheduleflows and scheduled work off. It captures stdout through the sameBootLogCaptureserveuses, collects throughcollectAutomationSummary, and prints the real banner. Pins:a 'schedule' trigger, and it names all 8 flows;The premise is asserted first: the producer warned once per flow into the capture. Formatter legs cover distinct classes, the first-sentence cut, the hint only when shortened, a JSON-format record, a record the banner did not restate (never withheld), the shadowed restatement withheld while the collision record is kept, and the no-banner path replaying everything.
pnpm --filter @objectstack/cli exec vitest run --project unit: 260 files, 3797 passed.pnpm --filter @objectstack/cli typecheck: exit 0. The new file is intsconfig.json's program (--listFilesOnly), andcheck:test-typecheckis OK. Theintegrationtier is declared to CI: the diff touches no spawn entry, no integration-tier file and no driver.pnpm --filter @objectstack/plugin-auth test: 126 files, 2623 passed, 10 skipped.pnpm --filter @objectstack/plugin-auth typecheck: exit 0.Ablations. Each mutation went through
scripts/ablation-replace.mjsagainst the committed fix, with the anchor hit verified on disk and the restore proven as blob == HEAD with an emptygit diff HEAD. The subjects resolve from source, so no rebuild was needed.info. 6 red: every loopback spelling.Gates.
node scripts/pm/dispatch-gates.mjs --ranreports 72 derived, 72 run, 0 NOT-MEASURED, 0 UNRUN atde6b4a08. The derivation addedpnpm --filter @objectstack/lint run check:doc-formula-expressionsandpnpm check:platform-checklistto the PM's list.check:dual-build-cjs-loadsandcheck:i18n-coveragefirst exited 3 (PREREQUISITE NOT MET, unbuilt workspace packages). Afterpnpm turbo run build, they and the other dist-reading gates were re-run, all exit 0. Fullpnpm lintexits 0 atde6b4a08. Narrowed eslint (--no-inline-config --format json) over the 4 changed TS files: 0 errors, 0 warnings. The other 3 changed files are outside eslint's population ("no matching configuration"), and the config enables no type-aware linting (eslint.config.mjs:327).Acceptance notes (not filed)
⚠ Server is ready — DEGRADED: missing core services: …with the kernel'sSystem started with degraded capabilities …record. This is deliberately left: it is the ready-line status, not a banner-list entry, andserve-ready-degraded-boot.e2e.test.tsasserts the kernel sentence in the same output as its premise.warn(Flow 'NAME' is registered but NOT armed on trigger 'TYPE' — …). That is a different record from the bootstrap audit, so it still replays beside the banner's class line. The case is rare, and the record carries the subflow detail.Generated by Claude Code