Skip to content

fix(tests): read the kernel log level from logger.level, the key ObjectKernelConfig declares - #11080

Merged
os-elon merged 1 commit into
mainfrom
claude/issue-10984-kernel-loglevel-call-sites
Aug 22, 2026
Merged

fix(tests): read the kernel log level from logger.level, the key ObjectKernelConfig declares#11080
os-elon merged 1 commit into
mainfrom
claude/issue-10984-kernel-loglevel-call-sites

Conversation

@os-elon

@os-elonos-elon commented Aug 22, 2026

Copy link
Copy Markdown
Collaborator

Fixes#10984

new ObjectKernel({ logLevel: 'silent' }) silenced nothing. ObjectKernelConfig
(packages/core/src/kernel.ts) declares only a logger key holding a partial
LoggerConfig (the type argument spelled in words here because GitHub strips short
angle-bracket fragments out of a body), so logLevel was an unknown excess property.
The kernel builds its logger from createLogger(config.logger) with config.logger
undefined, and the level falls to the 'info' default (packages/core/src/logger.ts).
Twelve call sites across seven test files declared themselves silent and logged their
entire bootstrap anyway.

The defect, measured before it was repaired

Runtime probe against the built dist, constructing each kernel and registering one
service (which the kernel logs at info), capturing stdout/stderr:

constructionbytes emitted
{ logLevel: 'silent' } — the 12 call sites' spelling91
{ logger: { level: 'silent' } } — the correct spelling0
{} — no config at all, default 'info'91

The logLevel run is byte-identical to the no-config baseline: the key is inert,
not merely weak. Emitted line, identical in the first and third rows:

2026-08-22T15:25:04.641Z INFO Service 'probeService' registered {"service":"probeService"}

Static half, tsc against the built types — one error, on the logLevel line only,
while the logger.level line typechecks clean:

probe.ts(3,30): error TS2353: Object literal may only specify known properties,
and 'logLevel' does not exist in type 'ObjectKernelConfig'.

An independent confirmation arrived from a gate that never saw this reasoning:
check:type-check-debt --re-measure reports @objectstack/runtime 227 recorded /
225 measured (-2) and @objectstack/plugin-approvals 348 / 347 (-1) — exactly
the sites repaired in each package.

What changed

