Repository navigation
fix(cli)!: os validate refuses a field naming a sibling package's picklist without a declared dependency - #31864
Conversation
…declared closure A field naming a sibling package's list with no declared dependency on it is refused at the author-time door, in the runtime's sentence (ADR-0150 D3, maintainer ruling N1). The door calls resolvePackageScopedHolders over packageResolutionClosure, the rule and walk the registry's picklist verdict uses. Extensions still resolve with no caller. Claude-Session: https://claude.ai/code/session_01ADHkmQYiGGwc3V7yGZ4iTo Co-authored-by: Claude <noreply@anthropic.com>
…or narrowing Claude-Session: https://claude.ai/code/session_01ADHkmQYiGGwc3V7yGZ4iTo Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 2 package(s): 17 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: ⛔ 8 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails. What this run could not see
Coarse fallback — 37 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 699afe9bb9222379d8760ac98ddeb476780e351b && git checkout 699afe9bb9222379d8760ac98ddeb476780e351b
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin ac8fede8f938439d2228f5b1404042d113a3812e 661fbd90d018703958917f7fea8484e6a5e1ba25 && git checkout -B drift-repro ac8fede8f938439d2228f5b1404042d113a3812e && git merge --no-ff 661fbd90d018703958917f7fea8484e6a5e1ba25
node scripts/docs-audit/affected-docs.mjs --json ac8fede8f938439d2228f5b1404042d113a3812e
|
…xt follows the closure rule The picklist-reference-unverified explanation now says how a field's picklist resolves (declaring package, declared dependency closure plus sys, ADR-0150 D3). The notice for a reference held only outside the declared dependencies names the holders in one line instead of quoting the resolver's whole sentence. Claude-Session: https://claude.ai/code/session_01ADHkmQYiGGwc3V7yGZ4iTo Co-authored-by: Claude <noreply@anthropic.com>
|
domain:server#4 · os-steve · session_01ADHkmP8JJo4zF2sjTBRwnX Contract-review-claim: session_01ADHkmP8JJo4zF2sjTBRwnX · head 688a960 · 2026-11-09T08:47Z |
|
domain:server#4 · os-steve · session_01ADHkmP8JJo4zF2sjTBRwnX Contract reviewServed-tier: Inputs read: card #31832 body and all four comments (claim 6734213007, triage 6734232029, dev report 6735269712 on Gates: 69 check-runs on ① Derived judgmentsAccept-set changes (author-time door,
Is the door now exactly the runtime's resolver, with no second closure walk? Yes for the rule and the closure. Do the refusal sentence and the boot refusal agree? Yes, for one holder, pinned on a real kernel. The door's Extensions (ruling X): Outside-dependency info notice: kept in kind for The new ≤200-char closure notice: the fixture's message is 199 characters, one under the pin. The bound is not held by construction. The message carries Public surface: no export added, removed or re-typed. ② Semver level
③ Boundary flags
④ Design judgmentThe narrowing against the published spec on Cases tried:
Findings:
No must-fix. The narrowing makes the spec's own refusal real and takes nothing declared away. F1 and F2 should land on this PR (both stay inside its file surface) or on a named follow-up before the epic seat closes E6's door work. Implemented-by: VERDICT: PASS |
…side dependencies Only a reference held outside the declaring package's closure softens to the info notice when that package depends on a package the stack does not carry; two holders one reference reaches are refused, as the boot refuses them. Pins the two-holder field and extension cases with boot parity, an objectExtensions field, the notice's length at realistic names, and makes the residual-parity CONTROL a stack the door accepts. Claude-Session: https://claude.ai/code/session_01ADHkmQYiGGwc3V7yGZ4iTo Co-authored-by: Claude <noreply@anthropic.com>
|
domain:server#4 · os-steve · session_01ADHkmP8JJo4zF2sjTBRwnX Contract-review-claim: session_01ADHkmP8JJo4zF2sjTBRwnX · head e32fe7b · 2026-11-09T09:58Z |
|
domain:server#4 · os-steve · session_01ADHkmP8JJo4zF2sjTBRwnX Contract reviewServed-tier: Round 2, a delta review. The r1 record (issuecomment-6736073411, head
The card has no new dev report. The dev's claims arrived through the seat and the PR body's Verification section, and are checked here against the tree. Gates: 69 check-runs on ① Derived judgmentsDelta against the r1 findings:
Nothing else moved. The delta touches the same 5 paths as r1: the changeset,
The Accept-set on the final tree (
Is the door still exactly the runtime's resolver, with no second closure walk? Yes. Same two calls as Public surface: no export, rule id, severity or ② Semver level
The added refusals narrow and add no public name. ③ Boundary flags
④ Design judgmentThe narrowing on the final tree, against the published spec on Cases re-tried on the final tree:
Tried to break the new routing:
Findings:
No must-fix. F1 and F2 are closed and pinned, the three folded nits are closed, and nothing outside the claimed surface moved. Implemented-by: VERDICT: PASS |
…header The door now resolves through ADR-0150 D3's rule (this PR), so the dated note saying the code still resolves against every body's picklists contradicts the code below it. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ADHkmQYiGGwc3V7yGZ4iTo
|
epic:#31527#1 · os-steve · session_01ADHkmP7o3uDTti21wLTfPf Epic red-line review of #31864 (#31832, E6 follow-up) at head 661fbd9: PASS. It lands when CI is green.
|
|
domain:server#4 · os-steve · session_01ADHkmP8JJo4zF2sjTBRwnX ACCEPT · PR #31864 · #31832 · 2026-11-09T11:03ZHead-sha:
|
⛔ merge queue 构建失败 — 先分诊,再决定要不要重排队列构建 42609535314 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集), 分类: 失败的 job(日志抽取,best effort):
跨 PR 相同签名(24h,按失败测试文件聚合):
历史信号:
分诊清单:
Generated by Claude Code · merge-queue-triage workflow (#4859) |
|
domain:server#4 · os-steve · session_01ADHkmP8JJo4zF2sjTBRwnX Re-queue · PR #31864 · 2026-11-09T11:58ZHead-sha:
|
…gated-answer ground-truth posture pin (objectstack-ai#31935) <!-- claude-projects-attribution: {"github_login":"os-steve"} --> _Requested by **Steve** · [project thread](https://claude.ai/code/project/chan_01FdL1XeUyvVNxLreh3F4Qi9?thread=cmsg_01FdL1XeUyvVNxLreh3F4Qi9WRooEf1dKxwnV3qGKCubny)_ Fixes objectstack-ai#31919 The card's fix: the ground-truth case that booted one rig per subject state inside a single `it` is split into one `it` per state, the shape commit 7200f54 already gave the sibling block in the same file. Claim 6739551496 (`domain:server#1`, card-thread session_01ADHkmSzW7smpTAzJNeCnD2). Clause-②: yes (test only, no contract change) — measured: the diff is one test file; no source file, request shape or answer changes, so nothing accepted on base is refused on head and nothing refused is accepted. Metadata-protocol: none `skip-changeset`: the diff is one test file under `src/` that the package does not ship (`plugin-security`'s `files[]` publishes `dist`, `README.md`, `CHANGELOG.md`; after a full build the `dist/` tree holds 0 files with this case's title text, against a positive control, `DelegationScopeViolationError`, in 2 built files), so nothing any released package publishes changes. ## 维护者速读 - 改了什么:plugin-security 的一个权限测试里,"真值"用例原来在同一个用例里为 5 种用户状态各启动一套环境,现在每种状态是自己的用例;同文件里另外 3 个同样"循环里每轮启动一套环境"的对照用例也按同样方式拆开。 - 为什么改:这个用例在合并队列负载高时超过默认 5 秒超时,把同批不相关的 PR(objectstack-ai#31864)一起踢出队列;拆开后每种状态有自己的 5 秒预算和自己的失败行。 - 风险与回滚:只改一个测试文件,产品代码零改动;断言一条没删没改,用例数 202 → 226。回滚就是 revert 这一个提交。 - 先看:`packages/plugins/plugin-security/src/delegated-admin-own-name-set.posture.test.ts` 的 ground truth 那段(约 869 行),再看 913、1130、1256 行附近的三处对照拆分。 - 两分钟验证:`pnpm --filter @objectstack/plugin-security exec vitest run src/delegated-admin-own-name-set.posture.test.ts`,预期 226 个用例全绿。 - 需要你做的:无,席位落地。 ## What changes One test file, four splits, each the same mechanical shape (a loop that boots a rig per iteration inside one `it` becomes a `describe` keeping the old title, with one `it.each` case per iteration): | Case (per posture: single, group, isolated) | Before | After | |---|---|---| | ground truth: the delegate reads the subject exactly in the states where it is a user the delegate may read | 1 `it`, 5 boots | 5 `it` (`[...STATES, CONTROL]`), 1 boot each | | CONTROL: a tenant-level administrator and the organization administrator still remove the stored grant … | 1 `it`, 2 boots | 2 `it`, 1 boot each | | CONTROL: a tenant-level administrator still moves the grant FROM / TO a holder outside the scope … (one case per end) | 1 `it` per end, 2 boots | 2 `it` per end, 1 boot each | | CONTROL: a tenant-level administrator re-names a grant freely … | 1 `it`, 2 boots | 2 `it`, 1 boot each | Case count: 202 → 226 (+12 ground truth, +3 removal, +6 moves, +3 re-naming). **Assertion targets, before and after: the same set.** Each `expect` keeps its subject, matcher, expected value and message label byte-identical; only its enclosing `it` changes. - ground truth: `expect(reads, state).toBe(state !== 'an id naming no user' && state !== UNREADABLE_INSIDE)`, once per state, 5 states × 3 postures = 15, before and after. - removal: `expect(removed, who).toEqual({ status: 200 })` and `expect(left, who).toEqual([])`, per remover, 2 × 2 × 3 = 12, before and after. - moves: `expect(throughWriteDoor(…), state).toEqual({ status: 200 })` and `expect(holderOf(…), state).toBe(ENDS[end].user_id)`, per state per end, 2 × 2 × 2 × 3 = 24, before and after. - re-naming: `expect(renameThroughWriteDoor(…), label).toEqual({ status: 200 })` and `expect(setOf(…), label).toBe(to)`, per re-naming, 2 × 2 × 3 = 12, before and after. ⛔ No timeout raised: the slowest split case measured 360 ms locally, far from the 5 s budget. No forced test edit elsewhere. **Scan of the other plugin-security posture files** (`by-id-write-row-reread`, `delegable-scope-no-identity-positions`, `org-admin-assignment-boundary`): every `it` there boots one rig and loops only over calls against it, so none has the shape; nothing changed there. In this file, the other loops inside an `it` (delegable-scope report, explain per unit, writes per unit) also run against one rig. ## Tests Local runs on head 2a121ac (the commit this body describes), 3 workers as CI's `VITEST_MAX_WORKERS`, `--sequence.concurrent=false`, JSON reporter, under the verify lock: - this file alone 5× on base 6a8f415: 5/5 green, 202 cases each. Old single cases, max of 5 runs: ground truth 797 / 706 / 558 ms (group / isolated / single); moves-FROM 767 ms (group); re-naming 528 ms (single); removal 340 ms (group). - this file alone 5× on head: 5/5 green, 226 cases each, 0 failures. The slowest of the 24 split cases: 360 ms (moves FROM, single, unreadable-inside); ground-truth states 104–237 ms. - the CI timeout itself does not reproduce locally (old case max 0.8 s against 5 s); the cause is shard load in the merge group, which the split answers by giving each boot its own budget. - `pnpm --filter @objectstack/plugin-security exec vitest run --maxWorkers=2`: Test Files 252 passed (252), Tests 6092 passed | 45 skipped (6137). - `pnpm --filter @objectstack/plugin-security typecheck`: exit 0 (`check:test-typecheck` OK, 0 errors). - `eslint packages/plugins/plugin-security --no-inline-config` (the package's `lint` script spelling, from the root): exit 0. No ablation: no tested behaviour changes, and no negative pin is added or moved. ## Acceptance notes - Each split case still boots its own rig, as the old loop did per iteration; the work per posture is the same, only the budget is per case. - The removal and re-naming `it.each` tables carry contexts built with `delegateCtx` at collection time; the old loop built the same objects inside the `it`. `delegateCtx` is a pure object literal. ## Verification Derived gates on head 2a121ac: `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` derived 53 commands (1 changed path); all 53 ran, all exit 0; `--ran` reconciliation: "53 derived famil(ies) accounted for — 53 run, 0 NOT-MEASURED". Every other family is CI's, on this PR's check-runs. <details><summary>53 derived commands and exit codes</summary> ```text node scripts/check-closing-keyword-parity.mjs :: exit 0 node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0 node scripts/check-comment-mask-adoption.mjs :: exit 0 node scripts/check-comment-mask-adoption.mjs --self-test :: exit 0 node scripts/check-comment-mask-corpus.mjs :: exit 0 node scripts/check-dts-emitted.mjs --self-test :: exit 0 node scripts/check-issue-citations.mjs :: exit 0 node scripts/check-plugin-teardown-shape.mjs :: exit 0 node scripts/check-plugin-teardown-shape.mjs --self-test :: exit 0 node scripts/check-tenant-audit-census.mjs :: exit 0 node scripts/check-tenant-audit-census.mjs --self-test :: exit 0 node scripts/ci/assert-turbo-remote-cache.mjs --self-test :: exit 0 node scripts/ci/plan-test-core-shards.mjs --self-test :: exit 0 node scripts/ci/prune-turbo-cache.mjs --self-test :: exit 0 node scripts/docs-audit/check-affected-docs.mjs :: exit 0 node scripts/docs-audit/check-drift-comment.mjs :: exit 0 pnpm --filter @objectstack/spec run check:duration-unit-keys :: exit 0 pnpm check:ci-filter-parity :: exit 0 pnpm check:closed-ceremony-text :: exit 0 pnpm check:cross-package-test-inputs :: exit 0 pnpm check:doc-authoring :: exit 0 pnpm check:driver-memory-census :: exit 0 pnpm check:dts-closure :: exit 0 pnpm check:dual-build-cjs-loads :: exit 0 pnpm check:engine-double-contract :: exit 0 pnpm check:gitlink-declared :: exit 0 pnpm check:i18n :: exit 0 pnpm check:i18n-stale-fill :: exit 0 pnpm check:issue-citations :: exit 0 pnpm check:keyed-text-bounds :: exit 0 pnpm check:lean-entry-closure :: exit 0 pnpm check:logger-receiver-detach :: exit 0 pnpm check:nul-bytes :: exit 0 pnpm check:objectql-double-limit :: exit 0 pnpm check:org-identifier :: exit 0 pnpm check:page-declaration-shape :: exit 0 pnpm check:platform-object-tenancy-census :: exit 0 pnpm check:published-files :: exit 0 pnpm check:query-options-erasure :: exit 0 pnpm check:refd-timer-probe :: exit 0 pnpm check:registry-log-declared :: exit 0 pnpm check:rest-log-spy-declared :: exit 0 pnpm check:slot-lookup :: exit 0 pnpm check:sourcemap-no-sources-content :: exit 0 pnpm check:system-context-census :: exit 0 pnpm check:test-source-alias :: exit 0 pnpm check:tier-file-adoption :: exit 0 pnpm check:type-check-coverage :: exit 0 pnpm check:type-check-debt :: exit 0 pnpm check:undeclared-dep-imports :: exit 0 pnpm check:vouch-for-reference-callers :: exit 0 pnpm check:watch-hint-literal :: exit 0 pnpm check:where-matcher :: exit 0 ``` </details> --- _Generated by [Claude Code](https://claude.ai/code/session_01ADHkmSzW7smpTAzJNeCnD2)_ Co-authored-by: Claude <noreply@anthropic.com>
Requested by Steve · project thread
Fixes #31832
Clause-②: no (narrowing)
Metadata-protocol: none
Narrowing: yes. Measured: on base
cc16008athe door accepts a field naming a sibling package's list with no declared dependency (the new refusal pin is RED there, see Verification); on head it refuses it aspicklist-reference-unknown. The runtime already refuses the same reference since E6 (#31731): the boot warns and serves no options, the post-seal install judge refuses. Narrowing face:os validate/os buildexit 1 for apackages[]artifact whose fieldpicklistis held only by packages outside the declaring package's declared dependency closure (plussys), when that package declares no dependency outside the artifact. Two further refusals, both already refused by the boot (INVALID_METADATA, quoting the same sentence) and refused here whatever the outside dependencies: (a) a field whose package declares dependencies on two packages of the artifact that both declare the list (ambiguous-in-closure); (b) apicklistExtensionstarget two packages of the artifact declare (ambiguous-in-environment). Both were accepted on base. First-party count: 0 (noexamples/**app and no platform package declares a picklist; E6 measured the same).Skills-docs: none. No published skill teaches a field naming another package's picklist;
skills/objectstack-data/SKILL.mdmentions picklist only forfieldPatches.维护者速读
os validate/os build现在按运行时的规则解析字段的picklist:先找声明该字段的包自己的列表,再找它声明的依赖闭包(加sys)。同一发布包里兄弟包的列表,如果没有声明依赖,会被拒绝,提示句与运行时一字不差,并给出要加的那一行dependencies。os validate当场说清楚。packages/cli/src/utils/picklist-references.ts的referenceResolver(直接调用resolvePackageScopedHolders+packageResolutionClosure,没有第二套闭包遍历)和refusedReferenceHint;picklist-references.boot-parity.test.ts末尾新 describe(门的句子等于启动告警的句子)。pnpm --filter @objectstack/cli exec vitest run src/utils/picklist-references.test.ts src/utils/picklist-references.boot-parity.test.ts,预期 53 passed;其中 “a field naming a sibling package's list (ADR-0150 D3)” 一组在 main 上是红的。What changes
judgePicklistReferencesbuilds the holders of each list from the bodies that declare it (packageScopedHolderSource), and asksresolvePackageScopedHolders(@objectstack/core) with the closure frompackageResolutionClosure(@objectstack/metadata-core), over a registry view of the artifact (each body answersgetPackagefor its own id). Those are the calls the registry'spicklistReferenceVerdictmakes, so no second closure walk exists. Both packages were already direct dependencies of@objectstack/cli.picklistresolves from the package that declares the field: own list, then the unique holder in its declared dependency closure plussys, else R's refusal. A refusal ispicklist-reference-unknownwith R's message verbatim (it names every candidate package and says to declare the dependency), and a hint carrying the exact entry, for example'com.example.core': '^1.0.0'added to thedependenciesof the declaring package, plus the consequence (no options, values refused). No new rule id, error code or export.outside-closure), is reported aspicklist-reference-unverified(info, never gating), because that dependency could supply the list. Two holders one reference reaches (ambiguous-in-closure,ambiguous-in-environment) are refused even then: an outside package can add a holder, never remove one (contract review F1).picklistExtensionsentry'sextendresolves with no caller (epic-seat ruling X on the E6 card; the registry does the same): any single holder in the stack, so an extension of a sibling's list still needs no dependency (pinned as a CONTROL), and a target two packages hold is refused, naming both.packages[]is unchanged: it is one package, every list it declares is its own.os explain picklist-reference-unverifiedfollows the rule (packages/lint/src/rule-explanations.ts, added to the surface by the seat's claim amendment 6735317750): its first paragraph now says how a field'spicklistresolves (declaring package, declared dependencies transitively, plussys; a sibling counts only when declared; an extension against the one package that holds the list; two holders refused), and its fix paragraph adds declaring the dependency.FIELD: 'LIST' is only in 'PKG', undeclared — not judged: 'DEP' may hold it) instead of quoting R's whole sentence; pinned at most 200 characters with realistic package and field names, and its hint names the dependency to declare. The refusal keeps R's sentence verbatim.Tests
picklist-references.test.ts, new describe "a field naming a sibling package's list (ADR-0150 D3)", 12 cases: refused with R's sentence (packageScopedRefusalMessage) and the dependency hint; two undeclared sibling holders both named; reported, not refused, when the declaring package has an outside dependency (hint names the dependency); that notice at most 200 characters with realistic names; two declared sibling holders refusedambiguous-in-closure, with and without an outside dependency; anobjectExtensionsfield judged from the extending package; CONTROL declared dependency; CONTROL transitive dependency; CONTROL own list; CONTROLsyslist with no dependency; CONTROL sibling extension with no dependency; an extension target two packages hold refusedambiguous-in-environment, with and without an outside dependency.picklist-references.boot-parity.test.ts, new describe, 4 cases on a real kernel: the door's refusal message equals the boot'sfindOutsideClosurePicklistReferences()refusal for the same field; CONTROL with the dependency declared, the door accepts and the boot reports nothing; a two-holder field and a two-holder extension target, each refused by the door and by the boot (INVALID_METADATA, the boot's message quoting the door's sentence).picklistRefusalHeadingCONTROL pin (heading text now names the closure case; renamed from "keep the heading they always had"); the boot-parity file's metadata save door: a select field bound to a picklist no package declares saves and publishes, then is served with no options and every write to it is refused #29898 describe docblock (it described the sibling allowance under ADR-0130). the first boot-parity describe's CONTROL stack now declares the core body's dependency on the app body, and asserts the door accepts the whole stack (before, it was a stack this door now refuses, passing only because it filtered topicklist-residual-unreferenced). No assertion of an existing case changed otherwise.Acceptance notes
661fbd90; the file is again the blob it was ate32fe7b2.packages/cli/src/utils/picklist-references.ts, its two test files, root.changeset/31832-*.md, andpackages/lint/src/rule-explanations.ts(thepicklist-reference-unverifiedentry's text only, claim amendment 6735317750). No test pin on that text moved:rule-explanations.test.tsandpackages/cli/test/explain-rule-id.test.tslist the id only.Verification
RED on main, then green on head (e32fe7b)
Head implementation swapped for base
cc16008a's blob (931bc8f2), tests from head, under the verify lock: 11 failed / 42 passed. Failing: every new refusal and notice pin (sibling refusal, two undeclared holders, outside-dependency notice, realistic-length notice, two declared holders,objectExtensionsfield, two-holder extension), the three refusal boot-parity pins, and the forced heading pin. Every new CONTROL passed on main. Restored withgit checkout HEAD,git diff HEADempty.Head: Test Files 2 passed, Tests 53 passed.
Ablations (each through scripts/ablation-replace.mjs; each restored: blob equals HEAD 0e54d2bc, git diff HEAD empty)
closureOfreturns every artifact package): 5 failed / 48 passed — sibling refusal, outside-dependency notice, realistic-length notice,objectExtensionsfield, boot-parity sentence. The two-holder pins stay green in the expected direction: with every package reachable they are still refused as ambiguous.judged.outside.length === 0): 2 failed / 51 passed — two declared holders with an outside dependency, two-holder extension with an outside dependency.outside-closurerefusal skipped like a resolution): 4 failed / 49 passed — both two-holder unit pins and both two-holder boot-parity pins.Gates on head e32fe7b, rerun on 661fbd9
After merging main (
ac8fede8) and deleting the note, on661fbd90: closure build, the two picklist suites (53 passed), cli typecheck and cli unit (406 files, 5807 tests) exit 0; the same 69 derived families all exit 0,--ran69/69, 0 NOT-MEASURED, 0 UNRUN.node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack: 69 families derived for the 5 changed paths. All 69 ran one32fe7b2, every one exit 0;--ranreconciliation: 69 derived, 69 run, 0 NOT-MEASURED, 0 UNRUN. (Earlier:check-adr-0087-registrationwas red onfcf3a4b9until8f820082added the changeset'snot-required (no-migration-prescription)marker; 63/63 green on8f820082, 69/69 on688a9602.)pnpm --filter @objectstack/lint build,test(197 files, 8365 tests) andtypecheck: exit 0.pnpm --filter @objectstack/cli buildandtypecheck(tsc pluscheck:test-typecheck): exit 0.pnpm --filter @objectstack/cli exec vitest run --project unit: 406 files, 5807 tests passed. The integration tier is declared to CI (no spawn entry or integration file touched).eslint
--no-inline-configover the changed source files: exit 0 (neither package has alintscript).Full turbo build of
packages/**(onfcf3a4b9): exit 0.Full dogfood battery on
e32fe7b2: exit 0; 441 files passed, 3 skipped; 3662 tests passed, 42 skipped; no failures.🤖 Generated with Claude Code
https://claude.ai/code/session_01ADHkmQYiGGwc3V7yGZ4iTo
Generated by Claude Code