Repository navigation
finding: test-suite console chatter measured at 61k lines per full repo run — dogfood 41k and objectql 5k dominate, and the loudness is a logger-default question #13517
Description
Activity
zhuangjianguo commented
on Aug 31, 2026 CollaboratorMore actions路由(skills 席代分诊):
domain:devx,暂不定级 —— 卡自述无紧迫(代价是 CI 日志体量非正确性),修法是 vitest 下 logger 默认级的取舍(三个候选在卡)。devx 车道按日志/测试基建口径排;若与 objectql logger 默认耦合再拉 engine 会审。
Generated by Claude Code
- added and removed
on Aug 31, 2026 分诊定级 →
domain:engine(自domain:devx改路由)· p3 · tests ·pm:queue。摘finding。⚠️ 改路由,理由是落点读数。 skills 席 2026-08-31 代分诊挂了domain:devx,但按锚定规则(域 = 修复落地的那个包所属的域,读代码判定)量下来不成立 —— 卡片贡献的 41k 行里占主导的那类文本出自:packages/objectql/src/registry.ts:1510 this.log(`[Registry] Registered namespace: ${namespace} → ${packageId}`) packages/objectql/src/registry.ts:1744 this.log(`[Registry] Registered object: ${fqn} (${ownership}, priority=…) from ${packageId}`) packages/objectql/src/registry.ts:3042 this.log(`[Registry] Registered ${type}: ${storageKey}`)packages/objectql⇒domain:engine。卡片自己也把结论指到这里:"quieting the chatter is a question about@objectstack/objectql's (and the registry's) default log level under test, separate from the teardown-race mechanism" —— 它给的三个候选修法里有两个(vitest 下调低 registry/engine 默认级别、把逐对象注册行降到 debug)都落 objectql,只有第三个(测试 harness 里文档化OS_LOG_LEVEL默认值)落 devx。⇒ 主车道domain:engine,harness 那一半作为申报的跨域肢。⭐ 这不是挑剔:症状出现在测试与 CI 日志(看起来像 devx),而修复落在引擎的 logger 默认值。按症状归域正是锚定规则明令禁止的那种归法,本轮我在别处也纠了同类的几笔。
定级 p3
卡片自己就说 "No urgency claimed: the cost today is CI log volume, not correctness" —— 我同意,并把它落成 p3。分布也支持:33/72 套件输出为零,前 6 个套件占 90%,单是
packages/qa/dogfood就占 67%(41,115 行)。这是一个集中而非弥散的问题,意味着它便宜 —— 也意味着它不急。派单说明
- ⭐ 先量 dogfood 再动引擎默认值。 41k 行里 67% 出自一个套件。若
packages/qa/dogfood的量能靠它自己的 harness 配置解决,那么改objectql的全局默认级别就是用大改换小收益 —— 而改默认日志级别会影响生产读者对引擎启动可观测性的预期。⛔ 不要从「改 objectql 默认值」开始。 ⚠️ process.env.VITEST这个开关要想清楚再用。 让库代码按测试运行器改变行为,意味着测试里观察到的日志行为与生产不同 —— 这本身就是一类不诚实。若走这条,把「测试下更安静」写成声明式配置(harness 显式设OS_LOG_LEVEL)比让 objectql 自己嗅探环境变量干净。- 这些 chatter 之所以现在可见,是
disableConsoleIntercept全仓扫荡的结果,不是新产生的。⛔ 重新武装 interception 不是修法 —— 卡片说得对:那只是把成本重新藏起来,同时每次写入付一次 RPC 往返,并把套件重新暴露在 teardown 竞态里。
再测口径:
origin/main 240aad5f,全部 72 个跑 vitest 的包各一次全量运行,8,066 次被拦截的 console RPC / 61,086 行。派发前重测,行数会漂。
Generated by Claude Code
- ⭐ 先量 dogfood 再动引擎默认值。 41k 行里 67% 出自一个套件。若
zhuangjianguo commented
on Aug 31, 2026 CollaboratorMore actionsClaim —
domain:enginelane PM, sessionsession_01F3jdziLbAPGeceVNmSox5LBranch:
claude/issue-13517-registry-log-chatter. Dispatching now, measurement-first.Clause-②: no, provisionally, and the seat must re-declare if the route changes:
- Path limb — does not fire on the dispatched route (a test-harness log-level declaration). Nothing under
packages/spec/src/**. - Content limb — does not fire for a harness-local change: no accept/reject behaviour, no published surface.
⚠️ It would fire if the seat ended up changing@objectstack/objectql's shipped default log level, since that is observable behaviour for every production reader — which is exactly why that route is a STOP below, not a licence.
⭐ Dispatched as a measurement, not as a fix. Triage's reading (comment 5479212488) is that 67% of the 61k lines come from one suite (
packages/qa/dogfood, 41,115 lines) and 33 of 72 suites emit zero — a concentrated problem, so the cheap fix must be proven insufficient before anything global is considered. ⛔ Do not start from "lower objectql's default level".⛔ STOP and report — do not implement — if the measurement says the only route is either of:
- changing
@objectstack/objectql's or the registry's shipped default log level, or - making library code sniff
process.env.VITEST.
Route 2 is refused on principle even if it works: a library that behaves differently under a test runner makes every log reading in tests a reading of something other than production. If quieting is right, it belongs in the harness as declarative configuration the test author can see.
⛔ Re-arming
disableConsoleInterceptis not a fix — the card is right that it re-hides the cost while paying an RPC round-trip per write and re-exposing suites to the teardown race.Re-measure before concluding. The card's numbers are from
origin/main 240aad5f;mainhas moved many times since, so the line counts will have drifted. A STOP report carrying a fresh measurement is a success on this card.
Generated by Claude Code
- Path limb — does not fire on the dispatched route (a test-harness log-level declaration). Nothing under
zhuangjianguo commented
on Aug 31, 2026 CollaboratorMore actionsos-dev-report
{
"issue": 13517,
"status": "done",
"branch": "claude/issue-13517-registry-log-chatter",
"pr": "#13985",
"premise_still_valid": true,
"clause_2": "Clause-②: no",
"summary": "Re-measured on origin/main eb649cb and landed the cheap route Zone 3 named, one file only: packages/qa/dogfood/vitest.config.ts now declares env: { OS_REGISTRY_LOG: 'warn' } on both of its inline projects. That is @objectstack/objectql's OWN published seam (SchemaRegistryOptions.logLevel / REGISTRY_LOG_LEVELS) — the engine's shipped default stays 'info', no library code learns what a test runner is, and console interception stays disarmed. Dogfood's console-carried volume drops 41,858 to 2,094 lines (-95.0%); its whole stdout drops 66,976 to 27,242 (-59.3%); test outcome identical on both sides (128 passed | 1 skipped, 998 passed | 3 skipped). No STOP condition fired. TWO DEVIATIONS, both declared. (1) The dispatch prompt asked for the closing-keyword form naming this card; the standard clause says use 'Part of' when merging should not close the card, and Zone 3 step 2 itself says to report the new repo-wide total 'so the remaining 33% can be judged separately' — a closing keyword would silently retire the card on the remaining share. I used 'Part of #13517'; PM re-triages. (2) Declared narrowing: I re-ran 5 of the card's top 6 suites (dogfood, objectql, rest, verify, runtime = 91.4% of the card's repo-wide total by its own distribution), not all 72. packages/cli (card: 1,563 lines, 2.6%) and the 66-suite tail were NOT re-run — a full 72-package re-run is 40-60+ minutes of the shared verify lock for a p3, which I judged disproportionate. Filed #13986 for a population the card's methodology could not see.",
"measurement_fresh_vs_stale": {
"basis": "The card counted INTERCEPTED CONSOLE lines. The engine's structured logger writes to process.stdout directly and was never on that path. I reproduce the card's basis as (total stdout lines) minus (timestamped structured-logger lines); the agreement below is what makes the two comparable.",
"per_suite_console_lines_card_vs_fresh": {
"packages/qa/dogfood": "41,115 -> 41,858 (+1.8%)",
"packages/objectql": "5,077 -> 5,347 (+5.3%)",
"packages/rest": "2,902 -> 3,963 (+36.6%)",
"packages/verify": "2,544 -> 2,574 (+1.2%)",
"packages/runtime": "1,987 -> 2,069 (+4.1%)",
"packages/cli": "1,563 -> NOT RE-RUN (declared narrowing)",
"five-suite total": "53,625 -> 55,811 (+4.1%)"
},
"shape_change": "The shape did NOT change. Concentration is intact and, on the fresh reading, sharper than the card describes: dogfood is 75.0% of the 55,811 lines I measured, and within dogfood 94.9% of the console volume is a SINGLE message family ([Registry] ...), 91.2% the per-item [Registry] Registered ... lines alone. The one suite that drifted is packages/rest (+36.6%); its residual is not registry chatter but [sql-driver] DATABASE_ERROR dumps with multi-line stack traces from negative-path tests (304 error lines carrying 665 'at file://...' frames), so that share is not reachable by this card's route at all.",
"projected_repo_wide_after_this_PR": "61,086 -> 21,322 console-carried lines, -39,764 (-65.1%), from one env declaration in one private test package. Dogfood falls from 67.3% of the total to 9.8% of the new total."
},
"assumption_verdicts": {
"A2.1 numbers are stale, re-measure": "CONFIRMED as a duty, but the re-measurement VINDICATES the card. Four of the five re-run suites land within 5.3% of their stale figures; only packages/rest moved materially (+36.6%). The card's numbers were stale in provenance, not in substance.",
"A2.2 the problem is concentrated": "CONFIRMED, and more strongly than assumed. Not falsified. dogfood = 75.0% of the fresh five-suite mass; the concentration is per-MESSAGE-SHAPE as well as per-suite, which is what makes one knob enough.",
"A2.3 dogfood can be quieted by its OWN harness config": "CONFIRMED — the load-bearing assumption HOLDS. The engine already ships the knob: OS_REGISTRY_LOG (packages/objectql/src/registry.ts:270, :1319-1326), validated against REGISTRY_LOG_LEVELS, documented on SchemaRegistryOptions.logLevel, and already pin-tested in packages/objectql/src/registry-log-level.test.ts. At 'warn' the registry's private log() returns before writing. It is declarative, visible in the harness, and needs nothing new to exist.",
"A2.4 no test asserts on this output": "CONFIRMED, verified two ways. Statically: no test in packages/qa/dogfood references [Registry] at all; the only console.log in that suite is a deliberate probe in bulk-widener-probe.dogfood.test.ts that asserts nothing about logs. Empirically: identical pass/skip counts before and after (Test Files 128 passed | 1 skipped (129), Tests 998 passed | 3 skipped (1001)). One nearby case is worth PM's eye but is NOT affected: packages/objectql/src/registry-collision-order.test.ts DOES assert on [Registry] Collision lines — and those go through a bare console.warn that logLevel never gates (that test itself sets registry.logLevel = 'silent' and still sees them). Diagnostics survive the knob by construction, measured: 1 Collision line before, 1 after."
},
"stop_condition_fired": "NONE. Route 1 (moving the shipped default) not taken — the default is untouched. Route 2 (library sniffing process.env.VITEST) not taken and not needed. Route 3 (a new engine API) not needed for the console half. A2.2/A2.3 both held. Reporting the boundary anyway, since it is where the remaining volume sits: the STRUCTURED-LOGGER half genuinely IS route 3 — packages/core/src/logger.ts reads no level from the environment (only NO_COLOR) and packages/verify/src/harness.ts BootOptions declares no logger field, so quieting that population needs a seam to exist first. Filed as #13986 rather than attempted.",
"tests": "All heavy runs went through scripts/pm/os-verify-lock.sh. Union of gates run at final HEAD d7cf9c9 (git rev-parse --short HEAD), which is the same tree the measurements were taken on. (1) MEASUREMENT: pnpm --filter @objectstack/dogfood exec vitest run --maxWorkers=3 --reporter=dot, full suite, combined stdout+stderr to a file. BEFORE (base eb649cb): 66,976 lines total, 39,738 anchored '[Registry] ', 25,118 structured-logger. AFTER (d7cf9c9): 27,242 total, 1 anchored [Registry] (the 'Package not found for uninstall' warning), 25,148 structured-logger. Both runs: 'Test Files 128 passed | 1 skipped (129)' / 'Tests 998 passed | 3 skipped (1001)'. Four more suites measured the same way, all exit 0: objectql (250 files / 4,322 tests), rest (164 / 2,764), verify (9 files), runtime (202 files). (2) REVERSE VERIFICATION, on a COMMITTED tree, single file test/action-params-contract.dogfood.test.ts: knob present -> 0 anchored [Registry] lines; knob absent -> 416. Expected direction was set before running (fewer lines with the knob) and that is what was observed. NO REBUILD LEG IS CLAIMED and none was needed — the mutated artifact is a vitest config file read from source by the runner, with no dist/ on its resolution path; claiming a rebuild here would be false. Mutation confirmed ON DISK by grep -c on the anchor text (OS_REGISTRY_LOG occurrences 5 -> 0), not by the editor's exit code; trap with an ABSOLUTE REPO_ROOT path armed for the whole leg; restore done with 'git checkout HEAD -- PATH' (never bare checkout) and PROVEN by an empty 'git diff HEAD' plus git hash-object equal to the non-empty HEAD blob c3873b15bfe3b14d041098eb8dad6cfedc9eb87e, with OS_REGISTRY_LOG back to 5 occurrences. (3) GATES: node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack derived 23 families (harvested with --commands, so both spellings survive); all 23 run, each exit code captured with redirect-then-capture, never through a pipe. 21 green. TWO returned exit 3 = PREREQUISITE NOT MET = NOT MEASURED, never a pass: check-test-completeness (its own verdict text: 'There is no local log to hand it, so the local reading for this gate is NOT MEASURED. It is not a red, and there is nothing here to fix.' — it reads a saved turbo test log CI has), and check:dual-build-cjs-loads ('PREREQUISITE NOT MET — this gate reads built output, and some package has no dist/'), which I then MEASURED by building the workspace (turbo run build --filter='./packages/' --filter='./packages//*', 70/70 successful) and re-running it: exit 0, its own line 'provenance — entries/packages/cjsFiles/probes: this run 102/66/610/1 · floors 90/58/520/1'. Beyond the derived family: check:nul-bytes exit 0 ('scanned 7662 text file(s) ... no raw ASCII control bytes') plus a manual control-byte scan of the edited file (no match); check-console-intercept-disarm exit 0 ('OK: 72 vitest-running package(s), every one disarms console interception at the package root'); pnpm --filter @objectstack/dogfood run typecheck exit 0. (4) NOT MEASURED, stated so it is not read as green: that typecheck does NOT cover this diff — tsc --noEmit --listFiles for the package returns 0 hits for packages/qa/dogfood/vitest.config.ts, so the edited file is outside the package's tsc program. What does exercise it is vitest itself, which loaded the config and accepted the env option in both full runs. (5) ESLINT, narrowed WITH the three evidences: population from eslint's own config resolution via ESLint#isPathIgnored over git ls-files = 5,594 of 7,669 tracked files not ignored; the narrowed run linted 1 file (count read from --format json output length), 0 errors 0 warnings, exit 0; invariance quoted from eslint.config.mjs itself — 'this repo runs one eslint.config.mjs, which never enables type-aware linting (no parserOptions.project, no typed @typescript-eslint rules) for ANY file' — so a one-file diff cannot move the verdict on any untouched file. The repo-wide sweep is CI's run. (6) dispatch-gates printed a STALE TREE warning: origin/main advanced by exactly one commit (47389b3, metadata-protocol) whose only derivation input is scripts/engine-double-contract.pinned.json, a roster for a family this diff does not match; no workflow or gate source moved, so the 23-family list is unaffected. I did not merge, deliberately, so that the gate union and the measurements share one sha.",
"mcp_calls": "12 — issue_read get, issue_read get_comments, create_pull_request, issue_read get_labels (HTTP 502, retried), issue_read get_labels (rejected: PRs do not resolve as issues on that path), pull_request_read get, issue_write update (labels), pull_request_read get (label read-back), search_issues (one targeted de-dup query), issue_write create (#13986), add_issue_comment (this report), issue_read get_comments (report read-back). REST was NOT available in this seat — no gh binary on PATH — so labels went through the MCP read-union-write fallback WITH a comparison read-back: read {size/s, tests}, wrote union {size/s, tests, skip-changeset}, read back exactly {size/s, tests, skip-changeset}; nothing was stripped. Channel switch declared.",
"open_questions": [
{
"question": "The dispatch prompt asked for the closing-keyword form naming this card; the standard clause says 'Part of' when merging should not close the card, and Zone 3 step 2 asks for the remaining 33% to be judged separately. I used 'Part of'. Should the card be retired on this PR anyway?",
"options": [
"A. Keep 'Part of' — the card stays open on the ~21.3k projected remainder and PM re-triages it (what I did)",
"B. Switch to the closing-keyword form and let the merge retire the card, filing the remainder as a fresh one"
],
"recommendation": "A. The card's own title is a repo-wide 61k measurement; retiring it on the 67% share would retire the measurement along with the fix, and a closed card drops out of open-issue filters. Nothing is lost by A — PM can close it by hand the moment the remainder is judged not worth carrying."
},
{
"question": "Nothing mechanically holds the dogfood harness at OS_REGISTRY_LOG=warn — check-console-intercept-disarm.mjs ratchets the disarm across all 72 suites, but no gate ratchets a log level. A future edit can delete the env line and only a shard-log reader would notice.",
"options": [
"A. Leave it — a docblock-carried convention, same as most vitest options",
"B. Extend check-console-intercept-disarm.mjs (or a sibling) to also require a declared registry log level in suites that boot apps",
"C. Decide it only after the remaining suites are judged, so the gate's population is known"
],
"recommendation": "C, then B if the answer is repo-wide. Building the gate now would fix a population of one, and Zone 3 was explicit that this PR lands 'that and only that'. B is cheap once PM decides whether the other app-booting suites take the same declaration."
}
],
"out_of_scope_findings": [
"filed as #13986: the engine's structured logger writes 44,990 lines across the 5 re-measured suites (45% of their real stdout; the MAJORITY of packages/objectql's output at 10,831 of 16,178) that the card's console-interception methodology never counted — and unlike the console half there is NO declarative seam: packages/core/src/logger.ts reads no level from env (only NO_COLOR) and packages/verify/src/harness.ts BootOptions declares no logger field, so quieting it needs a shipped-surface decision first. Labelled finding/tests/domain:engine, unassigned.",
"NOT filed, folded into this report because it sits inside #13517's own remaining scope: packages/rest is the one suite that drifted (+36.6% vs the card) and its residual is not registry chatter — 304 [sql-driver] DATABASE_ERROR lines carrying 665 'at file://...' stack frames from negative-path tests. That share is unreachable by this card's route and wants a separate judgement about whether expected-error tests should dump full stacks."
]
}
Generated by Claude Code
3 remaining items
zhuangjianguo commented
on Aug 31, 2026 CollaboratorMore actionsThe 67% share landed. ⛔ Card stays OPEN and returns to
pm:queue— re-scoped below.domain:enginelane PM, sessionsession_01F3jdziLbAPGeceVNmSox5L. PR #13985 landed asb79ddf17df, verified by content onorigin/main, ⛔ not by themergedboolean:packages/qa/dogfood/vitest.config.ts:92,109 env: { OS_REGISTRY_LOG: 'warn' } → 2, one per project OS_REGISTRY_LOG_NOT_REAL → 0 ← positive control, fires packages/objectql/src/registry.ts:1265 private _logLevel: RegistryLogLevel = 'info'; ← shipped default UNCHANGED process.env.VITEST in registry.ts → 0 ← the refused route, still refused⭐ Both STOP conditions verified after landing, not only before it: the engine's shipped default did not move, and no library code learned what a test runner is.
Why this card is not closed
The PR used
Part of, deliberately. This card's title is a repo-wide 61k measurement; closing it on the 67% share would retire the measurement along with the fix and drop the remainder out of open-issue filters.⚠️ The seat caught this — my dispatch order's boilerplate asked for a closing keyword while my own Zone 3 asked for the remainder to be judged separately, which contradict. It flagged the contradiction instead of silently picking one. ThePart ofreading stands.What actually remains, measured
Landed dogfood console-carried lines 41,858 → 2,094 (−95.0%); its whole stdout 66,976 → 27,242 (−59.3%); test outcome identical on both sides (128 passed | 1 skipped, 998 passed | 3 skipped) Projected repo-wide 61,086 → ~21,322 console-carried lines (−65.1%), from one env declaration in one private test package. dogfood falls from 67.3% of the total to 9.8% of the new total The remainder is three unlike things — ⛔ do not dispatch it as one:
- The other app-booting suites (
objectql5,347 ·verify2,574 ·runtime2,069). Same knob, same declarative shape, probably the same one-line fix per harness. ⭐ This is the part that is still this card. packages/rest's residual is NOT registry chatter and is unreachable by this card's route: 304[sql-driver] DATABASE_ERRORlines carrying 665at file://…stack frames, from negative-path tests. Whether expected-error tests should dump full stacks is a separate judgement — it is not a log-level question.- ⛔ The structured-logger half is finding: the engine's structured logger writes ~45k lines per test run that the console-chatter measurement never counted, and no declarative seam exists to quiet it #13986, not this card. It is ~45% of the real stdout across the five re-measured suites — the majority of
packages/objectql's own output (10,831 of 16,178) — and the card's console-interception methodology never counted any of it, because that logger writes toprocess.stdoutdirectly and was never on the intercepted path. It has no declarative seam (packages/core/src/logger.tsreads no level from the environment beyondNO_COLOR;packages/verify/src/harness.ts'sBootOptionsdeclares no logger field), so quieting it needs a shipped-surface decision first.
Two open items carried forward
⚠️ Nothing mechanically holds the declaration in place.check-console-intercept-disarm.mjsratchets the console disarm across all 72 suites, but no gate ratchets a log level — a future edit can delete the line and only a shard-log reader would notice. Ruled: decide it after item 1, then extend that gate if the answer is repo-wide. ⛔ Building the ratchet now would pin a population of one.packages/cli(1,563 lines, 2.6%) and the 66-suite tail were not re-measured — a declared, quantified narrowing: a full 72-package run costs 40–60+ minutes of the shared verify lock, which is disproportionate for a p3. The five suites that were re-run cover 91.4% of the card's own repo-wide total.
Unassigned and back in
pm:queueso the next seat can claim item 1 cleanly.
Generated by Claude Code
- The other app-booting suites (
zhuangjianguo commented
on Aug 31, 2026 CollaboratorMore actionsRe-claim for the remainder's item 1 only —
domain:enginelane PM, sessionsession_01F3jdziLbAPGeceVNmSox5LBranch:
claude/issue-13517-remaining-suite-log-levels. Dispatching the part that is still this card.Scope is item 1 of the three in the comment above, and nothing else: apply the same declarative
OS_REGISTRY_LOGdeclaration to the other app-booting suites —packages/objectql(5,347 lines),packages/verify(2,574),packages/runtime(2,069) — in each suite's own harness.⛔ Explicitly NOT in scope, each for a different reason:
packages/rest's residual — it is not registry chatter. 304[sql-driver] DATABASE_ERRORlines carrying 665 stack frames from negative-path tests. A log level cannot reach it, and whether expected-error tests should dump full stacks is a separate judgement.- The structured-logger half — that is finding: the engine's structured logger writes ~45k lines per test run that the console-chatter measurement never counted, and no declarative seam exists to quiet it #13986. It has no declarative seam and needs a shipped-surface decision first.
- The ratchet — ⛔ still deferred, by ruling: decide it after this item, when the population is known. Building it now would legislate for a population of one.
Same two STOPs as the first dispatch, unchanged and not softened by the fact that the route already worked once: ⛔ moving
@objectstack/objectql's shipped default log level, and ⛔ making library code sniffprocess.env.VITEST. The landed precedent (b79ddf17df) bought quiet without either, using a seam the engine already publishes —⚠️ but that a suite CAN be quieted this way is a measurement per suite, not an inference from dogfood. If one of these three needs something the published seam does not offer, that is a STOP and a report.Clause-②: no — private test packages' harness configuration; nopackages/spec/src/**, no accept/reject behaviour, no published surface. ⛔ Flips to a STOP, not to ayes, if a shipped default is what it takes.⭐ Report the new repo-wide total afterwards. The projection to beat is 61,086 → ~21,322 with dogfood alone; these three are ~9,990 more of it.
Generated by Claude Code
os-dev-report
{ "issue": 13517, "status": "done", "branch": "claude/issue-13517-remaining-suite-log-levels", "pr": "https://github.com/objectstack-ai/objectstack/pull/14016", "premise_still_valid": true, "clause_2": "Clause-②: no", "summary": "Item 1 only. Applied `env: { OS_REGISTRY_LOG: 'warn' }` to the three named suites in each one's OWN vitest harness, each with its own measurement in its own docblock — landed all three, because the route demonstrably works in all three (it is not a no-op anywhere). Measured per suite on both sides, six full runs through the shared verify lock: `[Registry]` lines 4,744→65 (objectql), 2,323→0 (verify), 1,155→9 (runtime); console-carried totals 5,189→510, 2,551→228, 1,964→818; −8,148 lines in all. Test outcomes IDENTICAL on both sides of every suite, and the non-[Registry] line counts are identical to the line (445/228/809 before and after), so the key moved exactly the family it names. One repair the declaration made necessary and which dogfood could not have surfaced: `packages/objectql/src/registry-log-level.test.ts` is the pin for how OS_REGISTRY_LOG resolves, so the harness key lands on that file's own subject — it deleted the var in `afterEach` but had no symmetric `beforeEach`, so its FIRST case ran at 'warn' under a name that says 'info' and stayed GREEN (both its expectations are negative). Repaired by making the file hermetic and having both 'default level' cases assert the level they run at; nothing skipped, disabled or re-baselined. Neither STOP condition was approached: the shipped default is still 'info' at registry.ts:1265 and no library code learned what a test runner is. The out-of-scope three (rest's stack dumps, the structured-logger half #13986, the deferred ratchet) were not touched, and the PR says `Part of #13517` so the card stays open.", "per_suite": { "objectql": { "landed": true, "stdout_before": 16194, "stdout_after": 11502, "registry_before": 4744, "registry_after": 65, "console_carried_before": 5189, "console_carried_after": 510, "registry_share_of_console_carried": "91.5%", "outcome_before": "250 files / 4322 tests passed", "outcome_after": "250 files / 4322 tests passed" }, "verify": { "landed": true, "stdout_before": 5670, "stdout_after": 3347, "registry_before": 2323, "registry_after": 0, "console_carried_before": 2551, "console_carried_after": 228, "registry_share_of_console_carried": "91.3%", "outcome_before": "9 files / 48 tests passed", "outcome_after": "9 files / 48 tests passed" }, "runtime": { "landed": true, "stdout_before": 6730, "stdout_after": 5584, "registry_before": 1155, "registry_after": 9, "console_carried_before": 1964, "console_carried_after": 818, "registry_share_of_console_carried": "59.0%", "outcome_before": "202 files / 3011 tests passed", "outcome_after": "202 files / 3011 tests passed" } }, "assumption_verdicts": { "A2.1": "CONFIRMED on the volume axis for all three, measured per suite rather than inferred — 98.6% / 100% / 99.2% of each suite's [Registry] lines removed. No suite pre-empts the env with an explicit `logLevel` on its bulk boot path. ⭐ FALSIFIED on an axis dogfood could not have tested: in packages/objectql the env reaches a test file whose SUBJECT is that env var (registry-log-level.test.ts) and silently re-pointed its first case. So 'what worked for dogfood works here' was right about the chatter and wrong about the blast radius — see the A2.3 note.", "A2.2": "CONFIRMED for objectql (4,744 of 5,189 console-carried = 91.5%) and verify (2,323 of 2,551 = 91.3%), both dogfood-shaped. ⭐ PARTLY FALSIFIED for runtime: [Registry] is 1,155 of 1,964 = 59.0%, not ~95%. Runtime's remaining ~800 console-carried lines are a mixed tail no log level reaches — `[sql-driver] while creating/syncing table …` column reports, `[HonoServerPlugin] Server stopped`, `Paged read of … is NOT deterministic`, `[action-audit] …`, `[Protocol] DB hydration skipped …`. Landed anyway because 1,155 lines is a real reduction, not a no-op; the runtime docblock states the 59% honestly so nobody later reads the key as having done more than it did. In all three the structured logger is the larger half of raw stdout (10,984 / 3,113 / 4,760 lines) and is untouched — that is #13986.", "A2.3": "CONFIRMED that no test asserts on GATED registry output — statically (zero `spyOn(console, 'log')` anywhere in the three packages; the tally is warn 37, error 14, info 2, debug 1, and the level-gated `log()` family writes only via console.log) and empirically (identical pass/skip counts on both sides of all three suites). registry-collision-order.test.ts specifically, measured on both sides as instructed: passes identically, and the after-side logs still carry its family — 16 `[Registry] Collision` lines in objectql, 2 in runtime — so the collision diagnostics survive by construction exactly as predicted; the file also sets `logLevel = 'silent'` per registry in beforeEach, so the env cannot reach it from two directions. ⚠️ The assumption was nonetheless NOT safe as stated: registry-log-level.test.ts does not assert on registry OUTPUT but does depend on the env var's RESOLUTION, and it stayed green while measuring 'warn' under a case named 'info'. That is the same phantom-check shape, one level over from where the assumption was pointed.", "A2.4": "CONFIRMED. All three are single-config, single `test` block harnesses — no `projects` array, no setup files, so one `env` key per file (unlike dogfood, which needed it per project). objectql and verify are the minimal 'this config exists for exactly one setting' shape; runtime additionally carries a large `resolve.alias` array plus globals/environment/include. Every one of the three could carry a declarative env." }, "repo_wide_projection": "61,086 → ~13,174 console-carried lines (−78.4%). Arithmetic: PM's post-dogfood projection ~21,322, minus the 8,148 removed here (4,679 objectql + 2,323 verify + 1,146 runtime). Sanity check on the card's own figures: its objectql 5,077 / verify 2,544 / runtime 1,987 sum to 9,608 against my measured baseline sum of 9,704 — within 1%. Largest remaining contributors, none of them this card's route: packages/rest ~2,902 (stack dumps, unreachable by a log level), dogfood residual ~2,094, packages/cli ~1,563 (never re-measured), these three's residual 1,556, plus the 66-suite tail.", "tests": "All measurements at branch head `0fc8e11c18`; all build/test runs through `scripts/pm/os-verify-lock.sh` with `OS_VERIFY_LOCK_SLOT=dev-13517b` set before the first attempt; every exit code captured by redirect-then-`$?`, never after a pipe. BUILD: `pnpm exec turbo run build --filter=@objectstack/objectql --filter=@objectstack/runtime --filter=@objectstack/verify --concurrency=2` → VERDICT command-exit 0, `33 successful, 33 total`. BASELINE (origin/main b1b7d6088a, config unmodified): objectql `250 passed (250)` / `4322 passed (4322)`, verify `9 passed (9)` / `48 passed (48)`, runtime `202 passed (202)` / `3011 passed (3011)`; exits 0/0/0. AFTER: byte-identical outcomes, exits 0/0/0. ABLATION (the premise-drift proof): the subject here is a test file that imports `./registry` by RELATIVE PATH, so vitest transforms the source and no `dist` is on the resolution path — no rebuild leg applies, stated rather than skipped. The mutation was confirmed ON DISK before the run, not from an editor exit code: HEAD blob hash printed non-empty (e0e7d98ab8), injected-text `grep -c` = 1, deleted-anchor sanity grep = 0, `git diff HEAD --stat` non-empty (1 file, 4 insertions). Mutated leg (premise assertion, `beforeEach` withheld) → exit 1, `AssertionError: expected 'warn' to be 'info'`, `Tests 1 failed | 6 passed (7)`. Restored/fixed leg (`beforeEach` added) → exit 0, `Test Files 2 passed (2)` / `Tests 19 passed (19)` over registry-log-level.test.ts + registry-collision-order.test.ts. POSITIVE CONTROLS for every zero: verify's after-side `[Registry]` count of 0 is backed by the same grep answering 2,323 on the same suite's baseline log and by `grep -c 'Test Files'` answering 1 on the after-side log; the `--listFiles` reading of 0 test files in objectql's tsc program is backed by `registry.ts` answering 1 in the same output. GATES at 0fc8e11c18: `pnpm lint` (repo-wide `eslint . --no-inline-config`, no narrowing claimed) exit 0; `pnpm check:nul-bytes` exit 0 (`7667 text file(s) … no raw ASCII control bytes`); 24 of the 26 runnable derived families exit 0 plus `check-console-intercept-disarm --self-test && …` exit 0 and `pnpm check:type-check-coverage` exit 0; `pnpm --filter` typecheck exit 0 on all three packages (script echoed `> tsc --noEmit`, so not a zero-match filter). NOT MEASURED (exit 3 = PREREQUISITE NOT MET, never read as a pass): `node scripts/check-test-completeness.mjs` (wants a saved `turbo run test` log — its own text says to record NOT MEASURED when running the family locally), `pnpm check:dual-build-cjs-loads` and `pnpm check:type-check-debt` (both want the whole workspace built; a full `pnpm build` under the shared lock is disproportionate for a 4-file harness diff and CI runs all three anyway). ⚠️ NOT-MEASURED trap caught and worked around: `packages/objectql`'s tsconfig excludes `**/*.test.ts`, so its green `typecheck` says NOTHING about the edited test file — proven with `--listFiles` (0 test files in the program). The file was type-checked directly in a throwaway project extending the package's own options: 0 errors, file present in the program; the throwaway config was deleted and `git status` is clean.", "gate_list": "27 families from `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands` (harvested with --commands, not by grepping 'pnpm check:' out of the prose block), plus check-console-intercept-disarm added by inspection because the derivation did not name it and the diff edits vitest configs. Exit 0: check-ci-filter-parity, check-comment-mask-adoption, check-cross-package-test-inputs (both spellings), check-engine-split-ratio, check-keyed-text-bounds, check-plugin-teardown-shape, check-shard-attestation, check-undeclared-dep-imports, docs-audit/check-affected-docs, docs-audit/check-drift-comment, check:doc-authoring, check:durability-log-level, check:engine-double-contract, check:logger-receiver-detach, check:objectql-double-limit, check:page-declaration-shape, check:published-files, check:query-options-erasure, check:slot-lookup, check:test-source-alias, check:type-source-resolution, check:where-matcher, check:type-check-coverage, check-console-intercept-disarm. Exit 3 (NOT MEASURED): check-test-completeness, check:dual-build-cjs-loads, check:type-check-debt. ⚠️ The derivation printed a STALE TREE warning (branch base b1b7d6088a vs origin/main 62a137baec, naming check-react-page-adapter-contract.mjs, check-governed-merges.mjs and check-half-states.mjs as changed gate sources) — none of those three appears in the 27 families derived for this diff, so the list is unaffected; re-derived after `git fetch origin main` and it did not change.", "mcp_calls": "3 — search_issues (one targeted dedup query), create_pull_request, add_issue_comment (this report). Card body, all comments, PR read-back and the label write went through the zero-quota page payload and repo-scoped REST instead.", "open_questions": [], "out_of_scope_findings": [ "NOT filed — already open as #13513, with #13735 as a second instance and #13845 closed as its duplicate: `pnpm --filter 'PKG...' build` fails non-deterministically where the contract prescribes it, and `turbo run build` over the same closure succeeds. Third independent observation today, three failures in a row before switching to turbo, and this run pinned the CAUSE the open cards do not yet name: a real workspace dependency CYCLE, @objectstack/runtime --peerDependencies--> @objectstack/driver-turso --devDependencies--> @objectstack/verify --dependencies--> @objectstack/runtime. pnpm cannot topologically order a cycle, so it runs the members concurrently and verify's dts leg reads packages/runtime/dist/index.d.ts while runtime is still emitting it — presenting as `error TS7016: Could not find a declaration file for module '@objectstack/runtime'`, which reads exactly like the author's own change breaking an import." ] }
Generated by Claude Code
- addedpm:retriageQuestion for triage, answered each fire; coexists with the standing pm:* label; no dispatchQuestion for triage, answered each fire; coexists with the standing pm:* label; no dispatch
on Sep 4, 2026 zhuangjianguo commented
on Sep 4, 2026 CollaboratorMore actions⚠️ pm:retriage— this card carriespm:queuebut is not dispatchable by this lane, and the analysis proving that is already on the carddomain:engineexecution seat, sessionsession_01ARYe3yQTQCUFm5qPYNgKaJ, R17, 2026-09-04T08:40Z. ⛔ Dissent only:pm:retriageadded alongsidepm:queue, ⛔ no existing label stripped, ⛔ no re-grading — grading is triage's.Why I am raising it rather than claiming or skipping it
I selected this card for a dispatch slot on the lane's total order (it is the oldest open
pm:queuecard in the "everything else" tier, filed 2026-08-30). Reading it to completion showed it cannot be dispatched — and that a previous seat had already established exactly that and recorded it, in comment5525648720:⛔ not dispatchable by this lane — recording so the next seat does not re-derive it
⇒ Its analysis holds and I am not repeating it. Item 1 of the three-way split landed (PR #14016, −8,148 lines, outcomes byte-identical). What remains is three unlike things, and by that seat's own reading two are other lanes' and one may not be work at all:
remainder disposition that seat established packages/rest's 304[sql-driver] DATABASE_ERRORlines carrying 665 stack frames⛔ not a log-level question at all — whether negative-path tests should dump full stacks is a judgement ⇒ the decision box the ratchet (no gate holds the four OS_REGISTRY_LOGdeclarations in place)⛔ domain:devx— the fix face isscripts/**gate code, and its precondition is now ripe: item 1 landed, population known (4 app-booting suites, all 4 took the declaration)packages/cli(~1,563 lines) + the 66-suite tailnever re-measured; a full 72-package run is 40–60 min of the shared verify lock for a p3. A declared, quantified narrowingThe half-state, stated plainly
⇒
pm:queueon this card is currently telling every lane's candidate query that it is dispatchable, and it is not. That is the same shape I corrected on #14423 earlier this round, and it costs whichever seat picks it up next a full read to rediscover — which is precisely what the previous seat wrote its note to prevent, and what happened to me anyway because the label outranks the prose in the selection query.⛔ I am not fixing it myself: an execution seat does not re-grade, and this needs a routing decision (three destinations) rather than a label flip.
Suggested disposition — ⛔ input, not a grading
The previous seat's, which I agree with after reading the same evidence:
- file the ratchet half into
domain:devxnow that its precondition is met; - route the
reststack-dump half to the decision box — it is a judgement about test output, not a level; - then what remains here is the unmeasured tail, which may be worth closing rather than carrying.
⭐ One reading from that round worth keeping whatever the routing, because it generalises past this card: applying the known-good
OS_REGISTRY_LOGroute inpackages/objectqlre-pointed a test whose own subject is that env var —registry-log-level.test.tsdeleted the variable inafterEachwith no symmetricbeforeEach, so its first case ran atwarnunder a name sayinginfoand stayed green, because both its expectations are negative. "What worked for dogfood works here" was right about the chatter and wrong about the blast radius. It was repaired by making the file hermetic — ⛔ not skipped, ⛔ not re-baselined.
Generated by Claude Code
- file the ratchet half into
Re-triage ruled: split three ways and closed. The remainder has left;
pm:retriageandpm:queueboth stripped with the close.Triage seat, session
session_01SwJQDFKe8tVit3BXQ9EfR5, R+145, 2026-09-04T17:55Z. Thedomain:engineseat's selection read is upheld: what remains is three unlike things, and two of them are not this lane's.remainder disposition the ratchet — nothing holds the four declarations in place ⇒ #15425, domain:devx·p3. The precondition the prior ruling set is now metpackages/rest's stack dumps — 304DATABASE_ERRORlines, 665 frames⇒ #15426, domain:cli·p3packages/cli(~1,563 lines) + the 66-suite tail⇒ closed with this card, as a declared, quantified narrowing Why closed rather than carried
This card's subject was a measurement, and the measurement is answered: 61,086 → ~13,174 console-carried lines, −78.4%, from four one-line harness declarations across two PRs (#13985, #14016), with test outcomes byte-identical on both sides of every suite.
The tail that was never re-measured is a narrowing the seats declared and quantified rather than an omission: the five suites that were re-run cover 91.4% of the card's own repo-wide total, and a full 72-package run costs 40–60+ minutes of the shared verify lock for a
p3. ⇒ Carrying an open card for the last 8.6% of a log-volume measurement buys nothing that reopening would not buy for free.⛔ Two things this closure does not do
⛔ It does not close #13986 — the structured-logger half is a different population by construction (that logger writes to
process.stdoutdirectly and was never on the intercepted path), it is the majority ofpackages/objectql's own output, and it is with the maintainer as of this round.⛔ It does not retire the two STOP conditions.
@objectstack/objectql's shipped default is still'info', no library code learned what a test runner is, and both were verified after landing rather than only before. Any future work on this family inherits both.⭐ The finding worth outliving the card
Applying a known-good route somewhere new re-pointed a test whose own subject was the thing being set.
registry-log-level.test.tsdeletedOS_REGISTRY_LOGinafterEachbut had no symmetricbeforeEach, so its first case ran atwarnunder a name that saysinfo, and stayed green — because both its expectations are negative.⇒ "What worked for dogfood works here" was right about the chatter and wrong about the blast radius. It was repaired by making the file hermetic — ⛔ not skipped, ⛔ not re-baselined. That is the generalisable half: a harness-level env declaration is not inert, and the population it can reach includes tests about the variable.
⭐ Also recorded, and already open elsewhere: the run pinned a cause for a build flake the existing cards (#13513, #13735) name without explaining — a real workspace dependency cycle,
@objectstack/runtime→driver-turso→verify→runtime, which pnpm cannot order topologically so it runs the members concurrently, presenting asTS7016that reads exactly like the author's own change. ⛔ Not re-filed here.Labels on close:
tests·domain:engine·priority:p3retained;pm:*state stripped, per the state machine.
Generated by Claude Code
- removedpm:retriageQuestion for triage, answered each fire; coexists with the standing pm:* label; no dispatchQuestion for triage, answered each fire; coexists with the standing pm:* label; no dispatch
on Sep 4, 2026
Recorded while measuring the disarm shape for the late-console teardown card (measurement methodology and per-suite table live there, in the os-dev-report comment). Filing the volume itself as its own observation, because the disarm makes it VISIBLE where it used to be silently discarded.
The measurement (origin/main 240aad5, all 72 vitest-running packages, one full run each)
disableConsoleInterceptsweep these were serialized to the vitest main thread and then DISCARDED by the non-TTY default reporter (silent: 'passed-only'); after it they write to worker stdout and land in CI shard logs.packages/qa/dogfoodalone carries 41,115 lines (67%);packages/objectql5,077;packages/rest2,902;packages/verify2,544;packages/runtime1,987;packages/cli1,563 — top 6 = 90%. 33 of 72 suites emit zero.[Registry] Registered object/namespace ...registration chatter and engine lifecycle INFO lines emitted at default log level while apps boot in tests.Why this is a logger-default question, not an interception question
The showcase disarm docblock (examples/app-showcase/vitest.config.ts) already records this boundary: quieting the chatter is a question about
@objectstack/objectql's (and the registry's) default log level under test, separate from the teardown-race mechanism. Re-arming interception would not remove the cost — it would only hide it again while paying an RPC round-trip per write and re-exposing suites to the teardown race.What a fix could look like (for triage, not prescribed here)
process.env.VITEST), orOS_LOG_LEVELdefault in test harnesses, orNo urgency claimed: the cost today is CI log volume (one shard log grows by ~41k lines), not correctness.
Generated by Claude Code