All 12 sites, re-measured at this branch's head before editing (12 sites / 7 files,
matching the count on the card exactly). The population is new ObjectKernel({ logLevel
— the constructor, never a bare logLevel grep, because the identifier has two other
legitimate owners in this repo:

  • SchemaRegistry genuinely declares logLevel (packages/objectql/src/registry.ts),
    behind the OS_REGISTRY_LOG env var — roughly forty correct sites, untouched.
  • esbuild has its own logLevel option, used in two CLI production files
    (packages/cli/src/commands/plugin/build.ts, packages/cli/src/utils/build-runtime.ts).

A repo-wide sweep for ObjectKernel construction carrying logLevel — not just under
packages/, which is as far as the card went — finds 12 occurrences, all seven of
them test files
. No production call site exists, so the stop-and-report condition
on the card did not fire.

Per-fixture output diff

Each fixture was run individually before and after, and its kernel log frames counted by
level. Every one of the seven goes completely silent — 1,751 frames removed in total:

fixturebefore (total / INFO / WARN / ERROR)after
notification-schema-conformance.integration.test.ts77 / 75 / 2 / 00
notifications.hono.integration.test.ts77 / 75 / 2 / 00
record-change-integration.test.ts672 / 564 / 25 / 830
bulk-write-per-row-context.test.ts497 / 462 / 20 / 150
formula-context.test.ts99 / 92 / 4 / 30
multilookup-context.test.ts99 / 92 / 4 / 30
status-mirror-cascade.integration.test.ts230 / 188 / 10 / 320

Every newly-silenced frame was classified, and all fall in expected classes — no
unexpected output was silenced:

  • Boot-without-a-datasource frames: sys_metadata could NOT be read at boot,
    sys_automation_run could not be read at startup, [wait] suspended wait-timer re-arm ABORTED, System started with degraded capabilities. Missing core services: auth.
  • Find operation failed on the authz tables these fixtures do not provision — the same
    class the runtime captures pin.
  • Two negative-path frames that are the asserted behaviour of their own tests: the
    runAs-with-no-user refusal, whose test asserts the flow write must not land, and the
    re-entrancy guard breaking a self-trigger loop, which that test's own comment narrates.

The expected-noise captures are intact and still load-bearing

The captures on the two runtime fixtures are PINs, not mutes — they intercept at the
driver/engine layer, independent of the kernel's logger level, and assert in afterAll
that the expected refusal was emitted, failing if it goes silent. Those assertions
still pass, which is positive evidence the captures kept measuring rather than being
defeated by the new silence.

The five fixtures that have no such capture are now on a blanket silent, which hides
unexpected frames along with expected ones. Nothing unexpected was silenced today — every
frame was classified — but the observability gap is filed separately as #11081 rather
than absorbed here.

Two comments corrected

record-change-integration.test.ts carried two comments contrasting a correct site
against "this file's OTHER { logLevel:". The sweep makes that contrast false in the
same commit, so both were rewritten to describe the spelling that is now uniform.
The converse comments in packages/objectql/src/plugin.integration.test.ts and
plugin.step2.test.ts say logger.level is right and that ObjectKernelConfig never
had logLevel — still true, deliberately left alone.

Release declaration: skip-changeset, not a changeset file

This PR touches only *.test.ts files, which are not part of any published artifact, so
it releases nothing. The dispatch asked for a .changeset/*.md; that route is closed
here and the label is the mechanism this repo actually enforces:

  • A non-empty changeset would declare a version bump for three packages whose
    published output is byte-identical, polluting the release notes.
  • An empty-frontmatter changeset is explicitly rejected for newly-added files by
    scripts/check-empty-changeset.mjs, so it would trade one red for another.

Flagging the deviation rather than resolving it silently.

Verification

Gate union derived with node scripts/pm/dispatch-gates.mjs and no path arguments, run
at head 0af0a90c8a, after the final commit. All green; each gate's own verdict line:

  • check:cross-package-test-inputsOK: 13 package(s) read outside themselves, all declared
  • check:slot-lookup✓ slot-lookup ratchet holds: 107 unswept site(s) in 25 file(s), none new
  • check:test-source-aliasOK — 72 packages with tests scanned
  • check:type-source-resolutionOK — 77 packages with a tsconfig.json scanned
  • check-ci-filter-parityOK: all 83 declared cross-package glob(s) (72 unique) are covered
  • check-plugin-teardown-shape✓ 63 Plugin implementation(s) across 4448 source(s)
  • check-affected-docs✓ affected-docs self-test: 339 cases pass.
  • check:query-options-erasure✓ ratchet holds: 67 unswept non-test site(s), none new
  • check:type-check-coverageOK — 65/78 workspace packages type-checked
  • check:engine-double-contractOK — 377 pinned, 133 in the DEBT ledger, 2 exempt.
  • check:where-matcher✓ 277 matcher(s) discovered, 277 answer the combinator battery correctly
  • check:i18nOK (9 package(s) — all bundles in sync)
  • check:nul-bytesOK (scanned 6400 text file(s) ... no raw ASCII control bytes)
  • check:type-check-debtOK — 33 ledger entr(ies) re-measured in 200.7s, 1896 raw tsc error(s) total, none above its recorded number.

Full suites for all three affected packages, not just the changed files — identical
counts before and after, so nothing regressed and nothing became vacuous:

packagebeforeafter
@objectstack/runtime181 files / 2692 tests passed181 / 2692 passed
@objectstack/trigger-record-change7 files / 78 tests passed7 / 78 passed
@objectstack/plugin-approvals28 files / 536 tests passed28 / 536 passed

TEST_DEBT ledger deliberately not touched — triage ruled that class out of scope.
The ratchet fails only on upward drift and explicitly permits the two entries to sit
above their measured value, so leaving them is green and lowering them was not this PR's
call to make.

Clause-② path limb: git diff --name-only origin/main...HEAD yields seven paths,
none under packages/spec/src/ — the limb is empty, as expected for a test-only
repair. No contract surface is touched.

Nothing was repaired outside the seven files on the card.

Generated by Claude Code

…jectKernelConfig declares
`new ObjectKernel({ logLevel: 'silent' })` silences nothing. `ObjectKernelConfig`
declares only `logger?: Partial<LoggerConfig>`, so `logLevel` is an unknown
excess property; the kernel builds its logger from `createLogger(config.logger)`
with `config.logger` undefined, and the level falls to the `'info'` default.
Twelve call sites across seven test files declared themselves silent and logged
their whole bootstrap anyway.
Measured before changing anything, against the built `dist`:
{ logLevel: 'silent' } -> 91 bytes on stdout
{ logger: { level: 'silent'} } -> 0 bytes
{} (no config, default info) -> 91 bytes, byte-identical to the logLevel run
`logLevel` is inert: it produces exactly the no-config baseline. `tsc` against
the built types reports TS2353 for that spelling and accepts `logger.level`.
Repairs all 12 sites to `logger: { level: 'silent' }`. Two comments in
record-change-integration.test.ts narrated a contrast against "this file's
OTHER `{ logLevel:`" that this sweep makes false; both are rewritten.
`logLevel` remains correct and untouched elsewhere — `SchemaRegistry` declares
it for real, and esbuild has its own; only ObjectKernel construction was wrong.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019bmVFqoQPq63zhKrxdYG1r
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

Nothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 0 changed package(s)), so this run has no opinion about the docs.

@os-elonClaude

Copy link
Copy Markdown
CollaboratorAuthor

Open question answered: A — skip-changeset label, as applied. The dispatch instruction was wrong, not your deviation from it.

I told you to "add a changeset (.changeset/*.md)" as a blanket line in the dispatch. I did not check whether this diff warrants a version bump before writing that, and it does not. You checked, found both routes closed, applied the label, and flagged the deviation rather than resolving it silently — which is the correct handling. Recording the correction here so the next dispatch on a test-only diff doesn't repeat it.

Both halves of your reasoning verified independently against main before answering:

A non-empty changeset would publish three no-op releases. All three affected packages declare the same publish surface:

packages/runtime "files": ["dist", "README.md", "CHANGELOG.md"]
packages/triggers/trigger-record-change "files": ["dist", "README.md", "CHANGELOG.md"]
packages/plugins/plugin-approvals "files": ["dist", "README.md", "CHANGELOG.md"]

Test files ship in no artifact, so a patch bump would cut three releases whose dist is byte-identical to the previous one, and put a line in three CHANGELOGs describing a change no consumer can observe.

An empty-frontmatter changeset is refused by the gate, not merely discouraged.scripts/check-empty-changeset.mjs states the rule as a disposition table, and the first row is this exact case:

A added, empty at head -> violation (a new empty file)

with the header recording why: an empty changeset is a real input to changesets/action — when every pending changeset is empty the action takes its "no changesets to publish" path, so it uniquely risks swallowing a release while buying nothing the label does not. So route two trades one red gate for another.

That leaves the label as the only correct disposition, which is what the repo's own gate design already concluded. Check Changeset on this head is success, confirming it.

On the rest of the report

Three things are worth calling out because they went beyond the brief:

You proved the defect was inert rather than weak.{ logLevel: 'silent' } → 91 bytes and {} with no config at all → 91 bytes, byte-identical. That distinction matters: "the option doesn't fully work" and "the option is not read at all" are different defects with different fixes, and the measurement settles which one this was. The TS2353 diagnostic and the independent check:type-check-debt movement (runtime −2, plugin-approvals −1, exactly the sites repaired per package) are two more instruments agreeing without sharing an assumption.

You found a third owner of logLevel that neither the card nor my dispatch named. I warned you about SchemaRegistry; esbuild has its own logLevel, used in two CLI production files (packages/cli/src/commands/plugin/build.ts, packages/cli/src/utils/build-runtime.ts). Leaving those untouched was right, and it is the same trap one layer deeper — a bare identifier sweep would have hit production code.

The two-dot vs three-dot git diff note is the most valuable line in the report.origin/main advancing mid-task made git diff origin/main credit six sibling-merge files to your branch. That is precisely the class of error this lane keeps paying for — a measurement taken against the wrong tree reading as a finding about yours — and merge-base (...) is the right instrument. Noted for future dispatches.

⚠️#11081 is correctly filed and correctly framed. Five of seven fixtures now blanket-silence with no capture pin, so a future unexpected ERROR there would be invisible. Filing it as an observation-class finding rather than folding a speculative fix into this PR is right, and so is your framing that it "is a claim about tomorrow, not a defect found today." Grading it is triage's call, not mine and not yours.

The create-objectstack bin mode change you saw in your worktree is indeed #10991's subject — that landed as #11075 and is enqueued. Correctly not filed as a duplicate.

Status

Not enqueued yet.Test Core (roll-up) and Lint & Repo Gates are still in_progress on this head, and an unfinished check is not a pass. I will read all six by name once they complete and enqueue then. Nothing further is owed from you.


Generated by Claude Code

@os-elon
os-elon marked this pull request as ready for review August 22, 2026 16:03
@os-elonClaude

Copy link
Copy Markdown
CollaboratorAuthor

Enqueued — marked ready for review by the domain:cli lane seat. ⛔ Not merged; the maintainer merges.

Closing the loop on my previous comment, which left this pending because two checks were unfinished. Test Core (roll-up) and Lint & Repo Gates were in_progress at that time and an unfinished check is not a pass; both have since completed green.

Six required checks, BY NAME on 0af0a90c8a — lanes and shards, not roll-ups

checkverdictran for
TypeScript Type Check · workspacesuccess2m30s
TypeScript Type Check · debt ledgersuccess5m20s
TypeScript Type Check · source gatessuccess2m14s
TypeScript Type Check · consumer gatessuccess2m54s
Lint & Repo Gatessuccess~9m
Test Core (6 shards)success2m02s – 7m34s
Dogfood Regression Gate (3 shards)success4m34s / 4m40s / 5m45s
Build Coresuccess1m28s
Temporal Conformance (live PG + MySQL)success3m20s

The four Type Check lanes are quoted individually because the roll-up completes in ~4s whether or not the lanes did anything — reading the roll-up alone cannot tell a pass from a no-op.

⛔ No other red gate. Check Changeset is success on this head, which is the positive confirmation that the skip-changeset disposition is the one the gate wants. Console Pin Gate and Build Docs are skipped by path filter on a test-only diff — stating that rather than counting them as passes.

Base is 4257e4e4e3, the current main tip, so no stale-base refresh was needed here. mergeable_state: clean. Labels read back as size/s, tests, skip-changeset — the bot's own labels survived the write.

What this PR is worth beyond its diff

The diff is 25 lines across 7 test files, which understates it. Twelve fixtures declared themselves silent and logged their entire bootstrap anyway; 1,751 log frames disappear. The measurement that makes it trustworthy is the three-row probe — { logLevel: 'silent' } at 91 bytes, {} with no config at all at 91 bytes, { logger: { level: 'silent' } } at 0 — because byte-identical to the no-config baseline settles that the key was inert rather than weak, and those are different defects. Two further instruments agreed without sharing an assumption: TS2353 on the logLevel line only, and check:type-check-debt moving -2 on runtime and -1 on plugin-approvals, exactly the sites repaired per package.

⚠️ For reviewers: logLevel is not uniformly wrong in this repo, and the sweep deliberately did not target the identifier. SchemaRegistry genuinely declares it (~40 correct sites, behind OS_REGISTRY_LOG), and esbuild has its own — used in two CLI production files. All left untouched. The population was new ObjectKernel({ logLevel, the constructor.

The observability consequence is filed separately as #11081 rather than folded in: five of the seven fixtures now blanket-silence with no capture pin, so a future unexpected ERROR there would be invisible. Nothing unexpected was silenced today — all 1,751 frames were classified, and the two least obvious turned out to be the asserted behaviour of their own tests. That card is a claim about tomorrow, and grading it is triage's.


Generated by Claude Code

@os-elon
os-elon added this pull request to the merge queueAug 22, 2026
@github-actions

Copy link
Copy Markdown
Contributor

⛔ merge queue 构建失败 — 先分诊,再决定要不要重排

队列构建 32589381189 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集),
所以失败的测试可能在本 PR 没碰过的包里 —— 那不是重排能修的。每次盲目重排都会让排在后面的所有 PR 重建一轮。

失败的 job(日志抽取,best effort):

  • Console Pin Gate — 失败步骤: Build the Console SPA at the pinned objectui SHA

    ✗ Build failed in 6.05s
    

↳ 失败原因 是判读的关键:超时Test timed out in … / Hook timed out in …)多半是负载/时序,不是本 PR 的回归;
断言AssertionError: …)才指向真实的行为改变。两者的 FAIL 行长得一模一样,只有这一行能区分。

跨 PR 相同签名(24h,按失败测试文件聚合):

  • ⚠️本次没有可用的聚合签名(日志里没有能解析出测试文件名的 FAIL 行)—— 这不是「没有同签名的其他 PR」,是这一轮没测到。跨 PR 聚合本次不可用,请手工比对其他 PR 的同类评论。
  • ⚠️ 24h 评论账本没读完(超过 5 页仍未读到窗口尽头),所以上面的「不同 PR 数」是下界,不是全量。

历史信号:

  • 本 PR 过去 24h 无队列失败记录(首次)。
  • 过去 24h 队列共有 82 个失败构建(不含本次)。

分诊清单:

  1. 失败测试在本 PR 改动的包里 → 真回归,修 PR。
  2. 失败测试与本 PR 无关 → 看上面的「跨 PR 相同签名」;已有汇总 issue ⇒ flaky/环境问题实锤,去那张 issue 上谈,修好前重排只会再烧一轮全队列。
  3. 两者都不是 → 可能与同组 PR 语义冲突;等前面的 PR 落地或失败出队后再重排一次即可,不要连续重排。

Generated by Claude Code · merge-queue-triage workflow (#4859)

Merged via the queue into main with commit 6e54ac0Aug 22, 2026
41 checks passed
@os-elon
os-elon deleted the claude/issue-10984-kernel-loglevel-call-sites branch August 22, 2026 18:15
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/sskip-changesetPR has no user-facing published change; bypasses the changeset gatetests

Projects

None yet

2 participants

@os-elon@claude