Skip to content

fix(cli): os validate prints UI: 0 Apps instead of dropping the row - #10955

Merged
os-elon merged 1 commit into
mainfrom
claude/issue-10504-validate-zero-apps-row
Aug 21, 2026
Merged

fix(cli): os validate prints UI: 0 Apps instead of dropping the row#10955
os-elon merged 1 commit into
mainfrom
claude/issue-10504-validate-zero-apps-row

Conversation

@os-elon

Copy link
Copy Markdown
Collaborator

Fixes#10504

Premise re-derived at head

Reproduced the card's two-run measurement at this branch's head (commit
916a7e0b1e), spawning the real CLI (bin/run-dev.js validate, tsx over
src/, no dependency on packages/cli/dist) against a minimal blank-shaped
config (one object, two fields) with and without one apps: [...] entry:

Zero apps (before fix):

 ✓ Validation passed (71ms)
My App v0.1.0
Data: 1 Objects 2 Fields
⚠ No apps or plugins defined — this stack may not do much
⚠ Missing manifest.namespace — required for multi-app hosting

One app (before fix):

 ✓ Validation passed (78ms)
My App v0.1.0
Data: 1 Objects 2 Fields
UI: 1 Apps
⚠ Missing manifest.namespace — required for multi-app hosting

Identical to the card's report: the UI: row is absent (not 0) at zero
apps, both exit 0. Premise still holds at main head — nothing on
main fixed this since the card was filed.

What I measured about the printer's shape (brief fence #1)

packages/cli/src/utils/format.tsprintMetadataStats renders four
sections (Data, UI, Logic, Security) from one shared loop that drops
the whole row when every item in a section is 0. This is a general
pattern, not UI-specific — Logic: (flows/workflows/agents/apis all zero)
and Security: (positions/permissions both zero) vanish exactly the same
way. Triage's ruling in #10504 named only UI:, so I did not widen the fix
to the other three sections — filed as its own finding:
#10952 ([finding] os validate's summary printer drops ANY zero-count section row, not just UI:).

