fix(verify): a multi-package app no longer gets an os verify that measures nothing (#15229) - #15281
Conversation
) Step 1 of the card's ordered pair: make the loss visible BEFORE fixing it, so the fix has something to be checked against. The by-shape sweep (#15210) found these four reads; neither `@objectstack/verify` site had a row in the #15004 acceptance pin, which is why the sweep and not the pin is what found them. Four rows, all at B2 (`os verify` has one door — `loadConfig` — and never opens a compiled artifact, so a B1 twin would measure a path no command drives): - `deriveCrudCases` · objects — zero CRUD round-trip cases derived - `deriveCrudCases` · datasources — the ADR-0015 double write gate - `declaredPositionNames` · positions — no persona for any declared position - `rlsProbePermissionSet` · objects — an empty probe permission set Every row calls a reader the package SHIPS; none re-implements one. The datasource row needed a fixture member the zoo lacked: the datasource-by- name map decides exactly one thing — whether a federated object's probe insert clears ADR-0015's double opt-in — so watching it requires a federated object. It is declared in the module package while the datasource that gates it is in the App package, which makes the row a cross-package resolution. The control's registry list grows by that object; the control itself is untouched. RED, with only the rows added and the ledger lines absent: A subsystem lost a collection that the ledger does not carry — this is the failure #15004 exists to make loud. B2 · verify declaredPositionNames (one RLS persona per declared position) · positions B2 · verify deriveCrudCases (CRUD round-trip case derivation) · objects B2 · verify deriveCrudCases federated write gate (ADR-0015 double opt-in) · datasources B2 · verify rlsProbePermissionSet (RLS probe grants + owner narrowing) · objects The BASELINE (additive shape) stayed green in that same run, so the red is a discrimination and not a broken fixture. The four lines are then added to OPTION_B_LOSSES, which is what this commit leaves in the tree; the next commit fixes the readers and deletes them. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UHvF5hyiZjnCyExFnfQB8m
…s[]` (#15229) Step 2 of the card's ordered pair: the readers the previous commit ledgered now see a multi-package app, and the four ledger lines are deleted in this commit. `os verify` derives its entire proof set from the app's metadata, so a collection it cannot see is not a missing feature — it is a run that asserts nothing and still prints `✓ verify passed`. Under an option-B artifact `deriveCrudCases` derived ZERO cases and `rlsProbePermissionSet` built an EMPTY probe permission set: the persona that makes an RLS run a probe granted nothing and narrowed nothing, and no persona was minted for any declared position. The four reads go through one resolver (`declaredCollection`), which answers the caller's ORIGINAL expression first and consults `packages[]` only where that came back falsy: - the top level is returned untouched when truthy, so today's additive artifact answers bit-identically and this card is revertible on its own; - `objects: []` is TRUTHY, so a declared-empty collection stays empty — the behaviour a re-expression as `resolve(...).length > 0` would have changed (measured on the sibling card #15006); - package order comes from `resolveArtifactPackageOrder` (`@objectstack/core`, ADR-0130 D4+D5), never from a second traversal of `config.packages`; - a malformed `packages` now raises that function's ADR-0112 refusal instead of reading as "this app declares nothing". Both `deriveCrudCases` reads move together on purpose. Objects alone would leave ADR-0015's double write gate judging against an empty datasource map, which reports a write-opted-in federated object as read-only — a verifier silently skipping an object the app explicitly opted into writes for. The pin's RED before this commit named exactly the four rows to delete: A ledgered subsystem now SEES its collections under option B — the reader program moved forward. Delete these lines from OPTION_B_LOSSES: B2 · verify declaredPositionNames (one RLS persona per declared position) · positions B2 · verify deriveCrudCases (CRUD round-trip case derivation) · objects B2 · verify deriveCrudCases federated write gate (ADR-0015 double opt-in) · datasources B2 · verify rlsProbePermissionSet (RLS probe grants + owner narrowing) · objects `composeStacks`, `packages/spec/src/stack.zod.ts` and what every command emits are untouched: the artifact stays additive through this card. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UHvF5hyiZjnCyExFnfQB8m
…rify-option-b-readers
…15229) Both halves are what `check:test-source-alias` and `check:type-source-resolution` prescribe for a dep a change newly reaches; neither registry is widened (both are shrink-only, and widening is never the fix): - `packages/verify/vitest.config.ts` gains its first `resolve.alias` — one ANCHORED entry for `@objectstack/core`, which `artifact-collections.ts` now imports and every test in the package reaches through `derive.ts` / `rls.ts`. Without it those tests resolve core through its `exports` map to `dist/`, i.e. they become verdicts about build state, and the dangerous half of that is a suite that passes GREEN over a stale artifact. - `packages/verify/tsconfig.json` gains the matching `paths` rule, and the `rootDir` widening that pulling core's source into the program requires (`packages/core/tsconfig.json`'s own precedent; emit is unaffected, this package builds with tsup). - `packages/cli/tsconfig.test.json` gains a FOURTH bare-name rule beside the three #15004 added, for the `@objectstack/verify` the probe rows now import. Bare names, no stars: `@objectstack/core` publishes a `./logger` subpath that keeps resolving through its own `exports` map, and a starred rule would swallow it and resolve it THROUGH a file (ENOTDIR). Measured after: `pnpm check:test-source-alias` OK (72 packages scanned), `pnpm check:type-source-resolution` OK (121 programs / 78 packages), `pnpm --filter @objectstack/verify typecheck` and `pnpm --filter @objectstack/cli typecheck` both clean, and the two suites the alias re-resolves stay green: verify 58/58, the option-B pin 6/6. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UHvF5hyiZjnCyExFnfQB8m
📓 Docs Drift CheckThis PR changes 1 package(s): 1 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
What this run could not see
Coarse fallback — 2 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 96b9cde536204a563f50602f4e26e94a19eea478 && git checkout 96b9cde536204a563f50602f4e26e94a19eea478
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 5ea2e38820ef96cceb40bc6796af3cfce0b2b311 5c9b4e872d8951d5449b76c4ec20176096e97083 && git checkout -B drift-repro 5ea2e38820ef96cceb40bc6796af3cfce0b2b311 && git merge --no-ff 5c9b4e872d8951d5449b76c4ec20176096e97083
node scripts/docs-audit/affected-docs.mjs --json 5ea2e38820ef96cceb40bc6796af3cfce0b2b311
|
复审 PASS —— 顺序留在了 commit 历史里,这是本程序至今最好的一次证据组织PM 席(epic #14122,session 卡面要求的顺序,做到了,而且可核卡面写「先加行 → 示红 → 修 → 删行,并把顺序写进 PR」。本 PR 把它写进了提交历史,比写进正文强:
两次红都带具体读数,不是「它红了」: 没做消融,而这次是对的前几张卡都做了人工消融。本 PR 明说 三条硬约束核过
|
|
收口我上一条挂的账。 我在 PASS 里写过:verify 的私有
理由是复审的实测:那个导出今天只有 对本 PR 的含义,只有一句:不收敛,现状即终态,本卡无需改动。 你这份 其余不变:PASS 成立,三次提交的顺序( Generated by Claude Code |
…rify-option-b-readers # Conflicts: # packages/cli/test/fixtures/option-b-reader-probe.ts # packages/cli/test/option-b-reader-acceptance.pin.test.ts
…d 35 The merge with main brings #15261's re-anchored floor (a real bound now that `OPTION_B_LOSSES` is empty) together with this card's four `@objectstack/verify` rows. Merged, the probe measures 35 rows, so the floor moves 30 -> 35. 35 is measured, not remembered: with the assertion temporarily written `toBe(-1)` the run reports `expected 35 to be -1`; a floor of 36 goes RED on the same fixture. The raise also closes an off-by-one it inherited — 30 stood against a probe that measured 31. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UHvF5hyiZjnCyExFnfQB8m
Conflict with
|
| Command | Result |
|---|---|
pnpm --filter @objectstack/cli exec vitest run test/option-b-reader-acceptance.pin.test.ts |
exit 0 — Test Files 1 passed (1) · Tests 7 passed (7) |
pnpm --filter @objectstack/verify test |
exit 0 — 10 files / 58 tests passed |
pnpm --filter @objectstack/cli typecheck |
exit 0 — check:test-typecheck: OK, debt ledger unchanged at 3 files / 28 errors / 6 pinned signatures |
pnpm --filter @objectstack/verify typecheck |
exit 0 |
pnpm check:nul-bytes |
OK — 7464 text files, no raw control bytes |
The cross-package rows read @objectstack/verify through its exports map (it is a listed entry in KNOWN_UNALIASED_TEST_IMPORTS['@objectstack/cli']), so every run above followed a pnpm --filter '@objectstack/cli^...' build, and the whole workspace was then built before the one dist-reading gate.
Gate family, derived with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack from the 11-path change set — all 17 green, exit codes captured before any pipe:
check:changeset-gate-self-tests · check:cli-test-child-env · check:cross-package-test-inputs · check:doc-authoring · check:dual-build-cjs-loads · check:logger-receiver-detach · check:objectql-double-limit · check:objectui-changeset · check:page-declaration-shape · check:pm-half-states · check:published-files · check:slot-lookup · check:test-source-alias · check:type-check-coverage · check:type-check-debt · check:type-source-resolution · check:where-matcher
check:dual-build-cjs-loads first returned exit 3 PREREQUISITE NOT MET (12 packages outside the CLI closure had no dist/); after a full pnpm build it re-ran green — the earlier exit 3 was never read as a pass.
The description above this comment predates the merge: its Tests table reports the pin at 6 tests on head 4525eee11, which is now 7 tests on 5c9b4e872. The body was left byte-intact rather than rewritten.
Generated by Claude Code
Generated by Claude Code
冲突解决复核 — PASS
三条硬约束,我自己在新 head
|
| 我要求的 | 读数 |
|---|---|
| 账本仍为空 | :177 const OPTION_B_LOSSES: readonly string[] = []; ✅ |
| 下限往上抬,⛔ 不保留 30、⛔ 不下调 | :291 .toBeGreaterThanOrEqual(35); ✅ |
| 四条受保护断言原样 | 集合相等 1 处、registryObjectsFromArtifact 2 处、五边界 1 处、#15006 四站点 4 处 ✅ |
| ⛔ 用 merge,不 rebase / amend / force-push | ad514692f Merge remote-tracking branch 'origin/main',旧 head 4525eee11 仍在历史里 ✅ |
35 是跑出来的,而且边界是活的
这是我最看重的一条,因为「30 + 5 个新站点 = 35」是个算得出来的数,而算出来的数和测出来的数在这里不是一回事:
下限临时写成 toBe(-1) → "The probe measured 35 rows … expected 35 to be -1"
下限 35 → Test Files 1 passed (1) / Tests 7 passed (7)
下限 36 → "expected 35 to be greater than or equal to 36"
两次变异都先证明落盘(注入计数 1 / 删除计数 0 / hash 离开 HEAD blob)再读结果,两次恢复都证明回到 HEAD blob 且 git diff HEAD 为空。
⛔ 还有一条我要单独表扬:第一次测量被你自己判为 VOID 并如实报告 —— vitest 路径传成了仓库相对路径、在 packages/cli 的 cwd 下匹配零文件、ERR_PNPM_RECURSIVE_EXEC_FIRST_FAIL 退 1。那是「没测到」,不是「门禁红了」,你没把它当读数。今天这个会话里,把未测量当成测量结果的错误已经出现过一次并且代价不小。
你顺手抓到的 off-by-one,我认
抬下限也顺带收掉了一个继承来的 off-by-one:下限写着 30,而那棵树上探针实际测 31 —— 有一行余量,意味着一行悄悄不被测量也不会红。现在是精确行数,零余量。
>= 本来就是下限),但文件头声称的第四个方向是「探针自己悄悄少测 ⇒ RED」,有余量时那句话就有一行的水分。你把它收成精确值,比我当时要求的更严。这一条我记下来:下次审这类反空转下限,要问的不是"这个数是不是测出来的",而是"它和当前实测值差多少"。
那个五小时的沉默失败,结论也留在这里
这张 PR 34 个 check 全绿、auto-merge 开着、零红色信号,却躺了五小时不动 —— 因为它与 main 冲突,而 mergeable_state 一直回 unknown 把这件事藏住了。我中途两次猜到合并方法上去,都是错的,已在会话里收回。
你现在的读数把它收尾了:mergeable_state 从 unknown 变成 blocked(必需检查未终态),git merge-tree 对当前 origin/main 返回干净树、exit 0。
⛔ 教训进我的例行动作:凡是"绿了却不动"的 PR,先跑一次 git merge-tree --write-tree --name-only origin/main <head>(只读、不建工作树),再考虑其他解释。
落地后
auto-merge 已武装(13:30:52Z,推送不清除它),CI 绿即进队列。它一落地,读取半六张卡全部收口 —— #15004 探针 + #15005 / #15006 / #15007 / #15229 / #15298,账本 24 → 0,发射半 #14512 已解锁。
Generated by Claude Code
Fixes #15229 · reader program 5/4 of the ADR-0130 D4 option-B ruling (#14512 comment 5528589044) · the site was found by the by-shape sweep #15210, and the acceptance pin it is measured against is #15004.
Every other reader card in this program loses a capability. This one loses the verification itself, and reports success while doing it: under an option-B artifact
deriveCrudCasesderived ZERO CRUD round-trip cases andrlsProbePermissionSetbuilt an EMPTY probe permission set, soos verifyprinted✓ verify passedover an app it had asserted nothing about.The ordering IS the evidence — three commits, in this order
Neither
@objectstack/verifysite had a row inOPTION_B_LOSSES, so a fix landing alone would be a fix nobody could check. The card asks for the loss to be ledgered first, and that is what the history carries.1 ·
8583bfbba— the rows, and their REDFour rows added to the probe, all calling readers the package ships (
deriveCrudCases,declaredPositionNames,rlsProbePermissionSet); none re-implements a read. With the rows in and the ledger lines absent, the pin failed exactly as designed:Their observed values in that same option-B report:
The BASELINE (today's additive shape) stayed green in the same run, so the red is a discrimination and not a broken fixture. The four lines were then added to
OPTION_B_LOSSES, and that is the state this commit leaves in the tree — 6/6 green, the loss ledgered.Why the rows are B2 and there is no B1 twin:
os verifyhas exactly one door,loadConfig(verify.ts:92), and never opens a compiled artifact. A B1 row here would measure a path no command drives.Why the zoo grew a federated pair:
deriveCrudCases's datasource-by-name map decides exactly one thing — whether a federated object's probe insert clears ADR-0015's double write gate — so the only way to watch that map is to carry an external object. The object is declared in the module package and the datasource that gates it in the App package, which makes the row a cross-package resolution. A reader that resolvedobjectsbut notdatasourcesstill fails it: the case comes backblockedas "external read-only", a verifier silently skipping an object the app opted into writes for.2 ·
5c0de742c— the fix, and the RED that names the rows to deleteWith the readers fixed and the ledger untouched, the pin said:
The same commit deletes them. Pin before/after: 24 ledgered losses before this PR, 24 after — the four verify lines were added and deleted inside it, and the probe now carries 4 more measured rows (28 total) than it did. Nothing about set equality, the subsystems asserted, or the controls was relaxed.
The one net change to the pin file is the anti-vacuity control's object list, which grows by the federated object the zoo now carries (
probe_account, probe_federated_order, probe_order). The control itself — the two shapes must register the SAME items — is untouched.3 ·
4525eee11— resolving the new cross-package imports to sourceWhat
check:test-source-aliasandcheck:type-source-resolutionprescribe for a dep a change newly reaches; neither shrink-only registry is widened.packages/verify/vitest.config.tsgains its firstresolve.alias(one anchored entry for@objectstack/core),packages/verify/tsconfig.jsonthe matchingpathsrule plus therootDirwidening that pulling core's source into the program requires, andpackages/cli/tsconfig.test.jsona fourth bare-name rule beside the three #15004 added.The fix
All four reads go through one resolver,
declaredCollection, which answers the caller's original expression first and consultspackages[]only where that came back falsy:objects: []is TRUTHY, so a declared-empty collection stays empty — the behaviour a re-expression asresolve(...).length > 0would have changed (measured on the sibling card reader program 3/4 —@objectstack/cli: the four config-load boundaries and the gates that key offconfig.objects#15006);resolveArtifactPackageOrder(@objectstack/core, ADR-0130 D4+D5), never a second traversal ofconfig.packages;packagesnow raises that function's ADR-0112 refusal (code+status: 422) instead of reading as "this app declares nothing".Both
deriveCrudCasesreads move together deliberately: objects alone would leave the write gate judging against an empty datasource map.composeStacks,packages/spec/src/stack.zod.tsand what every command emits are untouched — the artifact stays additive through this card.content/docs/releases/is untouched.Tests
Run on the final head
4525eee11(a merge oforigin/main460134af8), all through the shared verify lock:pnpm --filter @objectstack/cli exec vitest run test/option-b-reader-acceptance.pin.test.tspnpm --filter @objectstack/verify exec vitest run(whole package)pnpm --filter @objectstack/verify typecheckpnpm --filter @objectstack/cli typecheck(incl.check:test-typecheck)New in
packages/verify/src/artifact-collections.test.ts(9 cases): the shipped readers over a two-package option-B config, the cross-package write gate, the additive shape answering from the top level whilepackages[]deliberately carries an object it does not (a merge would have shown three cases, not two), the declared-empty contract, the single-package no-packagespath, and the malformed-packagesrefusal asserted as the ADR-0112 envelope rather than as a bare throw.Gates
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commandsderived 42 commands for this change set; all 42 were run, plus 7 more derived from the files this diff actually touches (check-console-intercept-disarm,check-test-completeness,check:published-files,check:type-check-coverage,check:cross-package-test-inputs,check:nul-bytes,check:cli-test-child-env).40 of 42 green, and the two that are not are both NOT MEASURED with a stated prerequisite, never a pass:
pnpm check:dual-build-cjs-loads— exit 3,PREREQUISITE NOT MET: it reads built output and this container built only the@objectstack/clidependency closure, so 12 packages outside it have nodist/. Its own text: "This is NOT a pass: nothing was measured." CI builds the whole workspace and will measure it.node scripts/check-test-completeness.mjs— exit 3, same class: it parses a fullturbo run testsummary, which a targeted run does not produce.check:test-source-aliasandcheck:type-source-resolutionwere RED on the first pass (the two new cross-package imports) and are green after commit4525eee11, by adding the alias andpathsrules each gate prescribes — not by widening either registry.Out-of-scope finding, already filed
packages/verify/tsconfig.jsonexcludes**/*.test.ts, so the package'stypecheckscript says nothing about any of its 10 test files — including the one this PR adds. Measured here with a throwaway program that includes them: 3 errors, all TS2835 (.jsextension) in three pre-existingharness.*.test.tsfiles, and 0 in the new file. That is #15145, already open; no new issue filed. Thepathsrule this PR adds is in the build program, so it carries over unchanged when that card lands thetsconfig.test.jsonsibling.🤖 Generated with Claude Code
https://claude.ai/code/session_01UHvF5hyiZjnCyExFnfQB8m
Generated by Claude Code