What I measured about --json (brief fence #2)

No divergence on the card's specific symptom: --json already reports
"apps": 0 explicitly at zero apps (stats is a plain object with every
key always present, never filtered). Confirmed both ends:

zero apps --json: "stats": { ..., "apps": 0, ... }, "warnings": []
one app --json: "stats": { ..., "apps": 1, ... }, "warnings": []

I did find an unrelated text/--json divergence while checking this:
the four inline advisories (No objects defined, No apps or plugins defined, Missing manifest.id, Missing manifest.namespace) are computed
after the --json branch's early return, so --json's warnings
array never carries any of them, regardless of the zero-apps question —
filed as #10953 ([finding] os validate --json never carries the four inline non-blocking warnings the text summary prints). Not touched by this
PR.

Existing test pinning the old output (brief fence #3)

None found. Searched packages/cli/test/** for printMetadataStats,
collectMetadataStats, UI:, and the exact strings from the card
(My App v0.1.0, Data: 1 Objects, No apps or plugins) — no test asserts
the current absent-row shape. No test to update.

The fix

printMetadataStats gains an opt-in zeroFallback per section — the one
item to force-print at 0 instead of dropping the row when every item in
that section is zero. Set only on UI ('Apps'), per triage's ruling
(comment 5366623624): the blank/crud/full templates all ship zero apps
deliberately, so a warning would fire on every clean scaffold's first run —
this changes nothing about what validate accepts, rejects, or exits with.
Data:/Logic:/Security: keep their current behavior untouched.

Sanity check that the shared function's nonzero path is unaffected: the
full-workspace build (below) ran os build on example-showcase (24
objects, 1 app, 29 flows, …) and its printed summary is unchanged in shape:

 Data: 24 Objects 210 Fields 1 Extensions 1 Datasources
UI: 1 Apps 6 Views 28 Pages 3 Dashboards 4 Reports 70 Actions
Logic: 29 Flows 2 APIs
Security: 10 Positions 9 Permissions

New test — pins both ends of the 0→1 transition (brief fence #4)

packages/cli/test/print-metadata-stats-zero-row.test.ts — unit-tests
printMetadataStats directly (the formatZodErrors pattern already used in
format-zod-union.test.ts: capture console.log, strip ANSI, assert on the
plain text), rather than spawning the full CLI, since the fix is entirely
inside this one function:

Reverse verification (run twice — once before, once after a real catch, see below)

Predicted direction: reverting only the production change
(packages/cli/src/utils/format.ts) should turn the new test's zero-apps
assertion (UI: 0 Apps present) RED with exactly the card's symptom (the row
absent instead of zero), while the CLI's own exit code stays 0 throughout
(this was never a correctness gate). Committed the fix first, then, at the
final commit 916a7e0b1e:

$ git checkout HEAD~1 -- packages/cli/src/utils/format.ts # revert fix only, test stays
$ node_modules/.bin/vitest run print-metadata-stats-zero-row.test.ts
❯ test/print-metadata-stats-zero-row.test.ts (4 tests | 1 failed)
× prints "UI: 0 Apps" — not an absent row — when apps=0
AssertionError: expected ' Data: 1 Objects 2 Fields' to contain 'UI: 0 Apps'
Test Files 1 failed (1) · Tests 1 failed | 3 passed (4)
$ git checkout HEAD -- packages/cli/src/utils/format.ts # restore
$ node_modules/.bin/vitest run print-metadata-stats-zero-row.test.ts
Test Files 1 passed (1) · Tests 4 passed (4)

Both legs matched the prediction exactly: RED on revert with the card's
literal symptom (Received shows the row is simply absent, not a
value-mismatch), GREEN on restore.

A real ratchet catch, fixed before push (not weakened)

check:type-check-debt's --re-measure (after building the full workspace
closure — 70/70 tasks) caught a genuine +1 drift:

• @objectstack/cli: TEST_DEBT records 146 raw tsc error(s), `tsc --noEmit`
now reports 147 (+1).

Root cause: my new test file's relative import
(from '../src/utils/format') was missing the .js extension required
under moduleResolution: NodeNextTS2835. (The sibling
format-zod-union.test.ts has the identical unextended import, already
absorbed into the frozen 146 baseline; mine was simply a new occurrence of
the same pattern.) Fixed by adding the extension
(from '../src/utils/format.js') — no ledger edit, no gate touched.
Re-measured clean at the final commit:

check-type-check-coverage --re-measure: OK — 33 ledger entr(ies) re-measured
in 327.2s, 1908 raw tsc error(s) total, none above its recorded number.

(The same run surfaces an unrelated, pre-existing @objectstack/plugin-auth
TEST_DEBT surplus — 109 recorded vs. 97 measured, a lowering opportunity
already tracked generically under #6376 — not caused by this diff and not
touched here.)

Gates — derived at the final commit, run there

node scripts/pm/dispatch-gates.mjs at 916a7e0b1e (3 paths vs. merge base
376b3dc0b0: .changeset/validate-zero-apps-row.md,
packages/cli/src/utils/format.ts,
packages/cli/test/print-metadata-stats-zero-row.test.ts) named:

GateVerdict quoted
check:changeset-gate-self-tests✓ all three self-tests pass
check:objectui-changeset✓ objectui-range --self-test: all checks passed
check:slot-lookup✓ ratchet holds: 107 unswept site(s), none new
check:test-source-aliascheck-test-source-alias OK — 72 packages scanned
check:type-source-resolutioncheck-type-source-resolution OK — 76 packages scanned
check-adr-0087-registration.mjs✓ this PR adds no declared-breaking changeset
check-changeset-no-major.mjs✓ This diff introduces no major bump
check-ci-filter-parity.mjsOK: all 82 declared cross-package glob(s) covered
check-empty-changeset.mjs✓ No empty-frontmatter changeset introduced
check-plugin-teardown-shape.mjs✓ 57 Plugin implementation(s), all conform
docs-audit/check-affected-docs.mjsran clean (no missing-doc findings for this diff)
check:query-options-erasure✓ ratchet holds: 67 unswept site(s), none new
check:engine-double-contractOK — 376 pinned, 133 in DEBT ledger, 2 exempt
check:where-matcher✓ conformance holds: 275 matchers, 0 silently-wrong, none new
check:type-check-coverageOK — 64/77 packages type-checked, 13 in DEBT, 1 exempt
check:type-check-debt (--re-measure, full closure built)OK — 33 ledger entries re-measured, none above its recorded number (after the .js-extension fix above)

Plus, directly (not just via the derivation):

  • pnpm --filter '@objectstack/cli^...' build — dependency closure — green.
  • pnpm --filter @objectstack/cli test — 149 files / 1647 tests — green
    (whole package suite, re-run at the final commit, not just the new file).
  • pnpm --filter @objectstack/cli typecheck — green.
  • node scripts/check-nul-bytes.mjs — clean, whole repo.

No gate weakened, no threshold moved, no baseline raised. No change to
content/docs/releases/**. Changeset: .changeset/validate-zero-apps-row.md
(patch, @objectstack/cli).


Generated by Claude Code

…10504)
printMetadataStats (packages/cli/src/utils/format.ts, shared by os validate,
os info and os compile) gains an opt-in zeroFallback per summary section --
the one item to force-print at 0 instead of dropping the whole row when
every item in that section is zero. Set only on UI ('Apps'), matching the
triage ruling on #10504 (comment 5366623624): the blank/crud/full templates
all ship zero apps deliberately, so a warning would fire on every clean
scaffold's first run.
Measured at this branch's head: a zero-apps run and a one-app run of the
real CLI printed an identical summary except for the UI: line being entirely
absent (not 0) in the zero-apps case -- both exited 0. Reproduced unchanged
from the card's original 17.1.0 measurement.
Data:/Logic:/Security: keep their current drop-at-zero behavior -- filed as
its own finding (#10952) rather than widened here. The --json path already
reported "apps": 0 explicitly; an unrelated --json warnings gap is filed
separately (#10953).
New test: packages/cli/test/print-metadata-stats-zero-row.test.ts pins both
ends of the 0->1 transition directly against printMetadataStats.
@github-actionsgithub-actionsBot added size/m documentation Improvements or additions to documentation tests tooling labels Aug 21, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

1 anchor(s) derived from 1 changed package(s); no hand-written page names any of them. ✅

What this run could not see
  • the SDK route bridge reached 45 of 221 client-bound route-ledger rows — the other 176 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run: node scripts/docs-audit/affected-docs.mjs --bridge-coverage

Coarse fallback — 23 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 9cc6777d3e228952bc554c5f1c4f34b063fcf105packageMentionDocs.

Which tree this was computed on

This run read content/docs from e6936599ffe06a1c1d7886fbe7a8de4e58ae09c5 — the merge of head 916a7e0b1e43f54a5fd07297899af132001cecf9 into base 9cc6777d3e228952bc554c5f1c4f34b063fcf105, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin e6936599ffe06a1c1d7886fbe7a8de4e58ae09c5 && git checkout e6936599ffe06a1c1d7886fbe7a8de4e58ae09c5
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 9cc6777d3e228952bc554c5f1c4f34b063fcf105 916a7e0b1e43f54a5fd07297899af132001cecf9 && git checkout -B drift-repro 9cc6777d3e228952bc554c5f1c4f34b063fcf105 && git merge --no-ff 916a7e0b1e43f54a5fd07297899af132001cecf9
node scripts/docs-audit/affected-docs.mjs --json 9cc6777d3e228952bc554c5f1c4f34b063fcf105

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

@github-actions

Copy link
Copy Markdown
Contributor

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

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

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

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

    ✗ Build failed in 6.19s
    

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

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

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

历史信号:

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

分诊清单:

  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 ff5733eAug 21, 2026
32 checks passed
@os-elon
os-elon deleted the claude/issue-10504-validate-zero-apps-row branch August 21, 2026 23:51
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/mteststooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[finding] os validate drops the whole UI: row at zero apps, so a project with no navigable UI reads identically to one that was never asked

2 participants

@os-elon@claude